Composite is now generic over size in memory.
Previously the Composite trait accepted a Vec<Value> for reading from memory, and returned a Vec<Value> for writing to memory. This meant the caller might provide the wrong size of Vec (e.g. a Point3d requires 3 memory addresses, so the code has to check and handle if the user only passes in 2). Now those methods accept/return a [Value; N] which is statically guaranteed to be the right size! This means there's no more need for runtime checks that the Vec is the right size -- because the array is guaranteed to be the right size. This involves removing the SIZE constant and instead changing it into a SIZE const generic.
This commit is contained in:
		| @ -1,36 +1,24 @@ | ||||
| use crate::{ExecutionError, Value}; | ||||
| use crate::{ExecutionError, NumericValue, Value}; | ||||
|  | ||||
| /// 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; | ||||
| pub trait Composite<const SIZE: usize>: Sized { | ||||
|     /// Store the value in memory. | ||||
|     fn into_parts(self) -> Vec<Value>; | ||||
|     fn into_parts(self) -> [Value; SIZE]; | ||||
|     /// Read the value from memory. | ||||
|     fn from_parts(values: Vec<Value>) -> Result<Self, ExecutionError>; | ||||
|     fn from_parts(values: [Value; SIZE]) -> Result<Self, ExecutionError>; | ||||
| } | ||||
|  | ||||
| impl Composite for kittycad::types::Point3D { | ||||
|     fn into_parts(self) -> Vec<Value> { | ||||
|         let points = [self.x, self.y, self.z]; | ||||
|         points | ||||
|             .into_iter() | ||||
|             .map(|x| Value::NumericValue(crate::NumericValue::Float(x))) | ||||
|             .collect() | ||||
| impl Composite<3> for kittycad::types::Point3D { | ||||
|     fn into_parts(self) -> [Value; 3] { | ||||
|         [self.x, self.y, self.z] | ||||
|             .map(NumericValue::Float) | ||||
|             .map(Value::NumericValue) | ||||
|     } | ||||
|  | ||||
|     const SIZE: usize = 3; | ||||
|  | ||||
|     fn from_parts(values: Vec<Value>) -> Result<Self, ExecutionError> { | ||||
|         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, | ||||
|             }); | ||||
|         }; | ||||
|     fn from_parts(values: [Value; 3]) -> Result<Self, ExecutionError> { | ||||
|         let [x, y, z] = values; | ||||
|         let x = x.try_into()?; | ||||
|         let y = y.try_into()?; | ||||
|         let z = z.try_into()?; | ||||
|  | ||||
| @ -6,9 +6,10 @@ | ||||
| //! You can think of it as a domain-specific language for making KittyCAD API calls and using | ||||
| //! the results to make other API calls. | ||||
|  | ||||
| use std::{collections::HashMap, fmt}; | ||||
|  | ||||
| use composite::Composite; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use std::{collections::HashMap, fmt}; | ||||
|  | ||||
| mod composite; | ||||
| #[cfg(test)] | ||||
| @ -48,7 +49,7 @@ impl Memory { | ||||
|  | ||||
|     /// Store a composite value (i.e. a value which takes up multiple addresses in memory). | ||||
|     /// Store its parts in consecutive memory addresses starting at `start`. | ||||
|     pub fn set_composite<T: Composite>(&mut self, composite_value: T, start: Address) { | ||||
|     pub fn set_composite<T: Composite<{ N }>, const N: usize>(&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); | ||||
| @ -57,17 +58,14 @@ impl Memory { | ||||
|  | ||||
|     /// 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<T: Composite>(&self, start: Address) -> Result<T, ExecutionError> { | ||||
|         let addrs = start.0..start.0 + T::SIZE; | ||||
|         let values: Vec<Value> = addrs | ||||
|             .into_iter() | ||||
|             .map(|a| { | ||||
|                 let addr = Address(a); | ||||
|                 self.get(&addr) | ||||
|                     .map(|x| x.to_owned()) | ||||
|                     .ok_or(ExecutionError::MemoryEmpty { addr }) | ||||
|             }) | ||||
|             .collect::<Result<_, _>>()?; | ||||
|     pub fn get_composite<T: Composite<{ N }>, const N: usize>(&self, start: Address) -> Result<T, ExecutionError> { | ||||
|         let addrs: [Address; N] = core::array::from_fn(|i| Address(i + start.0)); | ||||
|         let values: [Value; N] = arr_res_to_res_array(addrs.map(|addr| { | ||||
|             self.get(&addr) | ||||
|                 .map(|x| x.to_owned()) | ||||
|                 .ok_or(ExecutionError::MemoryEmpty { addr }) | ||||
|         }))?; | ||||
|  | ||||
|         T::from_parts(values) | ||||
|     } | ||||
| } | ||||
| @ -273,6 +271,18 @@ pub enum ExecutionError { | ||||
|     CannotApplyOperation { op: Operation, operands: Vec<Value> }, | ||||
|     #[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 }, | ||||
| } | ||||
|  | ||||
| /// Take an array of result and return a result of array. | ||||
| /// If all members of the array are Ok(T), returns Ok with an array of the T values. | ||||
| /// If any member of the array was Err(E), return Err with the first E value. | ||||
| fn arr_res_to_res_array<T, E, const N: usize>(arr: [Result<T, E>; N]) -> Result<[T; N], E> { | ||||
|     let mut out = core::array::from_fn(|_| None); | ||||
|     for (i, res) in arr.into_iter().enumerate() { | ||||
|         out[i] = match res { | ||||
|             Ok(x) => Some(x), | ||||
|             Err(e) => return Err(e), | ||||
|         }; | ||||
|     } | ||||
|     Ok(out.map(|opt| opt.unwrap())) | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user