eclisp 0.2.0-beta.0

Bindings for Embeddable Common Lisp
use std::any::Any;

use crate::{
    LispContext, LispError, Value, ValueRef,
    clos::{Class, ObjectId},
};

struct ObjectSlot {
    generation: u64,
    value: Option<Box<dyn Any>>,
}

#[derive(Default)]
pub struct ObjectRegistry {
    objects: Vec<ObjectSlot>,
    free: Vec<usize>,
}

impl ObjectRegistry {
    pub fn insert<T: 'static>(&mut self, value: T) -> ObjectId {
        if let Some(index) = self.free.pop() {
            let slot = &mut self.objects[index];

            slot.value = Some(Box::new(value));

            return ObjectId {
                index,
                generation: slot.generation,
            };
        }

        let index = self.objects.len();

        self.objects.push(ObjectSlot {
            generation: 0,
            value: Some(Box::new(value)),
        });

        ObjectId {
            index,
            generation: 0,
        }
    }

    pub fn remove(&mut self, id: ObjectId) -> bool {
        let Some(slot) = self.objects.get_mut(id.index) else {
            return false;
        };

        if slot.generation != id.generation || slot.value.is_none() {
            return false;
        }

        slot.value = None;
        slot.generation = slot
            .generation
            .checked_add(1)
            .expect("Object generation exhausted");

        self.free.push(id.index);

        true
    }

    pub fn get<T: 'static>(&self, id: ObjectId) -> Option<&T> {
        let slot = self.objects.get(id.index)?;

        if slot.generation != id.generation {
            return None;
        }

        slot.value.as_ref()?.downcast_ref::<T>()
    }

    pub fn get_mut<T: 'static>(&mut self, id: ObjectId) -> Option<&mut T> {
        let slot = self.objects.get_mut(id.index)?;

        if slot.generation != id.generation {
            return None;
        }

        slot.value.as_mut()?.downcast_mut::<T>()
    }

    pub fn read<'rt, T: 'static>(
        &self,
        id: ObjectId,
        class: &Class<T>,
        property: &str,
        ctx: &LispContext<'rt>,
    ) -> Result<Option<Value<'rt>>, LispError> {
        let obj = self
            .get::<T>(id)
            .ok_or_else(|| LispError::RuntimeError("Invalid object ID or type".into()))?;

        class.read(property, obj, ctx)
    }

    pub fn write<T: 'static>(
        &mut self,
        id: ObjectId,
        class: &Class<T>,
        property: &str,
        value: ValueRef<'_>,
    ) -> Result<bool, LispError> {
        let obj = self
            .get_mut::<T>(id)
            .ok_or_else(|| LispError::RuntimeError("Invalid object ID or type".into()))?;

        class.write(property, obj, value)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    struct Transform {
        x: f32,
    }

    #[test]
    fn object_registry() {
        let mut registry = ObjectRegistry::default();

        let id = registry.insert(Transform { x: 10.0 });

        assert_eq!(registry.get::<Transform>(id).unwrap().x, 10.0);

        registry.get_mut::<Transform>(id).unwrap().x = 42.0;

        assert_eq!(registry.get::<Transform>(id).unwrap().x, 42.0);
        assert!(registry.get::<String>(id).is_none());
    }

    #[test]
    fn stale_handles_are_invalid() {
        let mut registry = ObjectRegistry::default();

        let first = registry.insert(42i32);

        assert_eq!(registry.get::<i32>(first), Some(&42));

        assert!(registry.remove(first));
        assert!(registry.get::<i32>(first).is_none());

        let second = registry.insert(100i32);

        assert_ne!(first, second);
        assert!(registry.get::<i32>(first).is_none());
        assert_eq!(registry.get::<i32>(second), Some(&100));

        assert!(!registry.remove(first));
    }
}