# SvelteKit + Rust + WASM on Unified Transportation Software Ecosystem In this tech blog, I would like to share some tips on connecting the Rust library and SvelteKit through WebAssembly (WASM). This knowledge was gained through part of the process of making the transportation calculation software **Cross Traffic** (the detailed paper/blog will be published soon). The whole codes are published on [GitHub](https://github.com/crosstraffic). ## What is Cross Traffic? Cross Traffic is the unified development ecosystem for transportation software, configurations, and manuals. It will be the solution for all the transportation software to share the basic equations and knowledge about transportation, and it will be useful for the researchers to provide an easy setup simulator. As my first scope, I wanted the software to calculate all the large manual works of Highway Capacity Manual's (HCM) calculations. HCM is a core manual for transportation engineers to determine whether the road segment is reasonable using the simple approximation method of road capacity. Unlike most of the micro-simulations, it doesn't need a lot of parameters, and fewer calibrations are required for each location. Some of the software already supports HCM calculation methods, such as Highway Capacity Software (HCS) for calculation and VISSIM for visualization. However, these are all closed systems owned by the company, and they only support HCM methods for validating traffic operations or plans. My Cross Traffic is a completely open system, and I indulge in implementing and testing new transportation systems! It is like a tunnel in a dune mountain on the playground. My first idea was to build a web UI for simple calculations, and now I focus on knowledge sharing using Rust libraries. ## User Interface with Sveltekit <img src="https://hackmd.io/_uploads/Syw-R2i4C.png" alt="drawing" width="150"/> The web user interface, named [Cross Traffic Web Calculator](https://hcm-calculator.com), has been used for two years to support HCM Chapter 15, Two-Lane Highways. It is basically written in SvelteKit (v2.5.4 as of May 2024) with simple aesthetics. Users can input parameters required to calculate Level-of-Service (LOS) at the highway section and monitor the calculation results in the middle process. It also supports JSON files to store input parameters for reproducing the results. ## All Calculations are Written in Rust When I started my development in 2022, the calculations were all written in SvelteKit. However, when I came up with a bigger architecture to maintain the software development along with the transportation manuals, I decided to re-work the calculations written in Rust completely. ![image](https://hackmd.io/_uploads/B1kYX6sVR.png) My architecture was only web-UI at first time, but now there are three different repositories connected to each other: **cross-traffic-web-calculator**, **cross-traffic-middleware**, **transportations-library**. All equations and parameter information are stored in the transportaions library. Users are faced with a cross-traffic web calculator. Cross-traffic middleware bridges these two repositories using WASM. Let's look at the example of identifying the vertical class. This is a simple value selection process from the table in HCM. <details><summary>Sveltekit code</summary> ```js function identifyVerticalClass(PT, vc) { // Identify Facility Study Boundaries and Corresponding Segmentation var _min = 0; var _max = 0; if (vc == 1 && PT == 'Passing Constrained') { _min = 0.25; _max = 3.0; } else if (vc == 1 && PT == 'Passing Zone') { _min = 0.25; _max = 2.0; } else if (vc == 1 && PT == 'Passing Lane') { _min = 0.5; _max = 3.0; } else if (vc == 2 && PT == 'Passing Constrained') { _min = 0.25; _max = 3.0; } else if (vc == 2 && PT == 'Passing Zone') { _min = 0.25; _max = 2.0; } else if (vc == 2 && PT == 'Passing Lane') { _min = 0.5; _max = 3.0; } else if (vc == 3 && PT == 'Passing Constrained') { _min = 0.25; _max = 1.1; } else if (vc == 3 && PT == 'Passing Zone') { _min = 0.25; _max = 1.1; } else if (vc == 3 && PT == 'Passing Lane') { _min = 0.5; _max = 1.1; } else if (vc == 4 && PT == 'Passing Constrained') { _min = 0.5; _max = 3.0; } else if (vc == 4 && PT == 'Passing Zone') { _min = 0.5; _max = 2.0; } else if (vc == 4 && PT == 'Passing Lane') { _min = 0.5; _max = 3.0; } else if (vc == 5 && PT == 'Passing Constrained') { _min = 0.5; _max = 3.0; } else if (vc == 5 && PT == 'Passing Zone') { _min = 0.5; _max = 2.0; } else if (vc == 5 && PT == 'Passing Lane') { _min = 0.5; _max = 3.0; } return [_min, _max]; } ``` </details> The code written in SvelteKit is re-writed by Rust and called from front-end with the code at the later section. <details><summary>Rust code</summary> ```rust pub fn identify_vertical_class(&mut self, seg_num: usize) -> (f64, f64) { let mut _min = 0.0; let mut _max = 0.0; let vc = self.segments[seg_num].get_vertical_class(); let pt = self.segments[seg_num].get_passing_type(); if (vc == 1) || (vc == 2){ if pt == 0 { _min = 0.25; _max = 3.0; } else if pt == 1 { _min = 0.25; _max = 2.0; } else if pt == 2 { _min = 0.5; _max = 3.0; } } else if vc == 3 { if pt == 0 { _min = 0.25; _max = 1.1; } else if pt == 1 { _min = 0.25; _max = 1.1; } else if pt == 2 { _min = 0.5; _max = 1.1; } } else if (vc == 4) || (vc == 5) { if pt == 0 { _min = 0.5; _max = 3.0; } else if pt == 1 { _min = 0.5; _max = 2.0; } else if pt == 2 { _min = 0.5; _max = 3.0; } }; (_min, _max) } ``` </details> Rust has the advantage of typing safe calculations with test functions and can be branched into various languages. I connect Rust to SvelteKit through WebAssembly (WASM), which can support faster calculations with binary files. With these code re-works, the number of rows in SvelteKit calculations decreased 6 times less (1210 -> 200 lines). I didn't record the exact calculation time, but, notably, Rust is faster than JS (without any optimization). ## Call the Constructor in SvelteKit Here is the structure tree of the most important functions. ```bash . └── cross-traffic-web-calculator ├── HCM-middleware/ │ ├── src/ │ │ └── lib.rs │ ├── target/ │ │ │ ├── pkg/ │ │ │ └── Cargo.toml │ └── src/ ├── routes/ │ ├── Calc/ │ ├── Row/ │ ├── SubRow/ │ └── +layout.svelte └── app.html ``` Before using WASM, you need to run wasm-pack and compile Rust libraries in a WASM fashion. Here is one way to run additional configurations automatically in package.json. ```json "scripts": { "modulize-wasm": "node HCM-middleware/modulize.js", "wasm": "wasm-pack build HCM-middleware --target web && npm run modulize-wasm", "dev": "npm run wasm && vite dev", "pre-build": "npm run wasm && vite build" } ``` Next, I create the folder (HCM-middleware) to call the WASM wrapper. In the file you want to use these wrappers, call those items in SvelteKit by simply importing the implementation's name. ```rust import init, { WasmSegment, WasmSubSegment, WasmTwoLaneHighways } from "HCM-middleware"; ``` Then, initialize memory addresses using the init() function in the main file. ```rust onMount(async() => { await init(); // init initializes memory addresses needed by WASM and that will be used by JS/TS }); ``` In the Calc component, I created an instance of the struct TwoLaneHighways. Each parameter is extracted from user inputs or initial values. Similarly, I initialized wasmSegment and wasmSubSegment. In the example code below, the getting parameter is excluded, and it calls a new constructor and gathers with JS arrays. ```rust var wasmSegment = []; // Initialization for(let i=0; i < rows_len; i++) { var subrows_len = rows[i].subrows.length; var wasmSubSegment = []; wasmSubSegment[0] = new WasmSubSegment(0.0, 0.0, 0, 0.0, 0.0); if (is_hc == true && subrows_len > 0){ for (let j=0; j < subrows_len; j++){ wasmSubSegment[j] = new WasmSubSegment(subSeg_len[i][j], 0.0, 0, rad[j], sup_ele[j]); } } const inst_wasmSegment = new WasmSegment(parseInt(passing_type[i]), seg_length, seg_grade, Spl, is_hc, Vi, Vo, 0.0, 0.0, 0, 0.0, 0.0, ver_class, wasmSubSegment, PHF, PHV, 0.0, 0.0, 0.0, 0); wasmSegment.push(inst_wasmSegment); seg_len[i] = seg_length; } var wasmTwoLaneHighways = new WasmTwoLaneHighways(wasmSegment, LW, SW, APD, PMHVFL, 0.0); ``` Then, the Rust function is called from SvelteKit in the following way, where i is the segment number. ```js [pass_min, pass_max] = wasmTwoLaneHighways.identify_vertical_class(i); ``` ## WASM Conversion There are several tricks to using Rust equations in SvelteKit using WASM. All you need to start with is using a wasm-bindgen crate. One of the biggest problems I faced was that each component was dependent. For example, TwoLaneHighways comprises several Segments, and a SubSegment is a sub-element of a Segment. Thus, every time you want to define these components, you have to define the smallest sub-component, SubSegment first, then Segment, and then TwoLaneHighways in order. Segment and SubSegment can be more than one, so I implemented these as Vectors. However, in the current wasm-bingen, Vector type and Trait are not implemented yet, so when I tried to wrap the Vector type implements with wasm-bingen, it got an error. Therefore, I decided to wrap the Vector type as a JsValue type. In my case, I implemented Segment and SubSegment as Vector types. In the WASM convertor, each JS object has to be converted to JsValue. This is simply done as below. ```rust pub fn to_js_value(&self) -> JsValue { let obj = js_sys::Object::new(); js_sys::Reflect::set(&obj, &JsValue::from_str("length"), &JsValue::from(self.get_length())).unwrap(); js_sys::Reflect::set(&obj, &JsValue::from_str("avg_speed"), &JsValue::from(self.get_avg_speed())).unwrap(); js_sys::Reflect::set(&obj, &JsValue::from_str("hor_class"), &JsValue::from(self.get_hor_class())).unwrap(); js_sys::Reflect::set(&obj, &JsValue::from_str("design_rad"), &JsValue::from(self.get_design_rad())).unwrap(); js_sys::Reflect::set(&obj, &JsValue::from_str("sup_ele"), &JsValue::from(self.get_sup_ele())).unwrap(); // Convert the JavaScript object to a JsValue JsValue::from(obj) } ``` Yes, this is simple. But do you remember my TwoLaneHighways or Segment implementation, which allows multiple children, a.k.a. Vector? Unfortunately, there is no direct way to call the Vector type in the method. So, my solution when calling SubSegment Vector is as follows. ```rust pub fn to_js_value(&self) -> JsValue { let obj = js_sys::Object::new(); . . . js_sys::Reflect::set(&obj, &JsValue::from_str("subsegments"), &self.get_subsegments()).unwrap(); . . . JsValue::from(obj) } ``` While converting String to JsValue, I call another method which inherently converts SubSegment object to JsValue. This is similar to to_js_value() except for calling the SubSegment method from the inner instance. In this way, this program recalls the stored information of user inputs. ```rust pub fn subsegs_to_js_value(&self) -> JsValue { let js_array = js_sys::Array::new(); // Call subsegment object let subsegments = self.inner.get_subsegments(); for (_, subseg) in subsegments.iter().enumerate() { let obj = js_sys::Object::new(); js_sys::Reflect::set(&obj, &JsValue::from_str("length"), &JsValue::from(subseg.get_length())).unwrap(); js_sys::Reflect::set(&obj, &JsValue::from_str("avg_speed"), &JsValue::from(subseg.get_avg_speed())).unwrap(); js_sys::Reflect::set(&obj, &JsValue::from_str("hor_class"), &JsValue::from(subseg.get_hor_class())).unwrap(); js_sys::Reflect::set(&obj, &JsValue::from_str("design_rad"), &JsValue::from(subseg.get_design_rad())).unwrap(); js_sys::Reflect::set(&obj, &JsValue::from_str("sup_ele"), &JsValue::from(subseg.get_sup_ele())).unwrap(); // Convert the JavaScript object to a JsValue js_array.push(&obj); } JsValue::from(js_array) } ``` This is all! Make sure that you publish your library in [crates.io](https://crates.io/) so that it can be used from different published repositories. You can also specify those libraries with relative paths in your local settings. I set up [wasm wrapped library](https://crates.io/crates/crosstraffic_middleware) and [transporations library as crates](transportations_library). Using this crate, I can use those libraries in the SvelteKit environment. ## Remarks & References Thanks for reading! This was my work in March 2024, and there will be new features or functions in these applications. I'm happy to see any comments about good WASM and transportation software tips! I'm glad to have pull requests as well. Here are the references I used while coding. These are simply very useful for starting up the WASM + Rust application in SvelteKit! 1. [Using WASM With SvelteKit](https://www.mbaraa.com/blog/using-wasm-with-sveltekit) 2. [Integrating a Svelte app with Rust using WebAssembly](https://blog.logrocket.com/integrating-svelte-app-rust-webassembly/) 3. [SvelteKit + Rust/Wasm Project Setup Tutorial](https://github.com/Leangeful/SvelteKit-Rust-Wasm-Tutorial)