diff --git a/lyra-ecs/src/main.rs b/lyra-ecs/src/main.rs index bb39af6..fc9dd56 100644 --- a/lyra-ecs/src/main.rs +++ b/lyra-ecs/src/main.rs @@ -6,6 +6,7 @@ mod bundle; mod component; mod query; mod component_info; +mod resource; #[cfg(test)] mod tests; diff --git a/lyra-ecs/src/resource.rs b/lyra-ecs/src/resource.rs new file mode 100644 index 0000000..a8e7a32 --- /dev/null +++ b/lyra-ecs/src/resource.rs @@ -0,0 +1,65 @@ +use std::{any::TypeId, alloc::Layout, cell::{RefCell, Ref, RefMut}, ptr::NonNull, alloc}; + +/// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually. +/* pub trait Resource: Send + Sync + 'static {} +impl Resource for T {} */ + +/// A type erased storage for a Resource. +/// +/// A pointer instead of a generic is used since Rust may have no idea of the type of the data. +pub struct ResourceData { + data: RefCell>, + type_id: TypeId, + layout: Layout, +} + +impl ResourceData { + pub fn new(mut data: T) -> Self { + let layout = Layout::new::(); + + let ptr = unsafe { + if let Some(ptr) = NonNull::new(alloc::alloc(layout)) { + let data = (&mut data as *mut T) as *mut u8; // thx i hate it + std::ptr::copy_nonoverlapping(data, ptr.as_ptr(), 1); + ptr + } else { + alloc::handle_alloc_error(layout) + } + }; + + Self { + data: RefCell::new(ptr), + type_id: TypeId::of::(), + layout, + } + } + + /// Returns a boolean indicating whether or not T is of the same type that is inside this Resource + pub fn is(&self) -> bool { + self.type_id == TypeId::of::() + } + + /// Borrow the data inside of the resource. + /// + /// # Safety + /// + /// If the type of T is not the same as the expected type, it will panic. + pub fn get<'a, T: 'static>(&'a self) -> Ref<'a, T> { + assert!(TypeId::of::() == self.type_id); + + let data = self.data.borrow(); + Ref::map(data, |ptr| unsafe { &*ptr.cast().as_ptr() }) + } + + /// Mutably borrow the data inside of the resource. + /// + /// # Safety + /// + /// If the type of T is not the same as the expected type, it will panic. + pub fn get_mut<'a, T: 'static>(&'a self) -> RefMut<'a, T> { + assert!(TypeId::of::() == self.type_id); + + let data = self.data.borrow_mut(); + RefMut::map(data, |ptr| unsafe { &mut *ptr.cast().as_ptr() }) + } +} \ No newline at end of file diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 1c0dfb1..a9f5852 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -1,6 +1,6 @@ -use std::collections::{HashMap, VecDeque}; +use std::{collections::{HashMap, VecDeque}, any::TypeId, cell::{Ref, RefMut}}; -use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, component::Component, query::{ViewIter, View, DefaultQuery}}; +use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, component::Component, query::{ViewIter, View, DefaultQuery}, resource::ResourceData}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct EntityId(pub u64); @@ -27,6 +27,7 @@ pub struct World { entity_index: HashMap, dead_entities: VecDeque, next_entity_id: EntityId, + resources: HashMap, } impl World { @@ -37,6 +38,7 @@ impl World { entity_index: HashMap::new(), dead_entities: VecDeque::new(), next_entity_id: EntityId(0), + resources: HashMap::new(), } } @@ -118,6 +120,44 @@ impl World { let v = View::new(T::default_query(), archetypes); v.into_iter() } + + pub fn add_resource(&mut self, data: T) { + self.resources.insert(TypeId::of::(), ResourceData::new(data)); + } + + /// Gets a resource from the World. + /// + /// Will panic if the resource is not in the world. See [`try_get_resource`] for + /// a function that returns an option. + pub fn get_resource<'a, T: 'static>(&'a self) -> Ref<'a, T> { + self.resources.get(&TypeId::of::()).unwrap() + .get() + } + + /// Attempts to get a resource from the World. + /// + /// Returns `None` if the resource was not found. + pub fn try_get_resource<'a, T: 'static>(&'a self) -> Option> { + self.resources.get(&TypeId::of::()) + .map(|r| r.get()) + } + + /// Gets a mutable borrow of a resource from the World. + /// + /// Will panic if the resource is not in the world. See [`try_get_resource_mut`] for + /// a function that returns an option. + pub fn get_resource_mut<'a, T: 'static>(&'a self) -> RefMut<'a, T> { + self.resources.get(&TypeId::of::()).unwrap() + .get_mut() + } + + /// Attempts to get a mutable borrow of a resource from the World. + /// + /// Returns `None` if the resource was not found. + pub fn try_get_resource_mut<'a, T: 'static>(&'a self) -> Option> { + self.resources.get(&TypeId::of::()) + .map(|r| r.get_mut()) + } } #[cfg(test)] @@ -171,4 +211,28 @@ mod tests { let record = world.entity_index.get(&last_en.id).unwrap(); assert_eq!(record.index.0, 1); } + + #[test] + fn simple_resource() { + struct SimpleCounter(i32); + + let mut world = World::new(); + { + let counter = SimpleCounter(0); + world.add_resource(counter); + } + + let counter = world.get_resource::(); + assert!(counter.0 == 0); + drop(counter); + + let mut counter = world.get_resource_mut::(); + counter.0 += 4582; + drop(counter); + + let counter = world.get_resource::(); + assert!(counter.0 == 4582); + + assert!(world.try_get_resource::().is_none()); + } } \ No newline at end of file