anput 0.23.3

Scriptable Entity-Component-System (powered by Intuicio)
Documentation
use crate::{
    archetype::ArchetypeColumnInfo,
    bundle::{Bundle, BundleColumns},
    component::{Component, ComponentRef, ComponentRefMut},
    entity::Entity,
    query::{TypedLookupFetch, TypedRelationLookupFetch},
    world::{Relation, World, WorldError},
};
use intuicio_core::{context::Context, function::FunctionHandle, registry::Registry};
use intuicio_data::{
    data_stack::DataStackPack,
    lifetime::Lifetime,
    managed::{DynamicManaged, DynamicManagedRef},
};
use std::collections::HashMap;

pub use intuicio_core::function::Function as ActorMessageFunction;

pub struct ActorChild;
pub struct ActorParent;

#[derive(Debug, Default, Clone)]
pub struct ActorMessageListeners(HashMap<String, FunctionHandle>);

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Actor(Entity);

impl Actor {
    pub fn spawn(
        world: &mut World,
        bundle: impl Bundle + Send + Sync + 'static,
    ) -> Result<Self, WorldError> {
        let entity = world.spawn((
            ActorMessageListeners::default(),
            Relation::<ActorChild>::default(),
            Relation::<ActorParent>::default(),
        ))?;
        world.insert(entity, bundle)?;
        Ok(Self(entity))
    }

    pub fn despawn(self, world: &mut World) -> Result<(), WorldError> {
        world.despawn(self.0)
    }

    pub fn insert(
        self,
        world: &mut World,
        bundle: impl Bundle + Send + Sync + 'static,
    ) -> Result<(), WorldError> {
        world.insert(self.0, bundle)
    }

    pub fn remove<T: BundleColumns>(self, world: &mut World) -> Result<(), WorldError> {
        world.remove::<T>(self.0)
    }

    pub fn remove_raw(
        self,
        world: &mut World,
        columns: Vec<ArchetypeColumnInfo>,
    ) -> Result<(), WorldError> {
        world.remove_raw(self.0, columns)
    }

    pub fn exists(self, world: &World) -> bool {
        world.has_entity(self.0)
    }

    pub fn entity(self) -> Entity {
        self.0
    }

    pub fn fetch<'a, const LOCKING: bool, Fetch: TypedLookupFetch<'a, LOCKING>>(
        self,
        world: &'a World,
    ) -> Option<Fetch::Value> {
        world.entity::<LOCKING, Fetch>(self.0)
    }

    pub fn component<const LOCKING: bool, T: Component>(
        self,
        world: &'_ World,
    ) -> Result<ComponentRef<'_, LOCKING, T>, WorldError> {
        world.component::<LOCKING, T>(self.0)
    }

    pub fn component_mut<const LOCKING: bool, T: Component>(
        self,
        world: &'_ World,
    ) -> Result<ComponentRefMut<'_, LOCKING, T>, WorldError> {
        world.component_mut::<LOCKING, T>(self.0)
    }

    pub fn access<'a, const LOCKING: bool, Fetch: TypedLookupFetch<'a, LOCKING>>(
        self,
        world: &'a World,
    ) -> Option<Fetch::ValueOne> {
        world.lookup_one::<LOCKING, Fetch>(self.0)
    }

    pub fn add_child<const LOCKING: bool>(
        self,
        world: &mut World,
        other: Self,
    ) -> Result<(), WorldError> {
        world.relate_pair::<LOCKING, _, _>(ActorParent, ActorChild, self.0, other.0)?;
        Ok(())
    }

    pub fn remove_child<const LOCKING: bool>(
        self,
        world: &mut World,
        other: Self,
    ) -> Result<(), WorldError> {
        world.unrelate_pair::<LOCKING, ActorParent, ActorChild>(self.0, other.0)?;
        Ok(())
    }

    pub fn children<const LOCKING: bool>(self, world: &World) -> impl Iterator<Item = Self> + '_ {
        world
            .relations_outgoing::<LOCKING, ActorChild>(self.0)
            .map(|(_, _, entity)| Self(entity))
    }

    pub fn parents<const LOCKING: bool>(self, world: &World) -> impl Iterator<Item = Self> + '_ {
        world
            .relations_outgoing::<LOCKING, ActorParent>(self.0)
            .map(|(_, _, entity)| Self(entity))
    }

    pub fn relation_lookup<'a, const LOCKING: bool, Fetch: TypedRelationLookupFetch<'a>>(
        &self,
        world: &'a World,
    ) -> impl Iterator<Item = Fetch::Value> {
        world.relation_lookup::<LOCKING, Fetch>(self.0)
    }

    pub fn register_message_listener<const LOCKING: bool>(
        self,
        world: &World,
        id: impl ToString,
        function: ActorMessageFunction,
    ) -> Result<(), WorldError> {
        let mut listeners = self.component_mut::<LOCKING, ActorMessageListeners>(world)?;
        listeners.0.insert(id.to_string(), function.into_handle());
        Ok(())
    }

    pub fn unregister_message_listener<const LOCKING: bool>(
        self,
        world: &World,
        id: &str,
    ) -> Result<(), WorldError> {
        let mut listeners = self.component_mut::<LOCKING, ActorMessageListeners>(world)?;
        listeners.0.remove(id);
        Ok(())
    }

    pub fn message_listener<const LOCKING: bool>(
        self,
        world: &World,
        id: &str,
    ) -> Result<Option<FunctionHandle>, WorldError> {
        let listeners = self.component::<LOCKING, ActorMessageListeners>(world)?;
        Ok(listeners.0.get(id).cloned())
    }

    pub fn invoke_message<const LOCKING: bool>(
        self,
        world: &World,
        id: &str,
        context: &mut Context,
        registry: &Registry,
    ) -> Result<(), WorldError> {
        let listeners = self.component::<LOCKING, ActorMessageListeners>(world)?;
        if let Some(function) = listeners.0.get(id).cloned() {
            context.stack().push(DynamicManaged::new(self).unwrap());
            let lifetime = Lifetime::default();
            let value = DynamicManagedRef::new(world, lifetime.borrow().unwrap());
            context.stack().push(value);
            function.invoke(context, registry);
        }
        Ok(())
    }

    pub fn dispatch_message<const LOCKING: bool, O: DataStackPack, I: DataStackPack>(
        self,
        world: &World,
        id: &str,
        context: &mut Context,
        registry: &Registry,
        inputs: I,
    ) -> Result<Option<O>, WorldError> {
        let listeners = self.component::<LOCKING, ActorMessageListeners>(world)?;
        if let Some(function) = listeners.0.get(id).cloned() {
            inputs.stack_push_reversed(context.stack());
            context.stack().push(DynamicManaged::new(self).unwrap());
            let lifetime = Lifetime::default();
            let value = DynamicManagedRef::new(world, lifetime.borrow().unwrap());
            context.stack().push(value);
            function.invoke(context, registry);
            Ok(Some(O::stack_pop(context.stack())))
        } else {
            Ok(None)
        }
    }

    pub fn dispatch_message_hierarchy<const LOCKING: bool, I: DataStackPack + Clone>(
        self,
        world: &World,
        id: &str,
        context: &mut Context,
        registry: &Registry,
        inputs: I,
    ) -> Result<(), WorldError> {
        self.dispatch_message::<LOCKING, (), I>(world, id, context, registry, inputs.clone())?;
        for child in self.children::<LOCKING>(world) {
            child.dispatch_message_hierarchy::<LOCKING, I>(
                world,
                id,
                context,
                registry,
                inputs.clone(),
            )?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use intuicio_core::{
        transformer::{DynamicManagedValueTransformer, ValueTransformer},
        types::struct_type::NativeStructBuilder,
    };
    use intuicio_derive::intuicio_function;

    fn is_async<T: Send + Sync>() {}

    struct Attack(usize);

    struct Lives(usize);

    #[derive(Debug, Default, Clone)]
    struct Counter {
        odd: usize,
        even: usize,
    }

    #[intuicio_function(transformer = "DynamicManagedValueTransformer")]
    fn attack(world: &World, this: Actor, other: Actor) {
        let this_attack = this.component::<true, Attack>(world).unwrap();
        let mut other_lives = other.component_mut::<true, Lives>(world).unwrap();
        other_lives.0 = other_lives.0.saturating_sub(this_attack.0);
    }

    #[test]
    fn test_actor() {
        is_async::<Actor>();

        let registry = Registry::default()
            .with_basic_types()
            .with_type(NativeStructBuilder::new_uninitialized::<DynamicManaged>().build())
            .with_type(NativeStructBuilder::new_uninitialized::<DynamicManagedRef>().build());
        let mut context = Context::new(10240, 10240);
        let mut world = World::default();

        let player = Actor::spawn(&mut world, ("player".to_owned(), Attack(2), Lives(1))).unwrap();
        player
            .register_message_listener::<true>(&world, "attack", attack::define_function(&registry))
            .unwrap();
        assert!(player.exists(&world));
        assert_eq!(player.component::<true, Attack>(&world).unwrap().0, 2);
        assert_eq!(player.component::<true, Lives>(&world).unwrap().0, 1);

        let enemy = Actor::spawn(&mut world, ("enemy".to_owned(), Attack(1), Lives(2))).unwrap();
        assert!(enemy.exists(&world));
        assert_eq!(enemy.component::<true, Attack>(&world).unwrap().0, 1);
        assert_eq!(enemy.component::<true, Lives>(&world).unwrap().0, 2);

        player
            .dispatch_message::<true, (), _>(
                &world,
                "attack",
                &mut context,
                &registry,
                (DynamicManaged::new(enemy).unwrap(),),
            )
            .unwrap();
        assert_eq!(enemy.component::<true, Lives>(&world).unwrap().0, 0);
    }

    #[test]
    fn test_actor_singleton() {
        let mut world = World::default();
        let resources = Actor::spawn(&mut world, (Counter::default(),)).unwrap();

        for index in 0..5usize {
            world.spawn((index,)).unwrap();
        }

        let mut counter = resources.component_mut::<true, Counter>(&world).unwrap();
        for value in world.query::<true, &usize>() {
            if *value % 2 == 0 {
                counter.even += 1;
            } else {
                counter.odd += 1;
            }
        }
        assert_eq!(counter.odd, 2);
        assert_eq!(counter.even, 3);
    }
}