diff options
58 files changed, 3354 insertions, 1410 deletions
@@ -257,7 +257,6 @@ dependencies = [ "criterion", "ecs-macros", "hashbrown", - "linkme", "parking_lot", "paste", "seq-macro", @@ -503,26 +502,6 @@ dependencies = [ ] [[package]] -name = "linkme" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22d227772b5999ddc0690e733f734f95ca05387e329c4084fe65678c51198ffe" -dependencies = [ - "linkme-impl", -] - -[[package]] -name = "linkme-impl" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71a98813fa0073a317ed6a8055dcd4722a49d9b862af828ee68449adb799b6be" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/ecs-macros/src/lib.rs b/ecs-macros/src/lib.rs index aab8dd2..7d00736 100644 --- a/ecs-macros/src/lib.rs +++ b/ecs-macros/src/lib.rs @@ -14,7 +14,6 @@ use syn::{ ItemStruct, ItemUnion, Path, - Type, }; use toml::value::{Table as TomlTable, Value as TomlValue}; @@ -49,15 +48,11 @@ macro_rules! syn_path_segment { /// - Not attributed to a type item /// - The attributed-to type item is generic /// - If parsing the user crate's `Cargo.toml` file fails. -#[proc_macro_derive(Component, attributes(component))] +#[proc_macro_derive(Component)] pub fn component_derive(input: TokenStream) -> TokenStream { let item: TypeItem = parse::<Item>(input).unwrap().try_into().unwrap(); - let ComponentAttribute { handle_type, handle_mut_type } = item - .attribute::<ComponentAttribute>("component") - .unwrap_or_default(); - let item_ident = item.ident(); let (impl_generics, type_generics, where_clause) = item.generics().split_for_impl(); @@ -88,10 +83,6 @@ pub fn component_derive(input: TokenStream) -> TokenStream use ::std::sync::{LazyLock, Mutex}; use #ecs_path::component::Component; - use #ecs_path::component::{ - Handle as ComponentHandle, - HandleMut as ComponentHandleMut - }; use #ecs_path::uid::{Uid, Kind as UidKind}; use #ecs_path::system::Input as SystemInput; @@ -102,9 +93,6 @@ pub fn component_derive(input: TokenStream) -> TokenStream impl #impl_generics Component for #item_ident #type_generics #where_clause { - type HandleMut<'component> = #handle_mut_type; - type Handle<'component> = #handle_type; - fn id() -> Uid { *#id_var_ident @@ -326,61 +314,3 @@ fn find_engine_ecs_crate_path() -> Option<Path> None }) } - -#[derive(Debug)] -struct ComponentAttribute -{ - handle_type: proc_macro2::TokenStream, - handle_mut_type: proc_macro2::TokenStream, -} - -impl FromAttribute for ComponentAttribute -{ - fn from_attribute(attribute: &Attribute) -> Result<Self, syn::Error> - { - let mut handle_type: Option<Type> = None; - let mut handle_mut_type: Option<Type> = None; - - attribute.parse_nested_meta(|meta| { - let Some(flag) = meta.path.get_ident() else { - return Err(meta.error("Not a single identifier")); - }; - - if flag == "handle_type" { - let value = meta.value()?; - - handle_type = Some(value.parse::<Type>()?); - - return Ok(()); - } else if flag == "handle_mut_type" { - let value = meta.value()?; - - handle_mut_type = Some(value.parse::<Type>()?); - - return Ok(()); - } - - Err(meta.error("Unrecognized token")) - })?; - - Ok(Self { - handle_type: handle_type - .map_or_else(|| Self::default().handle_type, ToTokens::into_token_stream), - handle_mut_type: handle_mut_type.map_or_else( - || Self::default().handle_mut_type, - ToTokens::into_token_stream, - ), - }) - } -} - -impl Default for ComponentAttribute -{ - fn default() -> Self - { - Self { - handle_type: quote! { ComponentHandle<'component, Self> }, - handle_mut_type: quote! { ComponentHandleMut<'component, Self> }, - } - } -} diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml index cf35a74..5ea9fc7 100644 --- a/ecs/Cargo.toml +++ b/ecs/Cargo.toml @@ -11,7 +11,6 @@ seq-macro = "0.3.5" paste = "1.0.14" thiserror = "1.0.49" tracing = "0.1.39" -linkme = "0.3.29" hashbrown = "0.15.2" parking_lot = "0.12.3" ecs-macros = { path = "../ecs-macros" } diff --git a/ecs/examples/component_changed_event.rs b/ecs/examples/component_changed_event.rs new file mode 100644 index 0000000..f707255 --- /dev/null +++ b/ecs/examples/component_changed_event.rs @@ -0,0 +1,80 @@ +use ecs::event::component::Changed; +use ecs::pair::Pair; +use ecs::phase::UPDATE as UPDATE_PHASE; +use ecs::system::observer::Observe; +use ecs::{Component, Query, World}; + +#[derive(Component)] +struct SomeData +{ + num: u64, +} + +#[derive(Component)] +struct Greeting +{ + greeting: String, +} + +fn say_hello(query: Query<(&SomeData, &mut Greeting)>) +{ + for (data, mut greeting) in &query { + println!("{}: {}", greeting.greeting, data.num); + + if greeting.greeting == "Good evening" { + greeting.greeting = "Good morning".to_string(); + greeting.set_changed(); + } + } +} + +fn print_changed_greetings(observe: Observe<'_, Pair<Changed, Greeting>>) +{ + println!("\nChanged greetings:"); + + for ent in &observe { + let Some(greeting) = ent.get::<Greeting>() else { + unreachable!(); + }; + + println!("A greeting changed to {}", greeting.greeting); + } + + println!(""); +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*UPDATE_PHASE, say_hello); + + world.register_observer(print_changed_greetings); + + world.create_entity(( + SomeData { num: 987_654 }, + Greeting { + greeting: "Good afternoon".to_string(), + }, + )); + + world.create_entity(( + SomeData { num: 345 }, + Greeting { greeting: "Good evening".to_string() }, + )); + + world.step(); + + world.step(); + + for (mut greeting,) in &world.query::<(&mut Greeting,), ()>() { + if greeting.greeting == "Good afternoon" { + greeting.greeting = "Yo yo".to_string(); + greeting.set_changed(); + } + } + + world.step(); + + world.step(); +} diff --git a/ecs/examples/component_relationship.rs b/ecs/examples/component_relationship.rs new file mode 100644 index 0000000..4453e3a --- /dev/null +++ b/ecs/examples/component_relationship.rs @@ -0,0 +1,56 @@ +use ecs::pair::Pair; +use ecs::phase::START as START_PHASE; +use ecs::{Component, Query, World}; + +#[derive(Component)] +struct Person +{ + name: String, +} + +fn print_dog_likers(query: Query<(&Person, Pair<Likes, &Dogs>)>) +{ + for (person, liked_dogs) in &query { + println!( + "{} likes {} dogs!", + person.name, + if liked_dogs.large { "large" } else { "small" }, + ); + } +} + +#[derive(Component)] +struct Likes; + +#[derive(Component)] +struct Cats; + +#[derive(Component)] +struct Dogs +{ + large: bool, +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*START_PHASE, print_dog_likers); + + world.create_entity(( + Person { name: "Irving".to_string() }, + Pair::new_with_comp_target::<Likes>(Dogs { large: true }), + )); + + world.create_entity(( + Person { name: "Mark".to_string() }, + Pair::new_with_comp_target::<Likes>(Cats), + )); + + world.create_entity(( + Person { name: "Helena".to_string() }, + Pair::new_with_comp_target::<Likes>(Dogs { large: false }), + )); + + world.step(); +} diff --git a/ecs/examples/event_loop.rs b/ecs/examples/event_loop.rs index 61d7ba4..cc2f7f4 100644 --- a/ecs/examples/event_loop.rs +++ b/ecs/examples/event_loop.rs @@ -1,7 +1,7 @@ use ecs::actions::Actions; use ecs::pair::{ChildOf, Pair}; use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; -use ecs::{static_entity, Component, Query, World}; +use ecs::{declare_entity, Component, Query, World}; #[derive(Component)] struct Wool @@ -65,16 +65,20 @@ fn age(query: Query<(&mut Health, &Name)>, mut actions: Actions) } } -static_entity!(SHEER_PHASE, (Phase, Pair::new::<ChildOf>(*UPDATE_PHASE))); +declare_entity!(SHEER_PHASE, (Phase, Pair::new::<ChildOf>(*UPDATE_PHASE))); -static_entity!(FEED_PHASE, (Phase, Pair::new::<ChildOf>(*SHEER_PHASE))); +declare_entity!(FEED_PHASE, (Phase, Pair::new::<ChildOf>(*SHEER_PHASE))); -static_entity!(AGE_PHASE, (Phase, Pair::new::<ChildOf>(*FEED_PHASE))); +declare_entity!(AGE_PHASE, (Phase, Pair::new::<ChildOf>(*FEED_PHASE))); fn main() { let mut world = World::new(); + world.create_declared_entity(&SHEER_PHASE); + world.create_declared_entity(&FEED_PHASE); + world.create_declared_entity(&AGE_PHASE); + world.register_system(*SHEER_PHASE, sheer); world.register_system(*FEED_PHASE, feed); world.register_system(*AGE_PHASE, age); diff --git a/ecs/examples/relationship.rs b/ecs/examples/relationship.rs index b607398..6ad08e7 100644 --- a/ecs/examples/relationship.rs +++ b/ecs/examples/relationship.rs @@ -1,6 +1,5 @@ -use ecs::pair::Pair; +use ecs::pair::{Pair, Wildcard}; use ecs::phase::START as START_PHASE; -use ecs::uid::Wildcard; use ecs::{Component, Query, World}; #[derive(Component)] @@ -23,10 +22,10 @@ struct Holding; fn print_player_stats(player_query: Query<(&Player, &Health, Pair<Holding, Wildcard>)>) { - for (_, health, sword_relationship) in &player_query { + for (_, health, target_sword) in &player_query { println!("Player health: {}", health.health); - if let Some(sword_ent) = sword_relationship.get_target_entity() { + if let Some(sword_ent) = target_sword.get_entity() { let sword = sword_ent .get::<Sword>() .expect("Sword entity is missing sword component"); diff --git a/ecs/examples/with_local.rs b/ecs/examples/with_local.rs index 4658fc0..7a36d0e 100644 --- a/ecs/examples/with_local.rs +++ b/ecs/examples/with_local.rs @@ -1,6 +1,7 @@ use ecs::component::local::Local; use ecs::phase::UPDATE as UPDATE_PHASE; -use ecs::system::{Into, System}; +use ecs::system::initializable::Initializable; +use ecs::system::Into; use ecs::{Component, Query, World}; #[derive(Component)] diff --git a/ecs/examples/with_sole.rs b/ecs/examples/with_sole.rs index c3feaab..a292f06 100644 --- a/ecs/examples/with_sole.rs +++ b/ecs/examples/with_sole.rs @@ -1,7 +1,7 @@ use ecs::pair::{ChildOf, Pair}; use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; use ecs::sole::Single; -use ecs::{static_entity, Component, Query, Sole, World}; +use ecs::{declare_entity, Component, Query, Sole, World}; #[derive(Component)] struct Ammo @@ -31,7 +31,7 @@ fn print_total_ammo_count(ammo_counter: Single<AmmoCounter>) assert_eq!(ammo_counter.counter, 19); } -static_entity!( +declare_entity!( PRINT_AMMO_COUNT_PHASE, (Phase, Pair::new::<ChildOf>(*UPDATE_PHASE)) ); @@ -40,6 +40,8 @@ fn main() { let mut world = World::new(); + world.create_declared_entity(&PRINT_AMMO_COUNT_PHASE); + world.register_system(*UPDATE_PHASE, count_ammo); world.register_system(*PRINT_AMMO_COUNT_PHASE, print_total_ammo_count); diff --git a/ecs/src/actions.rs b/ecs/src/actions.rs index e0efeda..2dd68bf 100644 --- a/ecs/src/actions.rs +++ b/ecs/src/actions.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use std::rc::{Rc, Weak}; use crate::component::{Parts as ComponentParts, Sequence as ComponentSequence}; -use crate::system::{Param as SystemParam, System}; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; use crate::uid::{Kind as UidKind, Uid}; use crate::{ActionQueue, World}; @@ -100,17 +100,7 @@ impl<'world> SystemParam<'world> for Actions<'world> { type Input = (); - fn initialize<SystemImpl>( - _system: &mut impl System<'world, SystemImpl>, - _input: Self::Input, - ) - { - } - - fn new<SystemImpl>( - _system: &'world impl System<'world, SystemImpl>, - world: &'world World, - ) -> Self + fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self { Self::new(&world.data.action_queue) } diff --git a/ecs/src/component.rs b/ecs/src/component.rs index a0ed752..e4ecfce 100644 --- a/ecs/src/component.rs +++ b/ecs/src/component.rs @@ -1,10 +1,11 @@ use std::any::{type_name, Any}; -use std::error::Error; use std::fmt::Debug; use std::ops::{Deref, DerefMut}; use seq_macro::seq; +use crate::event::component::Changed; +use crate::event::Submitter as EventSubmitter; use crate::lock::{ Error as LockError, MappedReadGuard, @@ -12,6 +13,7 @@ use crate::lock::{ ReadGuard, WriteGuard, }; +use crate::pair::Pair; use crate::system::Input as SystemInput; use crate::uid::Uid; use crate::util::Array; @@ -23,14 +25,6 @@ pub(crate) mod storage; pub trait Component: SystemInput + Any { - type HandleMut<'component>: HandleFromEntityComponentRef<'component> - where - Self: Sized; - - type Handle<'component>: HandleFromEntityComponentRef<'component> - where - Self: Sized; - /// Returns the ID of this component. fn id() -> Uid where @@ -77,87 +71,44 @@ pub trait Sequence fn into_parts_array(self) -> Self::PartsArray; } -/// [`Component`] metadata. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct Metadata +#[derive(Debug)] +pub struct Handle<'a, DataT: 'static> { - pub id: Uid, + inner: MappedReadGuard<'a, DataT>, } -impl Metadata +impl<'comp, DataT: 'static> Handle<'comp, DataT> { - #[must_use] - pub fn of<ComponentT: Component>() -> Self - { - Self { id: ComponentT::id() } - } -} - -pub trait HandleFromEntityComponentRef<'comp>: Sized -{ - type Error: Error; - /// Creates a new handle instance from a [`EntityComponentRef`]. /// /// # Errors - /// See the implementation's [`Self::Error`] type. - fn from_entity_component_ref( - entity_component_ref: Option<EntityComponentRef<'comp>>, - world: &'comp World, - ) -> Result<Self, Self::Error>; -} - -#[derive(Debug)] -pub struct Handle<'a, ComponentData: 'static> -{ - inner: MappedReadGuard<'a, ComponentData>, -} - -impl<'a, ComponentData: 'static> Handle<'a, ComponentData> -{ - pub(crate) fn new(inner: ReadGuard<'a, Box<dyn Any>>) -> Self + /// Will return `Err` if acquiring the component's lock fails. + pub fn from_entity_component_ref( + entity_component_ref: &EntityComponentRef<'comp>, + ) -> Result<Self, HandleError> { - Self { - inner: inner.map(|component| { - component - .downcast_ref::<ComponentData>() - .unwrap_or_else(|| { - panic!( - "Failed to downcast component to type {}", - type_name::<ComponentData>() - ); - }) - }), - } + Self::new( + entity_component_ref + .component() + .read_nonblock() + .map_err(AcquireLockError)?, + ) } -} - -impl<'comp, ComponentData: 'static> HandleFromEntityComponentRef<'comp> - for Handle<'comp, ComponentData> -{ - type Error = HandleError; - fn from_entity_component_ref( - entity_component_ref: Option<EntityComponentRef<'comp>>, - _world: &'comp World, - ) -> Result<Self, Self::Error> + fn new(inner: ReadGuard<'comp, Box<dyn Any>>) -> Result<Self, HandleError> { - let entity_comp = - entity_component_ref.ok_or(HandleError::ComponentDoesNotExist)?; - - Ok(Self::new( - entity_comp - .component() - .read_nonblock() - .map_err(AcquireComponentLockFailed)?, - )) + Ok(Self { + inner: ReadGuard::try_map(inner, |component| { + component.downcast_ref::<DataT>() + }) + .map_err(|_| HandleError::IncorrectType)?, + }) } } -impl<ComponentData: 'static> Deref for Handle<'_, ComponentData> +impl<DataT: 'static> Deref for Handle<'_, DataT> { - type Target = ComponentData; + type Target = DataT; fn deref(&self) -> &Self::Target { @@ -166,55 +117,51 @@ impl<ComponentData: 'static> Deref for Handle<'_, ComponentData> } #[derive(Debug)] -pub struct HandleMut<'a, ComponentData: 'static> +pub struct HandleMut<'a, DataT: 'static> { - inner: MappedWriteGuard<'a, ComponentData>, + entity_component_ref: EntityComponentRef<'a>, + inner: MappedWriteGuard<'a, DataT>, + event_submitter: EventSubmitter<'a>, } -impl<'a, ComponentData: 'static> HandleMut<'a, ComponentData> +impl<'comp, DataT: 'static> HandleMut<'comp, DataT> { - pub(crate) fn new(inner: WriteGuard<'a, Box<dyn Any>>) -> Self + /// Creates a new handle instance from a [`EntityComponentRef`]. + /// + /// # Errors + /// Will return `Err` if acquiring the component's lock fails. + pub fn from_entity_component_ref( + entity_component_ref: &EntityComponentRef<'comp>, + world: &'comp World, + ) -> Result<Self, HandleError> { - Self { - inner: inner.map(|component| { - component - .downcast_mut::<ComponentData>() - .unwrap_or_else(|| { - panic!( - "Failed to downcast component to type {}", - type_name::<ComponentData>() - ); - }) - }), - } + let inner = entity_component_ref + .component() + .write_nonblock() + .map_err(AcquireLockError)?; + + Ok(Self { + entity_component_ref: entity_component_ref.clone(), + inner: WriteGuard::try_map(inner, |component| { + component.downcast_mut::<DataT>() + }) + .map_err(|_| HandleError::IncorrectType)?, + event_submitter: world.event_submitter(), + }) } -} -impl<'comp, ComponentData: 'static> HandleFromEntityComponentRef<'comp> - for HandleMut<'comp, ComponentData> -{ - type Error = HandleError; - - fn from_entity_component_ref( - entity_component_ref: Option<EntityComponentRef<'comp>>, - _world: &'comp World, - ) -> Result<Self, Self::Error> + pub fn set_changed(&self) { - let entity_comp = - entity_component_ref.ok_or(HandleError::ComponentDoesNotExist)?; - - Ok(Self::new( - entity_comp - .component() - .write_nonblock() - .map_err(AcquireComponentLockFailed)?, - )) + self.event_submitter.submit_event( + &Pair::new::<Changed>(self.entity_component_ref.id()), + self.entity_component_ref.entity_id(), + ); } } -impl<ComponentData: 'static> Deref for HandleMut<'_, ComponentData> +impl<DataT: 'static> Deref for HandleMut<'_, DataT> { - type Target = ComponentData; + type Target = DataT; fn deref(&self) -> &Self::Target { @@ -222,7 +169,7 @@ impl<ComponentData: 'static> Deref for HandleMut<'_, ComponentData> } } -impl<ComponentData: 'static> DerefMut for HandleMut<'_, ComponentData> +impl<DataT: 'static> DerefMut for HandleMut<'_, DataT> { fn deref_mut(&mut self) -> &mut Self::Target { @@ -234,15 +181,15 @@ impl<ComponentData: 'static> DerefMut for HandleMut<'_, ComponentData> pub enum HandleError { #[error(transparent)] - AcquireComponentLockFailed(#[from] AcquireComponentLockFailed), + AcquireLockFailed(#[from] AcquireLockError), - #[error("Component does not exist")] - ComponentDoesNotExist, + #[error("Incorrect component type")] + IncorrectType, } #[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct AcquireComponentLockFailed(LockError); +#[error("Failed to acquire component lock")] +pub struct AcquireLockError(#[source] LockError); macro_rules! inner { ($c: tt) => { @@ -357,6 +304,12 @@ impl PartsBuilder data: Box::new(data), } } + + #[must_use] + pub fn build_with_any_data(self, id: Uid, data: Box<dyn Any>) -> Parts + { + Parts { id, name: self.name, data } + } } impl Default for PartsBuilder diff --git a/ecs/src/component/local.rs b/ecs/src/component/local.rs index 0f6f641..6b2463f 100644 --- a/ecs/src/component/local.rs +++ b/ecs/src/component/local.rs @@ -1,7 +1,17 @@ +use std::any::type_name; use std::ops::{Deref, DerefMut}; -use crate::component::{Component, HandleMut as ComponentHandleMut}; -use crate::system::{Param as SystemParam, System}; +use ecs_macros::Component; + +use crate::component::{ + Component, + HandleMut as ComponentHandleMut, + IntoParts as _, + Parts as ComponentParts, +}; +use crate::pair::Pair; +use crate::system::initializable::Param as InitializableParam; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; use crate::World; /// Holds a component which is local to a single system. @@ -17,27 +27,44 @@ where { type Input = LocalComponent; - fn initialize<SystemImpl>( - system: &mut impl System<'world, SystemImpl>, - input: Self::Input, - ) + fn new(world: &'world World, system_metadata: &SystemMetadata) -> Self { - system.set_local_component(input); - } + let Some(system_ent) = world.get_entity(system_metadata.ent_id) else { + panic!( + "System entity with ID {} does not exist", + system_metadata.ent_id + ); + }; - fn new<SystemImpl>( - system: &'world impl System<'world, SystemImpl>, - _world: &'world World, - ) -> Self - { - let local_component = system - .get_local_component_mut::<LocalComponent>() - .expect("Local component is uninitialized"); + let Some(local_component) = system_ent.get_with_id_mut::<LocalComponent>( + Pair::new::<IsLocalComponent>(LocalComponent::id()).id(), + ) else { + panic!( + "Local component {} of system with ID {} is uninitialized", + type_name::<LocalComponent>(), + system_metadata.ent_id + ); + }; Self { local_component } } } +impl<'world, LocalComponent, SystemT> InitializableParam<'world, SystemT> + for Local<'world, LocalComponent> +where + LocalComponent: Component, + SystemT: SystemWithLocalComponents, + Self: SystemParam<'world, Input = LocalComponent>, +{ + fn initialize(system: &mut SystemT, input: Self::Input) + { + system.add_local_component( + Pair::new_with_comp_target::<IsLocalComponent>(input).into_parts(), + ); + } +} + impl<LocalComponent> Deref for Local<'_, LocalComponent> where LocalComponent: Component, @@ -59,3 +86,11 @@ where &mut self.local_component } } + +pub trait SystemWithLocalComponents +{ + fn add_local_component(&mut self, component_parts: ComponentParts); +} + +#[derive(Component)] +struct IsLocalComponent; diff --git a/ecs/src/component/storage.rs b/ecs/src/component/storage.rs index b27b552..4ec5222 100644 --- a/ecs/src/component/storage.rs +++ b/ecs/src/component/storage.rs @@ -270,7 +270,7 @@ impl Storage entity.insert_component( component_id, - ArchetypeEntityComponent::new(component, component_id, component_name), + ArchetypeEntityComponent::new(component, component_name), add_edge_archetype, ); diff --git a/ecs/src/component/storage/archetype.rs b/ecs/src/component/storage/archetype.rs index a88e0e8..d96632e 100644 --- a/ecs/src/component/storage/archetype.rs +++ b/ecs/src/component/storage/archetype.rs @@ -122,7 +122,7 @@ impl Archetype pub fn get_matching_component_indices( &self, component_id: Uid, - ) -> MatchingComponentIter + ) -> MatchingComponentIter<'_> { assert!( component_id.kind() == UidKind::Component @@ -314,29 +314,18 @@ impl Entity #[derive(Debug)] pub struct EntityComponent { - id: Uid, component: Lock<Box<dyn Any>>, } impl EntityComponent { - pub fn new( - component: Box<dyn Any>, - component_id: Uid, - component_name: &'static str, - ) -> Self + pub fn new(component: Box<dyn Any>, component_name: &'static str) -> Self { Self { - id: component_id, component: Lock::new(component, component_name), } } - pub fn id(&self) -> Uid - { - self.id - } - pub fn component(&self) -> &Lock<Box<dyn Any>> { &self.component @@ -370,11 +359,10 @@ impl Id } for comp_id in component_id_iter { - if prev_component_id.is_some_and(|prev_comp_id| *comp_id < prev_comp_id) { - panic!( - "Cannot create archetype ID from a unsorted component metadata list" - ); - } + assert!( + prev_component_id.is_none_or(|prev_comp_id| *comp_id >= prev_comp_id), + "Cannot create archetype ID from a unsorted component metadata list" + ); prev_component_id = Some(*comp_id); diff --git a/ecs/src/component/storage/graph.rs b/ecs/src/component/storage/graph.rs index 29fa937..76200f9 100644 --- a/ecs/src/component/storage/graph.rs +++ b/ecs/src/component/storage/graph.rs @@ -80,7 +80,7 @@ impl Graph pub fn dfs_archetype_add_edges( &self, archetype_id: ArchetypeId, - ) -> Option<ArchetypeAddEdgeDfsIter> + ) -> Option<ArchetypeAddEdgeDfsIter<'_>> { let node = self.get_node_by_id(archetype_id)?; diff --git a/ecs/src/entity.rs b/ecs/src/entity.rs index 562f7ea..bec50cd 100644 --- a/ecs/src/entity.rs +++ b/ecs/src/entity.rs @@ -1,6 +1,6 @@ use std::any::type_name; - -use linkme::distributed_slice; +use std::ops::Deref; +use std::sync::LazyLock; use crate::component::storage::archetype::{ Archetype, @@ -10,19 +10,20 @@ use crate::component::storage::archetype::{ use crate::component::{ Component, Handle as ComponentHandle, - HandleFromEntityComponentRef, HandleMut as ComponentHandleMut, }; use crate::uid::{Kind as UidKind, Uid}; use crate::{EntityComponentRef, World}; +pub mod obtainer; + /// A handle to a entity. #[derive(Debug)] pub struct Handle<'a> { - world: &'a World, archetype: &'a Archetype, entity: &'a ArchetypeEntity, + world: &'a World, } impl<'a> Handle<'a> @@ -43,20 +44,21 @@ impl<'a> Handle<'a> /// - The component's ID is not a component ID /// - The component is mutably borrowed elsewhere #[must_use] - pub fn get<ComponentT: Component>(&self) -> Option<ComponentHandle<'_, ComponentT>> + pub fn get<ComponentT: Component>(&self) -> Option<ComponentHandle<'a, ComponentT>> { assert_eq!(ComponentT::id().kind(), UidKind::Component); let component = self.get_matching_components(ComponentT::id()).next()?; Some( - ComponentHandle::from_entity_component_ref(Some(component), self.world) - .unwrap_or_else(|err| { + ComponentHandle::from_entity_component_ref(&component).unwrap_or_else( + |err| { panic!( - "Taking component {} lock failed: {err}", + "Creating handle to component {} failed: {err}", type_name::<ComponentT>() ); - }), + }, + ), ) } @@ -70,23 +72,88 @@ impl<'a> Handle<'a> #[must_use] pub fn get_mut<ComponentT: Component>( &self, - ) -> Option<ComponentHandleMut<'_, ComponentT>> + ) -> Option<ComponentHandleMut<'a, ComponentT>> { assert_eq!(ComponentT::id().kind(), UidKind::Component); let component = self.get_matching_components(ComponentT::id()).next()?; Some( - ComponentHandleMut::from_entity_component_ref(Some(component), self.world) + ComponentHandleMut::from_entity_component_ref(&component, self.world) .unwrap_or_else(|err| { panic!( - "Taking component {} lock failed: {err}", + "Creating handle to component {} failed: {err}", type_name::<ComponentT>() ); }), ) } + /// Returns a reference to the component with the ID `id` in this entity. + /// `None` is returned if the component isn't found. + /// + /// # Panics + /// Will panic if: + /// - The ID is not a component/pair ID + /// - The component is borrowed mutably elsewhere + /// - The component type is incorrect + #[must_use] + pub fn get_with_id<ComponentDataT: 'static>( + &self, + id: Uid, + ) -> Option<ComponentHandle<'a, ComponentDataT>> + { + assert!( + matches!(id.kind(), UidKind::Component | UidKind::Pair), + "ID {id:?} is not a component/pair ID" + ); + + let component = self.get_matching_components(id).next()?; + + Some( + ComponentHandle::from_entity_component_ref(&component).unwrap_or_else( + |err| { + panic!( + "Creating handle to component {} failed: {err}", + type_name::<ComponentDataT>() + ); + }, + ), + ) + } + + /// Returns a mutable reference to the component with the ID `id` in this entity. + /// `None` is returned if the component isn't found. + /// + /// # Panics + /// Will panic if: + /// - The ID is not a component/pair ID + /// - The component is borrowed elsewhere + /// - The component type is incorrect + #[must_use] + pub fn get_with_id_mut<ComponentDataT: 'static>( + &self, + id: Uid, + ) -> Option<ComponentHandleMut<'a, ComponentDataT>> + { + assert!( + matches!(id.kind(), UidKind::Component | UidKind::Pair), + "ID {id:?} is not a component/pair ID" + ); + + let component = self.get_matching_components(id).next()?; + + Some( + ComponentHandleMut::from_entity_component_ref(&component, self.world) + .unwrap_or_else(|err| { + panic!( + "Creating handle to component {} failed: {err}", + type_name::<ComponentDataT>() + ); + }), + ) + } + #[inline] #[must_use] pub fn get_matching_components(&self, component_uid: Uid) @@ -98,13 +165,21 @@ impl<'a> Handle<'a> } } + /// Returns whether or not this entity contains a component with the specified `Uid`. + #[must_use] + pub fn has_component(&self, component_uid: Uid) -> bool + { + self.archetype + .contains_component_with_exact_id(component_uid) + } + pub(crate) fn new( - world: &'a World, archetype: &'a Archetype, entity: &'a ArchetypeEntity, + world: &'a World, ) -> Self { - Self { world, archetype, entity } + Self { archetype, entity, world } } } @@ -126,35 +201,53 @@ impl<'a> Iterator for MatchingComponentIter<'a> Some(EntityComponentRef::new( matching_component_id, self.entity.components().get(index).unwrap(), + self.entity.uid(), )) } } +/// The data type of a declaration of a entity. +#[derive(Debug)] +pub struct Declaration +{ + uid: LazyLock<Uid>, + create_func: fn(&mut World), +} + +impl Declaration +{ + pub(crate) fn create(&self, world: &mut World) + { + (self.create_func)(world); + } + + #[doc(hidden)] + pub const fn new(create_func: fn(&mut World)) -> Self + { + Self { + uid: LazyLock::new(|| Uid::new_unique(UidKind::Entity)), + create_func, + } + } +} + +impl Deref for Declaration +{ + type Target = Uid; + + fn deref(&self) -> &Self::Target + { + &self.uid + } +} + #[allow(clippy::module_name_repetitions)] #[macro_export] -macro_rules! static_entity { +macro_rules! declare_entity { ($visibility: vis $ident: ident, $components: expr) => { - $visibility static $ident: ::std::sync::LazyLock<$crate::uid::Uid> = - ::std::sync::LazyLock::new(|| { - $crate::uid::Uid::new_unique($crate::uid::Kind::Entity) + $visibility static $ident: $crate::entity::Declaration = + $crate::entity::Declaration::new(|world| { + world.create_entity_with_uid(*$ident, $components); }); - - $crate::private::paste::paste! { - mod [<__ecs_ $ident:lower _static_entity_priv>] { - use super::*; - - #[$crate::private::linkme::distributed_slice( - $crate::entity::CREATE_STATIC_ENTITIES - )] - #[linkme(crate=$crate::private::linkme)] - static CREATE_STATIC_ENTITY: fn(&mut $crate::World) = |world| { - world.create_entity_with_uid($components, *$ident); - }; - } - } } } - -#[distributed_slice] -#[doc(hidden)] -pub static CREATE_STATIC_ENTITIES: [fn(&mut World)]; diff --git a/ecs/src/entity/obtainer.rs b/ecs/src/entity/obtainer.rs new file mode 100644 index 0000000..6c2ea96 --- /dev/null +++ b/ecs/src/entity/obtainer.rs @@ -0,0 +1,29 @@ +use crate::entity::Handle as EntityHandle; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; +use crate::uid::Uid; +use crate::World; + +#[derive(Debug)] +pub struct Obtainer<'world> +{ + world: &'world World, +} + +impl<'world> SystemParam<'world> for Obtainer<'world> +{ + type Input = (); + + fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self + { + Self { world } + } +} + +impl Obtainer<'_> +{ + #[must_use] + pub fn get_entity(&self, entity_id: Uid) -> Option<EntityHandle<'_>> + { + self.world.get_entity(entity_id) + } +} diff --git a/ecs/src/event.rs b/ecs/src/event.rs index 9cea807..2934b82 100644 --- a/ecs/src/event.rs +++ b/ecs/src/event.rs @@ -1 +1,101 @@ +use crate::lock::Lock; +use crate::pair::Pair; +use crate::uid::{Kind as UidKind, Uid}; +use crate::util::VecExt; +use crate::World; + pub mod component; + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Emitted<'a> +{ + pub event: Uid, + pub match_ids: &'a [Uid], +} + +#[derive(Debug)] +pub struct Submitter<'world> +{ + new_events: &'world Lock<NewEvents>, +} + +impl<'world> Submitter<'world> +{ + /// Submits a event to be handled later. + /// + /// # Panics + /// Will panic if unable to acquire a read-write lock to the event store. + pub fn submit_event(&self, event: &Pair<Uid, Uid>, match_id: Uid) + { + let mut new_events_lock = self + .new_events + .write_nonblock() + .expect("Failed to acquire read-write lock to new events"); + + new_events_lock.push_event_match(event, match_id); + } + + pub(crate) fn new(world: &'world World) -> Self + { + Self { new_events: &world.data.new_events } + } +} + +#[derive(Debug, Default)] +pub(crate) struct NewEvents +{ + events: Vec<(Uid, Matches)>, +} + +impl NewEvents +{ + pub fn push_event_match(&mut self, event: &Pair<Uid, Uid>, match_id: Uid) + { + let event_id = event.id(); + + assert_eq!(event_id.kind(), UidKind::Pair); + + if let Ok(event_index) = self + .events + .binary_search_by_key(&event_id, |(other_event_id, _)| *other_event_id) + { + let Some((_, matches)) = self.events.get_mut(event_index) else { + unreachable!(); + }; + + matches.sorted_push(match_id); + + return; + } + + self.events.insert_at_partition_point_by_key( + (event_id, Matches { match_ids: Vec::from([match_id]) }), + |(other_event_id, _)| *other_event_id, + ); + } + + pub fn take(&mut self) -> Vec<(Uid, Matches)> + { + std::mem::take(&mut self.events) + } +} + +#[derive(Debug)] +pub(crate) struct Matches +{ + pub match_ids: Vec<Uid>, +} + +impl Matches +{ + fn sorted_push(&mut self, match_id: Uid) + { + if self.match_ids.binary_search(&match_id).is_ok() { + return; + } + + self.match_ids + .insert_at_partition_point_by_key(match_id, |other_match_id| *other_match_id); + } +} diff --git a/ecs/src/event/component.rs b/ecs/src/event/component.rs index ef09480..421c369 100644 --- a/ecs/src/event/component.rs +++ b/ecs/src/event/component.rs @@ -1,18 +1,22 @@ //! Component events. use std::convert::Infallible; -use std::fmt::Debug; use crate::Component; -/// Pair relation for events emitted when: -/// a) A entity with the target component is spawned. -/// b) The target component is added to a entity. -#[derive(Debug, Component)] -pub struct Added(Infallible); +// TODO: Implement +// /// Pair relation for events emitted when: +// /// a) A entity with the target component is spawned. +// /// b) The target component is added to a entity. +// #[derive(Debug, Component)] +// pub struct Added(Infallible); + +// TODO: Implement +// /// Pair relation for events emitted when: +// /// a) The target component is removed from a entity. +// /// b) A entity with the target component is despawned. +// #[derive(Debug, Component)] +// pub struct Removed(Infallible); -/// Pair relation for events emitted when: -/// a) The target component is removed from a entity. -/// b) A entity with the target component is despawned. #[derive(Debug, Component)] -pub struct Removed(Infallible); +pub struct Changed(Infallible); diff --git a/ecs/src/extension.rs b/ecs/src/extension.rs index 42ebef9..9c6614b 100644 --- a/ecs/src/extension.rs +++ b/ecs/src/extension.rs @@ -1,5 +1,7 @@ use crate::component::Sequence as ComponentSequence; +use crate::entity::Declaration as EntityDeclaration; use crate::sole::Sole; +use crate::system::observer::Observer; use crate::system::System; use crate::uid::Uid; use crate::{SoleAlreadyExistsError, World}; @@ -34,6 +36,15 @@ impl<'world> Collector<'world> self.world.register_system(phase_euid, system); } + /// Adds a observer system to the [`World`]. + pub fn add_observer<'this, SystemImpl>( + &'this mut self, + observer: impl Observer<'this, SystemImpl>, + ) + { + self.world.register_observer(observer); + } + /// Adds a entity to the [`World`]. pub fn add_entity<Comps>(&mut self, components: Comps) where @@ -42,6 +53,12 @@ impl<'world> Collector<'world> self.world.create_entity(components); } + /// Adds a declared entity to the [`World`]. + pub fn add_declared_entity(&mut self, entity_decl: &EntityDeclaration) + { + self.world.create_declared_entity(entity_decl); + } + /// Adds a globally shared singleton value to the [`World`]. /// /// # Errors diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs index 53abc6b..e9494a7 100644 --- a/ecs/src/lib.rs +++ b/ecs/src/lib.rs @@ -1,7 +1,6 @@ #![deny(clippy::all, clippy::pedantic)] use std::any::{type_name, Any, TypeId}; -use std::cell::RefCell; use std::fmt::Debug; use std::mem::ManuallyDrop; use std::rc::Rc; @@ -15,31 +14,30 @@ use crate::component::storage::archetype::EntityComponent as ArchetypeEntityComp use crate::component::storage::Storage as ComponentStorage; use crate::component::{ Component, + IntoParts as IntoComponentParts, Parts as ComponentParts, Sequence as ComponentSequence, }; -use crate::entity::CREATE_STATIC_ENTITIES; -use crate::event::component::{ - Added as ComponentAddedEvent, - Removed as ComponentRemovedEvent, -}; +use crate::entity::{Declaration as EntityDeclaration, Handle as EntityHandle}; +use crate::event::{Emitted as EmittedEvent, NewEvents, Submitter as EventSubmitter}; use crate::extension::{Collector as ExtensionCollector, Extension}; use crate::lock::Lock; -use crate::pair::{ChildOf, DependsOn, Pair}; +use crate::pair::{ChildOf, DependsOn, Pair, Wildcard}; use crate::phase::{Phase, START as START_PHASE}; use crate::query::flexible::Query as FlexibleQuery; use crate::query::term::Without; use crate::query::{ - Iter as QueryIter, TermWithFieldTuple as QueryTermWithFieldTuple, TermWithoutFieldTuple as QueryTermWithoutFieldTuple, Terms as QueryTerms, TermsBuilderInterface, + MAX_TERM_CNT as QUERY_MAX_TERM_CNT, }; -use crate::sole::Sole; +use crate::sole::{Single, Sole}; use crate::stats::Stats; -use crate::system::{System, SystemComponent}; -use crate::uid::{Kind as UidKind, Uid, Wildcard}; +use crate::system::observer::{Observer, WrapperComponent as ObserverWrapperComponent}; +use crate::system::{Callbacks, Metadata as SystemMetadata, System, SystemComponent}; +use crate::uid::{Kind as UidKind, Uid}; pub mod actions; pub mod component; @@ -56,9 +54,6 @@ pub mod tuple; pub mod uid; pub mod util; -#[doc(hidden)] -pub mod private; - mod lock; pub use ecs_macros::{Component, Sole}; @@ -84,49 +79,48 @@ impl World is_first_tick: AtomicBool::new(false), }; - world.add_sole(Stats::default()).ok(); + crate::phase::spawn_entities(&mut world); - for create_static_entity in CREATE_STATIC_ENTITIES { - create_static_entity(&mut world); - } + world.add_sole(Stats::default()).ok(); world } - /// Creates a new entity with the given components. + /// Creates a entity with the given components. A new unique [`Uid`] will be generated + /// for this entity. pub fn create_entity<Comps>(&mut self, components: Comps) -> Uid where Comps: ComponentSequence, { let entity_uid = Uid::new_unique(UidKind::Entity); - self.create_entity_with_uid(components, entity_uid); + self.create_entity_with_uid(entity_uid, components); entity_uid } + /// Creates a entity with the given components. The entity will have the specified + /// [`Uid`]. #[tracing::instrument(skip_all)] - #[doc(hidden)] - pub fn create_entity_with_uid<Comps>(&mut self, components: Comps, entity_uid: Uid) + pub fn create_entity_with_uid<Comps>(&mut self, entity_uid: Uid, components: Comps) where Comps: ComponentSequence, { - debug_assert_eq!(entity_uid.kind(), UidKind::Entity); - - if let Err(err) = self.data.component_storage.create_entity(entity_uid) { - tracing::warn!("Failed to create entity: {err}"); - return; - } + self.create_ent(entity_uid, components.into_parts_array()); + } - let added_component_ids = Self::add_entity_components( - entity_uid, - components.into_parts_array(), + pub fn add_component(&mut self, entity_id: Uid, component_parts: ComponentParts) + { + Self::add_entity_components( + entity_id, + [component_parts], &mut self.data.component_storage, ); + } - for comp_id in added_component_ids { - self.emit_event_by_id::<ComponentAddedEvent>(comp_id); - } + pub fn create_declared_entity(&mut self, entity_decl: &EntityDeclaration) + { + entity_decl.create(self); } /// Adds a globally shared singleton value. @@ -140,28 +134,42 @@ impl World self.data.sole_storage.insert(sole) } - pub fn register_system<'this, SystemImpl>( + pub fn register_observer<'this, SystemImpl, ObserverT>( &'this mut self, - phase_euid: Uid, - system: impl System<'this, SystemImpl>, - ) + observer: ObserverT, + ) where + ObserverT: Observer<'this, SystemImpl>, { - self.create_entity(( - SystemComponent { system: system.into_type_erased() }, - Pair::new::<DependsOn>(phase_euid), - )); + let (wrapper_comp, mut system_callbacks) = observer.finish_observer(); + + let ent_id = Uid::new_unique(UidKind::Entity); + + self.create_ent( + ent_id, + [wrapper_comp.into_parts()].into_iter().chain( + ObserverT::observed_events() + .into_iter() + .map(IntoComponentParts::into_parts), + ), + ); + + system_callbacks.on_created(self, SystemMetadata { ent_id }); } - pub fn register_observer_system<'this, SystemImpl>( + pub fn register_system<'this, SystemImpl>( &'this mut self, + phase_euid: Uid, system: impl System<'this, SystemImpl>, - event: Pair<Uid, Uid>, ) { - self.create_entity(( - SystemComponent { system: system.into_type_erased() }, - event, + let (type_erased_system, mut system_callbacks) = system.finish(); + + let system_ent_id = self.create_entity(( + SystemComponent { system: type_erased_system }, + Pair::new::<DependsOn>(phase_euid), )); + + system_callbacks.on_created(self, SystemMetadata { ent_id: system_ent_id }); } /// Adds a extensions. @@ -172,7 +180,9 @@ impl World extension.collect(extension_collector); } - pub fn query<FieldTerms, FieldlessTerms>(&self) -> Query<FieldTerms, FieldlessTerms> + pub fn query<FieldTerms, FieldlessTerms>( + &self, + ) -> Query<'_, FieldTerms, FieldlessTerms> where FieldTerms: QueryTermWithFieldTuple, FieldlessTerms: QueryTermWithoutFieldTuple, @@ -188,6 +198,30 @@ impl World FlexibleQuery::new(self, terms) } + pub fn get_entity(&self, entity_id: Uid) -> Option<EntityHandle<'_>> + { + let archetype = self + .data + .component_storage + .get_entity_archetype(entity_id)?; + + let Some(entity) = archetype.get_entity_by_id(entity_id) else { + unreachable!("Should exist since archetype was found by entity id"); + }; + + Some(EntityHandle::new(archetype, entity, self)) + } + + pub fn get_sole<SoleT: Sole>(&self) -> Option<Single<'_, SoleT>> + { + Some(Single::new(self.data.sole_storage.get::<SoleT>()?)) + } + + pub fn event_submitter(&self) -> EventSubmitter<'_> + { + EventSubmitter::new(self) + } + /// Performs a single tick. /// # Panics /// Will panic if mutable internal lock cannot be acquired. @@ -207,6 +241,8 @@ impl World self.perform_phases(); + self.emit_new_events(); + self.data.component_storage.create_imaginary_archetypes(); self.perform_queued_actions(); @@ -215,17 +251,9 @@ impl World return StepResult::Stop; } - let mut stats_lock = self - .data - .sole_storage - .get::<Stats>() - .expect("No stats sole found") - .write_nonblock() - .expect("Failed to aquire read-write stats sole lock"); - - let stats = stats_lock - .downcast_mut::<Stats>() - .expect("Casting stats sole to Stats type failed"); + let Some(mut stats) = self.get_sole::<Stats>() else { + unreachable!(); // Reason: is added in World::new + }; stats.current_tick += 1; @@ -285,23 +313,46 @@ impl World ) } + #[tracing::instrument(skip_all)] + fn create_ent( + &mut self, + entity_uid: Uid, + components: impl IntoIterator<Item = ComponentParts>, + ) + { + debug_assert_eq!(entity_uid.kind(), UidKind::Entity); + + if let Err(err) = self.data.component_storage.create_entity(entity_uid) { + tracing::warn!("Failed to create entity: {err}"); + return; + } + + Self::add_entity_components( + entity_uid, + components, + &mut self.data.component_storage, + ); + } + fn query_and_run_systems(&self, phase_euid: Uid) { - let system_query = self.flexible_query( - QueryTerms::<2>::builder() - .with_required([ - SystemComponent::id(), - Pair::new::<DependsOn>(phase_euid).id(), - ]) - .build(), + let system_query = Query::<(&SystemComponent,)>::from_flexible_query( + self.flexible_query( + QueryTerms::<QUERY_MAX_TERM_CNT>::builder() + .with_required([ + SystemComponent::id(), + Pair::new::<DependsOn>(phase_euid).id(), + ]) + .build(), + ), ); - for (system_component,) in - QueryIter::<(&SystemComponent,), _>::new(self, system_query.iter()) - { + for (system_ent_id, (system_component,)) in system_query.iter_with_euids() { // SAFETY: The world lives long enough unsafe { - system_component.system.run(self); + system_component + .system + .run(self, SystemMetadata { ent_id: system_ent_id }); } } } @@ -337,25 +388,39 @@ impl World } } - #[tracing::instrument(skip_all)] - fn perform_queued_actions(&mut self) + fn emit_new_events(&self) { - let mut active_action_queue = match *self.data.action_queue.active_queue.borrow() - { - ActiveActionQueue::A => &self.data.action_queue.queue_a, - ActiveActionQueue::B => &self.data.action_queue.queue_b, - } - .write_nonblock() - .unwrap_or_else(|err| { - panic!( - "Failed to take read-write action queue lock {:?}: {err}", - self.data.action_queue.active_queue + let new_events = self + .data + .new_events + .write_nonblock() + .expect("Failed to acquire read-write lock to new events") + .take(); + + for (event_id, event_matches) in new_events { + self.emit_event_observers( + event_id, + &EmittedEvent { + event: event_id, + match_ids: &event_matches.match_ids, + }, ); - }); + } + } - let mut has_swapped_active_queue = false; + #[tracing::instrument(skip_all)] + fn perform_queued_actions(&mut self) + { + let mut action_queue_lock = self + .data + .action_queue + .queue + .write_nonblock() + .unwrap_or_else(|err| { + panic!("Failed to take read-write action queue lock: {err}",); + }); - for action in active_action_queue.drain(..) { + for action in action_queue_lock.drain(..) { match action { Action::Spawn(components) => { let new_entity_uid = Uid::new_unique(UidKind::Entity); @@ -367,69 +432,32 @@ impl World continue; } - let added_component_ids = Self::add_entity_components( + Self::add_entity_components( new_entity_uid, components, &mut self.data.component_storage, ); - - if !has_swapped_active_queue { - self.swap_event_queue(&mut has_swapped_active_queue); - } - - for comp_id in added_component_ids { - self.emit_event_by_id::<ComponentAddedEvent>(comp_id); - } } Action::Despawn(entity_uid) => { - let removed_entity = - match self.data.component_storage.remove_entity(entity_uid) { - Ok(components) => components, - Err(err) => { - tracing::error!("Failed to despawn entity: {err}"); - return; - } - }; - - if !has_swapped_active_queue { - self.swap_event_queue(&mut has_swapped_active_queue); - } - - for removed_ent_comp in removed_entity.components() { - self.emit_event_by_id::<ComponentRemovedEvent>( - removed_ent_comp.id(), - ); + if let Err(err) = + self.data.component_storage.remove_entity(entity_uid) + { + tracing::error!("Failed to despawn entity: {err}"); } } Action::AddComponents(entity_uid, components) => { - let added_component_ids = Self::add_entity_components( + Self::add_entity_components( entity_uid, components, &mut self.data.component_storage, ); - - if !has_swapped_active_queue { - self.swap_event_queue(&mut has_swapped_active_queue); - } - - for comp_id in added_component_ids { - self.emit_event_by_id::<ComponentAddedEvent>(comp_id); - } } Action::RemoveComponents(entity_uid, component_ids) => { - let removed_component_ids = Self::remove_entity_components( + Self::remove_entity_components( entity_uid, component_ids, &mut self.data.component_storage, ); - - if !has_swapped_active_queue { - self.swap_event_queue(&mut has_swapped_active_queue); - } - - for comp_id in removed_component_ids { - self.emit_event_by_id::<ComponentRemovedEvent>(comp_id); - } } Action::Stop => { self.stop.store(true, Ordering::Relaxed); @@ -491,36 +519,28 @@ impl World removed_component_ids } - fn emit_event_by_id<Event: Component>(&self, target: Uid) + fn emit_event_observers(&self, event_id: Uid, emitted_event: &EmittedEvent<'_>) { - if target.kind() == UidKind::Pair { - return; - } - - let query = self.flexible_query( - QueryTerms::<2>::builder() - .with_required([SystemComponent::id(), Pair::new::<Event>(target).id()]) - .build(), + assert_eq!(event_id.kind(), UidKind::Pair); + + let query = Query::<(&ObserverWrapperComponent,)>::from_flexible_query( + self.flexible_query( + QueryTerms::<QUERY_MAX_TERM_CNT>::builder() + .with_required([ObserverWrapperComponent::id(), event_id]) + .build(), + ), ); - for (system,) in QueryIter::<(&SystemComponent,), _>::new(self, query.iter()) { + for (observer_ent_id, (observer,)) in query.iter_with_euids() { unsafe { - system.system.run(self); + observer.run( + self, + SystemMetadata { ent_id: observer_ent_id }, + emitted_event.clone(), + ); } } } - - fn swap_event_queue(&self, has_swapped_active_queue: &mut bool) - { - let mut active_queue = self.data.action_queue.active_queue.borrow_mut(); - - *active_queue = match *active_queue { - ActiveActionQueue::A => ActiveActionQueue::B, - ActiveActionQueue::B => ActiveActionQueue::A, - }; - - *has_swapped_active_queue = true; - } } impl Default for World @@ -541,31 +561,21 @@ pub enum StepResult Stop, } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct WorldData { component_storage: ComponentStorage, sole_storage: SoleStorage, action_queue: Rc<ActionQueue>, + new_events: Lock<NewEvents>, } -impl Default for WorldData -{ - fn default() -> Self - { - Self { - component_storage: ComponentStorage::default(), - sole_storage: SoleStorage::default(), - action_queue: Rc::new(ActionQueue::default()), - } - } -} - -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EntityComponentRef<'a> { component_id: Uid, component: &'a ArchetypeEntityComponent, + entity_id: Uid, } impl<'a> EntityComponentRef<'a> @@ -581,56 +591,37 @@ impl<'a> EntityComponentRef<'a> self.component_id } - fn new(component_id: Uid, comp: &'a ArchetypeEntityComponent) -> Self + #[must_use] + pub fn entity_id(&self) -> Uid { - Self { component_id, component: comp } + self.entity_id } -} -#[derive(Debug, Default, Clone, Copy)] -enum ActiveActionQueue -{ - #[default] - A, - B, + fn new(component_id: Uid, comp: &'a ArchetypeEntityComponent, entity_id: Uid) + -> Self + { + Self { + component_id, + component: comp, + entity_id, + } + } } -#[derive(Debug)] +#[derive(Debug, Default)] struct ActionQueue { - queue_a: Lock<Vec<Action>>, - queue_b: Lock<Vec<Action>>, - active_queue: RefCell<ActiveActionQueue>, + queue: Lock<Vec<Action>>, } impl ActionQueue { fn push(&self, action: Action) { - match *self.active_queue.borrow() { - ActiveActionQueue::A => self - .queue_a - .write_nonblock() - .expect("Failed to aquire read-write action queue A lock") - .push(action), - ActiveActionQueue::B => self - .queue_b - .write_nonblock() - .expect("Failed to aquire read-write action queue A lock") - .push(action), - } - } -} - -impl Default for ActionQueue -{ - fn default() -> Self - { - Self { - queue_a: Lock::new(Vec::new(), type_name::<Vec<Action>>()), - queue_b: Lock::new(Vec::new(), type_name::<Vec<Action>>()), - active_queue: RefCell::new(ActiveActionQueue::default()), - } + self.queue + .write_nonblock() + .expect("Failed to aquire read-write lock to action queue") + .push(action); } } diff --git a/ecs/src/lock.rs b/ecs/src/lock.rs index 0b36922..fe4e08b 100644 --- a/ecs/src/lock.rs +++ b/ecs/src/lock.rs @@ -1,3 +1,4 @@ +use std::any::type_name; use std::mem::forget; use std::ops::{Deref, DerefMut}; @@ -30,7 +31,7 @@ impl<Value> Lock<Value> /// /// # Errors /// Returns `Err` if unavailable (A mutable handle is hold). - pub fn read_nonblock(&self) -> Result<ReadGuard<Value>, Error> + pub fn read_nonblock(&self) -> Result<ReadGuard<'_, Value>, Error> { let guard = self.inner.try_read().ok_or(Error::ReadUnavailable)?; @@ -46,7 +47,7 @@ impl<Value> Lock<Value> /// /// # Errors /// Returns `Err` if unavailable (A mutable or immutable handle is hold). - pub fn write_nonblock(&self) -> Result<WriteGuard<Value>, Error> + pub fn write_nonblock(&self) -> Result<WriteGuard<'_, Value>, Error> { let guard = self.inner.try_write().ok_or(Error::WriteUnavailable)?; @@ -62,6 +63,14 @@ impl<Value> Lock<Value> } } +impl<Value: Default + 'static> Default for Lock<Value> +{ + fn default() -> Self + { + Self::new(Value::default(), type_name::<Value>()) + } +} + #[derive(Debug, thiserror::Error)] pub enum Error { @@ -81,21 +90,26 @@ pub struct ReadGuard<'guard, Value> impl<'guard, Value> ReadGuard<'guard, Value> { - pub fn map<NewValue>( - self, - func: impl FnOnce(&Value) -> &NewValue, - ) -> MappedReadGuard<'guard, NewValue> + pub fn try_map<NewValue>( + this: Self, + func: impl FnOnce(&Value) -> Option<&NewValue>, + ) -> Result<MappedReadGuard<'guard, NewValue>, Self> { - let value_type_name = self.value_type_name; + let value_type_name = this.value_type_name; // The 'inner' field cannot be moved out of ReadGuard in a normal way since // ReadGuard implements Drop - let inner = unsafe { std::ptr::read(&self.inner) }; - forget(self); - - MappedReadGuard { - inner: RwLockReadGuard::map(inner, func), - value_type_name, + let inner = unsafe { std::ptr::read(&raw const this.inner) }; + forget(this); + + match RwLockReadGuard::try_map(inner, func) { + Ok(mapped_guard) => { + Ok(MappedReadGuard { inner: mapped_guard, value_type_name }) + } + Err(unmapped_guard) => Err(Self { + inner: unmapped_guard, + value_type_name, + }), } } } @@ -155,21 +169,26 @@ pub struct WriteGuard<'guard, Value> impl<'guard, Value> WriteGuard<'guard, Value> { - pub fn map<NewValue>( - self, - func: impl FnOnce(&mut Value) -> &mut NewValue, - ) -> MappedWriteGuard<'guard, NewValue> + pub fn try_map<NewValue>( + this: Self, + func: impl FnOnce(&mut Value) -> Option<&mut NewValue>, + ) -> Result<MappedWriteGuard<'guard, NewValue>, Self> { - let value_type_name = self.value_type_name; + let value_type_name = this.value_type_name; // The 'inner' field cannot be moved out of ReadGuard in a normal way since // ReadGuard implements Drop - let inner = unsafe { std::ptr::read(&self.inner) }; - forget(self); - - MappedWriteGuard { - inner: RwLockWriteGuard::map(inner, func), - value_type_name, + let inner = unsafe { std::ptr::read(&raw const this.inner) }; + forget(this); + + match RwLockWriteGuard::try_map(inner, func) { + Ok(mapped_guard) => { + Ok(MappedWriteGuard { inner: mapped_guard, value_type_name }) + } + Err(unmapped_guard) => Err(Self { + inner: unmapped_guard, + value_type_name, + }), } } } diff --git a/ecs/src/pair.rs b/ecs/src/pair.rs index 2055d5e..553652e 100644 --- a/ecs/src/pair.rs +++ b/ecs/src/pair.rs @@ -1,6 +1,13 @@ +use std::any::type_name; use std::convert::Infallible; -use crate::component::{IntoParts as IntoComponentParts, Parts as ComponentParts}; +use crate::component::{ + Handle as ComponentHandle, + HandleError as ComponentHandleError, + HandleMut as ComponentHandleMut, + IntoParts as IntoComponentParts, + Parts as ComponentParts, +}; use crate::entity::{ Handle as EntityHandle, MatchingComponentIter as EntityMatchingComponentIter, @@ -10,22 +17,22 @@ use crate::query::{ TermsBuilder as QueryTermsBuilder, TermsBuilderInterface, }; -use crate::uid::{PairParams as UidPairParams, Uid, Wildcard, With as WithUid}; -use crate::{Component, World}; +use crate::uid::{PairParams as UidPairParams, Uid, With as WithUid}; +use crate::{Component, EntityComponentRef, World}; #[derive(Debug)] -pub struct Pair<RelationElem: Element, TargetElem: Element> +pub struct Pair<Relation, Target> { - relation: RelationElem, - target: TargetElem, + relation: Relation, + target: Target, } impl Pair<Uid, Uid> { #[must_use] - pub fn new<Relation: WithUid>(target: Uid) -> Self + pub fn new<Relation: Component>(target: Uid) -> Self { - Self { relation: Relation::uid(), target } + Self { relation: Relation::id(), target } } #[must_use] @@ -38,6 +45,20 @@ impl Pair<Uid, Uid> } } +impl<Target> Pair<Uid, Target> +where + Target: Component, +{ + /// Returns a new pair that contains the target component as data. + pub fn new_with_comp_target<Relation: Component>(target_component: Target) -> Self + { + Self { + relation: Relation::uid(), + target: target_component, + } + } +} + impl IntoComponentParts for Pair<Uid, Uid> { fn into_parts(self) -> ComponentParts @@ -46,12 +67,96 @@ impl IntoComponentParts for Pair<Uid, Uid> } } -impl<Relation, Target> QueryTermWithField for Pair<Relation, Target> +impl<Target> IntoComponentParts for Pair<Uid, Target> +where + Target: Component, +{ + fn into_parts(self) -> ComponentParts + { + let id = Uid::new_pair(&UidPairParams { + relation: self.relation, + target: Target::id(), + }); + + ComponentParts::builder() + .name("Pair") + .build(id, self.target) + } +} + +impl<Relation, Target> QueryTermWithField for Pair<Relation, &Target> +where + Relation: Component, + Target: Component, +{ + type Field<'a> = ComponentHandle<'a, Target>; + + fn apply_to_terms_builder<const MAX_TERM_CNT: usize>( + terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>, + ) + { + terms_builder.with_required([Self::uid()]); + } + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + _world: &'world World, + ) -> Self::Field<'world> + { + let target_component = entity_handle + .get_matching_components(Self::uid()) + .next() + .expect("Not possible"); + + Self::Field::from_entity_component_ref(&target_component).unwrap_or_else(|err| { + panic!( + "Creating handle to target component {} failed: {err}", + type_name::<Target>() + ); + }) + } +} + +impl<Relation, Target> QueryTermWithField for Pair<Relation, &mut Target> where - Relation: WithUid, - Target: WithUid, + Relation: Component, + Target: Component, { - type Field<'a> = Handle<'a>; + type Field<'a> = ComponentHandleMut<'a, Target>; + + fn apply_to_terms_builder<const MAX_TERM_CNT: usize>( + terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>, + ) + { + terms_builder.with_required([Self::uid()]); + } + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + world: &'world World, + ) -> Self::Field<'world> + { + let target_component = entity_handle + .get_matching_components(Self::uid()) + .next() + .expect("Not possible"); + + Self::Field::from_entity_component_ref(&target_component, world).unwrap_or_else( + |err| { + panic!( + "Creating handle to target component {} failed: {err}", + type_name::<Target>() + ); + }, + ) + } +} + +impl<Relation> QueryTermWithField for Pair<Relation, Wildcard> +where + Relation: Component, +{ + type Field<'a> = WildcardTargetHandle<'a>; fn apply_to_terms_builder<const MAX_TERM_CNT: usize>( terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>, @@ -70,17 +175,17 @@ where .next() .expect("Not possible"); - Handle { + WildcardTargetHandle { world, - pair_uid: first_matching_comp.id(), + component_ref: first_matching_comp, } } } -impl<Relation, Target> WithUid for Pair<Relation, Target> +impl<Relation, Target> WithUid for Pair<Relation, &Target> where - Relation: WithUid, - Target: WithUid, + Relation: Component, + Target: Component, { fn uid() -> Uid { @@ -91,11 +196,38 @@ where } } +impl<Relation, Target> WithUid for Pair<Relation, &mut Target> +where + Relation: Component, + Target: Component, +{ + fn uid() -> Uid + { + Uid::new_pair(&UidPairParams { + relation: Relation::uid(), + target: Target::uid(), + }) + } +} + +impl<Relation> WithUid for Pair<Relation, Wildcard> +where + Relation: Component, +{ + fn uid() -> Uid + { + Uid::new_pair(&UidPairParams { + relation: Relation::uid(), + target: Wildcard::uid(), + }) + } +} + impl<Relation> QueryTermWithField for &'static [Pair<Relation, Wildcard>] where - Relation: WithUid, + Relation: Component, { - type Field<'a> = HandleIter<'a>; + type Field<'a> = WildcardTargetIter<'a>; fn apply_to_terms_builder<const MAX_TERM_CNT: usize>( terms_builder: &mut QueryTermsBuilder<MAX_TERM_CNT>, @@ -109,7 +241,7 @@ where world: &'world World, ) -> Self::Field<'world> { - HandleIter { + WildcardTargetIter { inner: entity_handle .get_matching_components(Pair::<Relation, Wildcard>::uid()), world, @@ -117,73 +249,114 @@ where } } -pub struct Handle<'world> +pub struct WildcardTargetHandle<'world> { world: &'world World, - pair_uid: Uid, + component_ref: EntityComponentRef<'world>, } -impl Handle<'_> +impl WildcardTargetHandle<'_> { + /// Attempts to retrieve the target as a entity, returning `None` if not found. #[must_use] - pub fn get_target_entity(&self) -> Option<EntityHandle<'_>> + pub fn get_entity(&self) -> Option<EntityHandle<'_>> { let archetype = self .world .data .component_storage - .get_entity_archetype(self.pair_uid.target_entity())?; + .get_entity_archetype(self.component_ref.id().target_entity())?; let Some(archetype_entity) = - archetype.get_entity_by_id(self.pair_uid.target_entity()) + archetype.get_entity_by_id(self.component_ref.id().target_entity()) else { unreachable!(); }; - Some(EntityHandle::new(self.world, archetype, archetype_entity)) + Some(EntityHandle::new(archetype, archetype_entity, self.world)) + } + + /// Attempts to retrieve the target as a component, returning `None` if the component + /// type is incorrect. + /// + /// # Panics + /// Will panic if: + /// - The component is mutably borrowed elsewhere + #[must_use] + pub fn get_component<ComponentData>( + &self, + ) -> Option<ComponentHandle<'_, ComponentData>> + where + ComponentData: 'static, + { + ComponentHandle::<ComponentData>::from_entity_component_ref(&self.component_ref) + .map_or_else( + |err| match err { + ComponentHandleError::IncorrectType => None, + err @ ComponentHandleError::AcquireLockFailed(_) => { + panic!( + "Creating handle to component {} failed: {err}", + type_name::<ComponentData>() + ); + } + }, + Some, + ) + } + + /// Attempts to retrieve the target as a component, returning `None` if the component + /// type is incorrect. + /// + /// # Panics + /// Will panic if: + /// - The component is borrowed elsewhere + #[must_use] + pub fn get_component_mut<ComponentData>( + &self, + ) -> Option<ComponentHandleMut<'_, ComponentData>> + where + ComponentData: 'static, + { + ComponentHandleMut::<ComponentData>::from_entity_component_ref( + &self.component_ref, + self.world, + ) + .map_or_else( + |err| match err { + ComponentHandleError::IncorrectType => None, + err @ ComponentHandleError::AcquireLockFailed(_) => { + panic!( + "Creating handle to component {} failed: {err}", + type_name::<ComponentData>() + ); + } + }, + Some, + ) } } -pub struct HandleIter<'a> +pub struct WildcardTargetIter<'a> { inner: EntityMatchingComponentIter<'a>, world: &'a World, } -impl<'a> Iterator for HandleIter<'a> +impl<'a> Iterator for WildcardTargetIter<'a> { - type Item = Handle<'a>; + type Item = WildcardTargetHandle<'a>; fn next(&mut self) -> Option<Self::Item> { let matching_comp = self.inner.next()?; - Some(Handle { + Some(WildcardTargetHandle { world: self.world, - pair_uid: matching_comp.id(), + component_ref: matching_comp, }) } } -pub trait Element: sealed::Sealed -{ - type Value; -} - -impl Element for Uid -{ - type Value = Uid; -} - -impl sealed::Sealed for Uid {} - -impl<WithUidT: WithUid> Element for WithUidT -{ - type Value = Infallible; -} - -impl<WithUidT: WithUid> sealed::Sealed for WithUidT {} - /// Relation denoting a dependency to another entity #[derive(Debug, Default, Clone, Copy, Component)] pub struct DependsOn; @@ -192,7 +365,14 @@ pub struct DependsOn; #[derive(Debug, Default, Clone, Copy, Component)] pub struct ChildOf; -mod sealed +#[derive(Debug)] +pub struct Wildcard(Infallible); + +impl Wildcard { - pub trait Sealed {} + #[must_use] + pub fn uid() -> Uid + { + Uid::wildcard() + } } diff --git a/ecs/src/phase.rs b/ecs/src/phase.rs index 9f47fb8..c13c432 100644 --- a/ecs/src/phase.rs +++ b/ecs/src/phase.rs @@ -1,13 +1,19 @@ use ecs_macros::Component; use crate::pair::{ChildOf, Pair}; -use crate::static_entity; +use crate::{declare_entity, World}; #[derive(Debug, Default, Clone, Copy, Component)] pub struct Phase; -static_entity!(pub START, (Phase,)); +declare_entity!(pub START, (Phase,)); +declare_entity!(pub PRE_UPDATE, (Phase,)); +declare_entity!(pub UPDATE, (Phase, Pair::new::<ChildOf>(*PRE_UPDATE))); -static_entity!(pub PRE_UPDATE, (Phase,)); - -static_entity!(pub UPDATE, (Phase, Pair::new::<ChildOf>(*PRE_UPDATE))); +#[doc(hidden)] +pub(crate) fn spawn_entities(world: &mut World) +{ + world.create_declared_entity(&START); + world.create_declared_entity(&PRE_UPDATE); + world.create_declared_entity(&UPDATE); +} diff --git a/ecs/src/private.rs b/ecs/src/private.rs deleted file mode 100644 index 56a6552..0000000 --- a/ecs/src/private.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[doc(hidden)] -pub use {linkme, paste}; diff --git a/ecs/src/query.rs b/ecs/src/query.rs index 7e10c5b..5f13579 100644 --- a/ecs/src/query.rs +++ b/ecs/src/query.rs @@ -6,12 +6,11 @@ use seq_macro::seq; use crate::component::{ Component, Handle as ComponentHandle, - HandleFromEntityComponentRef, HandleMut as ComponentHandleMut, }; use crate::entity::Handle as EntityHandle; use crate::query::flexible::{Iter as FlexibleQueryIter, Query as FlexibleQuery}; -use crate::system::{Param as SystemParam, System}; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; use crate::uid::{Kind as UidKind, Uid, With as WithUid}; use crate::util::array_vec::ArrayVec; use crate::util::Array; @@ -20,15 +19,16 @@ use crate::World; pub mod flexible; pub mod term; +// A term tuple type can have a maximum of 17 elements +pub const MAX_TERM_CNT: usize = 17; + #[derive(Debug)] pub struct Query<'world, FieldTerms, FieldlessTerms = ()> where FieldTerms: TermWithFieldTuple, FieldlessTerms: TermWithoutFieldTuple, { - world: &'world World, - // A term tuple type can have a maximum of 17 elements - inner: FlexibleQuery<'world, 17>, + inner: FlexibleQuery<'world, MAX_TERM_CNT>, _pd: PhantomData<(FieldTerms, FieldlessTerms)>, } @@ -47,8 +47,8 @@ where tracing::trace!("Searching for {}", std::any::type_name::<FieldTerms>()); Iter { - world: self.world, - iter: self.inner.iter(), + world: self.inner.world(), + inner: self.inner.iter(), comps_pd: PhantomData, } } @@ -63,7 +63,7 @@ where tracing::trace!("Searching for {}", std::any::type_name::<FieldTerms>()); ComponentAndEuidIter { - world: self.world, + world: self.inner.world(), iter: self.inner.iter(), comps_pd: PhantomData, } @@ -85,8 +85,8 @@ where tracing::trace!("Searching for {}", std::any::type_name::<FieldTerms>()); Iter { - world: self.world, - iter: func(self.inner.iter()), + world: self.inner.world(), + inner: func(self.inner.iter()), comps_pd: PhantomData, } } @@ -98,6 +98,25 @@ where Some(self.inner.iter().nth(entity_index)?.uid()) } + /// Returns a new `Query` created from a [`FlexibleQuery`]. + /// + /// # Important notes + /// The terms in `FieldTerms` and `FieldlessTerms` must be compatible with the terms + /// in the given [`FlexibleQuery`], otherwise any method call or iterating might + /// panic. + #[must_use] + pub fn from_flexible_query( + flexible_query: FlexibleQuery<'world, MAX_TERM_CNT>, + ) -> Self + { + // TODO: Check compatability of terms + + Self { + inner: flexible_query, + _pd: PhantomData, + } + } + pub(crate) fn new(world: &'world World) -> Self { let mut terms_builder = Terms::builder(); @@ -106,7 +125,6 @@ where FieldlessTerms::apply_terms_to_builder(&mut terms_builder); Self { - world, inner: world.flexible_query(terms_builder.build()), _pd: PhantomData, } @@ -136,17 +154,7 @@ where { type Input = (); - fn initialize<SystemImpl>( - _system: &mut impl System<'world, SystemImpl>, - _input: Self::Input, - ) - { - } - - fn new<SystemImpl>( - _system: &'world impl System<'world, SystemImpl>, - world: &'world World, - ) -> Self + fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self { Self::new(world) } @@ -342,18 +350,26 @@ impl<ComponentT: Component> TermWithField for &ComponentT fn get_field<'world>( entity_handle: &EntityHandle<'world>, - world: &'world World, + _world: &'world World, ) -> Self::Field<'world> { assert_eq!(ComponentT::id().kind(), UidKind::Component); - Self::Field::from_entity_component_ref( - entity_handle - .get_matching_components(ComponentT::id()) - .next(), - world, - ) - .unwrap_or_else(|err| { + let Some(component) = entity_handle + .get_matching_components(ComponentT::id()) + .next() + else { + panic!( + concat!( + "Component {} was not found in entity {}. There ", + "is most likely a bug in the entity querying" + ), + type_name::<ComponentT>(), + entity_handle.uid() + ); + }; + + Self::Field::from_entity_component_ref(&component).unwrap_or_else(|err| { panic!( "Creating handle to component {} failed: {err}", type_name::<ComponentT>() @@ -380,13 +396,21 @@ impl<ComponentT: Component> TermWithField for &mut ComponentT { assert_eq!(ComponentT::id().kind(), UidKind::Component); - Self::Field::from_entity_component_ref( - entity_handle - .get_matching_components(ComponentT::id()) - .next(), - world, - ) - .unwrap_or_else(|err| { + let Some(component) = entity_handle + .get_matching_components(ComponentT::id()) + .next() + else { + panic!( + concat!( + "Component {} was not found in entity {}. There ", + "is most likely a bug in the entity querying" + ), + type_name::<ComponentT>(), + entity_handle.uid() + ); + }; + + Self::Field::from_entity_component_ref(&component, world).unwrap_or_else(|err| { panic!( "Creating handle to component {} failed: {err}", type_name::<ComponentT>() @@ -422,29 +446,10 @@ where EntityHandleIter: Iterator<Item = EntityHandle<'query>>, { world: &'world World, - iter: EntityHandleIter, + inner: EntityHandleIter, comps_pd: PhantomData<FieldTerms>, } -impl<'query, 'world, FieldTerms, EntityHandleIter> - Iter<'query, 'world, FieldTerms, EntityHandleIter> -where - FieldTerms: TermWithFieldTuple, - EntityHandleIter: Iterator<Item = EntityHandle<'query>>, - 'world: 'query, -{ - /// Creates a new iterator from the given entity handle iterator. - /// - /// # Important - /// All of the yielded entities of the entity handle iterator should match the - /// terms `Terms`. The [`Self::next`] function will panic if it encounters a - /// entity that does not match the terms `Terms`. - pub fn new(world: &'world World, iter: EntityHandleIter) -> Self - { - Self { world, iter, comps_pd: PhantomData } - } -} - impl<'query, 'world, FieldTerms, EntityHandleIter> Iterator for Iter<'query, 'world, FieldTerms, EntityHandleIter> where @@ -456,7 +461,7 @@ where fn next(&mut self) -> Option<Self::Item> { - let entity_handle = self.iter.next()?; + let entity_handle = self.inner.next()?; Some(FieldTerms::get_fields(&entity_handle, self.world)) } diff --git a/ecs/src/query/flexible.rs b/ecs/src/query/flexible.rs index 6d65ee0..936ab82 100644 --- a/ecs/src/query/flexible.rs +++ b/ecs/src/query/flexible.rs @@ -22,7 +22,6 @@ impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT> pub fn iter(&self) -> Iter<'_> { Iter { - world: self.world, iter: self .world .data @@ -37,9 +36,16 @@ impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT> .zip(archetype.entities()) }) as ComponentIterMapFn, ), + world: self.world, } } + #[must_use] + pub fn world(&self) -> &'world World + { + self.world + } + pub(crate) fn new(world: &'world World, terms: Terms<MAX_TERM_CNT>) -> Self { Self { world, terms } @@ -59,8 +65,8 @@ impl<'query, const MAX_TERM_CNT: usize> IntoIterator for &'query Query<'_, MAX_T pub struct Iter<'query> { - world: &'query World, iter: QueryEntityIter<'query>, + world: &'query World, } impl<'query> Iterator for Iter<'query> @@ -71,7 +77,7 @@ impl<'query> Iterator for Iter<'query> { let (archetype, entity) = self.iter.next()?; - Some(EntityHandle::new(self.world, archetype, entity)) + Some(EntityHandle::new(archetype, entity, self.world)) } } diff --git a/ecs/src/query/term.rs b/ecs/src/query/term.rs index 2e1ecca..0683918 100644 --- a/ecs/src/query/term.rs +++ b/ecs/src/query/term.rs @@ -4,7 +4,6 @@ use std::marker::PhantomData; use crate::component::{ Component, Handle as ComponentHandle, - HandleFromEntityComponentRef, HandleMut as ComponentHandleMut, }; use crate::query::{ @@ -65,17 +64,14 @@ impl<ComponentT: Component> TermWithField for Option<&ComponentT> fn get_field<'world>( entity_handle: &crate::entity::Handle<'world>, - world: &'world crate::World, + _world: &'world crate::World, ) -> Self::Field<'world> { Some( ComponentHandle::<'world, ComponentT>::from_entity_component_ref( - Some( - entity_handle - .get_matching_components(ComponentT::id()) - .next()?, - ), - world, + &entity_handle + .get_matching_components(ComponentT::id()) + .next()?, ) .unwrap_or_else(|err| { panic!( @@ -104,11 +100,9 @@ impl<ComponentT: Component> TermWithField for Option<&mut ComponentT> { Some( ComponentHandleMut::<'world, ComponentT>::from_entity_component_ref( - Some( - entity_handle - .get_matching_components(ComponentT::id()) - .next()?, - ), + &entity_handle + .get_matching_components(ComponentT::id()) + .next()?, world, ) .unwrap_or_else(|err| { diff --git a/ecs/src/sole.rs b/ecs/src/sole.rs index 1cce419..7cfcc24 100644 --- a/ecs/src/sole.rs +++ b/ecs/src/sole.rs @@ -5,7 +5,7 @@ use std::ops::{Deref, DerefMut}; use std::sync::{Arc, Weak}; use crate::lock::{Lock, WriteGuard}; -use crate::system::{Param as SystemParam, System}; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; use crate::World; /// A type which has a single instance and is shared globally. @@ -64,7 +64,7 @@ where } } - fn new(sole: &'world Arc<Lock<Box<dyn Sole>>>) -> Self + pub(crate) fn new(sole: &'world Arc<Lock<Box<dyn Sole>>>) -> Self { Self { sole: sole.write_nonblock().unwrap_or_else(|_| { @@ -85,17 +85,7 @@ where { type Input = (); - fn initialize<SystemImpl>( - _system: &mut impl System<'world, SystemImpl>, - _input: Self::Input, - ) - { - } - - fn new<SystemImpl>( - _system: &'world impl System<'world, SystemImpl>, - world: &'world World, - ) -> Self + fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self { let sole = world.data.sole_storage.get::<SoleT>().unwrap_or_else(|| { panic!("Sole {} was not found in world", type_name::<SoleT>()) diff --git a/ecs/src/system.rs b/ecs/src/system.rs index 603c015..95ab7a8 100644 --- a/ecs/src/system.rs +++ b/ecs/src/system.rs @@ -1,37 +1,28 @@ -use std::any::Any; -use std::convert::Infallible; use std::fmt::Debug; use ecs_macros::Component; use seq_macro::seq; -use crate::component::{Component, HandleMut as ComponentHandleMut}; -use crate::tuple::{ReduceElement as TupleReduceElement, Tuple}; +use crate::uid::Uid; use crate::World; +pub mod initializable; +pub mod observer; pub mod stateful; -pub trait System<'world, Impl>: 'static +/// Metadata of a system. +#[derive(Debug)] +#[non_exhaustive] +pub struct Metadata { - type Input; - - #[must_use] - fn initialize(self, input: Self::Input) -> Self; - - fn run<'this>(&'this self, world: &'world World) - where - 'this: 'world; - - fn into_type_erased(self) -> TypeErased; + pub ent_id: Uid, +} - fn get_local_component_mut<LocalComponent: Component>( - &self, - ) -> Option<ComponentHandleMut<LocalComponent>>; +pub trait System<'world, Impl>: 'static +{ + type Callbacks: Callbacks; - fn set_local_component<LocalComponent: Component>( - &mut self, - local_component: LocalComponent, - ); + fn finish(self) -> (TypeErased, Self::Callbacks); } macro_rules! impl_system { @@ -43,58 +34,23 @@ macro_rules! impl_system { Func: Fn(#(TParam~I,)*) + Copy + 'static, #(TParam~I: Param<'world, Input = ()>,)* { - type Input = Infallible; - - fn initialize(self, _input: Self::Input) -> Self - { - self - } + type Callbacks = NoCallbacks; - fn run<'this>(&'this self, world: &'world World) - where - 'this: 'world + fn finish(self) -> (TypeErased, Self::Callbacks) { - let func = *self; - - func(#({ - TParam~I::new(self, world) - },)*); - } - - fn into_type_erased(self) -> TypeErased - { - TypeErased { - data: Box::new(self), - run: Box::new(|data, world| { - // SAFETY: The caller of TypeErased::run ensures the lifetime - // is correct - let data = unsafe { &*std::ptr::from_ref(data) }; - - let me = data - .downcast_ref::<Func>() - .expect("Function downcast failed"); - + let type_erased = TypeErased { + run: Box::new(move |world, metadata| { // SAFETY: The caller of TypeErased::run ensures the lifetime // is correct let world = unsafe { &*std::ptr::from_ref(world) }; - me.run(world); + self(#({ + TParam~I::new(world, &metadata) + },)*); }), - } - } + }; - fn get_local_component_mut<LocalComponent: Component>( - &self, - ) -> Option<ComponentHandleMut<LocalComponent>> - { - panic!("System does not have any local components"); - } - - fn set_local_component<LocalComponent: Component>( - &mut self, - _local_component: LocalComponent, - ) { - panic!("System does not have any local components"); + (type_erased, NoCallbacks) } } }); @@ -105,7 +61,7 @@ seq!(C in 1..16 { impl_system!(C); }); -pub trait Into<Impl> +pub trait Into<'world, Impl> { type System; @@ -114,7 +70,6 @@ pub trait Into<Impl> pub struct TypeErased { - data: Box<dyn Any>, run: Box<TypeErasedRunFn>, } @@ -124,12 +79,9 @@ impl TypeErased /// /// # Safety /// `world_data` must live at least as long as the [`World`] the system belongs to. - pub unsafe fn run(&self, world: &World) + pub unsafe fn run(&self, world: &World, metadata: Metadata) { - // You have to dereference for downcasting to work for some reason - let data = &*self.data; - - (self.run)(data, world); + (self.run)(world, metadata); } } @@ -142,41 +94,29 @@ impl Debug for TypeErased } /// Function in [`TypeErased`] used to run the system. -type TypeErasedRunFn = dyn Fn(&dyn Any, &World); +type TypeErasedRunFn = dyn Fn(&World, Metadata); /// A parameter to a [`System`]. pub trait Param<'world> { type Input; - fn initialize<SystemImpl>( - system: &mut impl System<'world, SystemImpl>, - input: Self::Input, - ); - - fn new<SystemImpl>( - system: &'world impl System<'world, SystemImpl>, - world: &'world World, - ) -> Self; + fn new(world: &'world World, system_metadata: &Metadata) -> Self; } /// A type which can be used as input to a [`System`]. pub trait Input: 'static {} -/// Component tuple reducing operation to get the parameters that takes input. -pub struct ParamWithInputFilter; - -impl<InputT: Input, Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter> - for InputT -where - Accumulator: Tuple, +pub trait Callbacks { - type Return = Accumulator::WithElementAtEnd<Self>; + fn on_created(&mut self, world: &mut World, metadata: Metadata); } -impl<Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter> for () +pub struct NoCallbacks; + +impl Callbacks for NoCallbacks { - type Return = Accumulator; + fn on_created(&mut self, _world: &mut World, _metadata: Metadata) {} } #[derive(Debug, Component)] diff --git a/ecs/src/system/initializable.rs b/ecs/src/system/initializable.rs new file mode 100644 index 0000000..b6ec8e8 --- /dev/null +++ b/ecs/src/system/initializable.rs @@ -0,0 +1,131 @@ +use std::marker::PhantomData; + +use seq_macro::seq; + +use crate::system::{Input, Param as SystemParam, System}; +use crate::tuple::{Reduce as TupleReduce, ReduceElement as TupleReduceElement, Tuple}; + +/// A initializable system. +pub trait Initializable<'world, Impl>: System<'world, Impl> +{ + type Inputs; + + #[must_use] + fn initialize(self, inputs: Self::Inputs) -> Self; +} + +pub trait Param<'world, SystemT>: SystemParam<'world> +{ + fn initialize(system: &mut SystemT, input: Self::Input); +} + +pub struct ParamTupleFilter<'world, SystemT> +{ + _pd: PhantomData<(&'world (), SystemT)>, +} + +impl<'world, SystemT, ParamT, Accumulator> + TupleReduceElement<Accumulator, ParamTupleFilter<'world, SystemT>> for ParamT +where + ParamT: SystemParam< + 'world, + Input: AppendInitializableParam<'world, Accumulator, ParamT, SystemT>, + >, + Accumulator: Tuple, +{ + type Return = <ParamT::Input as AppendInitializableParam< + 'world, + Accumulator, + ParamT, + SystemT, + >>::Return; +} + +pub trait AppendInitializableParam<'world, Accumulator, ParamT, SystemT> +{ + type Return; +} + +impl<'world, InputT, ParamT, Accumulator, SystemT> + AppendInitializableParam<'world, Accumulator, ParamT, SystemT> for InputT +where + InputT: Input, + Accumulator: Tuple, + ParamT: Param<'world, SystemT>, +{ + type Return = Accumulator::WithElementAtEnd<ParamT>; +} + +impl<ParamT, Accumulator, SystemT> + AppendInitializableParam<'_, Accumulator, ParamT, SystemT> for () +where + Accumulator: Tuple, +{ + type Return = Accumulator; +} + +pub trait ParamTuple<'world, SystemT> +{ + type Inputs; + + fn initialize_all(system: &mut SystemT, inputs: Self::Inputs); +} + +macro_rules! impl_initializable_param_tuple { + ($c: tt) => { + seq!(I in 0..$c { + impl<'world, SystemT, #(Param~I,)*> ParamTuple<'world, SystemT> + for (#(Param~I,)*) + where + #(Param~I: Param<'world, SystemT>,)* + { + type Inputs = (#(Param~I::Input,)*); + + fn initialize_all( + system: &mut SystemT, + inputs: Self::Inputs, + ) { + #( + <Param~I as Param<'world, SystemT>>::initialize( + system, + inputs.I + ); + )* + } + } + }); + }; +} + +seq!(C in 1..16 { + impl_initializable_param_tuple!(C); +}); + +impl<SystemT> ParamTuple<'_, SystemT> for () +{ + type Inputs = (); + + fn initialize_all(_system: &mut SystemT, _inputs: Self::Inputs) {} +} + +/// A tuple of system parameters that may or may not be initializable. +pub trait MaybeInitializableParamTuple<'world, SystemT> +{ + /// A tuple of the inputs of the initializable system parameters in this tuple. + type Inputs; + + fn init_initializable(system: &mut SystemT, inputs: Self::Inputs); +} + +impl<'world, SystemT, Params> MaybeInitializableParamTuple<'world, SystemT> for Params +where + Params: + TupleReduce<ParamTupleFilter<'world, SystemT>, Out: ParamTuple<'world, SystemT>>, +{ + type Inputs = <Params::Out as ParamTuple<'world, SystemT>>::Inputs; + + fn init_initializable(system: &mut SystemT, inputs: Self::Inputs) + { + Params::Out::initialize_all(system, inputs); + } +} diff --git a/ecs/src/system/observer.rs b/ecs/src/system/observer.rs new file mode 100644 index 0000000..5455fd4 --- /dev/null +++ b/ecs/src/system/observer.rs @@ -0,0 +1,276 @@ +use std::fmt::Debug; +use std::marker::PhantomData; +use std::mem::transmute; +use std::slice::Iter as SliceIter; + +use ecs_macros::Component; +use seq_macro::seq; + +use crate::component::Component; +use crate::entity::Handle as EntityHandle; +use crate::event::Emitted as EmittedEvent; +use crate::pair::Pair; +use crate::system::{ + Metadata, + NoCallbacks, + Param, + System, + TypeErased as TypeErasedSystem, +}; +use crate::uid::Uid; +use crate::util::Array; +use crate::World; + +pub trait Observed +{ + type Events: Array<Pair<Uid, Uid>>; + + fn events() -> Self::Events; +} + +impl<Relation, Target> Observed for Pair<Relation, Target> +where + Relation: Component, + Target: Component, +{ + type Events = [Pair<Uid, Uid>; 1]; + + fn events() -> Self::Events + { + [Pair::new::<Relation>(Target::id())] + } +} + +/// Observer system. +pub trait Observer<'world, Impl>: System<'world, Impl> +{ + type ObservedEvents: Array<Pair<Uid, Uid>>; + + fn observed_events() -> Self::ObservedEvents; + + fn finish_observer(self) -> (WrapperComponent, Self::Callbacks); +} + +pub struct Observe<'world, ObservedT: Observed> +{ + _pd: PhantomData<ObservedT>, + world: &'world World, + emitted_event: EmittedEvent<'world>, +} + +impl<'world, ObservedT: Observed> Observe<'world, ObservedT> +{ + pub fn new(world: &'world World, emitted_event: EmittedEvent<'world>) -> Self + { + Self { + _pd: PhantomData, + world, + emitted_event, + } + } + + #[must_use] + pub fn event(&self) -> Uid + { + self.emitted_event.event + } +} + +impl<ObservedT: Observed> Observe<'_, ObservedT> +{ + #[must_use] + pub fn iter(&self) -> ObserveIter<'_> + { + ObserveIter { + world: self.world, + inner: self.emitted_event.match_ids.iter(), + } + } +} + +impl<'a, ObservedT: Observed> IntoIterator for &'a Observe<'_, ObservedT> +{ + type IntoIter = ObserveIter<'a>; + type Item = EntityHandle<'a>; + + fn into_iter(self) -> Self::IntoIter + { + self.iter() + } +} + +pub struct ObserveIter<'observe> +{ + world: &'observe World, + inner: SliceIter<'observe, Uid>, +} + +impl<'observe> Iterator for ObserveIter<'observe> +{ + type Item = EntityHandle<'observe>; + + fn next(&mut self) -> Option<Self::Item> + { + let target = *self.inner.next()?; + + self.world.get_entity(target) + } +} + +macro_rules! impl_observer { + ($c: tt) => { + seq!(I in 0..$c { + impl<'world, ObservedT, Func, #(TParam~I,)*> System< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) + > for Func + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static, + #(TParam~I: Param<'world, Input = ()>,)* + { + type Callbacks = NoCallbacks; + + fn finish(self) -> (TypeErasedSystem, NoCallbacks) + { + unimplemented!(); + } + } + + impl<'world, ObservedT, Func, #(TParam~I,)*> Observer< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) + > for Func + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static, + #(TParam~I: Param<'world, Input = ()>,)* + { + type ObservedEvents = ObservedT::Events; + + fn observed_events() -> Self::ObservedEvents + { + ObservedT::events() + } + + fn finish_observer(self) -> (WrapperComponent, NoCallbacks) + { + let wrapper_comp = WrapperComponent { + run: Box::new(move |world, metadata, emitted_event| { + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let world = unsafe { &*std::ptr::from_ref(world) }; + + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let emitted_event = unsafe { + transmute::<EmittedEvent<'_>, EmittedEvent<'_>>( + emitted_event + ) + }; + + self(Observe::new(world, emitted_event), #({ + TParam~I::new(world, &metadata) + },)*); + }), + }; + + (wrapper_comp, NoCallbacks) + } + } + }); + }; +} + +seq!(C in 1..16 { + impl_observer!(C); +}); + +impl<'world, ObservedT, Func> System<'world, fn(Observe<'world, ObservedT>)> for Func +where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>) + Copy + 'static, +{ + type Callbacks = NoCallbacks; + + fn finish(self) -> (TypeErasedSystem, NoCallbacks) + { + const { + panic!("Observers cannot be used as regular systems"); + } + } +} + +impl<'world, ObservedT, Func> Observer<'world, fn(Observe<'world, ObservedT>)> for Func +where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>) + Copy + 'static, +{ + type ObservedEvents = ObservedT::Events; + + fn observed_events() -> Self::ObservedEvents + { + ObservedT::events() + } + + fn finish_observer(self) -> (WrapperComponent, NoCallbacks) + { + let wrapper_comp = WrapperComponent { + run: Box::new(move |world, _metadata, emitted_event| { + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let world = unsafe { &*std::ptr::from_ref(world) }; + + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let emitted_event = unsafe { + transmute::<EmittedEvent<'_>, EmittedEvent<'_>>(emitted_event) + }; + + self(Observe::new(world, emitted_event)); + }), + }; + + (wrapper_comp, NoCallbacks) + } +} + +#[derive(Component)] +pub struct WrapperComponent +{ + run: Box<RunFn>, +} + +impl WrapperComponent +{ + pub fn new(run: impl Fn(&World, Metadata, EmittedEvent<'_>) + 'static) -> Self + { + Self { run: Box::new(run) } + } + + /// Runs the observer system. + /// + /// # Safety + /// `world` must live at least as long as the [`World`] the system belongs to. + pub unsafe fn run( + &self, + world: &World, + metadata: Metadata, + emitted_event: EmittedEvent<'_>, + ) + { + (self.run)(world, metadata, emitted_event); + } +} + +impl Debug for WrapperComponent +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + formatter + .debug_struct("WrapperComponent") + .finish_non_exhaustive() + } +} + +type RunFn = dyn Fn(&World, Metadata, EmittedEvent<'_>); diff --git a/ecs/src/system/stateful.rs b/ecs/src/system/stateful.rs index 54f6979..e74ef31 100644 --- a/ecs/src/system/stateful.rs +++ b/ecs/src/system/stateful.rs @@ -1,152 +1,243 @@ -use std::any::{type_name, Any, TypeId}; +use std::mem::transmute; use std::panic::{RefUnwindSafe, UnwindSafe}; -use hashbrown::HashMap; use seq_macro::seq; -use crate::component::{Component, HandleMut as ComponentHandleMut}; -use crate::lock::Lock; -use crate::system::{ - Into as IntoSystem, - Param, - ParamWithInputFilter, - System, - TypeErased, +use crate::component::local::SystemWithLocalComponents; +use crate::component::Parts as ComponentParts; +use crate::event::Emitted as EmittedEvent; +use crate::system::initializable::{Initializable, MaybeInitializableParamTuple}; +use crate::system::observer::{ + Observe, + Observed, + Observer, + WrapperComponent as ObserverWrapperComponent, }; -use crate::tuple::{ - Reduce as TupleReduce, - Tuple, - WithAllElemLtStatic as TupleWithAllElemLtStatic, -}; -use crate::uid::Uid; +use crate::system::{Into as IntoSystem, Metadata, Param, System, TypeErased}; use crate::World; /// A stateful system. pub struct Stateful<Func> { func: Func, - local_components: HashMap<Uid, Lock<Box<dyn Any>>>, + local_components: Vec<ComponentParts>, } macro_rules! impl_system { ($c: tt) => { seq!(I in 0..$c { - impl<'world, Func, #(TParam~I,)*> System<'world, fn(&'world (), #(TParam~I,)*)> - for Stateful<Func> + impl<'world, Func, #(TParam~I,)*> + System<'world, fn(&'world (), #(TParam~I,)*)> for Stateful<Func> where Func: Fn(#(TParam~I,)*) + Copy + RefUnwindSafe + UnwindSafe + 'static, - #(TParam~I: Param<'world>,)* - #(TParam~I::Input: 'static,)* - (#(TParam~I::Input,)*): TupleReduce< - ParamWithInputFilter, - Out: Tuple<InOptions: TupleWithAllElemLtStatic> - >, + #(TParam~I: Param<'world, Input: 'static>,)* { - type Input = - <(#(TParam~I::Input,)*) as TupleReduce<ParamWithInputFilter>>::Out; + type Callbacks = Callbacks; - fn initialize(mut self, input: Self::Input) -> Self + fn finish(self) -> (TypeErased, Self::Callbacks) { - let mut option_input = input.into_in_options(); - - let mut index = 0; - - #( - if TypeId::of::<TParam~I::Input>() != - TypeId::of::<()>() - { - let input = option_input - .get_mut::<Option<TParam~I::Input>>(index) - .expect("Input element index out of range") - .take() - .expect("Input element is already taken"); - - TParam~I::initialize( - &mut self, - input - ); - - #[allow(unused_assignments)] - { - index += 1; - } - } - )* + let Self { func, local_components } = self; - self + let callbacks = Callbacks { local_components }; + + let type_erased = TypeErased { + run: Box::new(move |world, metadata| { + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let world = unsafe { &*std::ptr::from_ref(world) }; + + func(#({ + TParam~I::new(&world, &metadata) + },)*); + }), + }; + + + (type_erased, callbacks) } + } - fn run<'this>(&'this self, world: &'world World) - where - 'this: 'world + impl<'world, Func, #(TParam~I,)*> + Initializable<'world, fn(&'world (), #(TParam~I,)*)> for Stateful<Func> + where + Func: Fn(#(TParam~I,)*) + Copy + RefUnwindSafe + UnwindSafe + 'static, + #(TParam~I: Param<'world, Input: 'static>,)* + (#(TParam~I,)*): MaybeInitializableParamTuple<'world, Self> + { + type Inputs = < + (#(TParam~I,)*) as MaybeInitializableParamTuple<'world, Self> + >::Inputs; + + fn initialize(mut self, inputs: Self::Inputs) -> Self { - let func = self.func; + init_initializable_params::<_, (#(TParam~I,)*)>(&mut self, inputs); - func(#({ - TParam~I::new(self, &world) - },)*); + self } + } - fn into_type_erased(self) -> TypeErased + impl<'world, Func, #(TParam~I,)*> IntoSystem<'world, fn(#(TParam~I,)*)> + for Func + where + #(TParam~I: Param<'world>,)* + Func: Fn(#(TParam~I,)*) + Copy + 'static, + { + type System = Stateful<Func>; + + fn into_system(self) -> Self::System { - TypeErased { - data: Box::new(self), - run: Box::new(|data, world| { - // SAFETY: The caller of TypeErased::run ensures the lifetime - // is correct - let data = unsafe { &*std::ptr::from_ref::<dyn Any>(data) }; + Self::System { + func: self, + local_components: Vec::new(), // TODO: Use Vec::with_capacity + } + } + } + }); + }; +} - let me = data.downcast_ref::<Self>().unwrap(); +seq!(C in 1..16 { + impl_system!(C); +}); - // SAFETY: The caller of TypeErased::run ensures the lifetime - // is correct - let world = unsafe { &*std::ptr::from_ref(world) }; +impl<Func> SystemWithLocalComponents for Stateful<Func> +{ + fn add_local_component(&mut self, component_parts: ComponentParts) + { + self.local_components.push(component_parts); + } +} - me.run(world); - }), - } +#[derive(Debug)] +pub struct Callbacks +{ + local_components: Vec<ComponentParts>, +} + +impl crate::system::Callbacks for Callbacks +{ + fn on_created(&mut self, world: &mut World, metadata: Metadata) + { + for local_comp_parts in self.local_components.drain(..) { + world.add_component(metadata.ent_id, local_comp_parts); + } + } +} + +fn init_initializable_params<'world, SystemT, Params>( + system: &mut SystemT, + inputs: Params::Inputs, +) where + Params: MaybeInitializableParamTuple<'world, SystemT>, +{ + Params::init_initializable(system, inputs); +} + +macro_rules! impl_observer { + ($c: tt) => { + seq!(I in 0..$c { + impl<'world, ObservedT, Func, #(TParam~I,)*> System< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) + > for Stateful<Func> + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static, + #(TParam~I: Param<'world>,)* + { + type Callbacks = Callbacks; + + fn finish(self) -> (TypeErased, Callbacks) + { + unimplemented!(); } + } - fn get_local_component_mut<LocalComponent: Component>( - &self, - ) -> Option<ComponentHandleMut<LocalComponent>> + impl<'world, ObservedT, Func, #(TParam~I,)*> Initializable< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) + > for Stateful<Func> + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static, + #(TParam~I: Param<'world>,)* + (#(TParam~I,)*): MaybeInitializableParamTuple<'world, Self> + { + type Inputs = < + (#(TParam~I,)*) as MaybeInitializableParamTuple<'world, Self> + >::Inputs; + + fn initialize(mut self, inputs: Self::Inputs) -> Self { - let local_component = self.local_components - .get(&LocalComponent::id())? - .write_nonblock() - .expect("Failed to aquire read-write local component lock"); + init_initializable_params::<_, (#(TParam~I,)*)>(&mut self, inputs); - Some(ComponentHandleMut::new(local_component)) + self } + } + + impl<'world, ObservedT, Func, #(TParam~I,)*> Observer< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) + > for Stateful<Func> + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static, + #(TParam~I: Param<'world>,)* + { + type ObservedEvents = ObservedT::Events; - fn set_local_component<LocalComponent: Component>( - &mut self, - local_component: LocalComponent, - ) + fn observed_events() -> Self::ObservedEvents { - self.local_components - .insert( - LocalComponent::id(), - Lock::new( - Box::new(local_component), - type_name::<LocalComponent>() - ) - ); + ObservedT::events() + } + + fn finish_observer(self) -> (ObserverWrapperComponent, Callbacks) + { + let Self { func, local_components } = self; + + let callbacks = Callbacks { local_components }; + + let wrapper_comp = ObserverWrapperComponent::new( + move |world, metadata, emitted_event| { + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let world = unsafe { &*std::ptr::from_ref(world) }; + + // SAFETY: The caller of TypeErased::run ensures the lifetime + // is correct + let emitted_event = unsafe { + transmute::<EmittedEvent<'_>, EmittedEvent<'_>>( + emitted_event + ) + }; + + func(Observe::new(world, emitted_event), #({ + TParam~I::new(world, &metadata) + },)*); + }, + ); + + (wrapper_comp, callbacks) } } - impl<Func, #(TParam~I,)*> IntoSystem<fn(#(TParam~I,)*)> - for Func + impl<'world, Func, ObservedT, #(TParam~I,)*> IntoSystem< + 'world, + fn(Observe<'world, ObservedT>, + #(TParam~I,)*) + > for Func where - Func: Fn(#(TParam~I,)*) + Copy + 'static, + ObservedT: Observed, + #(TParam~I: Param<'world>,)* + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) + Copy + 'static, { type System = Stateful<Func>; - fn into_system(self) -> Self::System + fn into_system(self) -> Stateful<Func> { - Self::System { + Stateful { func: self, - local_components: HashMap::new(), + local_components: Vec::new(), // TODO: Use Vec::with_capacity } } } @@ -155,5 +246,5 @@ macro_rules! impl_system { } seq!(C in 1..16 { - impl_system!(C); + impl_observer!(C); }); diff --git a/ecs/src/uid.rs b/ecs/src/uid.rs index feed62c..a361882 100644 --- a/ecs/src/uid.rs +++ b/ecs/src/uid.rs @@ -218,7 +218,7 @@ pub struct PairParams pub target: Uid, } -pub trait With: 'static +pub trait With { fn uid() -> Uid; } @@ -230,14 +230,3 @@ impl<ComponentT: Component> With for ComponentT Self::id() } } - -#[derive(Debug)] -pub enum Wildcard {} - -impl With for Wildcard -{ - fn uid() -> Uid - { - Uid::wildcard() - } -} diff --git a/ecs/src/util/array_vec.rs b/ecs/src/util/array_vec.rs index a37b1f9..5d0aac9 100644 --- a/ecs/src/util/array_vec.rs +++ b/ecs/src/util/array_vec.rs @@ -45,8 +45,8 @@ impl<Item, const CAPACITY: usize> ArrayVec<Item, CAPACITY> unsafe { std::ptr::copy( - &self.items[index], - &mut self.items[index + 1], + &raw const self.items[index], + &raw mut self.items[index + 1], self.len - index, ); } diff --git a/ecs/tests/query.rs b/ecs/tests/query.rs index 062fd5a..8615e3a 100644 --- a/ecs/tests/query.rs +++ b/ecs/tests/query.rs @@ -1,4 +1,5 @@ use ecs::component::Component; +use ecs::pair::{Pair, Wildcard}; use ecs::query::term::Without; use ecs::query::{ TermWithFieldTuple as QueryTermWithFieldTuple, @@ -320,3 +321,59 @@ fn query_without_comp_and_archetype_nonexistant() vec![ent_1_id, ent_2_id], ); } + +#[test] +fn query_with_wildcard_target_pair() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, C)); + + world.create_entity((B,)); + + let ent_2_id = world.create_entity((B, Pair::new::<G>(ent_1_id))); + + world.create_entity((B, Pair::new::<F>(ent_1_id))); + world.create_entity((B, A, C, Pair::new::<F>(ent_1_id))); + + let ent_3_id = world.create_entity((B, Pair::new::<G>(ent_2_id))); + + let ent_4_id = world.create_entity((B, E, Pair::new_with_comp_target::<G>(D))); + + assert_query_finds_ents( + world.query::<(&B, Pair<G, Wildcard>), ()>(), + vec![ent_2_id, ent_3_id, ent_4_id], + ); +} + +#[test] +fn query_with_component_target_pair() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, C)); + + world.create_entity((B,)); + + world.create_entity((B, Pair::new::<G>(ent_1_id))); + + world.create_entity((B, Pair::new::<F>(ent_1_id))); + world.create_entity((B, A, C, Pair::new::<F>(ent_1_id))); + + let ent_2_id = world.create_entity((B, Pair::new_with_comp_target::<G>(F))); + + let ent_3_id = world.create_entity((B, E, Pair::new_with_comp_target::<G>(F))); + + assert_query_finds_ents( + world.query::<(&B, Pair<G, &F>), ()>(), + vec![ent_2_id, ent_3_id], + ); +} diff --git a/engine/Cargo.toml b/engine/Cargo.toml index f6cd5cf..a62f458 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -14,7 +14,8 @@ paste = "1.0.14" ecs = { path = "../ecs" } util-macros = { path = "../util-macros" } -[dependencies.image] +[dependencies.image_rs] version = "0.24.7" default-features = false features = ["png", "jpeg"] +package = "image" diff --git a/engine/src/asset.rs b/engine/src/asset.rs new file mode 100644 index 0000000..db4d23c --- /dev/null +++ b/engine/src/asset.rs @@ -0,0 +1,777 @@ +use std::any::{type_name, Any}; +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::HashMap; +use std::convert::Infallible; +use std::ffi::{OsStr, OsString}; +use std::fmt::{Debug, Display}; +use std::hash::{DefaultHasher, Hash, Hasher}; +use std::marker::PhantomData; +use std::path::{Path, PathBuf}; +use std::sync::mpsc::{ + channel as mpsc_channel, + Receiver as MpscReceiver, + Sender as MpscSender, +}; +use std::sync::Arc; + +use ecs::phase::PRE_UPDATE as PRE_UPDATE_PHASE; +use ecs::sole::Single; +use ecs::Sole; + +use crate::work_queue::{Work, WorkQueue}; + +/// Asset label. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Label<'a> +{ + pub path: Cow<'a, Path>, + pub name: Option<Cow<'a, str>>, +} + +impl Label<'_> +{ + pub fn to_owned(&self) -> LabelOwned + { + LabelOwned { + path: self.path.to_path_buf(), + name: self.name.as_ref().map(|name| name.to_string()), + } + } +} + +impl<'a> From<&'a Path> for Label<'a> +{ + fn from(path: &'a Path) -> Self + { + Self { path: path.into(), name: None } + } +} + +impl From<PathBuf> for Label<'_> +{ + fn from(path: PathBuf) -> Self + { + Self { path: path.into(), name: None } + } +} + +impl<'a> From<&'a LabelOwned> for Label<'a> +{ + fn from(label: &'a LabelOwned) -> Self + { + Self { + path: (&label.path).into(), + name: label.name.as_ref().map(|name| Cow::Borrowed(name.as_str())), + } + } +} + +impl Display for Label<'_> +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + write!(formatter, "{}", self.path.display())?; + + if let Some(name) = &self.name { + formatter.write_str("::")?; + formatter.write_str(&name)?; + } + + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct LabelOwned +{ + pub path: PathBuf, + pub name: Option<String>, +} + +impl LabelOwned +{ + pub fn to_label(&self) -> Label<'_> + { + Label { + path: (&self.path).into(), + name: self.name.as_ref().map(|name| Cow::Borrowed(name.as_str())), + } + } +} + +impl Display for LabelOwned +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + write!(formatter, "{}", self.path.display())?; + + if let Some(name) = &self.name { + formatter.write_str("::")?; + formatter.write_str(&name)?; + } + + Ok(()) + } +} + +#[derive(Debug, Sole)] +pub struct Assets +{ + assets: Vec<StoredAsset>, + asset_lookup: RefCell<HashMap<LabelHash, LookupEntry>>, + importers: Vec<WrappedImporterFn>, + importer_lookup: HashMap<OsString, usize>, + import_work_queue: WorkQueue<ImportWorkUserData>, + import_work_msg_receiver: MpscReceiver<ImportWorkMessage>, + import_work_msg_sender: MpscSender<ImportWorkMessage>, +} + +impl Assets +{ + pub fn with_capacity(capacity: usize) -> Self + { + let (import_work_msg_sender, import_work_msg_receiver) = + mpsc_channel::<ImportWorkMessage>(); + + Self { + assets: Vec::with_capacity(capacity), + asset_lookup: RefCell::new(HashMap::with_capacity(capacity)), + importers: Vec::new(), + importer_lookup: HashMap::new(), + import_work_queue: WorkQueue::new(), + import_work_msg_receiver, + import_work_msg_sender, + } + } + + pub fn set_importer<'file_ext, AssetSettings, Err>( + &mut self, + file_extensions: impl IntoIterator<Item: Into<Cow<'file_ext, str>>>, + func: impl Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>, + ) where + AssetSettings: 'static, + Err: std::error::Error + 'static, + { + self.importers.push(WrappedImporterFn::new(func)); + + let importer_index = self.importers.len() - 1; + + self.importer_lookup + .extend(file_extensions.into_iter().map(|file_ext| { + let file_ext: Cow<str> = file_ext.into(); + + (file_ext.into_owned().into(), importer_index) + })); + } + + #[tracing::instrument(skip_all, fields(asset_type=type_name::<Asset>()))] + pub fn get<'this, 'handle, Asset: 'static + Send + Sync>( + &'this self, + handle: &'handle Handle<Asset>, + ) -> Option<&'handle Asset> + where + 'this: 'handle, + { + let LookupEntry::Occupied(asset_index) = + *self.asset_lookup.borrow().get(&handle.id.label_hash)? + else { + return None; + }; + + let stored_asset = self.assets.get(asset_index).expect("Not possible"); + + let Some(asset) = stored_asset.strong.downcast_ref::<Asset>() else { + tracing::error!("Wrong asset type"); + return None; + }; + + Some(asset) + } + + #[tracing::instrument(skip(self))] + pub fn load<'i, Asset: 'static + Send + Sync>( + &self, + label: impl Into<Label<'i>> + Debug, + ) -> Handle<Asset> + { + let label = label.into(); + + let label_hash = LabelHash::new(&label); + + let mut asset_lookup = self.asset_lookup.borrow_mut(); + + if Self::is_pending(&asset_lookup, &label) { + return Handle::new(label_hash); + } + + let Some(lookup_entry) = asset_lookup.get(&label_hash) else { + self.add_import_work::<Infallible>( + &label, + label_hash, + None, + &mut asset_lookup, + ); + + return Handle::new(label_hash); + }; + + match *lookup_entry { + LookupEntry::Occupied(asset_index) => { + let stored_asset = self.assets.get(asset_index).expect("Not possible"); + + if stored_asset.strong.downcast_ref::<Asset>().is_none() { + tracing::error!("Wrong asset type {}", type_name::<Asset>()); + } + } + LookupEntry::Pending => {} + } + + Handle::new(label_hash) + } + + #[tracing::instrument(skip(self))] + pub fn load_with_settings<'i, Asset, AssetSettings>( + &self, + label: impl Into<Label<'i>> + Debug, + asset_settings: AssetSettings, + ) -> Handle<Asset> + where + Asset: Send + Sync + 'static, + AssetSettings: Send + Sync + Debug + 'static, + { + let label = label.into(); + + let label_hash = LabelHash::new(&label); + + let mut asset_lookup = self.asset_lookup.borrow_mut(); + + if Self::is_pending(&asset_lookup, &label) { + return Handle::new(label_hash); + } + + let Some(lookup_entry) = asset_lookup.get(&label_hash) else { + self.add_import_work::<AssetSettings>( + &label, + label_hash, + Some(asset_settings), + &mut asset_lookup, + ); + + return Handle::new(label_hash); + }; + + match *lookup_entry { + LookupEntry::Occupied(asset_index) => { + let stored_asset = self.assets.get(asset_index).expect("Not possible"); + + if stored_asset.strong.downcast_ref::<Asset>().is_none() { + tracing::error!( + "Wrong asset type {} for asset", + type_name::<Asset>() + ); + } + } + LookupEntry::Pending => {} + } + + Handle::new(label_hash) + } + + pub fn store_with_name<'name, Asset: 'static + Send + Sync>( + &mut self, + name: impl Into<Cow<'name, str>>, + asset: Asset, + ) -> Handle<Asset> + { + self.store_with_label( + Label { + path: Path::new("").into(), + name: Some(name.into()), + }, + asset, + ) + } + + #[tracing::instrument(skip(self, asset), fields(asset_type=type_name::<Asset>()))] + pub fn store_with_label<'i, Asset: 'static + Send + Sync>( + &mut self, + label: impl Into<Label<'i>> + Debug, + asset: Asset, + ) -> Handle<Asset> + { + let label = label.into(); + + let label_hash = LabelHash::new(&label); + + if matches!( + self.asset_lookup.get_mut().get(&label_hash), + Some(LookupEntry::Occupied(_)) + ) { + tracing::error!("Asset already exists"); + + return Handle::new(label_hash); + } + + tracing::debug!("Storing asset"); + + self.assets.push(StoredAsset::new(asset)); + + let index = self.assets.len() - 1; + + self.asset_lookup + .get_mut() + .insert(label_hash, LookupEntry::Occupied(index)); + + if label.name.is_some() { + let parent_asset_label_hash = + LabelHash::new(&Label { path: label.path, name: None }); + + if matches!( + self.asset_lookup.get_mut().get(&parent_asset_label_hash), + Some(LookupEntry::Pending) + ) { + self.asset_lookup.get_mut().remove(&parent_asset_label_hash); + } else if self + .asset_lookup + .get_mut() + .get(&parent_asset_label_hash) + .is_none() + { + self.assets + .push(StoredAsset::new::<Option<Infallible>>(None)); + + self.asset_lookup.get_mut().insert( + parent_asset_label_hash, + LookupEntry::Occupied(self.assets.len() - 1), + ); + } + } + + Handle::new(label_hash) + } + + fn is_pending(asset_lookup: &HashMap<LabelHash, LookupEntry>, label: &Label) -> bool + { + if label.name.is_some() { + if let Some(LookupEntry::Pending) = + asset_lookup.get(&LabelHash::new(&Label { + path: label.path.as_ref().into(), + name: None, + })) + { + return true; + } + } + + if let Some(LookupEntry::Pending) = asset_lookup.get(&LabelHash::new(label)) { + return true; + }; + + false + } + + fn add_import_work<AssetSettings>( + &self, + label: &Label<'_>, + label_hash: LabelHash, + asset_settings: Option<AssetSettings>, + asset_lookup: &mut HashMap<LabelHash, LookupEntry>, + ) where + AssetSettings: Any + Send + Sync, + { + let Some(file_ext) = label.path.extension() else { + tracing::error!("Asset file is missing a file extension"); + return; + }; + + let Some(importer) = self.get_importer(file_ext) else { + tracing::error!( + "No importer exists for asset file extension {}", + file_ext.to_string_lossy() + ); + return; + }; + + self.import_work_queue.add_work(Work { + func: |ImportWorkUserData { + import_work_msg_sender, + asset_path, + asset_settings, + importer, + }| { + if let Err(err) = importer.call( + import_work_msg_sender, + asset_path.as_path(), + asset_settings.as_deref(), + ) { + tracing::error!( + "Failed to load asset {}: {err}", + asset_path.display() + ); + } + }, + user_data: ImportWorkUserData { + import_work_msg_sender: self.import_work_msg_sender.clone(), + asset_path: label.path.to_path_buf(), + asset_settings: asset_settings.map(|asset_settings| { + Box::new(asset_settings) as Box<dyn Any + Send + Sync> + }), + importer: importer.clone(), + }, + }); + + asset_lookup.insert(label_hash, LookupEntry::Pending); + + if label.name.is_some() { + asset_lookup.insert( + LabelHash::new(&Label { + path: label.path.as_ref().into(), + name: None, + }), + LookupEntry::Pending, + ); + } + } + + fn get_importer(&self, file_ext: &OsStr) -> Option<&WrappedImporterFn> + { + let index = *self.importer_lookup.get(file_ext)?; + + Some(self.importers.get(index).expect("Not possible")) + } +} + +impl Default for Assets +{ + fn default() -> Self + { + Self::with_capacity(0) + } +} + +pub struct Submitter<'path> +{ + import_work_msg_sender: MpscSender<ImportWorkMessage>, + asset_path: &'path Path, +} + +impl Submitter<'_> +{ + pub fn submit_load_other<'label, Asset: Send + Sync + 'static>( + &self, + label: impl Into<Label<'label>>, + ) -> Handle<Asset> + { + let label = label.into(); + + let _ = self.import_work_msg_sender.send(ImportWorkMessage::Load { + do_load: |assets, label, _asset_settings| { + let _ = assets.load::<Asset>(label); + }, + label: label.to_owned(), + asset_settings: None, + }); + + Handle::new(LabelHash::new(&label)) + } + + pub fn submit_load_other_with_settings<'label, Asset, AssetSettings>( + &self, + label: impl Into<Label<'label>>, + asset_settings: AssetSettings, + ) -> Handle<Asset> + where + Asset: Send + Sync + 'static, + AssetSettings: Send + Sync + Debug + 'static, + { + let label = label.into(); + + let _ = self.import_work_msg_sender.send(ImportWorkMessage::Load { + do_load: |assets, label, asset_settings| { + let asset_settings = *asset_settings + .expect("Not possible") + .downcast::<AssetSettings>() + .expect("Not possible"); + + let _ = assets + .load_with_settings::<Asset, AssetSettings>(label, asset_settings); + }, + label: label.to_owned(), + asset_settings: Some(Box::new(asset_settings)), + }); + + Handle::new(LabelHash::new(&label)) + } + + pub fn submit_store<Asset: Send + Sync + 'static>( + &self, + asset: Asset, + ) -> Handle<Asset> + { + let label = LabelOwned { + path: self.asset_path.into(), + name: None, + }; + + let label_hash = LabelHash::new(&label.to_label()); + + let _ = self.import_work_msg_sender.send(ImportWorkMessage::Store { + do_store: |assets, label, boxed_asset| { + let Ok(asset) = boxed_asset.downcast::<Asset>() else { + unreachable!(); + }; + + assets.store_with_label::<Asset>(&label, *asset); + }, + label, + asset: Box::new(asset), + }); + + Handle::new(label_hash) + } + + pub fn submit_store_named<Asset: Send + Sync + 'static>( + &self, + name: impl AsRef<str>, + asset: Asset, + ) -> Handle<Asset> + { + let label = LabelOwned { + path: self.asset_path.into(), + name: Some(name.as_ref().into()), + }; + + let label_hash = LabelHash::new(&label.to_label()); + + let _ = self.import_work_msg_sender.send(ImportWorkMessage::Store { + do_store: |assets, label, boxed_asset| { + let Ok(asset) = boxed_asset.downcast::<Asset>() else { + unreachable!(); + }; + + assets.store_with_label::<Asset>(&label, *asset); + }, + label, + asset: Box::new(asset), + }); + + Handle::new(label_hash) + } +} + +/// Asset handle. +#[derive(Debug)] +pub struct Handle<Asset: 'static> +{ + id: Id, + _pd: PhantomData<Asset>, +} + +impl<Asset: 'static> Handle<Asset> +{ + pub fn id(&self) -> Id + { + self.id + } + + fn new(label_hash: LabelHash) -> Self + { + Self { + id: Id { label_hash }, + _pd: PhantomData, + } + } +} + +impl<Asset: 'static> Clone for Handle<Asset> +{ + fn clone(&self) -> Self + { + Self { id: self.id, _pd: PhantomData } + } +} + +/// Asset ID. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id +{ + label_hash: LabelHash, +} + +#[derive(Debug, thiserror::Error)] +enum ImporterError +{ + #[error("Settings has a incorrect type")] + IncorrectAssetSettingsType(PathBuf), + + #[error(transparent)] + Other(Box<dyn std::error::Error>), +} + +#[derive(Debug, Clone)] +struct WrappedImporterFn +{ + wrapper_func: fn( + MpscSender<ImportWorkMessage>, + &Path, + Option<&(dyn Any + Send + Sync)>, + ) -> Result<(), ImporterError>, +} + +impl WrappedImporterFn +{ + fn new<InnerFunc, AssetSettings, Err>(inner_func_param: InnerFunc) -> Self + where + InnerFunc: + Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>, + AssetSettings: 'static, + Err: std::error::Error + 'static, + { + assert_eq!(size_of::<InnerFunc>(), 0); + + let wrapper_func = + |import_work_msg_sender: MpscSender<ImportWorkMessage>, + asset_path: &Path, + asset_settings: Option<&(dyn Any + Send + Sync)>| { + let inner_func = unsafe { std::mem::zeroed::<InnerFunc>() }; + + let asset_settings = asset_settings + .map(|asset_settings| { + asset_settings + .downcast_ref::<AssetSettings>() + .ok_or_else(|| { + ImporterError::IncorrectAssetSettingsType( + asset_path.to_path_buf(), + ) + }) + }) + .transpose()?; + + inner_func( + &mut Submitter { import_work_msg_sender, asset_path }, + asset_path, + asset_settings, + ) + .map_err(|err| ImporterError::Other(Box::new(err)))?; + + Ok(()) + }; + + std::mem::forget(inner_func_param); + + Self { wrapper_func } + } + + fn call( + &self, + import_work_msg_sender: MpscSender<ImportWorkMessage>, + asset_path: &Path, + asset_settings: Option<&(dyn Any + Send + Sync)>, + ) -> Result<(), ImporterError> + { + (self.wrapper_func)(import_work_msg_sender, asset_path, asset_settings) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct LabelHash(u64); + +impl LabelHash +{ + fn new(label: &Label<'_>) -> Self + { + let mut hasher = DefaultHasher::new(); + + label.hash(&mut hasher); + + Self(hasher.finish()) + } +} + +#[derive(Debug, Default)] +pub(crate) struct Extension +{ + pub assets: Assets, +} + +impl ecs::extension::Extension for Extension +{ + fn collect(self, mut collector: ecs::extension::Collector<'_>) + { + let _ = collector.add_sole(self.assets); + + collector.add_system(*PRE_UPDATE_PHASE, add_received_assets); + } +} + +fn add_received_assets(mut assets: Single<Assets>) +{ + while let Some(import_work_msg) = assets.import_work_msg_receiver.try_recv().ok() { + match import_work_msg { + ImportWorkMessage::Store { do_store, label, asset } => { + do_store(&mut assets, label, asset); + } + ImportWorkMessage::Load { do_load, label, asset_settings } => { + do_load( + &assets, + Label { + path: label.path.as_path().into(), + name: label.name.as_deref().map(|name| name.into()), + }, + asset_settings, + ); + } + } + } +} + +#[derive(Debug)] +struct ImportWorkUserData +{ + import_work_msg_sender: MpscSender<ImportWorkMessage>, + asset_path: PathBuf, + asset_settings: Option<Box<dyn Any + Send + Sync>>, + importer: WrappedImporterFn, +} + +#[derive(Debug)] +enum ImportWorkMessage +{ + Store + { + do_store: fn(&mut Assets, LabelOwned, Box<dyn Any + Send + Sync>), + label: LabelOwned, + asset: Box<dyn Any + Send + Sync>, + }, + + Load + { + do_load: fn(&Assets, Label<'_>, Option<Box<dyn Any + Send + Sync>>), + label: LabelOwned, + asset_settings: Option<Box<dyn Any + Send + Sync>>, + }, +} + +#[derive(Debug, Clone, Copy)] +enum LookupEntry +{ + Occupied(usize), + Pending, +} + +#[derive(Debug)] +struct StoredAsset +{ + strong: Arc<dyn Any + Send + Sync>, +} + +impl StoredAsset +{ + fn new<Asset: Any + Send + Sync>(asset: Asset) -> Self + { + let strong = Arc::new(asset); + + Self { strong } + } +} diff --git a/engine/src/camera/fly.rs b/engine/src/camera/fly.rs index 254d020..b1214db 100644 --- a/engine/src/camera/fly.rs +++ b/engine/src/camera/fly.rs @@ -1,14 +1,15 @@ use ecs::component::local::Local; use ecs::phase::UPDATE as UPDATE_PHASE; use ecs::sole::Single; -use ecs::system::{Into, System}; +use ecs::system::initializable::Initializable; +use ecs::system::Into; use ecs::{Component, Query}; +use crate::builder; use crate::camera::{Active as ActiveCamera, Camera}; use crate::delta_time::DeltaTime; use crate::input::{Cursor, CursorFlags, Key, KeyState, Keys}; use crate::transform::WorldPosition; -use crate::util::builder; use crate::vector::{Vec2, Vec3}; builder! { diff --git a/engine/src/draw_flags.rs b/engine/src/draw_flags.rs index df5eed1..426f865 100644 --- a/engine/src/draw_flags.rs +++ b/engine/src/draw_flags.rs @@ -1,6 +1,6 @@ use ecs::Component; -use crate::util::builder; +use crate::builder; builder! { /// Flags for how a object should be drawn. diff --git a/engine/src/file_format/wavefront/mtl.rs b/engine/src/file_format/wavefront/mtl.rs index 7d1c570..f3c7a64 100644 --- a/engine/src/file_format/wavefront/mtl.rs +++ b/engine/src/file_format/wavefront/mtl.rs @@ -234,6 +234,24 @@ fn statements_to_materials( path: Path::new(texture_file_path).to_path_buf(), }); } + Keyword::Ns => { + if statement.arguments.len() != 1 { + return Err(Error::UnsupportedArgumentCount { + keyword: statement.keyword.to_string(), + arg_count: statement.arguments.len(), + line_no, + }); + } + + let shininess = statement.get_float_arg(0, line_no)?; + + tracing::debug!( + "Adding shininess {shininess} to material {}", + curr_material.name + ); + + curr_material.shininess = shininess; + } Keyword::Newmtl => {} } } @@ -279,5 +297,7 @@ keyword! { #[keyword(rename = "map_Ks")] MapKs, + + Ns, } } diff --git a/engine/src/image.rs b/engine/src/image.rs new file mode 100644 index 0000000..0e04412 --- /dev/null +++ b/engine/src/image.rs @@ -0,0 +1,184 @@ +use std::fs::File; +use std::io::BufReader; +use std::path::Path; + +use image_rs::GenericImageView as _; + +use crate::asset::{Assets, Submitter as AssetSubmitter}; +use crate::color::Color; +use crate::data_types::dimens::Dimens; +use crate::builder; + +#[derive(Debug)] +pub struct Image +{ + inner: image_rs::DynamicImage, +} + +impl Image +{ + pub fn open(path: impl AsRef<Path>) -> Result<Self, Error> + { + let buffered_reader = + BufReader::new(File::open(&path).map_err(Error::ReadFailed)?); + + let image_reader = image_rs::io::Reader::with_format( + buffered_reader, + image_rs::ImageFormat::from_path(path) + .map_err(|_| Error::UnsupportedFormat)?, + ); + + Ok(Self { + inner: image_reader + .decode() + .map_err(|err| Error::DecodeFailed(DecodeError(err)))?, + }) + } + + pub fn from_color(dimens: impl Into<Dimens<u32>>, color: impl Into<Color<u8>>) + -> Self + { + let dimens: Dimens<u32> = dimens.into(); + + let color: Color<u8> = color.into(); + + Self { + inner: image_rs::RgbImage::from_pixel( + dimens.width, + dimens.height, + image_rs::Rgb([color.red, color.green, color.blue]), + ) + .into(), + } + } + + pub fn dimensions(&self) -> Dimens<u32> + { + self.inner.dimensions().into() + } + + pub fn color_type(&self) -> ColorType + { + self.inner.color().into() + } + + pub fn as_bytes(&self) -> &[u8] + { + self.inner.as_bytes() + } +} + +builder! { +#[builder(name = SettingsBuilder, derives=(Debug, Clone))] +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct Settings { +} +} + +impl Settings +{ + pub fn builder() -> SettingsBuilder + { + SettingsBuilder::default() + } +} + +impl Default for SettingsBuilder +{ + fn default() -> Self + { + Settings::default().into() + } +} + +/// An enumeration over supported color types and bit depths +#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] +#[non_exhaustive] +pub enum ColorType +{ + /// Pixel is 8-bit luminance + L8, + + /// Pixel is 8-bit luminance with an alpha channel + La8, + + /// Pixel contains 8-bit R, G and B channels + Rgb8, + + /// Pixel is 8-bit RGB with an alpha channel + Rgba8, + + /// Pixel is 16-bit luminance + L16, + + /// Pixel is 16-bit luminance with an alpha channel + La16, + + /// Pixel is 16-bit RGB + Rgb16, + + /// Pixel is 16-bit RGBA + Rgba16, + + /// Pixel is 32-bit float RGB + Rgb32F, + + /// Pixel is 32-bit float RGBA + Rgba32F, +} + +impl From<image_rs::ColorType> for ColorType +{ + fn from(color_type: image_rs::ColorType) -> Self + { + match color_type { + image_rs::ColorType::L8 => Self::L8, + image_rs::ColorType::La8 => Self::La8, + image_rs::ColorType::Rgb8 => Self::Rgb8, + image_rs::ColorType::Rgba8 => Self::Rgba8, + image_rs::ColorType::L16 => Self::L16, + image_rs::ColorType::La16 => Self::La16, + image_rs::ColorType::Rgb16 => Self::Rgb16, + image_rs::ColorType::Rgba16 => Self::Rgba16, + image_rs::ColorType::Rgb32F => Self::Rgb32F, + image_rs::ColorType::Rgba32F => Self::Rgba32F, + _ => { + panic!("Unrecognized image_rs::ColorType variant"); + } + } + } +} + +pub fn set_asset_importers(assets: &mut Assets) +{ + assets.set_importer::<_, _>(["png", "jpg"], import_asset); +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to read image file")] + ReadFailed(#[source] std::io::Error), + + #[error("Failed to decode image")] + DecodeFailed(DecodeError), + + #[error("Unsupported image format")] + UnsupportedFormat, +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct DecodeError(image_rs::ImageError); + +fn import_asset( + asset_submitter: &mut AssetSubmitter<'_>, + path: &Path, + _settings: Option<&'_ Settings>, +) -> Result<(), Error> +{ + asset_submitter.submit_store::<Image>(Image::open(path)?); + + Ok(()) +} diff --git a/engine/src/input.rs b/engine/src/input.rs index d6a82f6..af89271 100644 --- a/engine/src/input.rs +++ b/engine/src/input.rs @@ -4,7 +4,7 @@ use ecs::extension::Collector as ExtensionCollector; use ecs::pair::{ChildOf, Pair}; use ecs::phase::{Phase, PRE_UPDATE as PRE_UPDATE_PHASE, START as START_PHASE}; use ecs::sole::Single; -use ecs::{static_entity, Sole}; +use ecs::{declare_entity, Sole}; use crate::vector::Vec2; use crate::window::{Window, UPDATE_PHASE as WINDOW_UPDATE_PHASE}; @@ -16,7 +16,7 @@ mod reexports pub use reexports::*; -static_entity!( +declare_entity!( SET_PREV_KEY_STATE_PHASE, (Phase, Pair::new::<ChildOf>(*WINDOW_UPDATE_PHASE)) ); @@ -149,6 +149,8 @@ impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ExtensionCollector<'_>) { + collector.add_declared_entity(&SET_PREV_KEY_STATE_PHASE); + collector.add_system(*START_PHASE, initialize); collector.add_system(*PRE_UPDATE_PHASE, maybe_clear_cursor_is_first_move); collector.add_system(*SET_PREV_KEY_STATE_PHASE, set_pending_key_states); diff --git a/engine/src/lib.rs b/engine/src/lib.rs index c537e06..a18cebb 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -3,29 +3,35 @@ use ecs::component::Sequence as ComponentSequence; use ecs::extension::Extension; -use ecs::pair::Pair; use ecs::phase::PRE_UPDATE as PRE_UPDATE_PHASE; use ecs::sole::Sole; +use ecs::system::initializable::Initializable; +use ecs::system::observer::Observer; use ecs::system::{Into, System}; use ecs::uid::Uid; use ecs::{SoleAlreadyExistsError, World}; +use crate::asset::{Assets, Extension as AssetExtension}; use crate::delta_time::{update as update_delta_time, DeltaTime, LastUpdate}; mod opengl; mod util; +mod work_queue; +pub mod asset; pub mod camera; pub mod collision; pub mod data_types; pub mod delta_time; pub mod draw_flags; pub mod file_format; +pub mod image; pub mod input; pub mod lighting; pub mod material; pub mod math; pub mod mesh; +pub mod model; pub mod projection; pub mod renderer; pub mod texture; @@ -37,6 +43,8 @@ pub extern crate ecs; pub(crate) use crate::data_types::matrix; pub use crate::data_types::{color, vector}; +const INITIAL_ASSET_CAPACITY: usize = 128; + #[derive(Debug)] pub struct Engine { @@ -60,6 +68,13 @@ impl Engine .initialize((LastUpdate::default(),)), ); + let mut assets = Assets::with_capacity(INITIAL_ASSET_CAPACITY); + + crate::model::set_asset_importers(&mut assets); + crate::image::set_asset_importers(&mut assets); + + world.add_extension(AssetExtension { assets }); + Self { world } } @@ -79,13 +94,12 @@ impl Engine self.world.register_system(phase_euid, system); } - pub fn register_observer_system<'this, SystemImpl>( + pub fn register_observer<'this, SystemImpl>( &'this mut self, - system: impl System<'this, SystemImpl>, - event: Pair<Uid, Uid>, + observer: impl Observer<'this, SystemImpl>, ) { - self.world.register_observer_system(system, event); + self.world.register_observer(observer); } /// Adds a globally shared singleton value. diff --git a/engine/src/lighting.rs b/engine/src/lighting.rs index 09dd980..9ab2ca8 100644 --- a/engine/src/lighting.rs +++ b/engine/src/lighting.rs @@ -1,8 +1,8 @@ use ecs::{Component, Sole}; +use crate::builder; use crate::color::Color; use crate::data_types::vector::Vec3; -use crate::util::builder; builder! { #[builder(name = PointLightBuilder, derives = (Debug, Clone))] @@ -59,7 +59,6 @@ pub struct AttenuationParams impl Default for AttenuationParams { - #[must_use] fn default() -> Self { Self { diff --git a/engine/src/material.rs b/engine/src/material.rs index e368519..56ff15f 100644 --- a/engine/src/material.rs +++ b/engine/src/material.rs @@ -1,21 +1,19 @@ use ecs::Component; use crate::color::Color; -use crate::data_types::dimens::Dimens; -use crate::texture::{Id as TextureId, Texture}; -use crate::util::builder; +use crate::texture::Texture; +use crate::builder; -#[derive(Debug, Clone, Component)] +#[derive(Debug, Clone)] #[non_exhaustive] pub struct Material { pub ambient: Color<f32>, pub diffuse: Color<f32>, pub specular: Color<f32>, - pub ambient_map: TextureId, - pub diffuse_map: TextureId, - pub specular_map: TextureId, - pub textures: Vec<Texture>, + pub ambient_map: Option<Texture>, + pub diffuse_map: Option<Texture>, + pub specular_map: Option<Texture>, pub shininess: f32, } @@ -27,17 +25,24 @@ impl Material } } +impl Default for Material +{ + fn default() -> Self + { + Self::builder().build() + } +} + /// [`Material`] builder. #[derive(Debug, Clone)] pub struct Builder { - ambient: Option<Color<f32>>, - diffuse: Option<Color<f32>>, - specular: Option<Color<f32>>, - ambient_map: Option<TextureId>, - diffuse_map: Option<TextureId>, - specular_map: Option<TextureId>, - textures: Vec<Texture>, + ambient: Color<f32>, + diffuse: Color<f32>, + specular: Color<f32>, + ambient_map: Option<Texture>, + diffuse_map: Option<Texture>, + specular_map: Option<Texture>, shininess: f32, } @@ -47,13 +52,12 @@ impl Builder pub fn new() -> Self { Self { - ambient: None, - diffuse: None, - specular: None, + ambient: Color::WHITE_F32, + diffuse: Color::WHITE_F32, + specular: Color::WHITE_F32, ambient_map: None, diffuse_map: None, specular_map: None, - textures: Vec::new(), shininess: 32.0, } } @@ -61,7 +65,7 @@ impl Builder #[must_use] pub fn ambient(mut self, ambient: Color<f32>) -> Self { - self.ambient = Some(ambient); + self.ambient = ambient; self } @@ -69,7 +73,7 @@ impl Builder #[must_use] pub fn diffuse(mut self, diffuse: Color<f32>) -> Self { - self.diffuse = Some(diffuse); + self.diffuse = diffuse; self } @@ -77,13 +81,13 @@ impl Builder #[must_use] pub fn specular(mut self, specular: Color<f32>) -> Self { - self.specular = Some(specular); + self.specular = specular; self } #[must_use] - pub fn ambient_map(mut self, ambient_map: TextureId) -> Self + pub fn ambient_map(mut self, ambient_map: Texture) -> Self { self.ambient_map = Some(ambient_map); @@ -91,7 +95,7 @@ impl Builder } #[must_use] - pub fn diffuse_map(mut self, diffuse_map: TextureId) -> Self + pub fn diffuse_map(mut self, diffuse_map: Texture) -> Self { self.diffuse_map = Some(diffuse_map); @@ -99,7 +103,7 @@ impl Builder } #[must_use] - pub fn specular_map(mut self, specular_map: TextureId) -> Self + pub fn specular_map(mut self, specular_map: Texture) -> Self { self.specular_map = Some(specular_map); @@ -107,22 +111,6 @@ impl Builder } #[must_use] - pub fn textures(mut self, textures: impl IntoIterator<Item = Texture>) -> Self - { - self.textures = textures.into_iter().collect(); - - self - } - - #[must_use] - pub fn texture(mut self, texture: Texture) -> Self - { - self.textures.push(texture); - - self - } - - #[must_use] pub fn shininess(mut self, shininess: f32) -> Self { self.shininess = shininess; @@ -135,43 +123,15 @@ impl Builder /// # Panics /// Will panic if no ambient map, diffuse map or specular map is set. #[must_use] - pub fn build(mut self) -> Material + pub fn build(self) -> Material { - let ambient_map = self.ambient_map.unwrap_or_else(|| { - let texture = create_1x1_white_texture(); - let texture_id = texture.id(); - - self.textures.push(texture); - - texture_id - }); - - let diffuse_map = self.diffuse_map.unwrap_or_else(|| { - let texture = create_1x1_white_texture(); - let texture_id = texture.id(); - - self.textures.push(texture); - - texture_id - }); - - let specular_map = self.specular_map.unwrap_or_else(|| { - let texture = create_1x1_white_texture(); - let texture_id = texture.id(); - - self.textures.push(texture); - - texture_id - }); - Material { - ambient: self.ambient.unwrap_or(Color::WHITE_F32), - diffuse: self.diffuse.unwrap_or(Color::WHITE_F32), - specular: self.specular.unwrap_or(Color::WHITE_F32), - ambient_map, - diffuse_map, - specular_map, - textures: self.textures, + ambient: self.ambient, + diffuse: self.diffuse, + specular: self.specular, + ambient_map: self.ambient_map, + diffuse_map: self.diffuse_map, + specular_map: self.specular_map, shininess: self.shininess, } } @@ -206,8 +166,3 @@ impl Flags FlagsBuilder::default() } } - -fn create_1x1_white_texture() -> Texture -{ - Texture::new_from_color(&Dimens { width: 1, height: 1 }, &Color::WHITE_U8) -} diff --git a/engine/src/mesh.rs b/engine/src/mesh.rs index 91d199e..fb977af 100644 --- a/engine/src/mesh.rs +++ b/engine/src/mesh.rs @@ -1,11 +1,9 @@ -use ecs::Component; - -use crate::util::builder; +use crate::builder; use crate::vector::{Vec2, Vec3}; pub mod cube; -#[derive(Debug, Clone, Component)] +#[derive(Debug, Clone, Default)] pub struct Mesh { vertices: Vec<Vertex>, diff --git a/engine/src/mesh/cube.rs b/engine/src/mesh/cube.rs index 4d2c470..e91cf0e 100644 --- a/engine/src/mesh/cube.rs +++ b/engine/src/mesh/cube.rs @@ -1,7 +1,7 @@ use crate::data_types::dimens::Dimens3; use crate::math::calc_triangle_surface_normal; use crate::mesh::{Mesh, Vertex}; -use crate::util::builder; +use crate::builder; use crate::vector::{Vec2, Vec3}; builder! { diff --git a/engine/src/model.rs b/engine/src/model.rs new file mode 100644 index 0000000..9f5840c --- /dev/null +++ b/engine/src/model.rs @@ -0,0 +1,176 @@ +use std::borrow::Cow; +use std::collections::HashMap; +use std::fs::read_to_string; +use std::path::Path; + +use ecs::Component; + +use crate::asset::{Assets, Handle as AssetHandle, Submitter as AssetSubmitter}; +use crate::material::Material; +use crate::mesh::Mesh; +use crate::texture::Texture; + +#[derive(Debug, Clone, Component)] +#[non_exhaustive] +pub struct Model +{ + pub asset_handle: AssetHandle<Data>, +} + +impl Model +{ + pub fn new(asset_handle: AssetHandle<Data>) -> Self + { + Self { asset_handle } + } +} + +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct Data +{ + pub mesh: Mesh, + pub materials: HashMap<String, Material>, +} + +impl Data +{ + pub fn builder() -> DataBuilder + { + DataBuilder::default() + } +} + +#[derive(Debug, Default, Clone)] +pub struct DataBuilder +{ + mesh: Mesh, + materials: HashMap<String, Material>, +} + +impl DataBuilder +{ + pub fn mesh(mut self, mesh: Mesh) -> Self + { + self.mesh = mesh; + + self + } + + pub fn material<'name>( + mut self, + name: impl Into<Cow<'name, str>>, + material: Material, + ) -> Self + { + self.materials.insert(name.into().into_owned(), material); + + self + } + + pub fn build(self) -> Data + { + Data { + mesh: self.mesh, + materials: self.materials, + } + } +} + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Settings {} + +#[derive(Debug, thiserror::Error)] +enum Error +{ + #[error("Failed to read model file")] + ReadModelFileFailed(#[source] std::io::Error), + + #[error("Failed to read material file")] + ReadMaterialFileFailed(#[source] std::io::Error), + + #[error("Failed to parse model file")] + ParsingFailed(#[from] ParsingError), +} + +pub fn set_asset_importers(assets: &mut Assets) +{ + assets.set_importer(["obj"], import_wavefront_obj_asset); +} + +#[derive(Debug, thiserror::Error)] +enum ParsingError +{ + #[error(transparent)] + Obj(#[from] crate::file_format::wavefront::obj::Error), + + #[error(transparent)] + Mtl(#[from] crate::file_format::wavefront::mtl::Error), +} + +fn import_wavefront_obj_asset( + asset_submitter: &mut AssetSubmitter<'_>, + path: &Path, + _settings: Option<&'_ Settings>, +) -> Result<(), Error> +{ + let obj = crate::file_format::wavefront::obj::parse( + &read_to_string(path).map_err(Error::ReadModelFileFailed)?, + ) + .map_err(|err| Error::ParsingFailed(ParsingError::Obj(err)))?; + + let mesh = obj + .to_mesh() + .map_err(|err| Error::ParsingFailed(ParsingError::Obj(err)))?; + + let mut materials = + HashMap::<String, Material>::with_capacity(obj.mtl_libs.iter().flatten().count()); + + for mtl_lib_path in obj.mtl_libs.iter().flatten() { + materials.extend(import_mtl(asset_submitter, &mtl_lib_path)?); + } + + asset_submitter.submit_store(Data { mesh, materials }); + + Ok(()) +} + +fn import_mtl<'a>( + asset_submitter: &'a AssetSubmitter<'_>, + path: &Path, +) -> Result<impl Iterator<Item = (String, Material)> + 'a, Error> +{ + let named_materials = crate::file_format::wavefront::mtl::parse( + &read_to_string(path).map_err(Error::ReadMaterialFileFailed)?, + ) + .map_err(|err| Error::ParsingFailed(ParsingError::Mtl(err)))?; + + Ok(named_materials.into_iter().map(|named_material| { + let mut material_builder = Material::builder() + .ambient(named_material.ambient) + .diffuse(named_material.diffuse) + .specular(named_material.specular) + .shininess(named_material.shininess); + + if let Some(ambient_map) = named_material.ambient_map { + material_builder = material_builder.ambient_map(Texture::new( + asset_submitter.submit_load_other(ambient_map.path.as_path()), + )); + } + + if let Some(diffuse_map) = named_material.diffuse_map { + material_builder = material_builder.diffuse_map(Texture::new( + asset_submitter.submit_load_other(diffuse_map.path.as_path()), + )); + } + + if let Some(specular_map) = named_material.specular_map { + material_builder = material_builder.specular_map(Texture::new( + asset_submitter.submit_load_other(specular_map.path.as_path()), + )); + } + + (named_material.name, material_builder.build()) + })) +} diff --git a/engine/src/opengl/texture.rs b/engine/src/opengl/texture.rs index 52c8554..80a5f37 100644 --- a/engine/src/opengl/texture.rs +++ b/engine/src/opengl/texture.rs @@ -1,5 +1,4 @@ use crate::data_types::dimens::Dimens; -use crate::texture::Properties; #[derive(Debug)] pub struct Texture @@ -20,10 +19,10 @@ impl Texture Self { texture } } - pub fn bind(&self) + pub fn bind_to_texture_unit(&self, texture_unit: u32) { unsafe { - gl::BindTexture(gl::TEXTURE_2D, self.texture); + gl::BindTextureUnit(texture_unit, self.texture); } } @@ -41,16 +40,9 @@ impl Texture } } - pub fn apply_properties(&mut self, properties: &Properties) - { - self.set_wrap(properties.wrap); - self.set_magnifying_filter(properties.magnifying_filter); - self.set_minifying_filter(properties.minifying_filter); - } - pub fn set_wrap(&mut self, wrapping: Wrapping) { - let wrapping_gl = wrapping.to_gl(); + let wrapping_gl = wrapping as gl::types::GLenum; #[allow(clippy::cast_possible_wrap)] unsafe { @@ -61,7 +53,7 @@ impl Texture pub fn set_magnifying_filter(&mut self, filtering: Filtering) { - let filtering_gl = filtering.to_gl(); + let filtering_gl = filtering as gl::types::GLenum; #[allow(clippy::cast_possible_wrap)] unsafe { @@ -75,7 +67,7 @@ impl Texture pub fn set_minifying_filter(&mut self, filtering: Filtering) { - let filtering_gl = filtering.to_gl(); + let filtering_gl = filtering as gl::types::GLenum; #[allow(clippy::cast_possible_wrap)] unsafe { @@ -132,43 +124,21 @@ impl Drop for Texture /// Texture wrapping. #[derive(Debug, Clone, Copy)] +#[repr(u32)] pub enum Wrapping { - Repeat, - MirroredRepeat, - ClampToEdge, - ClampToBorder, -} - -impl Wrapping -{ - fn to_gl(self) -> gl::types::GLenum - { - match self { - Self::Repeat => gl::REPEAT, - Self::MirroredRepeat => gl::MIRRORED_REPEAT, - Self::ClampToEdge => gl::CLAMP_TO_EDGE, - Self::ClampToBorder => gl::CLAMP_TO_BORDER, - } - } + Repeat = gl::REPEAT, + MirroredRepeat = gl::MIRRORED_REPEAT, + ClampToEdge = gl::CLAMP_TO_EDGE, + ClampToBorder = gl::CLAMP_TO_BORDER, } #[derive(Debug, Clone, Copy)] +#[repr(u32)] pub enum Filtering { - Nearest, - Linear, -} - -impl Filtering -{ - fn to_gl(self) -> gl::types::GLenum - { - match self { - Self::Linear => gl::LINEAR, - Self::Nearest => gl::NEAREST, - } - } + Nearest = gl::NEAREST, + Linear = gl::LINEAR, } /// Texture pixel data format. @@ -197,44 +167,3 @@ impl PixelDataFormat } } } - -pub fn set_active_texture_unit(texture_unit: TextureUnit) -{ - unsafe { - gl::ActiveTexture(texture_unit.into_gl()); - } -} - -macro_rules! texture_unit_enum { - (cnt=$cnt: literal) => { - seq_macro::seq!(N in 0..$cnt { - #[derive(Debug, Clone, Copy)] - pub enum TextureUnit { - #( - No~N, - )* - } - - impl TextureUnit { - fn into_gl(self) -> gl::types::GLenum { - match self { - #( - Self::No~N => gl::TEXTURE~N, - )* - } - } - - pub fn from_num(num: usize) -> Option<Self> { - match num { - #( - N => Some(Self::No~N), - )* - _ => None - } - } - } - }); - }; -} - -texture_unit_enum!(cnt = 31); diff --git a/engine/src/projection.rs b/engine/src/projection.rs index faa741f..115ca39 100644 --- a/engine/src/projection.rs +++ b/engine/src/projection.rs @@ -1,6 +1,6 @@ use crate::data_types::dimens::Dimens3; use crate::matrix::Matrix; -use crate::util::builder; +use crate::builder; use crate::vector::Vec3; #[derive(Debug)] diff --git a/engine/src/renderer.rs b/engine/src/renderer.rs index 17bc925..044554f 100644 --- a/engine/src/renderer.rs +++ b/engine/src/renderer.rs @@ -1,7 +1,7 @@ +use ecs::declare_entity; use ecs::pair::{ChildOf, Pair}; use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; -use ecs::static_entity; pub mod opengl; -static_entity!(pub RENDER_PHASE, (Phase, Pair::new::<ChildOf>(*UPDATE_PHASE))); +declare_entity!(pub RENDER_PHASE, (Phase, Pair::new::<ChildOf>(*UPDATE_PHASE))); diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs index 858a899..b48bac5 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -12,17 +12,21 @@ use ecs::component::Handle as ComponentHandle; use ecs::phase::START as START_PHASE; use ecs::query::term::Without; use ecs::sole::Single; -use ecs::system::{Into as _, System}; +use ecs::system::initializable::Initializable; +use ecs::system::Into as _; use ecs::{Component, Query}; +use crate::asset::{Assets, Id as AssetId}; use crate::camera::{Active as ActiveCamera, Camera}; use crate::color::Color; use crate::data_types::dimens::Dimens; use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; +use crate::image::{ColorType as ImageColorType, Image}; use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; use crate::material::{Flags as MaterialFlags, Material}; use crate::matrix::Matrix; use crate::mesh::Mesh; +use crate::model::Model; use crate::opengl::buffer::{Buffer, Usage as BufferUsage}; use crate::opengl::debug::{ enable_debug_output, @@ -44,9 +48,10 @@ use crate::opengl::shader::{ Shader as GlShader, }; use crate::opengl::texture::{ - set_active_texture_unit, + Filtering as GlTextureFiltering, + PixelDataFormat as GlTexturePixelDataFormat, Texture as GlTexture, - TextureUnit, + Wrapping as GlTextureWrapping, }; use crate::opengl::vertex_array::{ DataType as VertexArrayDataType, @@ -64,7 +69,11 @@ use crate::opengl::{ use crate::projection::{ClipVolume, Projection}; use crate::renderer::opengl::vertex::{AttributeComponentType, Vertex}; use crate::renderer::RENDER_PHASE; -use crate::texture::{Id as TextureId, Texture}; +use crate::texture::{ + Filtering as TextureFiltering, + Properties as TextureProperties, + Wrapping as TextureWrapping, +}; use crate::transform::{Scale, WorldPosition}; use crate::util::{defer, Defer, RefOrValue}; use crate::vector::{Vec2, Vec3}; @@ -72,9 +81,12 @@ use crate::window::Window; mod vertex; +const AMBIENT_MAP_TEXTURE_UNIT: u32 = 0; +const DIFFUSE_MAP_TEXTURE_UNIT: u32 = 1; +const SPECULAR_MAP_TEXTURE_UNIT: u32 = 2; + type RenderableEntity<'a> = ( - &'a Mesh, - &'a Material, + &'a Model, Option<&'a MaterialFlags>, Option<&'a WorldPosition>, Option<&'a Scale>, @@ -90,6 +102,8 @@ impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ecs::extension::Collector<'_>) { + collector.add_declared_entity(&RENDER_PHASE); + collector.add_system(*START_PHASE, initialize); collector.add_system( @@ -134,6 +148,7 @@ fn initialize(window: Single<Window>) enable(Capability::MultiSample); } +#[tracing::instrument(skip_all)] #[allow(clippy::too_many_arguments)] fn render( query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>, @@ -142,6 +157,7 @@ fn render( camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>, window: Single<Window>, global_light: Single<GlobalLight>, + assets: Single<Assets>, mut gl_objects: Local<GlobalGlObjects>, mut actions: Actions, ) @@ -156,6 +172,7 @@ fn render( let GlobalGlObjects { shader_program, textures: gl_textures, + default_1x1_texture: default_1x1_gl_texture, } = &mut *gl_objects; let shader_program = @@ -163,18 +180,23 @@ fn render( clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH); - for ( + 'subject_loop: for ( euid, - (mesh, material, material_flags, position, scale, draw_flags, gl_objects), + (model, material_flags, position, scale, draw_flags, gl_objects), ) in query.iter_with_euids() { + let Some(model_data) = assets.get(&model.asset_handle) else { + tracing::trace!("Missing model asset"); + continue; + }; + let material_flags = material_flags .map(|material_flags| material_flags.clone()) .unwrap_or_default(); let gl_objs = match gl_objects.as_deref() { Some(gl_objs) => RefOrValue::Ref(gl_objs), - None => RefOrValue::Value(Some(GlObjects::new(&mesh))), + None => RefOrValue::Value(Some(GlObjects::new(&model_data.mesh))), }; defer!(|gl_objs| { @@ -194,6 +216,22 @@ fn render( window.size().expect("Failed to get window size"), ); + if model_data.materials.len() > 1 { + tracing::warn!(concat!( + "Multiple model materials are not supported ", + "so only the first material will be used" + )); + } + + let material = match model_data.materials.values().next() { + Some(material) => material, + None => { + tracing::warn!("Model has no materials. Using default material"); + + &Material::default() + } + }; + apply_light( &material, &material_flags, @@ -208,16 +246,48 @@ fn render( &camera_world_pos, ); - for (index, texture) in material.textures.iter().enumerate() { - let gl_texture = gl_textures - .entry(texture.id()) - .or_insert_with(|| create_gl_texture(texture)); + let material_texture_maps = [ + (&material.ambient_map, AMBIENT_MAP_TEXTURE_UNIT), + (&material.diffuse_map, DIFFUSE_MAP_TEXTURE_UNIT), + (&material.specular_map, SPECULAR_MAP_TEXTURE_UNIT), + ]; + + for (texture, texture_unit) in material_texture_maps { + let Some(texture) = texture else { + let gl_texture = default_1x1_gl_texture.get_or_insert_with(|| { + create_gl_texture( + &Image::from_color(1, Color::WHITE_U8), + &TextureProperties::default(), + ) + }); - let texture_unit = TextureUnit::from_num(index).expect("Too many textures"); + gl_texture.bind_to_texture_unit(texture_unit); - set_active_texture_unit(texture_unit); + continue; + }; + + let texture_image_asset_id = texture.asset_handle.id(); + + let gl_texture = match gl_textures.get(&texture_image_asset_id) { + Some(gl_texture) => gl_texture, + None => { + let Some(image) = assets.get::<Image>(&texture.asset_handle) else { + tracing::trace!("Missing texture asset"); + continue 'subject_loop; + }; + + gl_textures.insert( + texture_image_asset_id, + create_gl_texture(image, &texture.properties), + ); + + gl_textures + .get(&texture.asset_handle.id()) + .expect("Not possible") + } + }; - gl_texture.bind(); + gl_texture.bind_to_texture_unit(texture_unit); } shader_program.activate(); @@ -246,7 +316,8 @@ fn render( struct GlobalGlObjects { shader_program: Option<GlShaderProgram>, - textures: HashMap<TextureId, GlTexture>, + textures: HashMap<AssetId, GlTexture>, + default_1x1_texture: Option<GlTexture>, } fn set_viewport(position: Vec2<u32>, size: Dimens<u32>) @@ -273,17 +344,31 @@ fn draw_mesh(gl_objects: &GlObjects) } } -fn create_gl_texture(texture: &Texture) -> GlTexture +fn create_gl_texture(image: &Image, texture_properties: &TextureProperties) -> GlTexture { let mut gl_texture = GlTexture::new(); gl_texture.generate( - *texture.dimensions(), - texture.image().as_bytes(), - texture.pixel_data_format(), + image.dimensions(), + image.as_bytes(), + match image.color_type() { + ImageColorType::Rgb8 => GlTexturePixelDataFormat::Rgb8, + ImageColorType::Rgba8 => GlTexturePixelDataFormat::Rgba8, + _ => { + unimplemented!(); + } + }, ); - gl_texture.apply_properties(texture.properties()); + gl_texture.set_wrap(texture_wrapping_to_gl(texture_properties.wrap)); + + gl_texture.set_magnifying_filter(texture_filtering_to_gl( + texture_properties.magnifying_filter, + )); + + gl_texture.set_minifying_filter(texture_filtering_to_gl( + texture_properties.minifying_filter, + )); gl_texture } @@ -560,29 +645,18 @@ fn apply_light<'point_light>( gl_shader_program .set_uniform(c"material.specular", &Vec3::from(material.specular.clone())); - let texture_map = material - .textures - .iter() - .enumerate() - .map(|(index, texture)| (texture.id(), index)) - .collect::<HashMap<_, _>>(); - #[allow(clippy::cast_possible_wrap)] - gl_shader_program.set_uniform( - c"material.ambient_map", - &(*texture_map.get(&material.ambient_map).unwrap() as i32), - ); + gl_shader_program + .set_uniform(c"material.ambient_map", &(AMBIENT_MAP_TEXTURE_UNIT as i32)); #[allow(clippy::cast_possible_wrap)] - gl_shader_program.set_uniform( - c"material.diffuse_map", - &(*texture_map.get(&material.diffuse_map).unwrap() as i32), - ); + gl_shader_program + .set_uniform(c"material.diffuse_map", &(DIFFUSE_MAP_TEXTURE_UNIT as i32)); #[allow(clippy::cast_possible_wrap)] gl_shader_program.set_uniform( c"material.specular_map", - &(*texture_map.get(&material.specular_map).unwrap() as i32), + &(SPECULAR_MAP_TEXTURE_UNIT as i32), ); gl_shader_program.set_uniform(c"material.shininess", &material.shininess); @@ -751,3 +825,23 @@ fn create_transformation_matrix(transformation: Transformation) -> Matrix<f32, 4 matrix } + +#[inline] +fn texture_wrapping_to_gl(texture_wrapping: TextureWrapping) -> GlTextureWrapping +{ + match texture_wrapping { + TextureWrapping::Repeat => GlTextureWrapping::Repeat, + TextureWrapping::MirroredRepeat => GlTextureWrapping::MirroredRepeat, + TextureWrapping::ClampToEdge => GlTextureWrapping::ClampToEdge, + TextureWrapping::ClampToBorder => GlTextureWrapping::ClampToBorder, + } +} + +#[inline] +fn texture_filtering_to_gl(texture_filtering: TextureFiltering) -> GlTextureFiltering +{ + match texture_filtering { + TextureFiltering::Linear => GlTextureFiltering::Linear, + TextureFiltering::Nearest => GlTextureFiltering::Nearest, + } +} diff --git a/engine/src/texture.rs b/engine/src/texture.rs index 4a4fe86..d02b9ff 100644 --- a/engine/src/texture.rs +++ b/engine/src/texture.rs @@ -1,194 +1,34 @@ -use std::fmt::Display; -use std::path::Path; -use std::sync::atomic::{AtomicU32, Ordering}; - -use image::io::Reader as ImageReader; -use image::{DynamicImage, ImageError, Rgb, RgbImage}; - -use crate::color::Color; -use crate::data_types::dimens::Dimens; -use crate::opengl::texture::PixelDataFormat; -use crate::util::builder; - -static NEXT_ID: AtomicU32 = AtomicU32::new(0); - -mod reexports -{ - pub use crate::opengl::texture::{Filtering, Wrapping}; -} - -pub use reexports::*; +use crate::asset::Handle as AssetHandle; +use crate::image::Image; +use crate::builder; #[derive(Debug, Clone)] +#[non_exhaustive] pub struct Texture { - id: Id, - image: DynamicImage, - pixel_data_format: PixelDataFormat, - dimensions: Dimens<u32>, - properties: Properties, + pub asset_handle: AssetHandle<Image>, + pub properties: Properties, } impl Texture { - pub fn builder() -> Builder - { - Builder::default() - } - - /// Opens a texture image. - /// - /// # Errors - /// Will return `Err` if: - /// - Opening the image fails - /// - The image data is not 8-bit/color RGB - pub fn open(path: &Path) -> Result<Self, Error> - { - Self::builder().open(path) - } - - #[must_use] - pub fn new_from_color(dimensions: &Dimens<u32>, color: &Color<u8>) -> Self - { - Self::builder().build_with_single_color(dimensions, color) - } - - #[must_use] - pub fn id(&self) -> Id - { - self.id - } - - #[must_use] - pub fn properties(&self) -> &Properties - { - &self.properties - } - - pub fn properties_mut(&mut self) -> &mut Properties - { - &mut self.properties - } - - #[must_use] - pub fn dimensions(&self) -> &Dimens<u32> - { - &self.dimensions - } - - #[must_use] - pub fn pixel_data_format(&self) -> PixelDataFormat - { - self.pixel_data_format - } - - #[must_use] - pub fn image(&self) -> &DynamicImage - { - &self.image - } -} - -impl Drop for Texture -{ - fn drop(&mut self) - { - NEXT_ID.fetch_sub(1, Ordering::Relaxed); - } -} - -/// Texture builder. -#[derive(Debug, Default, Clone)] -pub struct Builder -{ - properties: Properties, -} - -impl Builder -{ - pub fn properties(mut self, properties: Properties) -> Self + pub fn new(asset_handle: AssetHandle<Image>) -> Self { - self.properties = properties; - self - } - - /// Opens a image as a texture. - /// - /// # Errors - /// Will return `Err` if: - /// - Opening the image fails - /// - Decoding the image fails - /// - The image data is in a unsupported format - pub fn open(&self, path: &(impl AsRef<Path> + ?Sized)) -> Result<Texture, Error> - { - let image = ImageReader::open(path) - .map_err(Error::OpenImageFailed)? - .decode() - .map_err(Error::DecodeImageFailed)?; - - let pixel_data_format = match &image { - DynamicImage::ImageRgb8(_) => PixelDataFormat::Rgb8, - DynamicImage::ImageRgba8(_) => PixelDataFormat::Rgba8, - _ => { - return Err(Error::UnsupportedImageDataFormat); - } - }; - - let dimensions = Dimens { - width: image.width(), - height: image.height(), - }; - - let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); - - Ok(Texture { - id: Id::new(id), - image, - pixel_data_format, - dimensions, - properties: self.properties.clone(), - }) + Self { + asset_handle, + properties: Properties::default(), + } } - #[must_use] - pub fn build_with_single_color( - &self, - dimensions: &Dimens<u32>, - color: &Color<u8>, - ) -> Texture + pub fn with_properties( + asset_handle: AssetHandle<Image>, + properties: Properties, + ) -> Self { - let image = RgbImage::from_pixel( - dimensions.width, - dimensions.height, - Rgb([color.red, color.green, color.blue]), - ); - - let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); - - Texture { - id: Id::new(id), - image: image.into(), - pixel_data_format: PixelDataFormat::Rgb8, - dimensions: *dimensions, - properties: self.properties.clone(), - } + Self { asset_handle, properties } } } -/// Texture error. -#[derive(Debug, thiserror::Error)] -pub enum Error -{ - #[error("Failed to open texture image")] - OpenImageFailed(#[source] std::io::Error), - - #[error("Failed to decode texture image")] - DecodeImageFailed(#[source] ImageError), - - #[error("Unsupported image data format")] - UnsupportedImageDataFormat, -} - builder! { /// Texture properties #[builder(name = PropertiesBuilder, derives=(Debug, Clone))] @@ -230,25 +70,21 @@ impl Default for PropertiesBuilder } } -/// Texture ID. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Id -{ - id: u32, -} - -impl Id +#[non_exhaustive] +pub enum Filtering { - fn new(id: u32) -> Self - { - Self { id } - } + Nearest, + Linear, } -impl Display for Id +/// Texture wrapping. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum Wrapping { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - self.id.fmt(formatter) - } + Repeat, + MirroredRepeat, + ClampToEdge, + ClampToBorder, } diff --git a/engine/src/util.rs b/engine/src/util.rs index 0f6c78c..cc4677d 100644 --- a/engine/src/util.rs +++ b/engine/src/util.rs @@ -25,6 +25,18 @@ macro_rules! or { pub(crate) use or; +#[macro_export] +macro_rules! expand_map_opt { + ($in: tt, no_occurance=($($no_occurance: tt)*), occurance=($($occurance: tt)*)) => { + $($occurance)* + }; + + (, no_occurance=($($no_occurance: tt)*), occurance=($($occurance: tt)*)) => { + $($no_occurance)* + }; +} + +#[macro_export] macro_rules! builder { ( $(#[doc = $doc: literal])* @@ -36,7 +48,8 @@ macro_rules! builder { $visibility: vis struct $name: ident { $( - $(#[$field_attr: meta])* + $(#[doc = $field_doc: literal])* + $(#[builder(skip_generate_fn$($field_skip_generate_fn: tt)?)])? $field_visibility: vis $field: ident: $field_type: ty, )* } @@ -46,7 +59,7 @@ macro_rules! builder { $visibility struct $name { $( - $(#[$field_attr])* + $(#[doc = $field_doc])* $field_visibility $field: $field_type, )* } @@ -62,12 +75,18 @@ macro_rules! builder { impl $builder_name { $( - #[must_use] - $visibility fn $field(mut self, $field: $field_type) -> Self - { - self.$field = $field; - self - } + $crate::expand_map_opt!( + $(true $($field_skip_generate_fn)?)?, + no_occurance=( + #[must_use] + $visibility fn $field(mut self, $field: $field_type) -> Self + { + self.$field = $field; + self + } + ), + occurance=() + ); )* #[must_use] @@ -82,6 +101,7 @@ macro_rules! builder { impl From<$name> for $builder_name { + #[allow(unused_variables)] fn from(built: $name) -> Self { Self { @@ -94,8 +114,6 @@ macro_rules! builder { }; } -pub(crate) use builder; - pub enum RefOrValue<'a, T> { Ref(&'a T), diff --git a/engine/src/window.rs b/engine/src/window.rs index d342341..a335856 100644 --- a/engine/src/window.rs +++ b/engine/src/window.rs @@ -7,7 +7,7 @@ use ecs::extension::Collector as ExtensionCollector; use ecs::pair::{ChildOf, Pair}; use ecs::phase::{Phase, START as START_PHASE}; use ecs::sole::Single; -use ecs::{static_entity, Sole}; +use ecs::{declare_entity, Sole}; use glfw::window::{Hint as WindowCreationHint, HintValue as WindowCreationHintValue}; use glfw::WindowSize; use util_macros::VariantArr; @@ -16,7 +16,7 @@ use crate::data_types::dimens::Dimens; use crate::renderer::RENDER_PHASE; use crate::vector::Vec2; -static_entity!( +declare_entity!( pub UPDATE_PHASE, (Phase, Pair::new::<ChildOf>(*RENDER_PHASE)) ); @@ -692,8 +692,10 @@ impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ExtensionCollector<'_>) { + collector.add_declared_entity(&self::UPDATE_PHASE); + collector.add_system(*START_PHASE, initialize); - collector.add_system(*UPDATE_PHASE, update); + collector.add_system(*self::UPDATE_PHASE, update); let window = self .window_builder diff --git a/engine/src/work_queue.rs b/engine/src/work_queue.rs new file mode 100644 index 0000000..7226c7d --- /dev/null +++ b/engine/src/work_queue.rs @@ -0,0 +1,44 @@ +use std::marker::PhantomData; +use std::sync::mpsc::{channel as mpsc_channel, Sender as MpscSender}; +use std::thread::JoinHandle as ThreadHandle; + +pub struct Work<UserData: Send + Sync + 'static> +{ + pub func: fn(UserData), + pub user_data: UserData, +} + +#[derive(Debug)] +pub struct WorkQueue<UserData: Send + Sync + 'static> +{ + work_sender: MpscSender<Work<UserData>>, + _thread: ThreadHandle<()>, + _pd: PhantomData<UserData>, +} + +impl<UserData: Send + Sync + 'static> WorkQueue<UserData> +{ + pub fn new() -> Self + { + let (work_sender, work_receiver) = mpsc_channel::<Work<UserData>>(); + + Self { + work_sender, + _thread: std::thread::spawn(move || { + let work_receiver = work_receiver; + + while let Ok(work) = work_receiver.recv() { + (work.func)(work.user_data); + } + }), + _pd: PhantomData, + } + } + + pub fn add_work(&self, work: Work<UserData>) + { + if self.work_sender.send(work).is_err() { + tracing::error!("Cannot add work to work queue. Work queue thread is dead"); + } + } +} diff --git a/src/main.rs b/src/main.rs index 438b2bf..d5154ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::error::Error; -use std::fs::read_to_string; use std::path::Path; +use engine::asset::Assets; use engine::camera::fly::{ Extension as FlyCameraExtension, Fly as FlyCamera, @@ -9,21 +9,20 @@ use engine::camera::fly::{ }; use engine::camera::{Active as ActiveCamera, Camera}; use engine::color::Color; -use engine::data_types::dimens::Dimens; +use engine::data_types::dimens::{Dimens, Dimens3}; +use engine::ecs::actions::Actions; use engine::ecs::phase::START as START_PHASE; use engine::ecs::sole::Single; -use engine::file_format::wavefront::mtl::parse as parse_mtl; -use engine::file_format::wavefront::obj::parse as parse_obj; use engine::input::Extension as InputExtension; use engine::lighting::{AttenuationParams, GlobalLight, PointLight}; -use engine::material::{Builder as MaterialBuilder, Flags as MaterialFlags, Material}; +use engine::material::{Flags as MaterialFlags, Material}; use engine::mesh::cube::{ create as cube_mesh_create, CreationSpec as CubeMeshCreationSpec, }; +use engine::model::{Data as ModelData, Model}; use engine::renderer::opengl::Extension as OpenglRendererExtension; -use engine::texture::Texture; -use engine::transform::Position; +use engine::transform::WorldPosition; use engine::vector::Vec3; use engine::window::{ Builder as WindowBuilder, @@ -60,81 +59,9 @@ fn main() -> Result<(), Box<dyn Error>> let mut engine = Engine::new(); - let teapot_obj = - parse_obj(&read_to_string(Path::new(RESOURCE_DIR).join("teapot.obj"))?)?; - - let teapot_mat_name = teapot_obj - .faces - .first() - .and_then(|face| face.material_name.as_ref()); - - let teapot_mats = teapot_obj.read_and_parse_material_libs(parse_mtl)?; - - let teapot_named_mat = teapot_mats - .into_iter() - .find(|mat| Some(&mat.name) == teapot_mat_name) - .ok_or("Teapot material was not found")?; - - let mut teapot_mat_builder = Material::builder() - .ambient(teapot_named_mat.ambient) - .diffuse(teapot_named_mat.diffuse) - .specular(teapot_named_mat.specular); - - if let Some(ambient_map) = teapot_named_mat.ambient_map { - let texture = Texture::open(&ambient_map.path).unwrap(); - - teapot_mat_builder = teapot_mat_builder - .ambient_map(texture.id()) - .texture(texture); - } - - if let Some(diffuse_map) = teapot_named_mat.diffuse_map { - let texture = Texture::open(&diffuse_map.path).unwrap(); - - teapot_mat_builder = teapot_mat_builder - .diffuse_map(texture.id()) - .texture(texture); - } - - if let Some(specular_map) = teapot_named_mat.specular_map { - let texture = Texture::open(&specular_map.path).unwrap(); - - teapot_mat_builder = teapot_mat_builder - .specular_map(texture.id()) - .texture(texture); - } - - engine.spawn(( - teapot_obj.to_mesh()?, - teapot_mat_builder.build(), - Position::from(Vec3 { x: 1.6, y: 0.0, z: 0.0 }), - )); - - engine.spawn(( - PointLight::builder() - .diffuse(YELLOW) - .attenuation_params(AttenuationParams { - linear: 0.045, - quadratic: 0.0075, - ..Default::default() - }) - .build(), - Position::from(Vec3 { x: -6.0, y: 3.0, z: 3.0 }), - cube_mesh_create( - CubeMeshCreationSpec::builder() - .width(2.0) - .height(2.0) - .depth(2.0) - .build(), - |face_verts, _, _| face_verts, - ), - MaterialBuilder::new().ambient(YELLOW * 5.0).build(), - MaterialFlags::builder().use_ambient_color(true).build(), - )); - engine.spawn(( Camera::default(), - Position { + WorldPosition { position: Vec3 { x: 0.0, y: 0.0, z: 3.0 }, }, ActiveCamera, @@ -143,7 +70,7 @@ fn main() -> Result<(), Box<dyn Error>> engine.add_sole(GlobalLight::default())?; - engine.register_system(*START_PHASE, prepare_window); + engine.register_system(*START_PHASE, init); engine.add_extension(OpenglRendererExtension::default()); @@ -164,7 +91,42 @@ fn main() -> Result<(), Box<dyn Error>> Ok(()) } -fn prepare_window(window: Single<Window>) +fn init(window: Single<Window>, mut assets: Single<Assets>, mut actions: Actions) { window.set_cursor_mode(CursorMode::Disabled).unwrap(); + + actions.spawn(( + PointLight::builder() + .diffuse(YELLOW) + .attenuation_params(AttenuationParams { + linear: 0.045, + quadratic: 0.0075, + ..Default::default() + }) + .build(), + WorldPosition::from(Vec3 { x: -6.0, y: 3.0, z: 3.0 }), + Model::new( + assets.store_with_name( + "light_cube", + ModelData::builder() + .mesh(cube_mesh_create( + CubeMeshCreationSpec::builder() + .dimens(Dimens3::from(2.0)) + .build(), + |face_verts, _, _| face_verts, + )) + .material( + "surface", + Material::builder().ambient(YELLOW * 5.0).build(), + ) + .build(), + ), + ), + MaterialFlags::builder().use_ambient_color(true).build(), + )); + + actions.spawn(( + Model::new(assets.load::<ModelData>(Path::new(RESOURCE_DIR).join("teapot.obj"))), + WorldPosition::from(Vec3 { x: 1.6, y: 0.0, z: 0.0 }), + )); } |