bevy_mod_ffi_guest 0.2.0

FFI utilities for Bevy guests
Documentation
use crate::{
    component::{HookContext, SharedComponent, StorageType},
    query::{QueryData, QueryFilter, QueryState},
    system::{
        IntoObserverSystem, IntoSystem, On, ParamBuilder, ParamCursor, SharedEvent, System,
        SystemParam, SystemRef, SystemState,
    },
};
use bevy_mod_ffi_core::{BundleComponent, ComponentHookFn, deferred_world, world};
use bevy_mod_ffi_guest_sys::system::ObserverClosure;
use bevy_reflect::TypePath;
use std::{
    alloc::Layout,
    ffi::CString,
    ptr::{self, NonNull},
};

pub use bevy_ecs::{
    component::ComponentId,
    entity::Entity,
    ptr::{Ptr, PtrMut},
};
pub use bytemuck::{Pod, Zeroable};

mod entity;
pub use entity::{EntityWorldMut, FilteredEntityMut};

mod deferred;
pub use deferred::DeferredWorld;

macro_rules! make_hook_wrapper {
    ($name:ident, $hook_getter:expr) => {
        unsafe extern "C" fn $name<C: SharedComponent>(
            deferred_ptr: *mut deferred_world,
            entity_bits: u64,
            component_id: usize,
        ) {
            if let Some(hook) = $hook_getter {
                let deferred = unsafe { DeferredWorld::from_ptr(deferred_ptr) };
                let context = HookContext {
                    entity: Entity::from_bits(entity_bits),
                    component_id: ComponentId::new(component_id),
                    caller: None,
                };
                hook(deferred, context);
            }
        }
    };
}

make_hook_wrapper!(on_add_wrapper, C::on_add());
make_hook_wrapper!(on_insert_wrapper, C::on_insert());
make_hook_wrapper!(on_replace_wrapper, C::on_replace());
make_hook_wrapper!(on_remove_wrapper, C::on_remove());
make_hook_wrapper!(on_despawn_wrapper, C::on_despawn());

pub struct World {
    pub(crate) ptr: *mut world,
}

impl World {
    #[doc(hidden)]
    pub unsafe fn from_ptr(ptr: *mut world) -> Self {
        Self { ptr }
    }

    pub fn register_component<C: SharedComponent>(&mut self) -> ComponentId {
        let name = C::type_path();

        let layout = Layout::new::<C>();
        let name_cstring = CString::new(name).unwrap();
        let name_bytes = name_cstring.as_bytes_with_nul();

        let on_add: Option<ComponentHookFn> =
            C::on_add().map(|_| on_add_wrapper::<C> as ComponentHookFn);
        let on_insert: Option<ComponentHookFn> =
            C::on_insert().map(|_| on_insert_wrapper::<C> as ComponentHookFn);
        let on_replace: Option<ComponentHookFn> =
            C::on_replace().map(|_| on_replace_wrapper::<C> as ComponentHookFn);
        let on_remove: Option<ComponentHookFn> =
            C::on_remove().map(|_| on_remove_wrapper::<C> as ComponentHookFn);
        let on_despawn: Option<ComponentHookFn> =
            C::on_despawn().map(|_| on_despawn_wrapper::<C> as ComponentHookFn);

        let mut id: usize = 0;
        let success = unsafe {
            bevy_mod_ffi_guest_sys::world::bevy_world_register_component(
                self.ptr,
                name_bytes.as_ptr(),
                name_bytes.len(),
                layout.size(),
                layout.align(),
                matches!(C::STORAGE_TYPE, StorageType::Table) as u8,
                on_add,
                on_insert,
                on_replace,
                on_remove,
                on_despawn,
                &mut id,
            )
        };

        assert!(success, "Failed to register component: {}", name);

        ComponentId::new(id)
    }

    pub fn get_resource_id<R>(&self) -> Option<ComponentId>
    where
        R: TypePath,
    {
        self.get_resource_id_from_type_path(R::type_path())
    }

    pub fn get_resource_id_from_type_path(&self, type_path: &str) -> Option<ComponentId> {
        let type_path_cstring = CString::new(type_path).unwrap();
        let type_path_bytes = type_path_cstring.as_bytes_with_nul();

        let mut id: usize = 0;

        let success = unsafe {
            bevy_mod_ffi_guest_sys::world::bevy_world_get_resource_id(
                self.ptr,
                type_path_bytes.as_ptr(),
                type_path_bytes.len(),
                &mut id,
            )
        };
        if !success {
            return None;
        }

        Some(ComponentId::new(id))
    }

    pub fn get_resource<R>(&self) -> Option<&R>
    where
        R: TypePath + Pod + Zeroable,
    {
        let id = self.get_resource_id_from_type_path(R::type_path())?;
        let ptr = self.get_resource_by_id(id)?;
        Some(unsafe { ptr.deref() })
    }

    pub fn get_resource_by_id(&self, id: ComponentId) -> Option<Ptr<'_>> {
        let mut out_ptr: *mut u8 = std::ptr::null_mut();

        let success = unsafe {
            bevy_mod_ffi_guest_sys::world::bevy_world_get_resource(
                self.ptr,
                id.index(),
                &mut out_ptr,
            )
        };
        if !success {
            return None;
        }

        let ptr = NonNull::new(out_ptr)?;
        Some(unsafe { Ptr::new(ptr) })
    }

    pub fn get_component_id<R>(&self) -> Option<ComponentId>
    where
        R: TypePath,
    {
        self.get_component_id_from_type_path(R::type_path())
    }

    pub fn get_component_id_from_type_path(&self, type_path: &str) -> Option<ComponentId> {
        let type_path_cstring = CString::new(type_path).unwrap();
        let type_path_bytes = type_path_cstring.as_bytes_with_nul();

        let mut id: usize = 0;

        let success = unsafe {
            bevy_mod_ffi_guest_sys::world::bevy_world_get_component_id(
                self.ptr,
                type_path_bytes.as_ptr(),
                type_path_bytes.len(),
                &mut id,
            )
        };
        if !success {
            return None;
        }

        Some(ComponentId::new(id))
    }

    pub fn query<D: QueryData>(&mut self) -> QueryState<D> {
        self.query_filtered()
    }

    pub fn query_filtered<D: QueryData, F: QueryFilter>(&mut self) -> QueryState<D, F> {
        QueryState::new(self)
    }

    pub fn run_system<Marker, In, Out, S>(&mut self, input: In, system: S) -> Out
    where
        S: IntoSystem<Marker, In = In, Out = Out>,
        S::System: 'static,
        <S::System as System>::Param: 'static,
        In: Pod,
        Out: Pod,
    {
        let r = SystemState::<<S::System as System>::Param>::new(self).build(system);
        self.run_system_ref(input, r)
    }

    pub fn run_system_ref<In, Out, S>(&mut self, input: In, system: SystemRef<S>) -> Out
    where
        In: Pod,
        Out: Pod,
    {
        let input_bytes = bytemuck::bytes_of(&input);
        let mut output = bytemuck::zeroed_box::<Out>();

        unsafe {
            bevy_mod_ffi_guest_sys::world::bevy_world_run_system(
                self.ptr,
                system.ptr as *mut _,
                input_bytes.as_ptr(),
                &mut *output as *mut _ as _,
            )
        };

        *output
    }

    pub fn spawn<B: Bundle>(&mut self, bundle: B) -> EntityWorldMut<'_> {
        let mut components = Vec::new();
        let mut storage = Vec::new();
        bundle.bundle(self, &mut components, &mut storage);

        let mut entity_bits: u64 = 0;
        let mut entity_ptr = ptr::null_mut();
        let success = unsafe {
            bevy_mod_ffi_guest_sys::world::bevy_world_spawn(
                self.ptr,
                components.as_ptr(),
                components.len(),
                &mut entity_bits,
                &mut entity_ptr,
            )
        };
        assert!(success, "Failed to spawn entity");

        let entity = Entity::from_bits(entity_bits);
        unsafe { EntityWorldMut::from_ptr(entity, entity_ptr, self) }
    }

    pub fn entity_mut(&mut self, entity: Entity) -> EntityWorldMut<'_> {
        let mut entity_ptr = ptr::null_mut();
        let success = unsafe {
            bevy_mod_ffi_guest_sys::world::bevy_world_entity_mut(
                self.ptr,
                entity.to_bits(),
                &mut entity_ptr,
            )
        };
        assert!(success, "Failed to get entity {:?}", entity);

        unsafe { EntityWorldMut::from_ptr(entity, entity_ptr, self) }
    }

    pub fn add_observer<E, Marker, S>(&mut self, observer: S)
    where
        E: SharedEvent + 'static,
        S: IntoObserverSystem<E, Marker>,
        S::System: 'static,
        <S::System as System>::Param: 'static,
    {
        let mut system = observer.into_system();
        let mut builder = ParamBuilder::new();
        let mut state = <<S::System as System>::Param as SystemParam>::build(self, &mut builder);
        let state_ptr = builder.build(self);

        let event_name = E::type_path();
        let event_name_cstring = CString::new(event_name).unwrap();
        let event_name_bytes = event_name_cstring.as_bytes_with_nul();

        let observer_boxed: ObserverClosure = Box::new(move |params, event_ptr| {
            let mut param_cursor = ParamCursor::new(params);
            let params = unsafe {
                <<S::System as System>::Param as SystemParam>::get_param(
                    &mut state,
                    &mut param_cursor,
                )
            };

            let event = unsafe { &*(event_ptr as *const E) };
            system.run(On { event }, params);
        });

        let success = unsafe {
            bevy_mod_ffi_guest_sys::system::bevy_system_state_build_on(
                self.ptr,
                state_ptr,
                event_name_bytes.as_ptr(),
                event_name_bytes.len(),
                Box::into_raw(Box::new(observer_boxed)) as _,
                bevy_mod_ffi_guest_sys::system::bevy_guest_run_observer,
            )
        };

        assert!(success, "Failed to add observer for event: {}", event_name);
    }

    pub fn trigger<E: SharedEvent>(&mut self, event: E) {
        let event_name = E::type_path();
        let event_name_cstring = CString::new(event_name).unwrap();
        let event_name_bytes = event_name_cstring.as_bytes_with_nul();
        let event_bytes = bytemuck::bytes_of(&event);

        let success = unsafe {
            bevy_mod_ffi_guest_sys::world::bevy_world_trigger_event(
                self.ptr,
                event_name_bytes.as_ptr(),
                event_name_bytes.len(),
                event_bytes.as_ptr(),
                event_bytes.len(),
            )
        };

        assert!(success, "Failed to trigger event: {}", event_name);
    }

    pub fn trigger_targets<E: SharedEvent>(&mut self, event: E, entity: bevy_ecs::entity::Entity) {
        let event_name = E::type_path();
        let event_name_cstring = CString::new(event_name).unwrap();
        let event_name_bytes = event_name_cstring.as_bytes_with_nul();
        let event_bytes = bytemuck::bytes_of(&event);

        let success = unsafe {
            bevy_mod_ffi_guest_sys::world::bevy_world_trigger_event_targets(
                self.ptr,
                event_name_bytes.as_ptr(),
                event_name_bytes.len(),
                event_bytes.as_ptr(),
                event_bytes.len(),
                entity.to_bits(),
            )
        };

        assert!(
            success,
            "Failed to trigger event for entity: {}",
            event_name
        );
    }
}

pub trait Bundle {
    fn bundle(
        self,
        world: &mut World,
        components: &mut Vec<BundleComponent>,
        storage: &mut Vec<Box<[u8]>>,
    );
}

impl<C: SharedComponent + Pod> Bundle for C {
    fn bundle(
        self,
        world: &mut World,
        components: &mut Vec<BundleComponent>,
        storage: &mut Vec<Box<[u8]>>,
    ) {
        let component_id = world.get_component_id::<C>().unwrap();
        let bytes = bytemuck::bytes_of(&self).to_vec().into_boxed_slice();
        let ptr = bytes.as_ptr();
        storage.push(bytes);
        components.push(BundleComponent {
            component_id: component_id.index(),
            ptr,
        });
    }
}

macro_rules! impl_bundle_tuple {
    ($($item:ident),+) => {
        impl<$($item: Bundle),+> Bundle for ($($item,)+) {
            fn bundle(self, world: &mut World, components: &mut Vec<BundleComponent>, storage: &mut Vec<Box<[u8]>>) {
                #[allow(non_snake_case)]
                let ($($item,)+) = self;
                $(
                    $item.bundle(world, components, storage);
                )+
            }
        }
    };
}

impl_bundle_tuple!(B0, B1);
impl_bundle_tuple!(B0, B1, B2);
impl_bundle_tuple!(B0, B1, B2, B3);
impl_bundle_tuple!(B0, B1, B2, B3, B4);
impl_bundle_tuple!(B0, B1, B2, B3, B4, B5);
impl_bundle_tuple!(B0, B1, B2, B3, B4, B5, B6);
impl_bundle_tuple!(B0, B1, B2, B3, B4, B5, B6, B7);