diff --git a/src/wasm-lib/execution-plan/src/composite.rs b/src/wasm-lib/execution-plan/src/composite.rs index 745f3ab0f..e0289b717 100644 --- a/src/wasm-lib/execution-plan/src/composite.rs +++ b/src/wasm-lib/execution-plan/src/composite.rs @@ -1,39 +1,13 @@ use crate::{ExecutionError, Value}; +mod impls; + /// Types that can be written to or read from KCEP program memory, /// but require multiple values to store. /// They get laid out into multiple consecutive memory addresses. pub trait Composite: Sized { - /// How many memory addresses are required to store this value? - const SIZE: usize; /// Store the value in memory. fn into_parts(self) -> Vec; /// Read the value from memory. - fn from_parts(values: Vec) -> Result; -} - -impl Composite for kittycad::types::Point3D { - fn into_parts(self) -> Vec { - let points = [self.x, self.y, self.z]; - points - .into_iter() - .map(|x| Value::NumericValue(crate::NumericValue::Float(x))) - .collect() - } - - const SIZE: usize = 3; - - fn from_parts(values: Vec) -> Result { - let n = values.len(); - let Ok([x, y, z]): Result<[Value; 3], _> = values.try_into() else { - return Err(ExecutionError::MemoryWrongSize { - actual: n, - expected: Self::SIZE, - }); - }; - let x = x.try_into()?; - let y = y.try_into()?; - let z = z.try_into()?; - Ok(Self { x, y, z }) - } + fn from_parts(values: &[Option]) -> Result; } diff --git a/src/wasm-lib/execution-plan/src/composite/impls.rs b/src/wasm-lib/execution-plan/src/composite/impls.rs new file mode 100644 index 000000000..924e57540 --- /dev/null +++ b/src/wasm-lib/execution-plan/src/composite/impls.rs @@ -0,0 +1,22 @@ +use crate::{ExecutionError, Value}; + +use super::Composite; + +impl Composite for kittycad::types::Point3D { + fn into_parts(self) -> Vec { + let points = [self.x, self.y, self.z]; + points + .into_iter() + .map(|x| Value::NumericValue(crate::NumericValue::Float(x))) + .collect() + } + + fn from_parts(values: &[Option]) -> Result { + let err = ExecutionError::MemoryWrongSize { expected: 3 }; + let [x, y, z] = [0, 1, 2].map(|n| values.get(n).ok_or(err.clone())); + let x = x?.to_owned().ok_or(err.clone())?.try_into()?; + let y = y?.to_owned().ok_or(err.clone())?.try_into()?; + let z = z?.to_owned().ok_or(err.clone())?.try_into()?; + Ok(Self { x, y, z }) + } +} diff --git a/src/wasm-lib/execution-plan/src/lib.rs b/src/wasm-lib/execution-plan/src/lib.rs index 6ba5c53cb..4a04e6524 100644 --- a/src/wasm-lib/execution-plan/src/lib.rs +++ b/src/wasm-lib/execution-plan/src/lib.rs @@ -8,16 +8,22 @@ use composite::Composite; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fmt}; +use std::fmt; mod composite; #[cfg(test)] mod tests; /// KCEP's program memory. A flat, linear list of values. -#[derive(Default, Debug)] +#[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] -pub struct Memory(HashMap); +pub struct Memory(Vec>); + +impl Default for Memory { + fn default() -> Self { + Self(vec![None; 1024]) + } +} /// An address in KCEP's program memory. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -37,13 +43,17 @@ impl From for Address { impl Memory { /// Get a value from KCEP's program memory. - pub fn get(&self, addr: &Address) -> Option<&Value> { - self.0.get(&addr.0) + pub fn get(&self, Address(addr): &Address) -> Option<&Value> { + self.0[*addr].as_ref() } /// Store a value in KCEP's program memory. - pub fn set(&mut self, addr: Address, value: Value) { - self.0.insert(addr.0, value); + pub fn set(&mut self, Address(addr): Address, value: Value) { + // If isn't big enough for this value, double the size of memory until it is. + while addr > self.0.len() { + self.0.extend(vec![None; self.0.len()]); + } + self.0[addr] = Some(value); } /// Store a composite value (i.e. a value which takes up multiple addresses in memory). @@ -51,23 +61,14 @@ impl Memory { pub fn set_composite(&mut self, composite_value: T, start: Address) { let parts = composite_value.into_parts().into_iter(); for (value, addr) in parts.zip(start.0..) { - self.0.insert(addr, value); + self.0[addr] = Some(value); } } /// Get a composite value (i.e. a value which takes up multiple addresses in memory). /// Its parts are stored in consecutive memory addresses starting at `start`. pub fn get_composite(&self, start: Address) -> Result { - let addrs = start.0..start.0 + T::SIZE; - let values: Vec = addrs - .into_iter() - .map(|a| { - let addr = Address(a); - self.get(&addr) - .map(|x| x.to_owned()) - .ok_or(ExecutionError::MemoryEmpty { addr }) - }) - .collect::>()?; + let values = &self.0[start.0..]; T::from_parts(values) } } @@ -204,7 +205,7 @@ impl Arithmetic { } /// Operations that can be applied to values in memory. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone, Copy)] pub enum Operation { Add, Mul, @@ -265,7 +266,7 @@ pub fn execute(mem: &mut Memory, plan: Vec) -> Result<(), Execution } /// Errors that could occur when executing a KittyCAD execution plan. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, Clone)] pub enum ExecutionError { #[error("Memory address {addr} was not set")] MemoryEmpty { addr: Address }, @@ -273,6 +274,6 @@ pub enum ExecutionError { CannotApplyOperation { op: Operation, operands: Vec }, #[error("Tried to read a '{expected}' from KCEP program memory, found an '{actual}' instead")] MemoryWrongType { expected: &'static str, actual: String }, - #[error("Wrong size of memory trying to read value from KCEP program memory: got {actual} but wanted {expected}")] - MemoryWrongSize { expected: usize, actual: usize }, + #[error("Wanted {expected} values but did not get enough")] + MemoryWrongSize { expected: usize }, } diff --git a/src/wasm-lib/execution-plan/src/tests.rs b/src/wasm-lib/execution-plan/src/tests.rs index 357dbe37f..2704eb67d 100644 --- a/src/wasm-lib/execution-plan/src/tests.rs +++ b/src/wasm-lib/execution-plan/src/tests.rs @@ -58,14 +58,11 @@ fn add_to_composite_value() { // Write a point to memory. let point_before = Point3D { x: 2.0, y: 3.0, z: 4.0 }; - let start_addr = Address(100); + let start_addr = Address(0); mem.set_composite(point_before, start_addr); - assert_eq!( - mem, - Memory(HashMap::from( - [(100, 2.0.into()), (101, 3.0.into()), (102, 4.0.into()),] - )) - ); + assert_eq!(mem.0[0], Some(2.0.into())); + assert_eq!(mem.0[1], Some(3.0.into())); + assert_eq!(mem.0[2], Some(4.0.into())); // Update the point's x-value in memory. execute(