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