Skip to main content

rooc/pipe/
pipe_wasm_runner.rs

1#[cfg(target_arch = "wasm32")]
2use serde::{Deserialize, Serialize};
3#[allow(unused_imports)]
4#[cfg(target_arch = "wasm32")]
5use crate::prelude::*;
6
7#[cfg(target_arch = "wasm32")]
8use crate::pipe::run_pipe;
9#[cfg(target_arch = "wasm32")]
10use crate::runtime_builtin::JsFunction;
11#[cfg(target_arch = "wasm32")]
12use {
13    crate::RoocParser,
14    crate::parser::model_transformer::Model,
15    crate::parser::pre_model::PreModel,
16    crate::pipe::PipeContext,
17    crate::pipe::pipe_definitions::{PipeDataType, PipeError, Pipeable, PipeableData},
18    crate::pipe::pipe_executors::{
19        AutoSolverPipe, BinarySolverPipe, CompilerPipe, IntegerBinarySolverPipe, LinearModelPipe,
20        MILPSolverPipe, ModelPipe, Pipes, PreModelPipe, RealSolver, StandardLinearModelPipe,
21        StepByStepSimplexPipe, TableauPipe,
22    },
23    crate::solvers::{OptimalTableau, OptimalTableauWithSteps, Tableau},
24    crate::transformers::StandardLinearModel,
25    crate::{Constant, Primitive},
26};
27#[cfg(target_arch = "wasm32")]
28use crate::{IntOrBoolValue, LinearModel, LpSolution, MILPValue};
29
30#[cfg(target_arch = "wasm32")]
31#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
32pub struct WasmPipeRunner {
33    pipes: Vec<Box<dyn Pipeable>>,
34}
35
36#[cfg(target_arch = "wasm32")]
37#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
38pub struct JsPipable {
39    function: Function,
40}
41#[cfg(target_arch = "wasm32")]
42#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
43impl JsPipable {
44    pub fn new_wasm(function: Function) -> JsPipable {
45        JsPipable { function }
46    }
47}
48
49#[cfg(target_arch = "wasm32")]
50#[derive(Serialize, Deserialize)]
51#[serde(tag = "type", content = "value")]
52enum JsPipableData {
53    String(String),
54    LinearModel(LinearModel),
55    BinarySolution(LpSolution<bool>),
56    IntegerBinarySolution(LpSolution<IntOrBoolValue>),
57    RealSolution(LpSolution<f64>),
58    MILPSolution(LpSolution<MILPValue>),
59}
60
61#[cfg(target_arch = "wasm32")]
62impl Pipeable for JsPipable {
63    fn pipe(
64        &self,
65        data: &mut PipeableData,
66        _pipe_context: &PipeContext,
67    ) -> Result<PipeableData, PipeError> {
68        let js_pipable = match data {
69            PipeableData::String(s) => JsPipableData::String(s.clone()),
70            PipeableData::LinearModel(lm) => JsPipableData::LinearModel(lm.clone()),
71            PipeableData::BinarySolution(sol) => JsPipableData::BinarySolution(sol.clone()),
72            PipeableData::IntegerBinarySolution(sol) => {
73                JsPipableData::IntegerBinarySolution(sol.clone())
74            }
75            PipeableData::RealSolution(sol) => JsPipableData::RealSolution(sol.clone()),
76            PipeableData::MILPSolution(sol) => JsPipableData::MILPSolution(sol.clone()),
77            _ => return Err(PipeError::Other("JsPipable data must be one of: String, LinearModel, BinarySolution, IntegerBinarySolution, RealSolution, MILPSolution".to_string()))
78        };
79        let js_pipable =
80            serialize_json_compatible(&js_pipable).map_err(|e| PipeError::Other(e.to_string()))?;
81        let js_args = js_sys::Array::new();
82        js_args.push(&js_pipable);
83        let result = self.function.apply(&JsValue::NULL, &js_args).map_err(|e| {
84            PipeError::Other(
85                e.as_string()
86                    .unwrap_or("Error thrown in function".to_string()),
87            )
88        })?;
89        let result: JsPipableData =
90            serde_wasm_bindgen::from_value(result).map_err(|e| PipeError::Other(e.to_string()))?;
91        let pipe_result = match result {
92            JsPipableData::String(s) => PipeableData::String(s),
93            JsPipableData::LinearModel(lm) => PipeableData::LinearModel(lm),
94            JsPipableData::BinarySolution(sol) => PipeableData::BinarySolution(sol),
95            JsPipableData::IntegerBinarySolution(sol) => PipeableData::IntegerBinarySolution(sol),
96            JsPipableData::RealSolution(sol) => PipeableData::RealSolution(sol),
97            JsPipableData::MILPSolution(sol) => PipeableData::MILPSolution(sol),
98        };
99        Ok(pipe_result)
100    }
101}
102
103#[cfg(target_arch = "wasm32")]
104#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
105impl WasmPipeRunner {
106    pub fn new_wasm() -> WasmPipeRunner {
107        WasmPipeRunner { pipes: vec![] }
108    }
109
110    pub fn add_step_by_name(&mut self, step: Pipes) {
111        let step: Box<dyn Pipeable> = match step {
112            Pipes::CompilerPipe => Box::new(CompilerPipe::new()),
113            Pipes::PreModelPipe => Box::new(PreModelPipe::new()),
114            Pipes::ModelPipe => Box::new(ModelPipe::new()),
115            Pipes::LinearModelPipe => Box::new(LinearModelPipe::new()),
116            Pipes::StandardLinearModelPipe => Box::new(StandardLinearModelPipe::new()),
117            Pipes::TableauPipe => Box::new(TableauPipe::new()),
118            Pipes::RealPipe => Box::new(RealSolver::new()),
119            Pipes::StepByStepSimplexPipe => Box::new(StepByStepSimplexPipe::new()),
120            Pipes::BinarySolverPipe => Box::new(BinarySolverPipe::new()),
121            Pipes::IntegerBinarySolverPipe => Box::new(IntegerBinarySolverPipe::new()),
122            Pipes::MILPSolverPipe => Box::new(MILPSolverPipe::new()),
123            Pipes::AutoSolverPipe => Box::new(AutoSolverPipe::new()),
124        };
125        self.pipes.push(step);
126    }
127
128    pub fn add_step_with_fn(&mut self, function: JsPipable) {
129        self.pipes.push(Box::new(function));
130    }
131
132    pub fn wasm_run_from_string(
133        &self,
134        data: String,
135        constants: JsValue,
136        fns: Vec<JsFunction>,
137    ) -> Result<Vec<WasmPipableData>, WasmPipeError> {
138        let data = PipeableData::String(data);
139        let constants: Vec<(String, Primitive)> = serde_wasm_bindgen::from_value(constants)
140            .map_err(|e| WasmPipeError::new(PipeError::Other(e.to_string()), vec![]))?;
141        let constants = constants
142            .into_iter()
143            .map(|v| Constant::from_primitive(&v.0, v.1))
144            .collect();
145        let fns = js_value_to_fns_map(fns);
146        match run_pipe(&self.pipes, data, &PipeContext::new(constants, &fns)) {
147            Ok(results) => Ok(results.into_iter().map(WasmPipableData::new).collect()),
148            Err((e, results)) => {
149                let results: Vec<WasmPipableData> =
150                    results.into_iter().map(WasmPipableData::new).collect();
151                Err(WasmPipeError::new(e, results))
152            }
153        }
154    }
155}
156
157#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
158#[cfg(target_arch = "wasm32")]
159pub struct WasmPipeError {
160    error: PipeError,
161    context: Vec<WasmPipableData>,
162}
163
164#[cfg(target_arch = "wasm32")]
165impl WasmPipeError {
166    pub fn new(error: PipeError, context: Vec<WasmPipableData>) -> WasmPipeError {
167        WasmPipeError { error, context }
168    }
169}
170
171#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
172#[cfg(target_arch = "wasm32")]
173impl WasmPipeError {
174    pub fn wasm_get_error(&self) -> String {
175        self.error.to_string()
176    }
177    pub fn wasm_get_context(&self) -> Vec<WasmPipableData> {
178        self.context.clone()
179    }
180    pub fn wasm_to_context(self) -> Vec<WasmPipableData> {
181        self.context
182    }
183}
184
185#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
186#[cfg(target_arch = "wasm32")]
187#[derive(Debug, Clone)]
188pub struct WasmPipableData {
189    data: PipeableData,
190}
191
192#[cfg(target_arch = "wasm32")]
193impl WasmPipableData {
194    pub fn new(data: PipeableData) -> WasmPipableData {
195        WasmPipableData { data }
196    }
197}
198
199#[cfg(target_arch = "wasm32")]
200impl From<WasmPipableData> for PipeableData {
201    fn from(data: WasmPipableData) -> Self {
202        data.data
203    }
204}
205#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
206#[allow(clippy::wrong_self_convention)]
207#[cfg(target_arch = "wasm32")]
208impl WasmPipableData {
209    pub fn wasm_get_type(&self) -> PipeDataType {
210        self.data.get_type()
211    }
212
213    //TODO is there a better way to do this instead of making singular functions for each type?
214    pub fn to_string_data(self) -> Result<String, JsValue> {
215        self.data
216            .to_string_data()
217            .map_err(|e| JsValue::from_str(&e.to_string()))
218    }
219    pub fn to_parser(self) -> Result<RoocParser, JsValue> {
220        self.data
221            .to_parser()
222            .map_err(|e| JsValue::from_str(&e.to_string()))
223    }
224    pub fn to_pre_model(self) -> Result<PreModel, JsValue> {
225        self.data
226            .to_pre_model()
227            .map_err(|e| JsValue::from_str(&e.to_string()))
228    }
229    pub fn to_model(self) -> Result<Model, JsValue> {
230        self.data
231            .to_model()
232            .map_err(|e| JsValue::from_str(&e.to_string()))
233    }
234    pub fn to_linear_model(self) -> Result<LinearModel, JsValue> {
235        self.data
236            .to_linear_model()
237            .map_err(|e| JsValue::from_str(&e.to_string()))
238    }
239    pub fn to_standard_linear_model(self) -> Result<StandardLinearModel, JsValue> {
240        self.data
241            .to_standard_linear_model()
242            .map_err(|e| JsValue::from_str(&e.to_string()))
243    }
244    pub fn to_tableau(self) -> Result<Tableau, JsValue> {
245        self.data
246            .to_tableau()
247            .map_err(|e| JsValue::from_str(&e.to_string()))
248    }
249    pub fn to_optimal_tableau(self) -> Result<OptimalTableau, JsValue> {
250        self.data
251            .to_optimal_tableau()
252            .map_err(|e| JsValue::from_str(&e.to_string()))
253    }
254    pub fn to_optimal_tableau_with_steps(self) -> Result<OptimalTableauWithSteps, JsValue> {
255        self.data
256            .to_optimal_tableau_with_steps()
257            .map_err(|e| JsValue::from_str(&e.to_string()))
258    }
259    pub fn to_binary_solution(self) -> Result<JsValue, JsValue> {
260        self.data
261            .to_binary_solution()
262            .map(|s| serde_wasm_bindgen::to_value(&s).unwrap())
263            .map_err(|e| JsValue::from_str(&e.to_string()))
264    }
265    pub fn to_integer_binary_solution(self) -> Result<JsValue, JsValue> {
266        self.data
267            .to_integer_binary_solution()
268            .map(|s| serde_wasm_bindgen::to_value(&s).unwrap())
269            .map_err(|e| JsValue::from_str(&e.to_string()))
270    }
271
272    pub fn to_real_solution(self) -> Result<JsValue, JsValue> {
273        self.data
274            .to_real_solution()
275            .map(|s| serde_wasm_bindgen::to_value(&s).unwrap())
276            .map_err(|e| JsValue::from_str(&e.to_string()))
277    }
278
279    pub fn to_milp_solution(self) -> Result<JsValue, JsValue> {
280        self.data
281            .to_milp_solution()
282            .map(|s| serde_wasm_bindgen::to_value(&s).unwrap())
283            .map_err(|e| JsValue::from_str(&e.to_string()))
284    }
285}