From 8022e8998290b067b8aa0cb9cba8ba410826bdab Mon Sep 17 00:00:00 2001 From: HampusM Date: Thu, 21 May 2026 17:55:20 +0200 Subject: chore: rename ecs* crates to engine-ecs* --- Cargo.lock | 60 +- Cargo.toml | 6 +- ecs-macros/Cargo.toml | 14 - ecs-macros/src/lib.rs | 316 ---------- ecs/Cargo.toml | 34 -- ecs/benches/query.rs | 141 ----- ecs/examples/component_changed_event.rs | 78 --- ecs/examples/component_events.rs | 64 -- ecs/examples/component_relationship.rs | 65 -- ecs/examples/component_removed_event.rs | 46 -- ecs/examples/error_handling.rs | 79 --- ecs/examples/event_loop.rs | 120 ---- ecs/examples/extension.rs | 70 --- ecs/examples/multiple_queries.rs | 85 --- ecs/examples/optional_component.rs | 81 --- ecs/examples/relationship.rs | 56 -- ecs/examples/simple.rs | 42 -- ecs/examples/with_local.rs | 70 --- ecs/examples/with_sole.rs | 61 -- ecs/src/actions.rs | 174 ------ ecs/src/component.rs | 324 ---------- ecs/src/component/local.rs | 104 ---- ecs/src/component/storage.rs | 795 ------------------------- ecs/src/component/storage/archetype.rs | 385 ------------ ecs/src/component/storage/graph.rs | 432 -------------- ecs/src/entity.rs | 295 --------- ecs/src/entity/obtainer.rs | 29 - ecs/src/error.rs | 270 --------- ecs/src/event.rs | 105 ---- ecs/src/event/component.rs | 103 ---- ecs/src/extension.rs | 72 --- ecs/src/lib.rs | 773 ------------------------ ecs/src/lock.rs | 259 -------- ecs/src/pair.rs | 687 --------------------- ecs/src/phase.rs | 22 - ecs/src/query.rs | 569 ------------------ ecs/src/query/flexible.rs | 92 --- ecs/src/query/term.rs | 116 ---- ecs/src/sole.rs | 104 ---- ecs/src/stats.rs | 8 - ecs/src/system.rs | 158 ----- ecs/src/system/initializable.rs | 131 ---- ecs/src/system/observer.rs | 280 --------- ecs/src/system/stateful.rs | 269 --------- ecs/src/tuple.rs | 238 -------- ecs/src/uid.rs | 261 -------- ecs/src/util.rs | 415 ------------- ecs/src/util/array_vec.rs | 131 ---- ecs/tests/phase.rs | 36 -- ecs/tests/query.rs | 413 ------------- engine-ecs-macros/Cargo.toml | 14 + engine-ecs-macros/src/lib.rs | 316 ++++++++++ engine-ecs/Cargo.toml | 34 ++ engine-ecs/benches/query.rs | 141 +++++ engine-ecs/examples/component_changed_event.rs | 78 +++ engine-ecs/examples/component_events.rs | 64 ++ engine-ecs/examples/component_relationship.rs | 65 ++ engine-ecs/examples/component_removed_event.rs | 46 ++ engine-ecs/examples/error_handling.rs | 79 +++ engine-ecs/examples/event_loop.rs | 120 ++++ engine-ecs/examples/extension.rs | 70 +++ engine-ecs/examples/multiple_queries.rs | 85 +++ engine-ecs/examples/optional_component.rs | 81 +++ engine-ecs/examples/relationship.rs | 56 ++ engine-ecs/examples/simple.rs | 42 ++ engine-ecs/examples/with_local.rs | 70 +++ engine-ecs/examples/with_sole.rs | 61 ++ engine-ecs/src/actions.rs | 174 ++++++ engine-ecs/src/component.rs | 324 ++++++++++ engine-ecs/src/component/local.rs | 101 ++++ engine-ecs/src/component/storage.rs | 795 +++++++++++++++++++++++++ engine-ecs/src/component/storage/archetype.rs | 385 ++++++++++++ engine-ecs/src/component/storage/graph.rs | 432 ++++++++++++++ engine-ecs/src/entity.rs | 295 +++++++++ engine-ecs/src/entity/obtainer.rs | 29 + engine-ecs/src/error.rs | 270 +++++++++ engine-ecs/src/event.rs | 105 ++++ engine-ecs/src/event/component.rs | 103 ++++ engine-ecs/src/extension.rs | 72 +++ engine-ecs/src/lib.rs | 773 ++++++++++++++++++++++++ engine-ecs/src/lock.rs | 259 ++++++++ engine-ecs/src/pair.rs | 687 +++++++++++++++++++++ engine-ecs/src/phase.rs | 20 + engine-ecs/src/query.rs | 569 ++++++++++++++++++ engine-ecs/src/query/flexible.rs | 92 +++ engine-ecs/src/query/term.rs | 116 ++++ engine-ecs/src/sole.rs | 104 ++++ engine-ecs/src/stats.rs | 8 + engine-ecs/src/system.rs | 157 +++++ engine-ecs/src/system/initializable.rs | 131 ++++ engine-ecs/src/system/observer.rs | 278 +++++++++ engine-ecs/src/system/stateful.rs | 269 +++++++++ engine-ecs/src/tuple.rs | 238 ++++++++ engine-ecs/src/uid.rs | 261 ++++++++ engine-ecs/src/util.rs | 415 +++++++++++++ engine-ecs/src/util/array_vec.rs | 131 ++++ engine-ecs/tests/phase.rs | 36 ++ engine-ecs/tests/query.rs | 413 +++++++++++++ engine/Cargo.toml | 2 +- engine/src/asset.rs | 12 +- engine/src/camera.rs | 2 +- engine/src/camera/fly.rs | 16 +- engine/src/collision.rs | 2 +- engine/src/delta_time.rs | 6 +- engine/src/draw_flags.rs | 2 +- engine/src/input.rs | 10 +- engine/src/lib.rs | 21 +- engine/src/lighting.rs | 2 +- engine/src/material.rs | 2 +- engine/src/model.rs | 2 +- engine/src/renderer.rs | 26 +- engine/src/renderer/main_render_pass.rs | 6 +- engine/src/renderer/object.rs | 2 +- engine/src/renderer/opengl.rs | 12 +- engine/src/shader.rs | 12 +- engine/src/shader/default.rs | 8 +- engine/src/transform.rs | 2 +- engine/src/util.rs | 2 +- engine/src/windowing.rs | 24 +- engine/src/windowing/keyboard.rs | 2 +- engine/src/windowing/mouse.rs | 2 +- engine/src/windowing/window.rs | 2 +- 122 files changed, 9586 insertions(+), 9595 deletions(-) delete mode 100644 ecs-macros/Cargo.toml delete mode 100644 ecs-macros/src/lib.rs delete mode 100644 ecs/Cargo.toml delete mode 100644 ecs/benches/query.rs delete mode 100644 ecs/examples/component_changed_event.rs delete mode 100644 ecs/examples/component_events.rs delete mode 100644 ecs/examples/component_relationship.rs delete mode 100644 ecs/examples/component_removed_event.rs delete mode 100644 ecs/examples/error_handling.rs delete mode 100644 ecs/examples/event_loop.rs delete mode 100644 ecs/examples/extension.rs delete mode 100644 ecs/examples/multiple_queries.rs delete mode 100644 ecs/examples/optional_component.rs delete mode 100644 ecs/examples/relationship.rs delete mode 100644 ecs/examples/simple.rs delete mode 100644 ecs/examples/with_local.rs delete mode 100644 ecs/examples/with_sole.rs delete mode 100644 ecs/src/actions.rs delete mode 100644 ecs/src/component.rs delete mode 100644 ecs/src/component/local.rs delete mode 100644 ecs/src/component/storage.rs delete mode 100644 ecs/src/component/storage/archetype.rs delete mode 100644 ecs/src/component/storage/graph.rs delete mode 100644 ecs/src/entity.rs delete mode 100644 ecs/src/entity/obtainer.rs delete mode 100644 ecs/src/error.rs delete mode 100644 ecs/src/event.rs delete mode 100644 ecs/src/event/component.rs delete mode 100644 ecs/src/extension.rs delete mode 100644 ecs/src/lib.rs delete mode 100644 ecs/src/lock.rs delete mode 100644 ecs/src/pair.rs delete mode 100644 ecs/src/phase.rs delete mode 100644 ecs/src/query.rs delete mode 100644 ecs/src/query/flexible.rs delete mode 100644 ecs/src/query/term.rs delete mode 100644 ecs/src/sole.rs delete mode 100644 ecs/src/stats.rs delete mode 100644 ecs/src/system.rs delete mode 100644 ecs/src/system/initializable.rs delete mode 100644 ecs/src/system/observer.rs delete mode 100644 ecs/src/system/stateful.rs delete mode 100644 ecs/src/tuple.rs delete mode 100644 ecs/src/uid.rs delete mode 100644 ecs/src/util.rs delete mode 100644 ecs/src/util/array_vec.rs delete mode 100644 ecs/tests/phase.rs delete mode 100644 ecs/tests/query.rs create mode 100644 engine-ecs-macros/Cargo.toml create mode 100644 engine-ecs-macros/src/lib.rs create mode 100644 engine-ecs/Cargo.toml create mode 100644 engine-ecs/benches/query.rs create mode 100644 engine-ecs/examples/component_changed_event.rs create mode 100644 engine-ecs/examples/component_events.rs create mode 100644 engine-ecs/examples/component_relationship.rs create mode 100644 engine-ecs/examples/component_removed_event.rs create mode 100644 engine-ecs/examples/error_handling.rs create mode 100644 engine-ecs/examples/event_loop.rs create mode 100644 engine-ecs/examples/extension.rs create mode 100644 engine-ecs/examples/multiple_queries.rs create mode 100644 engine-ecs/examples/optional_component.rs create mode 100644 engine-ecs/examples/relationship.rs create mode 100644 engine-ecs/examples/simple.rs create mode 100644 engine-ecs/examples/with_local.rs create mode 100644 engine-ecs/examples/with_sole.rs create mode 100644 engine-ecs/src/actions.rs create mode 100644 engine-ecs/src/component.rs create mode 100644 engine-ecs/src/component/local.rs create mode 100644 engine-ecs/src/component/storage.rs create mode 100644 engine-ecs/src/component/storage/archetype.rs create mode 100644 engine-ecs/src/component/storage/graph.rs create mode 100644 engine-ecs/src/entity.rs create mode 100644 engine-ecs/src/entity/obtainer.rs create mode 100644 engine-ecs/src/error.rs create mode 100644 engine-ecs/src/event.rs create mode 100644 engine-ecs/src/event/component.rs create mode 100644 engine-ecs/src/extension.rs create mode 100644 engine-ecs/src/lib.rs create mode 100644 engine-ecs/src/lock.rs create mode 100644 engine-ecs/src/pair.rs create mode 100644 engine-ecs/src/phase.rs create mode 100644 engine-ecs/src/query.rs create mode 100644 engine-ecs/src/query/flexible.rs create mode 100644 engine-ecs/src/query/term.rs create mode 100644 engine-ecs/src/sole.rs create mode 100644 engine-ecs/src/stats.rs create mode 100644 engine-ecs/src/system.rs create mode 100644 engine-ecs/src/system/initializable.rs create mode 100644 engine-ecs/src/system/observer.rs create mode 100644 engine-ecs/src/system/stateful.rs create mode 100644 engine-ecs/src/tuple.rs create mode 100644 engine-ecs/src/uid.rs create mode 100644 engine-ecs/src/util.rs create mode 100644 engine-ecs/src/util/array_vec.rs create mode 100644 engine-ecs/tests/phase.rs create mode 100644 engine-ecs/tests/query.rs diff --git a/Cargo.lock b/Cargo.lock index d864a95..16cb0e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -552,35 +552,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -[[package]] -name = "ecs" -version = "0.1.0" -dependencies = [ - "anyhow", - "backtrace", - "criterion", - "ecs-macros", - "hashbrown 0.15.2", - "parking_lot", - "paste", - "seq-macro", - "thiserror", - "tracing", - "tracing-subscriber", - "util-macros", - "vizoxide", -] - -[[package]] -name = "ecs-macros" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", - "toml", -] - [[package]] name = "either" version = "1.15.0" @@ -595,7 +566,7 @@ dependencies = [ "build-rs", "cfg_aliases", "crossbeam-channel", - "ecs", + "engine-ecs", "engine-macros", "glutin", "image", @@ -616,6 +587,35 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "engine-ecs" +version = "0.1.0" +dependencies = [ + "anyhow", + "backtrace", + "criterion", + "engine-ecs-macros", + "hashbrown 0.15.2", + "parking_lot", + "paste", + "seq-macro", + "thiserror", + "tracing", + "tracing-subscriber", + "util-macros", + "vizoxide", +] + +[[package]] +name = "engine-ecs-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "toml", +] + [[package]] name = "engine-macros" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 98b5502..5062219 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,13 +4,13 @@ version = "0.1.0" edition = "2021" [workspace] -members = ["engine*", "ecs*", "util-macros", "opengl-bindings"] +members = ["engine*", "util-macros", "opengl-bindings"] [workspace.dependencies] engine = { path = "engine" } engine-macros = { path = "engine-macros" } -ecs = { path = "ecs" } -ecs-macros = { path = "ecs-macros" } +engine-ecs = { path = "engine-ecs" } +engine-ecs-macros = { path = "engine-ecs-macros" } util-macros= { path = "util-macros" } opengl-bindings = { path = "opengl-bindings" } diff --git a/ecs-macros/Cargo.toml b/ecs-macros/Cargo.toml deleted file mode 100644 index cf323fc..0000000 --- a/ecs-macros/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "ecs-macros" -version = "0.1.0" -edition = "2021" - -[lib] -proc-macro = true - -[dependencies] -quote = "1.0.35" -syn = { version = "2.0.51", features = ["full"] } -proc-macro2 = "1.0.78" -toml = "0.8.12" - diff --git a/ecs-macros/src/lib.rs b/ecs-macros/src/lib.rs deleted file mode 100644 index 7d00736..0000000 --- a/ecs-macros/src/lib.rs +++ /dev/null @@ -1,316 +0,0 @@ -#![deny(clippy::all, clippy::pedantic)] -use std::path::PathBuf as FsPathBuf; - -use proc_macro::TokenStream; -use quote::{format_ident, quote, ToTokens}; -use syn::spanned::Spanned; -use syn::{ - parse, - Attribute, - Generics, - Ident, - Item, - ItemEnum, - ItemStruct, - ItemUnion, - Path, -}; -use toml::value::{Table as TomlTable, Value as TomlValue}; - -macro_rules! syn_path { - ($first_segment: ident $(::$segment: ident)*) => { - ::syn::Path { - leading_colon: None, - segments: ::syn::punctuated::Punctuated::from_iter([ - syn_path_segment!($first_segment), - $(syn_path_segment!($segment),)* - ]) - } - }; -} - -macro_rules! syn_path_segment { - ($segment: ident) => { - ::syn::PathSegment { - ident: ::proc_macro2::Ident::new( - stringify!($segment), - ::proc_macro2::Span::call_site(), - ), - arguments: ::syn::PathArguments::None, - } - }; -} - -/// Generates a `Component` implementation. -/// -/// # Panics -/// Will panic if: -/// - 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)] -pub fn component_derive(input: TokenStream) -> TokenStream -{ - let item: TypeItem = parse::(input).unwrap().try_into().unwrap(); - - let item_ident = item.ident(); - - let (impl_generics, type_generics, where_clause) = item.generics().split_for_impl(); - - let ecs_path = find_engine_ecs_crate_path().unwrap_or_else(|| syn_path!(ecs)); - - assert!( - item.generics().params.is_empty(), - "Generic types are not supported as components" - ); - - let id_var_ident = format_ident!("{}_ID", item_ident.to_string().to_uppercase()); - - let id_var = quote! { - static #id_var_ident: LazyLock = LazyLock::new(|| { - Uid::new_unique(UidKind::Component) - }); - }; - - let mod_ident = format_ident!( - "__ecs_priv_component_impl_{}", - item_ident.to_string().to_lowercase() - ); - - quote! { - mod #mod_ident { - use ::std::any::{Any, TypeId}; - use ::std::sync::{LazyLock, Mutex}; - - use #ecs_path::component::Component; - use #ecs_path::uid::{Uid, Kind as UidKind}; - use #ecs_path::system::Input as SystemInput; - - use super::*; - - #id_var - - impl #impl_generics Component for #item_ident #type_generics - #where_clause - { - fn id() -> Uid - { - *#id_var_ident - } - - fn name(&self) -> &'static str - { - std::any::type_name::() - } - } - - impl #impl_generics SystemInput for #item_ident #type_generics - #where_clause - { - } - } - } - .into() -} - -/// Generates a `Sole` implementation. -/// -/// # Panics -/// Will panic if not attributed to a type item or if parsing the user crate's -/// `Cargo.toml` file fails. -#[proc_macro_derive(Sole, attributes(sole))] -pub fn sole_derive(input: TokenStream) -> TokenStream -{ - let item: TypeItem = parse::(input).unwrap().try_into().unwrap(); - - let item_ident = item.ident(); - - let sole_attr = item.attribute::("sole").unwrap_or_default(); - - let drop_last = sole_attr.drop_last; - - let (impl_generics, type_generics, where_clause) = item.generics().split_for_impl(); - - let ecs_path = find_engine_ecs_crate_path().unwrap_or_else(|| syn_path!(ecs)); - - quote! { - impl #impl_generics #ecs_path::sole::Sole for #item_ident #type_generics - #where_clause - { - fn drop_last(&self) -> bool - { - #drop_last - } - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any - { - self - } - - fn as_any(&self) -> &dyn std::any::Any - { - self - } - } - } - .into() -} - -trait FromAttribute: Sized -{ - fn from_attribute(attribute: &Attribute) -> Result; -} - -enum TypeItem -{ - Struct(ItemStruct), - Enum(ItemEnum), - Union(ItemUnion), -} - -impl TypeItem -{ - fn ident(&self) -> &Ident - { - match self { - Self::Struct(struct_item) => &struct_item.ident, - Self::Enum(enum_item) => &enum_item.ident, - Self::Union(union_item) => &union_item.ident, - } - } - - fn attribute(&self, attr_ident: &str) -> Option - { - let item_attrs = match &self { - Self::Struct(struct_item) => &struct_item.attrs, - &Self::Enum(enum_item) => &enum_item.attrs, - &Self::Union(union_item) => &union_item.attrs, - }; - - let mut attr: Option<&Attribute> = None; - - for item_attr in item_attrs { - assert!(attr.is_none(), "Expected only one {attr_ident} attribute"); - - if item_attr.path().get_ident()? == attr_ident { - attr = Some(item_attr); - } - } - - Some(Attr::from_attribute(attr?).unwrap()) - } - - fn generics(&self) -> &Generics - { - match self { - Self::Struct(struct_item) => &struct_item.generics, - Self::Enum(enum_item) => &enum_item.generics, - Self::Union(union_item) => &union_item.generics, - } - } -} - -impl TryFrom for TypeItem -{ - type Error = syn::Error; - - fn try_from(item: Item) -> Result - { - match item { - Item::Struct(struct_item) => Ok(Self::Struct(struct_item)), - Item::Enum(enum_item) => Ok(Self::Enum(enum_item)), - Item::Union(union_item) => Ok(Self::Union(union_item)), - _ => Err(syn::Error::new( - item.span(), - "Expected a struct, a enum or a union", - )), - } - } -} - -impl ToTokens for TypeItem -{ - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) - { - match self { - Self::Struct(struct_item) => struct_item.to_tokens(tokens), - Self::Enum(enum_item) => enum_item.to_tokens(tokens), - Self::Union(union_item) => union_item.to_tokens(tokens), - } - } -} - -#[derive(Debug, Default)] -struct SoleAttribute -{ - drop_last: bool, -} - -impl FromAttribute for SoleAttribute -{ - fn from_attribute(attribute: &Attribute) -> Result - { - let mut drop_last = false; - - attribute.parse_nested_meta(|meta| { - if meta - .path - .get_ident() - .is_some_and(|flag| flag == "drop_last") - { - drop_last = true; - - return Ok(()); - } - - Err(meta.error("Unrecognized token")) - })?; - - Ok(Self { drop_last }) - } -} - -fn find_engine_ecs_crate_path() -> Option -{ - let cargo_manifest_dir = FsPathBuf::from(std::env::var("CARGO_MANIFEST_DIR").ok()?); - - let cargo_crate_name = std::env::var("CARGO_CRATE_NAME").ok()?; - let cargo_pkg_name = std::env::var("CARGO_PKG_NAME").ok()?; - - if cargo_pkg_name == "ecs" && cargo_crate_name != "ecs" { - // Macro is used by a ecs crate example/test/benchmark - return Some(syn_path!(ecs)); - } - - let crate_manifest = std::fs::read_to_string(cargo_manifest_dir.join("Cargo.toml")) - .ok()? - .parse::() - .expect("Failed to parse crate manifest file"); - - let package = match crate_manifest.get("package")? { - TomlValue::Table(package) => Some(package), - _ => None, - }?; - - let package_name = match package.get("name")? { - TomlValue::String(package_name) => Some(package_name), - _ => None, - }?; - - if package_name == "ecs" { - return Some(syn_path!(crate)); - } - - let crate_dependencies = match crate_manifest.get("dependencies")? { - TomlValue::Table(dependencies) => Some(dependencies), - _ => None, - }?; - - crate_dependencies.iter().find_map(|(crate_dep_name, _)| { - if crate_dep_name == "engine" { - return Some(syn_path!(engine::ecs)); - } - - None - }) -} diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml deleted file mode 100644 index 0a94ff0..0000000 --- a/ecs/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "ecs" -version = "0.1.0" -edition = "2021" - -[features] -vizoxide = ["dep:vizoxide"] - -[dependencies] -seq-macro = "0.3.5" -paste = "1.0.14" -thiserror = "1.0.49" -tracing = "0.1.39" -hashbrown = "0.15.2" -parking_lot = "0.12.3" -anyhow = "1.0.102" -backtrace = "0.3.76" -ecs-macros = { workspace = true } -util-macros = { workspace = true } -vizoxide = { version = "1.0.5", optional = true } - -[dev-dependencies.criterion] -version = "0.5.1" -default-features = false -features = ["cargo_bench_support"] - -[dev-dependencies.tracing-subscriber] -version = "0.3.17" -default-features = false -features = ["std", "ansi", "fmt", "smallvec", "env-filter", "chrono"] - -[[bench]] -name = "query" -harness = false diff --git a/ecs/benches/query.rs b/ecs/benches/query.rs deleted file mode 100644 index f14bb06..0000000 --- a/ecs/benches/query.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::hint::black_box; -use std::path::PathBuf; - -use criterion::{criterion_group, criterion_main, Criterion}; -use ecs::{Component, World}; - -#[derive(Component)] -struct Foo -{ - _text: String, -} - -#[derive(Component)] -struct Bar -{ - _path: PathBuf, - _num: u64, -} - -#[derive(Component)] -struct Position -{ - _x: f32, - _y: f32, - _z: f32, -} - -#[derive(Component)] -struct PosA -{ - _x: f32, - _y: f32, - _z: f32, -} - -#[derive(Component)] -struct PosB -{ - _x: f32, - _y: f32, - _z: f32, -} - -#[derive(Component)] -struct PosC -{ - _x: f32, - _y: f32, - _z: f32, -} - -#[derive(Component)] -struct MoreText -{ - _more_text: String, -} - -#[derive(Component)] -struct EvenMoreText -{ - _even_more_text: String, -} - -fn spawn_1000_entities(world: &mut World) -{ - for _ in 0..300 { - world.create_entity(( - Bar { - _path: "/dev/zero".into(), - _num: 65789, - }, - Position { _x: 13.98, _y: 27.0, _z: 0.2 }, - Foo { _text: "Hello there".to_string() }, - PosA { - _x: 1183.98, - _y: 272628.0, - _z: 3306.2, - }, - PosB { - _x: 171183.98, - _y: 28.0, - _z: 336.2901, - }, - PosC { _x: 8273.98, _y: 28.0, _z: 336.2901 }, - MoreText { - _more_text: "Lorem ipsum".to_string(), - }, - EvenMoreText { - _even_more_text: "Wow so much text".to_string(), - }, - )); - } - - for _ in 0..700 { - world.create_entity(( - Bar { - _path: "/dev/null".into(), - _num: 65789, - }, - Position { _x: 88.11, _y: 9.0, _z: 36.11 }, - Foo { _text: "Hey".to_string() }, - PosA { - _x: 118310.98, - _y: 272628.0, - _z: 3306.2, - }, - PosB { _x: 11323.98, _y: 28.0, _z: 336.2901 }, - PosC { - _x: 8273.98, - _y: 21818.0, - _z: 336.2901, - }, - MoreText { - _more_text: "Lorem ipsum".to_string(), - }, - EvenMoreText { - _even_more_text: "Wow much text".to_string(), - }, - )); - } -} - -fn benchbark(criterion: &mut Criterion) -{ - criterion.bench_function("Iterate 1000 entities", |bencher| { - let mut world = World::new(); - - spawn_1000_entities(&mut world); - - let query = world.query::<(&Bar, &Position, &Foo), ()>(); - - bencher.iter(|| { - for comps in query.iter() { - black_box(comps); - } - }) - }); -} - -criterion_group!(benches, benchbark); -criterion_main!(benches); diff --git a/ecs/examples/component_changed_event.rs b/ecs/examples/component_changed_event.rs deleted file mode 100644 index 2168df0..0000000 --- a/ecs/examples/component_changed_event.rs +++ /dev/null @@ -1,78 +0,0 @@ -use ecs::event::component::{Changed, EventMatchExt}; -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>) -{ - println!("\nChanged greetings:"); - - for evt_match in &observe { - let greeting = evt_match.get_ent_target_comp(); - - 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_events.rs b/ecs/examples/component_events.rs deleted file mode 100644 index 06e7fab..0000000 --- a/ecs/examples/component_events.rs +++ /dev/null @@ -1,64 +0,0 @@ -use ecs::actions::Actions; -use ecs::component::Component; -use ecs::event::component::{Changed, EventMatchExt, Removed}; -use ecs::pair::Pair; -use ecs::phase::UPDATE; -use ecs::system::observer::Observe; -use ecs::{Component, Query, World}; - -#[derive(Debug, Component)] -struct CheeseCrumbs -{ - cnt: usize, -} - -#[derive(Debug, Component)] -struct Cheese -{ - name: &'static str, -} - -fn eat_cheese(query: Query<(&Cheese, &mut CheeseCrumbs)>, mut actions: Actions) -{ - for (cheese_ent_id, (_, mut cheese_crumbs)) in query.iter_with_euids() { - println!("Eating cheese!"); - - cheese_crumbs.cnt += 40; - cheese_crumbs.set_changed(); - - actions.remove_components(cheese_ent_id, [Cheese::id()]); - } -} - -fn on_cheese_removed(observe: Observe>) -{ - for evt_match in &observe { - let cheese = evt_match.get_ent_target_comp(); - - println!("{} cheese was eaten", cheese.name); - } -} - -fn on_cheese_crumbs_changed(observe: Observe>) -{ - for evt_match in &observe { - let cheese_crumbs = evt_match.get_ent_target_comp(); - - println!("Cheese crumbs count changed to {}", cheese_crumbs.cnt); - } -} - -fn main() -{ - let mut world = World::new(); - - world.register_system(*UPDATE, eat_cheese); - world.register_observer(on_cheese_removed); - world.register_observer(on_cheese_crumbs_changed); - - world.create_entity((Cheese { name: "Brie" }, CheeseCrumbs { cnt: 0 })); - world.create_entity((Cheese { name: "Parmesan" }, CheeseCrumbs { cnt: 0 })); - world.create_entity((Cheese { name: "Gouda" }, CheeseCrumbs { cnt: 0 })); - - world.step(); -} diff --git a/ecs/examples/component_relationship.rs b/ecs/examples/component_relationship.rs deleted file mode 100644 index e07b214..0000000 --- a/ecs/examples/component_relationship.rs +++ /dev/null @@ -1,65 +0,0 @@ -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)>) -{ - 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::builder() - .relation::() - .target_as_data(Dogs { large: true }) - .build(), - )); - - world.create_entity(( - Person { name: "Mark".to_string() }, - Pair::builder() - .relation::() - .target_as_data(Cats) - .build(), - )); - - world.create_entity(( - Person { name: "Helena".to_string() }, - Pair::builder() - .relation::() - .target_as_data(Dogs { large: false }) - .build(), - )); - - world.step(); -} diff --git a/ecs/examples/component_removed_event.rs b/ecs/examples/component_removed_event.rs deleted file mode 100644 index 9b73b1a..0000000 --- a/ecs/examples/component_removed_event.rs +++ /dev/null @@ -1,46 +0,0 @@ -use ecs::actions::Actions; -use ecs::component::Component; -use ecs::event::component::{EventMatchExt, Removed}; -use ecs::pair::Pair; -use ecs::phase::UPDATE; -use ecs::system::observer::Observe; -use ecs::{Component, Query, World}; - -#[derive(Debug, Component)] -struct Cheese -{ - name: &'static str, -} - -fn eat_cheese(query: Query<(&Cheese,)>, mut actions: Actions) -{ - for (cheese_ent_id, (_,)) in query.iter_with_euids() { - println!("Eating cheese!"); - - actions.remove_components(cheese_ent_id, [Cheese::id()]); - } -} - -fn on_cheese_removed(observe: Observe>) -{ - for evt_match in &observe { - let cheese = evt_match.get_ent_target_comp(); - - println!("{} cheese was eaten", cheese.name); - } -} - -fn main() -{ - let mut world = World::new(); - - world.register_system(*UPDATE, eat_cheese); - world.register_observer(on_cheese_removed); - - world.create_entity((Cheese { name: "Brie" },)); - world.create_entity((Cheese { name: "Parmesan" },)); - world.create_entity((Cheese { name: "Gouda" },)); - - world.step(); - world.step(); -} diff --git a/ecs/examples/error_handling.rs b/ecs/examples/error_handling.rs deleted file mode 100644 index dc34c5f..0000000 --- a/ecs/examples/error_handling.rs +++ /dev/null @@ -1,79 +0,0 @@ -use ecs::error::Error; -use ecs::event::component::{Changed, EventMatchExt}; -use ecs::pair::Pair; -use ecs::phase::UPDATE; -use ecs::query::Query; -use ecs::system::observer::Observe; -use ecs::{Component, World, error}; -use tracing::level_filters::LevelFilter; -use tracing_subscriber::EnvFilter; -use tracing_subscriber::fmt::time::ChronoLocal; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::util::SubscriberInitExt; - -#[derive(Component)] -struct State -{ - value: u32, -} - -fn do_something_fallible(query: Query<(&mut State,)>) -> Result<(), Error> -{ - for (mut state,) in &query { - state.value += 1; - - state.set_changed(); - - if state.value > 3 { - return Err(error!("Invalid state value {}", state.value)); - } - } - - Ok(()) -} - -fn handle_state_changed(observe: Observe>) -> Result<(), Error> -{ - for evt_match in &observe { - let state = evt_match.get_ent_target_comp(); - - if state.value > 3 { - return Err(error!("Invalid state value {}", state.value)); - } - - tracing::info!("State has valid value {}", state.value); - } - - Ok(()) -} - -fn main() -{ - tracing_subscriber::registry() - .with( - tracing_subscriber::fmt::layer() - .with_timer(ChronoLocal::new("%T%.6f".to_string())), - ) - .with( - EnvFilter::builder() - .with_default_directive(LevelFilter::DEBUG.into()) - .from_env() - .unwrap(), - ) - .init(); - - let mut world = World::new(); - - world.set_err_handler(ecs::error::err_handler_log_error); - - world.create_entity((State { value: 0 },)); - - world.register_system(*UPDATE, do_something_fallible); - - world.register_observer(handle_state_changed); - - world.step(); - world.step(); - world.step(); - world.step(); -} diff --git a/ecs/examples/event_loop.rs b/ecs/examples/event_loop.rs deleted file mode 100644 index bec2c00..0000000 --- a/ecs/examples/event_loop.rs +++ /dev/null @@ -1,120 +0,0 @@ -use ecs::actions::Actions; -use ecs::pair::{ChildOf, Pair}; -use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; -use ecs::{declare_entity, Component, Query, World}; - -#[derive(Component)] -struct Wool -{ - remaining: u32, -} - -#[derive(Component)] -struct Health -{ - health: u32, -} - -#[derive(Component)] -struct Name -{ - name: &'static str, -} - -fn sheer(query: Query<(&mut Wool, &Name)>) -{ - for (mut wool, name) in &query { - if wool.remaining == 0 { - println!("{} Has no wool left", name.name); - - continue; - } - - // Sheer the whool - wool.remaining -= 5; - - println!("Sheered 5 wool from {}", name.name); - } -} - -fn feed(query: Query<(&mut Health, &Name)>) -{ - for (mut health, name) in &query { - health.health += 1; - - println!("Feeded {} which gained 1 health", name.name); - } -} - -fn age(query: Query<(&mut Health, &Name)>, mut actions: Actions) -{ - for (mut health, name) in &query { - if health.health <= 2 { - health.health = 0; - - println!("{} passed away", name.name); - - actions.stop(); - - continue; - } - - health.health -= 2; - - println!("{} aged and lost 2 health", name.name); - } -} - -declare_entity!( - SHEER_PHASE, - ( - Phase, - Pair::builder() - .relation::() - .target_id(*UPDATE_PHASE) - .build() - ) -); - -declare_entity!( - FEED_PHASE, - ( - Phase, - Pair::builder() - .relation::() - .target_id(*SHEER_PHASE) - .build() - ) -); - -declare_entity!( - AGE_PHASE, - ( - Phase, - Pair::builder() - .relation::() - .target_id(*FEED_PHASE) - .build() - ) -); - -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); - - world.create_entity(( - Wool { remaining: 30 }, - Health { health: 3 }, - Name { name: "Bessy" }, - )); - - world.start_loop(); -} diff --git a/ecs/examples/extension.rs b/ecs/examples/extension.rs deleted file mode 100644 index f6282e1..0000000 --- a/ecs/examples/extension.rs +++ /dev/null @@ -1,70 +0,0 @@ -use ecs::actions::Actions; -use ecs::extension::{Collector as ExtensionCollector, Extension}; -use ecs::phase::UPDATE as UPDATE_PHASE; -use ecs::{Component, Query, World}; - -#[derive(Debug, Component)] -struct Position -{ - x: u32, - y: u32, -} - -#[derive(Debug, Component)] -struct EnemySpawnSource; - -#[derive(Debug, Component)] -enum EvilnessLevel -{ - Medium, -} - -fn spawn_enemies( - spawner_query: Query<(&EnemySpawnSource, &Position)>, - enemies_query: Query<(&EvilnessLevel,)>, - mut actions: Actions, -) -{ - let Some((_, enemy_spawner_position)) = spawner_query.iter().next() else { - return; - }; - - let enemy_cnt = enemies_query.iter().count(); - - if enemy_cnt > 3 { - return; - } - - actions.spawn(( - EvilnessLevel::Medium, - Position { - x: enemy_spawner_position.x * enemy_cnt as u32, - y: enemy_spawner_position.y, - }, - )); - - println!("Spawned enemy with medium evilness and 45 strength"); -} - -struct EnemySpawningExtension; - -impl Extension for EnemySpawningExtension -{ - fn collect(self, mut collector: ExtensionCollector<'_>) - { - collector.add_system(*UPDATE_PHASE, spawn_enemies); - - collector.add_entity((Position { x: 187, y: 30 }, EnemySpawnSource)); - } -} - -fn main() -{ - let mut world = World::new(); - - world.add_extension(EnemySpawningExtension); - - for _ in 0..7 { - world.step(); - } -} diff --git a/ecs/examples/multiple_queries.rs b/ecs/examples/multiple_queries.rs deleted file mode 100644 index e0c957f..0000000 --- a/ecs/examples/multiple_queries.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::fmt::Display; - -use ecs::phase::START as START_PHASE; -use ecs::{Component, Query, World}; - -#[derive(Component)] -struct Health -{ - health: u32, -} - -#[derive(Component)] -enum AttackStrength -{ - Strong, - Weak, -} - -#[derive(Component)] -struct EnemyName -{ - name: String, -} - -impl Display for EnemyName -{ - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - self.name.fmt(formatter) - } -} - -fn do_attacks( - attacker_query: Query<(&AttackStrength,)>, - enemy_query: Query<(&mut Health, &EnemyName)>, -) -{ - for (attack_strength,) in &attacker_query { - for (mut health, enemy_name) in &enemy_query { - let damage = match *attack_strength { - AttackStrength::Strong => 20, - AttackStrength::Weak => 10, - }; - - if health.health <= damage { - println!("Enemy '{}' died", *enemy_name); - - health.health = 0; - - continue; - } - - health.health -= damage; - - println!("Enemy '{}' took {damage} damage", *enemy_name); - } - } -} - -fn main() -{ - let mut world = World::new(); - - world.register_system(*START_PHASE, do_attacks); - - world.create_entity(( - Health { health: 100 }, - EnemyName { name: "Big spider".to_string() }, - )); - - world.create_entity(( - Health { health: 30 }, - EnemyName { name: "Small goblin".to_string() }, - )); - - world.create_entity(( - Health { health: 30 }, - EnemyName { name: "Headcrab".to_string() }, - )); - - world.create_entity((AttackStrength::Strong,)); - world.create_entity((AttackStrength::Weak,)); - - world.step(); -} diff --git a/ecs/examples/optional_component.rs b/ecs/examples/optional_component.rs deleted file mode 100644 index ebc9115..0000000 --- a/ecs/examples/optional_component.rs +++ /dev/null @@ -1,81 +0,0 @@ -use ecs::phase::UPDATE as UPDATE_PHASE; -use ecs::{Component, Query, World}; - -#[derive(Debug, Component)] -struct PettingCapacity -{ - capacity_left: u32, -} - -#[derive(Debug, Clone, Copy, Component)] -enum Aggressivity -{ - High, - Medium, - Low, -} - -#[derive(Debug, Component)] -pub struct CatName -{ - name: String, -} - -fn pet_cats(query: Query<(&CatName, &mut PettingCapacity, Option<&Aggressivity>)>) -{ - for (cat_name, mut petting_capacity, aggressivity) in &query { - let Some(aggressivity) = aggressivity else { - println!("Aggressivity of cat {} is unknown. Skipping", cat_name.name); - continue; - }; - - if let Aggressivity::High = *aggressivity { - println!("Cat {} is aggressive. Skipping", cat_name.name); - continue; - } - - if petting_capacity.capacity_left == 0 { - println!( - "Cat {} have had enough of being petted. Skipping", - cat_name.name - ); - continue; - } - - println!("Petting cat {}", cat_name.name); - - petting_capacity.capacity_left -= 1; - } -} - -fn main() -{ - let mut world = World::new(); - - world.register_system(*UPDATE_PHASE, pet_cats); - - world.create_entity(( - CatName { name: "Jasper".to_string() }, - Aggressivity::Medium, - PettingCapacity { capacity_left: 5 }, - )); - - world.create_entity(( - CatName { name: "Otto".to_string() }, - PettingCapacity { capacity_left: 9 }, - )); - - world.create_entity(( - CatName { name: "Carrie".to_string() }, - PettingCapacity { capacity_left: 2 }, - Aggressivity::High, - )); - - world.create_entity(( - CatName { name: "Tommy".to_string() }, - PettingCapacity { capacity_left: 1 }, - Aggressivity::Low, - )); - - world.step(); -} diff --git a/ecs/examples/relationship.rs b/ecs/examples/relationship.rs deleted file mode 100644 index 4e94151..0000000 --- a/ecs/examples/relationship.rs +++ /dev/null @@ -1,56 +0,0 @@ -use ecs::pair::{Pair, Wildcard}; -use ecs::phase::START as START_PHASE; -use ecs::{Component, Query, World}; - -#[derive(Component)] -struct Sword -{ - attack_strength: u32, -} - -#[derive(Component)] -struct Player; - -#[derive(Component)] -struct Health -{ - health: u32, -} - -#[derive(Component)] -struct Holding; - -fn print_player_stats(player_query: Query<(&Player, &Health, Pair)>) -{ - for (_, health, target_sword) in &player_query { - println!("Player health: {}", health.health); - - if let Some(sword_ent) = target_sword.get_target_ent() { - let sword = sword_ent - .get::() - .expect("Sword entity is missing sword component"); - - println!("Player sword attack strength: {}", sword.attack_strength); - } - } -} - -fn main() -{ - let mut world = World::new(); - - world.register_system(*START_PHASE, print_player_stats); - - let sword_uid = world.create_entity((Sword { attack_strength: 17 },)); - - world.create_entity(( - Player, - Health { health: 180 }, - Pair::builder() - .relation::() - .target_id(sword_uid) - .build(), - )); - - world.step(); -} diff --git a/ecs/examples/simple.rs b/ecs/examples/simple.rs deleted file mode 100644 index 0169062..0000000 --- a/ecs/examples/simple.rs +++ /dev/null @@ -1,42 +0,0 @@ -use ecs::phase::START as START_PHASE; -use ecs::{Component, Query, World}; - -#[derive(Component)] -struct SomeData -{ - num: u64, -} - -#[derive(Component)] -struct Greeting -{ - greeting: String, -} - -fn say_hello(query: Query<(&SomeData, &Greeting)>) -{ - for (data, greeting) in &query { - println!("{}: {}", greeting.greeting, data.num); - } -} - -fn main() -{ - let mut world = World::new(); - - world.register_system(*START_PHASE, say_hello); - - 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(); -} diff --git a/ecs/examples/with_local.rs b/ecs/examples/with_local.rs deleted file mode 100644 index 7a36d0e..0000000 --- a/ecs/examples/with_local.rs +++ /dev/null @@ -1,70 +0,0 @@ -use ecs::component::local::Local; -use ecs::phase::UPDATE as UPDATE_PHASE; -use ecs::system::initializable::Initializable; -use ecs::system::Into; -use ecs::{Component, Query, World}; - -#[derive(Component)] -struct SomeData -{ - num: u64, -} - -#[derive(Component)] -struct Name -{ - name: String, -} - -#[derive(Component)] -struct SayHelloState -{ - cnt: usize, -} - -fn say_hello(query: Query<(&SomeData,)>, mut state: Local) -{ - for (data,) in &query { - println!("Hello there. Count {}: {}", state.cnt, data.num); - - state.cnt += 1; - } -} - -fn say_whats_up(query: Query<(&SomeData, &Name)>, mut state: Local) -{ - for (data, name) in &query { - println!( - "Whats up, {}. Number is {}. Count {}", - name.name, data.num, state.cnt - ); - - state.cnt += 1; - } -} - -fn main() -{ - let mut world = World::new(); - - world.register_system( - *UPDATE_PHASE, - say_hello - .into_system() - .initialize((SayHelloState { cnt: 0 },)), - ); - - world.register_system( - *UPDATE_PHASE, - say_whats_up - .into_system() - .initialize((SayHelloState { cnt: 0 },)), - ); - - world.create_entity((SomeData { num: 987_654 }, Name { name: "Bob".to_string() })); - - world.create_entity((SomeData { num: 345 },)); - - world.step(); - world.step(); -} diff --git a/ecs/examples/with_sole.rs b/ecs/examples/with_sole.rs deleted file mode 100644 index 7e89b0a..0000000 --- a/ecs/examples/with_sole.rs +++ /dev/null @@ -1,61 +0,0 @@ -use ecs::pair::{ChildOf, Pair}; -use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; -use ecs::sole::Single; -use ecs::{declare_entity, Component, Query, Sole, World}; - -#[derive(Component)] -struct Ammo -{ - ammo_left: u32, -} - -#[derive(Sole, Default)] -struct AmmoCounter -{ - counter: u32, -} - -fn count_ammo(query: Query<(&Ammo,)>, mut ammo_counter: Single) -{ - for (ammo,) in &query { - println!("Found {} ammo", ammo.ammo_left); - - ammo_counter.counter += ammo.ammo_left; - } -} - -fn print_total_ammo_count(ammo_counter: Single) -{ - println!("Total ammo count: {}", ammo_counter.counter); - - assert_eq!(ammo_counter.counter, 19); -} - -declare_entity!( - PRINT_AMMO_COUNT_PHASE, - ( - Phase, - Pair::builder() - .relation::() - .target_id(*UPDATE_PHASE) - .build() - ) -); - -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); - - world.create_entity((Ammo { ammo_left: 4 },)); - world.create_entity((Ammo { ammo_left: 7 },)); - world.create_entity((Ammo { ammo_left: 8 },)); - - world.add_sole(AmmoCounter::default()).unwrap(); - - world.step(); -} diff --git a/ecs/src/actions.rs b/ecs/src/actions.rs deleted file mode 100644 index 3d8afe6..0000000 --- a/ecs/src/actions.rs +++ /dev/null @@ -1,174 +0,0 @@ -use crate::component::{Parts as ComponentParts, Sequence as ComponentSequence}; -use crate::event::component::Removed; -use crate::pair::Pair; -use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; -use crate::uid::{Kind as UidKind, Uid, WithUidTuple}; -use crate::{ActionQueue, World}; - -/// Used to to queue up actions for a [`World`] to perform. -#[derive(Debug)] -pub struct Actions<'world> -{ - action_queue: &'world ActionQueue, - world: Option<&'world World>, -} - -impl Actions<'_> -{ - /// Queues up a entity to spawn at the end of the current tick, returning the [`Uid`] - /// that the entity will have. - pub fn spawn(&mut self, components: Comps) -> Uid - { - let new_entity_uid = Uid::new_unique(UidKind::Entity); - - self.action_queue.push(Action::Spawn( - new_entity_uid, - components.into_parts_array().into(), - )); - - new_entity_uid - } - - /// Queues up despawning a entity at the end of the current tick. - pub fn despawn(&mut self, entity_uid: Uid) - { - debug_assert_eq!(entity_uid.kind(), UidKind::Entity); - - let Some(world) = self.world else { - self.action_queue.push(Action::Despawn(entity_uid)); - return; - }; - - let Some(ent) = world.get_entity(entity_uid) else { - tracing::warn!("Cannot entity that doesn't exist"); - return; - }; - - // TODO: Submit all events with a single function call to reduce overhead - for comp_id in ent.component_ids() { - if comp_id.kind() == UidKind::Pair { - continue; - } - - world.event_submitter().submit_event( - &Pair::builder() - .relation::() - .target_id(comp_id) - .build(), - entity_uid, - ); - } - - self.action_queue.push(Action::Despawn(entity_uid)); - } - - /// Queues up adding component(s) to a entity at the end of the current tick. - pub fn add_components(&mut self, entity_uid: Uid, components: Comps) - where - Comps: ComponentSequence, - { - debug_assert_eq!(entity_uid.kind(), UidKind::Entity); - - if Comps::COUNT == 0 { - return; - } - - self.action_queue.push(Action::AddComponents( - entity_uid, - components.into_parts_array().into(), - )); - } - - /// Queues up removing component(s) from a entity at the end of the current tick. - #[tracing::instrument(skip(self, component_ids))] - pub fn remove_components( - &mut self, - entity_uid: Uid, - component_ids: impl IntoIterator, - ) - { - debug_assert_eq!(entity_uid.kind(), UidKind::Entity); - - let mut component_ids = component_ids.into_iter().peekable(); - - if component_ids.peek().is_none() { - return; - } - - let Some(world) = self.world else { - self.action_queue.push(Action::RemoveComponents( - entity_uid, - component_ids.collect(), - )); - return; - }; - - let Some(ent) = world.get_entity(entity_uid) else { - tracing::warn!("Cannot remove components from entity that doesn't exist"); - return; - }; - - let component_ids = component_ids - .filter(|comp_id| ent.has_component(*comp_id)) - .collect::>(); - - if component_ids.is_empty() { - return; - } - - // TODO: Submit all events with a single function call to reduce overhead - for comp_id in &component_ids { - if comp_id.kind() == UidKind::Pair { - continue; - } - - world.event_submitter().submit_event( - &Pair::builder() - .relation::() - .target_id(*comp_id) - .build(), - entity_uid, - ); - } - - self.action_queue - .push(Action::RemoveComponents(entity_uid, component_ids)); - } - - /// Queues up removing component(s) from a entity at the end of the current tick. - pub fn remove_comps(&mut self, entity_uid: Uid) - { - self.remove_components(entity_uid, Ids::uids()); - } - - /// Stops the [`World`]. The world will finish the current tick and that tick will be - /// the last. - pub fn stop(&mut self) - { - self.action_queue.push(Action::Stop); - } -} - -impl<'world> SystemParam<'world> for Actions<'world> -{ - type Input = (); - - fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self - { - Self { - action_queue: &world.data.action_queue, - world: Some(world), - } - } -} - -/// A action for a [`System`] to perform. -#[derive(Debug)] -pub(crate) enum Action -{ - Spawn(Uid, Vec), - Despawn(Uid), - AddComponents(Uid, Vec), - RemoveComponents(Uid, Vec), - Stop, -} diff --git a/ecs/src/component.rs b/ecs/src/component.rs deleted file mode 100644 index 17b279b..0000000 --- a/ecs/src/component.rs +++ /dev/null @@ -1,324 +0,0 @@ -use std::any::{type_name, Any}; -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, - MappedWriteGuard, - ReadGuard, - WriteGuard, -}; -use crate::pair::Pair; -use crate::system::Input as SystemInput; -use crate::uid::Uid; -use crate::util::Array; -use crate::{EntityComponentRef, World}; - -pub mod local; - -pub(crate) mod storage; - -pub trait Component: SystemInput + Any -{ - /// Returns the ID of this component. - fn id() -> Uid - where - Self: Sized; - - /// Returns the name of this component. - fn name(&self) -> &'static str; -} - -impl dyn Component -{ - pub fn downcast_mut(&mut self) -> Option<&mut Real> - { - (self as &mut dyn Any).downcast_mut() - } - - pub fn downcast_ref(&self) -> Option<&Real> - { - (self as &dyn Any).downcast_ref() - } - - pub fn is(&self) -> bool - { - (self as &dyn Any).is::() - } -} - -impl Debug for dyn Component -{ - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - formatter.debug_struct("Component").finish_non_exhaustive() - } -} - -/// A sequence of components. -pub trait Sequence -{ - /// The number of components in this component sequence. - const COUNT: usize; - - type PartsArray: Array; - - fn into_parts_array(self) -> Self::PartsArray; -} - -#[derive(Debug)] -pub struct Handle<'a, DataT: 'static> -{ - inner: MappedReadGuard<'a, DataT>, -} - -impl<'comp, DataT: 'static> Handle<'comp, DataT> -{ - /// 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>, - ) -> Result - { - Self::new( - entity_component_ref - .component() - .read_nonblock() - .map_err(AcquireLockError)?, - ) - } - - fn new(inner: ReadGuard<'comp, Box>) -> Result - { - Ok(Self { - inner: ReadGuard::try_map(inner, |component| { - component.downcast_ref::() - }) - .map_err(|_| HandleError::IncorrectType)?, - }) - } -} - -impl Deref for Handle<'_, DataT> -{ - type Target = DataT; - - fn deref(&self) -> &Self::Target - { - &self.inner - } -} - -#[derive(Debug)] -pub struct HandleMut<'a, DataT: 'static> -{ - entity_component_ref: EntityComponentRef<'a>, - inner: MappedWriteGuard<'a, DataT>, - event_submitter: EventSubmitter<'a>, -} - -impl<'comp, DataT: 'static> HandleMut<'comp, DataT> -{ - /// 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 - { - 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::() - }) - .map_err(|_| HandleError::IncorrectType)?, - event_submitter: world.event_submitter(), - }) - } - - pub fn set_changed(&self) - { - self.event_submitter.submit_event( - &Pair::builder() - .relation::() - .target_id(self.entity_component_ref.id()) - .build(), - self.entity_component_ref.entity_id(), - ); - } -} - -impl Deref for HandleMut<'_, DataT> -{ - type Target = DataT; - - fn deref(&self) -> &Self::Target - { - &self.inner - } -} - -impl DerefMut for HandleMut<'_, DataT> -{ - fn deref_mut(&mut self) -> &mut Self::Target - { - &mut self.inner - } -} - -#[derive(Debug, thiserror::Error)] -pub enum HandleError -{ - #[error(transparent)] - AcquireLockFailed(#[from] AcquireLockError), - - #[error("Incorrect component type")] - IncorrectType, -} - -#[derive(Debug, thiserror::Error)] -#[error("Failed to acquire component lock")] -pub struct AcquireLockError(#[source] LockError); - -macro_rules! inner { - ($c: tt) => { - seq!(I in 0..=$c { - impl<#(IntoCompParts~I: IntoParts,)*> Sequence for (#(IntoCompParts~I,)*) - { - const COUNT: usize = $c + 1; - - type PartsArray = [Parts; $c + 1]; - - fn into_parts_array(self) -> Self::PartsArray - { - [#({ - self.I.into_parts() - },)*] - } - } - }); - }; -} - -seq!(C in 0..=16 { - inner!(C); -}); - -impl Sequence for () -{ - type PartsArray = [Parts; 0]; - - const COUNT: usize = 0; - - fn into_parts_array(self) -> Self::PartsArray - { - [] - } -} - -pub trait IntoParts -{ - fn into_parts(self) -> Parts; -} - -impl IntoParts for ComponentT -where - ComponentT: Component, -{ - fn into_parts(self) -> Parts - { - Parts::builder() - .name(type_name::()) - .build(Self::id(), self) - } -} - -/// The parts of a component. -#[derive(Debug)] -#[non_exhaustive] -pub struct Parts -{ - id: Uid, - name: &'static str, - data: Box, -} - -impl Parts -{ - #[must_use] - pub fn id(&self) -> Uid - { - self.id - } - - #[must_use] - pub fn name(&self) -> &'static str - { - self.name - } - - #[must_use] - pub fn builder() -> PartsBuilder - { - PartsBuilder::default() - } - - pub(crate) fn into_data(self) -> Box - { - self.data - } -} - -#[derive(Debug)] -pub struct PartsBuilder -{ - name: &'static str, -} - -impl PartsBuilder -{ - #[must_use] - pub fn name(mut self, name: &'static str) -> Self - { - self.name = name; - self - } - - #[must_use] - pub fn build(self, id: Uid, data: Data) -> Parts - { - Parts { - id, - name: self.name, - data: Box::new(data), - } - } - - #[must_use] - pub fn build_with_any_data(self, id: Uid, data: Box) -> Parts - { - Parts { id, name: self.name, data } - } -} - -impl Default for PartsBuilder -{ - fn default() -> Self - { - Self { name: "(unspecified)" } - } -} diff --git a/ecs/src/component/local.rs b/ecs/src/component/local.rs deleted file mode 100644 index b19a30b..0000000 --- a/ecs/src/component/local.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::any::type_name; -use std::ops::{Deref, DerefMut}; - -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. -#[derive(Debug)] -pub struct Local<'world, LocalComponent: Component> -{ - local_component: ComponentHandleMut<'world, LocalComponent>, -} - -impl<'world, LocalComponent> SystemParam<'world> for Local<'world, LocalComponent> -where - LocalComponent: Component, -{ - type Input = LocalComponent; - - fn new(world: &'world World, system_metadata: &SystemMetadata) -> Self - { - let Some(system_ent) = world.get_entity(system_metadata.ent_id) else { - panic!( - "System entity with ID {} does not exist", - system_metadata.ent_id - ); - }; - - let Some(local_component) = system_ent.get_with_id_mut::( - Pair::builder() - .relation::() - .target::() - .build() - .id(), - ) else { - panic!( - "Local component {} of system with ID {} is uninitialized", - type_name::(), - 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::builder() - .relation::() - .target_as_data(input) - .build() - .into_parts(), - ); - } -} - -impl Deref for Local<'_, LocalComponent> -where - LocalComponent: Component, -{ - type Target = LocalComponent; - - fn deref(&self) -> &Self::Target - { - &self.local_component - } -} - -impl DerefMut for Local<'_, LocalComponent> -where - LocalComponent: Component, -{ - fn deref_mut(&mut self) -> &mut Self::Target - { - &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 deleted file mode 100644 index dc38b6a..0000000 --- a/ecs/src/component/storage.rs +++ /dev/null @@ -1,795 +0,0 @@ -use std::any::Any; -use std::array::IntoIter as ArrayIter; -use std::cell::RefCell; -use std::vec::IntoIter as VecIntoIter; - -use hashbrown::HashMap; - -use crate::component::storage::archetype::{ - Archetype, - Entity as ArchetypeEntity, - EntityComponent as ArchetypeEntityComponent, - Id as ArchetypeId, -}; -use crate::component::storage::graph::{ - ArchetypeAddEdgeDfsIter, - ArchetypeAddEdgeDfsIterResult, - ArchetypeEdges, - Graph, -}; -use crate::uid::{Kind as UidKind, Uid}; -use crate::util::{BorrowedOrOwned, Either, StreamingIterator, VecExt}; - -pub mod archetype; - -mod graph; - -#[derive(Debug)] -pub struct ArchetypeSearchTerms<'a> -{ - pub required_components: &'a [Uid], - pub excluded_components: &'a [Uid], -} - -impl ArchetypeSearchTerms<'_> -{ - fn excluded_contains(&self, comp_id: Uid) -> bool - { - let comp_id_kind = comp_id.kind(); - - debug_assert!( - comp_id_kind == UidKind::Component - || (comp_id_kind == UidKind::Pair - && comp_id.target_component() != Uid::wildcard()) - ); - - let is_found = self.excluded_components.binary_search(&comp_id).is_ok(); - - if !is_found && comp_id_kind == UidKind::Pair { - return self.excluded_components.iter().any(|excluded_comp_id| { - excluded_comp_id.kind() == UidKind::Pair - && excluded_comp_id.has_same_relation_as(comp_id) - && excluded_comp_id.target_component() == Uid::wildcard() - }); - } - - is_found - } - - fn contains_conflicting(&self) -> bool - { - self.excluded_components.iter().any(|excluded_comp_id| { - self.required_components - .binary_search(excluded_comp_id) - .is_ok() - }) - } - - fn archetype_contains_all_required(&self, archetype: &Archetype) -> bool - { - self.required_components - .iter() - .all(|comp_id| archetype.contains_matching_component(*comp_id)) - } -} - -#[derive(Debug, Default)] -pub struct Storage -{ - graph: Graph, - entity_archetype_lookup: HashMap, - imaginary_archetypes: RefCell>, -} - -impl Storage -{ - pub fn search_archetypes<'search_terms>( - &self, - search_terms: ArchetypeSearchTerms<'search_terms>, - ) -> ArchetypeRefIter<'_, 'search_terms> - { - let archetype_id = ArchetypeId::new(search_terms.required_components); - - if search_terms.contains_conflicting() { - return ArchetypeRefIter { - storage: self, - pre_iter: Either::B(Vec::new().into_iter()), - dfs_iter: ArchetypeAddEdgeDfsIter::new(&self.graph, &[]), - search_terms, - }; - } - - let Some(add_edge_recursive_iter) = - self.graph.dfs_archetype_add_edges(archetype_id) - else { - self.imaginary_archetypes - .borrow_mut() - .push(ImaginaryArchetype { - id: ArchetypeId::new(search_terms.required_components.iter().filter( - |required_comp_id| { - required_comp_id.kind() != UidKind::Pair - || required_comp_id.target_component() != Uid::wildcard() - }, - )), - component_ids: search_terms - .required_components - .iter() - .copied() - .filter(|required_comp_id| { - required_comp_id.kind() != UidKind::Pair - || required_comp_id.target_component() != Uid::wildcard() - }) - .collect(), - }); - - let found_archetypes = self.find_all_archetype_with_comps(&search_terms); - - return ArchetypeRefIter { - storage: self, - pre_iter: Either::B(found_archetypes.clone().into_iter()), - dfs_iter: ArchetypeAddEdgeDfsIter::new(&self.graph, &found_archetypes), - search_terms, - }; - }; - - ArchetypeRefIter { - storage: self, - pre_iter: Either::A([archetype_id].into_iter()), - dfs_iter: add_edge_recursive_iter, - search_terms, - } - } - - pub fn get_archetype_by_id(&self, id: ArchetypeId) -> Option<&Archetype> - { - Some(self.graph.get_node_by_id(id)?.archetype()) - } - - pub fn create_entity(&mut self, uid: Uid) -> Result<(), EntityAlreadyExistsError> - { - debug_assert_eq!(uid.kind(), UidKind::Entity); - - if self.entity_archetype_lookup.contains_key(&uid) { - return Err(EntityAlreadyExistsError); - } - - let empty_archetype_id = ArchetypeId::new_empty(); - - let archetype_node = self.graph.get_or_create_node(empty_archetype_id, &[]); - - archetype_node - .archetype_mut() - .push_entity(ArchetypeEntity::new(uid, [])); - - self.entity_archetype_lookup.insert(uid, empty_archetype_id); - - Ok(()) - } - - pub fn remove_entity(&mut self, entity_uid: Uid) -> Result - { - let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { - return Err(Error::EntityDoesNotExist(entity_uid)); - }; - - let archetype_node = self - .graph - .get_node_by_id_mut(*archetype_id) - .expect("Archetype should exist"); - - let entity = archetype_node - .archetype_mut() - .remove_entity(entity_uid) - .expect("Entity should exist in archetype"); - - self.entity_archetype_lookup.remove(&entity_uid); - - Ok(entity) - } - - pub fn get_entity_archetype(&self, entity_uid: Uid) -> Option<&Archetype> - { - let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?; - - self.get_archetype_by_id(*archetype_id) - } - - pub fn add_entity_component( - &mut self, - entity_uid: Uid, - (component_id, component_name, component): (Uid, &'static str, Box), - ) -> Result<(), Error> - { - let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { - return Err(Error::EntityDoesNotExist(entity_uid)); - }; - - let archetype_id = *archetype_id; - - let archetype_node = self - .graph - .get_node_by_id_mut(archetype_id) - .expect("Archetype should exist"); - - if archetype_node - .archetype() - .contains_component_with_exact_id(component_id) - { - return Err(Error::ComponentAlreadyInEntity { - entity: entity_uid, - component: component_id, - }); - } - - let add_edge_archetype_id = if let Some(add_edge_id) = archetype_node - .get_or_insert_edges(component_id, ArchetypeEdges::default) - .add - { - if !self.graph.contains_archetype(add_edge_id) { - let (_, add_edge_comp_ids) = self - .graph - .get_node_by_id(archetype_id) - .expect("Archetype should exist") - .make_add_edge(component_id); - - self.graph.create_node(add_edge_id, &add_edge_comp_ids); - } - - add_edge_id - } else { - let archetype_node = self - .graph - .get_node_by_id(archetype_id) - .expect("Archetype should exist"); - - let (add_edge_id, add_edge_comp_ids) = - archetype_node.make_add_edge(component_id); - - if !self.graph.contains_archetype(add_edge_id) { - self.graph.create_node(add_edge_id, &add_edge_comp_ids); - } - - add_edge_id - }; - - let archetype_node = self - .graph - .get_node_by_id_mut(archetype_id) - .expect("Archetype should exist"); - - let mut entity = archetype_node - .archetype_mut() - .remove_entity(entity_uid) - .expect("Entity should exist in archetype"); - - let add_edge_archetype = self - .graph - .get_node_by_id_mut(add_edge_archetype_id) - .expect("Add edge archetype should exist") - .archetype_mut(); - - entity.insert_component( - component_id, - ArchetypeEntityComponent::new(component, component_name), - add_edge_archetype, - ); - - add_edge_archetype.push_entity(entity); - - self.entity_archetype_lookup - .insert(entity_uid, add_edge_archetype_id); - - Ok(()) - } - - pub fn remove_entity_component( - &mut self, - entity_uid: Uid, - component_id: Uid, - ) -> Result<(), Error> - { - let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { - return Err(Error::EntityDoesNotExist(entity_uid)); - }; - - let archetype_id = *archetype_id; - - let archetype_node = self - .graph - .get_node_by_id_mut(archetype_id) - .expect("Archetype should exist"); - - if !archetype_node - .archetype() - .contains_component_with_exact_id(component_id) - { - return Err(Error::ComponentNotFoundInEntity { - entity: entity_uid, - component: component_id, - }); - } - - let remove_edge_id = archetype_node - .get_or_insert_edges(component_id, ArchetypeEdges::default) - .remove - .unwrap_or_else(|| { - let archetype_node = self - .graph - .get_node_by_id_mut(archetype_id) - .expect("Archetype should exist"); - - let (remove_edge_id, remove_edge_comp_ids) = - archetype_node.make_remove_edge(component_id); - - if !self.graph.contains_archetype(remove_edge_id) { - self.graph - .create_node(remove_edge_id, &remove_edge_comp_ids); - } - - remove_edge_id - }); - - let archetype_node = self - .graph - .get_node_by_id_mut(archetype_id) - .expect("Archetype should exist"); - - let mut entity = archetype_node - .archetype_mut() - .remove_entity(entity_uid) - .expect("Entity should exist in archetype"); - - let removed_component = - entity.remove_component(component_id, archetype_node.archetype()); - - self.graph - .get_node_by_id_mut(remove_edge_id) - .expect("Remove edge archetype should exist") - .archetype_mut() - .push_entity(entity); - - self.entity_archetype_lookup - .insert(entity_uid, remove_edge_id); - - tracing::debug!( - entity_id = %entity_uid, - component_id = %component_id, - component_name = removed_component.name(), - "Removed component from entity" - ); - - Ok(()) - } - - pub fn create_imaginary_archetypes(&mut self) - { - for imaginary_archetype in self.imaginary_archetypes.get_mut().drain(..) { - if self.graph.contains_archetype(imaginary_archetype.id) { - continue; - } - - self.graph - .create_node(imaginary_archetype.id, &imaginary_archetype.component_ids); - } - } - - fn find_all_archetype_with_comps( - &self, - search_terms: &ArchetypeSearchTerms<'_>, - ) -> Vec - { - let Some(mut search_iter) = - self.graph.dfs_archetype_add_edges(ArchetypeId::new_empty()) - else { - // If the root archetype doesn't exist, no other archetype can exist either - // - // TODO: The above comment is not true. Cases where imaginary archetypes have - // been created should be handled as well - return Vec::new(); - }; - - let mut found = Vec::::new(); - - while let Some(node_id) = search_iter.streaming_next() { - let ArchetypeAddEdgeDfsIterResult::AddEdge { - add_edge_archetype_id: node_id, - add_edge_component_id, - } = node_id - else { - continue; - }; - - if search_terms.excluded_contains(add_edge_component_id) { - search_iter.pop(); - continue; - } - - let node = self - .graph - .get_node_by_id(node_id) - .expect("Graph node found through DFS doesn't exist"); - - if node.archetype().component_cnt() < search_terms.required_components.len() { - continue; - } - - if !search_terms.archetype_contains_all_required(node.archetype()) { - continue; - } - - found.push(node.archetype().id()); - - search_iter.pop(); - } - - found - } -} - -#[cfg(feature = "vizoxide")] -impl Storage -{ - pub fn create_vizoxide_archetype_graph( - &self, - graph_name: impl AsRef, - params: VizoxideArchetypeGraphParams, - ) -> Result - { - let viz_graph = vizoxide::Graph::builder(graph_name.as_ref()) - .strict(true) - .directed(true) - .build()?; - - let mut viz_node_lookup = HashMap::new(); - - for node in self.graph.iter_nodes() { - let id = node.archetype().id(); - - if !viz_node_lookup.contains_key(&id) { - let node = self.graph.get_node_by_id(id).unwrap(); - - let viz_node = (params.create_node_cb)( - node.archetype(), - ArchetypeMetadata { is_imaginary: false }, - viz_graph.create_node(&(params.create_node_name)( - node.archetype(), - ArchetypeMetadata { is_imaginary: false }, - )), - ) - .build()?; - - viz_node_lookup.insert(id, viz_node); - } - - for (edge_comp_id, edges) in node.iter_edges() { - if let Some(add_edge) = edges.add { - if !viz_node_lookup.contains_key(&add_edge) { - let viz_node = self.create_vizoxide_archetype_graph_edge_node( - &viz_graph, - node, - add_edge, - *edge_comp_id, - ¶ms, - )?; - - viz_node_lookup.insert(add_edge, viz_node); - } - - (params.create_edge_cb)( - node.archetype(), - *edge_comp_id, - VizoxideArchetypeGraphEdgeKind::Add, - viz_graph.create_edge( - viz_node_lookup.get(&id).unwrap(), - viz_node_lookup.get(&add_edge).unwrap(), - Some(&format!("Add {}", edge_comp_id.id())), - ), - ) - .build()?; - } - - if let Some(remove_edge) = edges.remove { - if !viz_node_lookup.contains_key(&remove_edge) { - let viz_node = self.create_vizoxide_archetype_graph_edge_node( - &viz_graph, - node, - remove_edge, - *edge_comp_id, - ¶ms, - )?; - - viz_node_lookup.insert(remove_edge, viz_node); - } - - (params.create_edge_cb)( - node.archetype(), - *edge_comp_id, - VizoxideArchetypeGraphEdgeKind::Remove, - viz_graph.create_edge( - viz_node_lookup.get(&id).unwrap(), - viz_node_lookup.get(&remove_edge).unwrap(), - Some(&format!("Remove {}", edge_comp_id.id())), - ), - ) - .build()?; - } - } - } - - drop(viz_node_lookup); - - Ok(viz_graph) - } - - fn create_vizoxide_archetype_graph_edge_node<'vizoxide_graph>( - &self, - viz_graph: &'vizoxide_graph vizoxide::Graph, - node: &graph::ArchetypeNode, - edge_id: ArchetypeId, - edge_comp_id: Uid, - params: &VizoxideArchetypeGraphParams, - ) -> Result, vizoxide::GraphvizError> - { - match self.graph.get_node_by_id(edge_id) { - Some(edge_node) => (params.create_node_cb)( - edge_node.archetype(), - ArchetypeMetadata { is_imaginary: false }, - viz_graph.create_node(&(params.create_node_name)( - edge_node.archetype(), - ArchetypeMetadata { is_imaginary: false }, - )), - ) - .build(), - None => { - let mut comp_ids = - node.archetype().component_ids_sorted().collect::>(); - - let insert_index = comp_ids.partition_point(|cid| *cid <= edge_comp_id); - - comp_ids.insert(insert_index, edge_comp_id); - - let imaginary_edge_archetype = Archetype::new(edge_id, comp_ids); - - (params.create_node_cb)( - &imaginary_edge_archetype, - ArchetypeMetadata { is_imaginary: true }, - viz_graph.create_node(&(params.create_node_name)( - &imaginary_edge_archetype, - ArchetypeMetadata { is_imaginary: true }, - )), - ) - .build() - } - } - } -} - -#[cfg(feature = "vizoxide")] -pub struct VizoxideArchetypeGraphParams -{ - pub create_node_name: fn(&Archetype, ArchetypeMetadata) -> std::borrow::Cow<'_, str>, - pub create_node_cb: for<'storage, 'graph> fn( - &'storage Archetype, - ArchetypeMetadata, - vizoxide::NodeBuilder<'graph>, - ) -> vizoxide::NodeBuilder<'graph>, - pub create_edge_cb: for<'storage, 'graph> fn( - &'storage Archetype, - Uid, - VizoxideArchetypeGraphEdgeKind, - vizoxide::EdgeBuilder<'graph>, - ) -> vizoxide::EdgeBuilder<'graph>, -} - -#[cfg(feature = "vizoxide")] -#[derive(Debug, Clone)] -pub struct ArchetypeMetadata -{ - pub is_imaginary: bool, -} - -#[cfg(feature = "vizoxide")] -#[derive(Debug, Clone, Copy)] -pub enum VizoxideArchetypeGraphEdgeKind -{ - Add, - Remove, -} - -#[derive(Debug)] -pub struct ArchetypeRefIter<'storage, 'search_terms> -{ - storage: &'storage Storage, - pre_iter: Either, VecIntoIter>, - dfs_iter: ArchetypeAddEdgeDfsIter<'storage>, - search_terms: ArchetypeSearchTerms<'search_terms>, -} - -impl<'component_storage> Iterator for ArchetypeRefIter<'component_storage, '_> -{ - type Item = &'component_storage Archetype; - - fn next(&mut self) -> Option - { - if let Some(pre_iter_archetype_id) = self.pre_iter.next() { - return Some( - self.storage - .get_archetype_by_id(pre_iter_archetype_id) - .expect("Archetype should exist"), - ); - } - - let archetype_id = loop { - match self.dfs_iter.streaming_find(|res| { - matches!( - res, - ArchetypeAddEdgeDfsIterResult::AddEdge { .. } - | ArchetypeAddEdgeDfsIterResult::AddEdgeArchetypeNotFound { .. } - ) - })? { - ArchetypeAddEdgeDfsIterResult::AddEdge { - add_edge_archetype_id, - add_edge_component_id, - } => { - if self.search_terms.excluded_contains(add_edge_component_id) { - self.dfs_iter.pop(); - continue; - } - - break add_edge_archetype_id; - } - ArchetypeAddEdgeDfsIterResult::AddEdgeArchetypeNotFound { - archetype, - add_edge_archetype_id, - add_edge_component_id, - } => { - if self.search_terms.excluded_contains(add_edge_component_id) { - continue; - } - - let mut add_edge_archetype_comps = - archetype.component_ids_sorted().collect::>(); - - add_edge_archetype_comps - .insert_at_part_pt_by_key(add_edge_component_id, |comp_id| { - comp_id - }); - - self.storage.imaginary_archetypes.borrow_mut().push( - ImaginaryArchetype { - id: add_edge_archetype_id, - component_ids: add_edge_archetype_comps.clone(), - }, - ); - - let found = - self.find_edges_of_imaginary_archetype(&add_edge_archetype_comps); - - self.dfs_iter.push(( - BorrowedOrOwned::Owned(Archetype::new( - add_edge_archetype_id, - add_edge_archetype_comps.clone(), - )), - found.into_iter(), - )); - } - _ => { - unreachable!(); - } - } - }; - - Some( - self.storage - .get_archetype_by_id(archetype_id) - .expect("Archetype should exist"), - ) - } -} - -impl ArchetypeRefIter<'_, '_> -{ - fn find_edges_of_imaginary_archetype( - &self, - imaginary_archetype_comps: &[Uid], - ) -> Vec<(Uid, ArchetypeEdges)> - { - self.storage - .find_all_archetype_with_comps(&ArchetypeSearchTerms { - required_components: imaginary_archetype_comps, - excluded_components: &[], - }) - .into_iter() - .filter_map(|found_id| { - let found_archetype = self.storage.get_archetype_by_id(found_id).unwrap(); - - if found_archetype.component_cnt() < imaginary_archetype_comps.len() + 1 { - return None; - } - - let unique_comp_id = found_archetype - .component_ids_sorted() - .find(|found_archetype_comp_id| { - !imaginary_archetype_comps.iter().any( - |imaginary_archetype_comp_id| { - *imaginary_archetype_comp_id == *found_archetype_comp_id - }, - ) - }) - .expect("Oh noooo"); - - let mut add_edge_comp_ids = imaginary_archetype_comps.to_vec(); - - add_edge_comp_ids.insert_at_part_pt_by_key(unique_comp_id, |id| id); - - let add_edge = ArchetypeId::new(&add_edge_comp_ids); - - Some(( - unique_comp_id, - ArchetypeEdges { add: Some(add_edge), remove: None }, - )) - }) - .collect::>() - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error -{ - #[error("Entity with ID {0:?} does not exist")] - EntityDoesNotExist(Uid), - - #[error("Entity with ID {entity:?} already has component with ID {component:?}")] - ComponentAlreadyInEntity - { - entity: Uid, component: Uid - }, - - #[error("Entity with ID {entity:?} does not have component with ID {component:?}")] - ComponentNotFoundInEntity - { - entity: Uid, component: Uid - }, -} - -#[derive(Debug, thiserror::Error)] -#[error("Entity with already exists")] -pub struct EntityAlreadyExistsError; - -#[derive(Debug)] -struct ImaginaryArchetype -{ - id: ArchetypeId, - component_ids: Vec, -} - -#[cfg(test)] -mod tests -{ - use crate::component::storage::Storage; - use crate::component::storage::archetype::Id as ArchetypeId; - use crate::uid::{Kind as UidKind, Uid}; - - #[test] - fn create_entity_works() - { - let mut new_storage = Storage::default(); - - let uid = Uid::new_unique(UidKind::Entity); - - new_storage.create_entity(uid).expect("Expected Ok"); - - let archetype_node = new_storage - .graph - .get_node_by_id(ArchetypeId::new_empty()) - .expect("Archetype for entities with no component doesn't exist"); - - assert_eq!(archetype_node.archetype().component_cnt(), 0); - assert_eq!(archetype_node.archetype().entity_cnt(), 1); - - assert_eq!( - new_storage.entity_archetype_lookup.get(&uid).copied(), - Some(ArchetypeId::new_empty()) - ); - } -} diff --git a/ecs/src/component/storage/archetype.rs b/ecs/src/component/storage/archetype.rs deleted file mode 100644 index a7fe7ed..0000000 --- a/ecs/src/component/storage/archetype.rs +++ /dev/null @@ -1,385 +0,0 @@ -use std::any::Any; -use std::array::IntoIter as ArrayIntoIter; -use std::hash::{DefaultHasher, Hash, Hasher}; -use std::iter::{Enumerate, Filter, Map, RepeatN, Zip}; -use std::option::IntoIter as OptionIntoIter; -use std::slice::Iter as SliceIter; - -use hashbrown::HashMap; - -use crate::lock::Lock; -use crate::uid::{Kind as UidKind, Uid}; -use crate::util::{Either, HashMapExt}; - -#[derive(Debug)] -pub struct Archetype -{ - id: Id, - entities: Vec, - entity_index_lookup: HashMap, - component_index_lookup: HashMap, - component_ids: Vec, -} - -impl Archetype -{ - pub fn new(id: Id, component_ids: impl AsRef<[Uid]>) -> Self - { - Self { - id, - entities: Vec::new(), - entity_index_lookup: HashMap::new(), - component_index_lookup: component_ids - .as_ref() - .iter() - .enumerate() - .map(|(index, id)| (*id, index)) - .collect(), - component_ids: component_ids.as_ref().to_vec(), - } - } - - pub fn id(&self) -> Id - { - self.id - } - - pub fn is_superset(&self, other: &Self) -> bool - { - self.component_index_lookup - .keys_is_superset(&other.component_index_lookup) - } - - pub fn is_subset(&self, other: &Self) -> bool - { - self.component_index_lookup - .keys_is_subset(&other.component_index_lookup) - } - - pub fn get_entity_by_id(&self, entity_uid: Uid) -> Option<&Entity> - { - let index = *self.entity_index_lookup.get(&entity_uid)?; - - Some(self.entities.get(index).unwrap_or_else(|| { - panic!( - "In invalid state! Index of entity with ID {entity_uid:?} is out of bounds" - ); - })) - } - - pub fn push_entity(&mut self, entity: Entity) - { - self.entity_index_lookup - .insert(entity.uid, self.entities.len()); - - self.entities.push(entity); - } - - pub fn remove_entity(&mut self, entity_uid: Uid) -> Option - { - //debug_assert_eq!(entity_uid.kind(), UidKind::Entity); - - let entity_index = self.entity_index_lookup.remove(&entity_uid)?; - - if self.entities.len() == 1 { - return Some(self.entities.remove(entity_index)); - } - - let last_entity_uid = self - .entities - .last() - .expect(concat!( - "Invalid state. No entities in archetype but entry was ", - "removed successfully from entity index lookup" - )) - .uid; - - // By using swap_remove, no memory reallocation occurs and only one index in the - // entity lookup needs to be updated - let removed_entity = self.entities.swap_remove(entity_index); - - self.entity_index_lookup - .insert(last_entity_uid, entity_index); - - Some(removed_entity) - } - - pub fn entities(&self) -> EntityIter<'_> - { - EntityIter { iter: self.entities.iter() } - } - - pub fn entity_cnt(&self) -> usize - { - self.entities.len() - } - - pub fn component_cnt(&self) -> usize - { - self.component_index_lookup.len() - } - - pub fn get_matching_component_indices( - &self, - component_id: Uid, - ) -> MatchingComponentIter<'_> - { - assert!( - component_id.kind() == UidKind::Component - || component_id.kind() == UidKind::Pair - ); - - if component_id.kind() == UidKind::Pair - && component_id.target_component() == Uid::wildcard() - { - return MatchingComponentIter { - inner: Either::A( - self.component_ids - .iter() - .enumerate() - .zip(std::iter::repeat_n(component_id, self.component_ids.len())) - .filter( - (|((_, other_comp_id), component_id)| { - other_comp_id.kind() == UidKind::Pair - && other_comp_id.has_same_relation_as(*component_id) - }) - as MatchingComponentIterFilterFn, - ) - .map(|((index, other_comp_id), _)| (*other_comp_id, index)), - ), - }; - } - - MatchingComponentIter { - inner: Either::B( - [component_id] - .into_iter() - .zip(self.get_index_for_component(component_id)), - ), - } - } - - pub fn get_index_for_component(&self, component_id: Uid) -> Option - { - assert!( - component_id.kind() == UidKind::Component - || (component_id.kind() == UidKind::Pair - && component_id.target_component() != Uid::wildcard()) - ); - - self.component_index_lookup.get(&component_id).copied() - } - - pub fn component_ids_unsorted(&self) -> impl Iterator + '_ - { - self.component_index_lookup.keys().copied() - } - - pub fn component_ids_sorted(&self) -> impl Iterator + '_ - { - self.component_ids.iter().copied() - } - - pub fn contains_matching_component(&self, component_id: Uid) -> bool - { - let component_id_kind = component_id.kind(); - - debug_assert!( - component_id_kind == UidKind::Component || component_id_kind == UidKind::Pair - ); - - if component_id.kind() == UidKind::Pair - && component_id.target_component() == Uid::wildcard() - { - return self.component_ids.iter().any(|other_comp_id| { - other_comp_id.kind() == UidKind::Pair - && other_comp_id.has_same_relation_as(component_id) - }); - } - - self.contains_component_with_exact_id(component_id) - } - - pub fn contains_component_with_exact_id(&self, component_id: Uid) -> bool - { - let component_id_kind = component_id.kind(); - - debug_assert!( - component_id_kind == UidKind::Component - || (component_id_kind == UidKind::Pair - && component_id.target_component() != Uid::wildcard()) - ); - - self.component_index_lookup.contains_key(&component_id) - } -} - -type MatchingComponentIterFilterFn = fn(&((usize, &Uid), Uid)) -> bool; - -type MatchingComponentIterMapFn = fn(((usize, &Uid), Uid)) -> (Uid, usize); - -type InnerMatchingComponentIterA<'archetype> = Map< - Filter< - Zip>, RepeatN>, - MatchingComponentIterFilterFn, - >, - MatchingComponentIterMapFn, ->; - -type InnerMatchingComponentIterB = Zip, OptionIntoIter>; - -#[derive(Debug)] -pub struct MatchingComponentIter<'archetype> -{ - inner: Either, InnerMatchingComponentIterB>, -} - -impl Iterator for MatchingComponentIter<'_> -{ - type Item = (Uid, usize); - - fn next(&mut self) -> Option - { - self.inner.next() - } -} - -#[derive(Debug)] -pub struct EntityIter<'archetype> -{ - iter: SliceIter<'archetype, Entity>, -} - -impl<'archetype> Iterator for EntityIter<'archetype> -{ - type Item = &'archetype Entity; - - fn next(&mut self) -> Option - { - self.iter.next() - } -} - -#[derive(Debug)] -pub struct Entity -{ - uid: Uid, - components: Vec, -} - -impl Entity -{ - pub fn new(uid: Uid, components: impl IntoIterator) -> Self - { - Self { - uid, - components: components.into_iter().collect(), - } - } - - pub fn uid(&self) -> Uid - { - self.uid - } - - pub fn components(&self) -> &[EntityComponent] - { - &self.components - } - - pub fn remove_component( - &mut self, - component_id: Uid, - archetype: &Archetype, - ) -> EntityComponent - { - let index = archetype - .get_index_for_component(component_id) - .expect("Archetype should contain component"); - - self.components.remove(index) - } - - pub fn insert_component( - &mut self, - component_id: Uid, - component: EntityComponent, - archetype: &Archetype, - ) - { - let index = archetype - .get_index_for_component(component_id) - .expect("Archetype should contain component"); - - self.components.insert(index, component); - } -} - -#[derive(Debug)] -pub struct EntityComponent -{ - component: Lock>, - name: &'static str, -} - -impl EntityComponent -{ - pub fn new(component: Box, component_name: &'static str) -> Self - { - Self { - component: Lock::new(component, component_name), - name: component_name, - } - } - - pub fn component(&self) -> &Lock> - { - &self.component - } - - pub fn name(&self) -> &str - { - self.name - } -} - -/// Archetype ID. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct Id -{ - hash: u64, -} - -impl Id -{ - pub fn new_empty() -> Self - { - Self { hash: 0 } - } - - pub fn new<'a>(component_ids: impl IntoIterator) -> Self - { - let mut hasher = DefaultHasher::new(); - - let mut prev_component_id: Option = None; - - let mut component_id_iter = component_ids.into_iter().peekable(); - - if component_id_iter.peek().is_none() { - return Self::new_empty(); - } - - for comp_id in component_id_iter { - 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); - - comp_id.hash(&mut hasher); - } - - Self { hash: hasher.finish() } - } -} diff --git a/ecs/src/component/storage/graph.rs b/ecs/src/component/storage/graph.rs deleted file mode 100644 index 76200f9..0000000 --- a/ecs/src/component/storage/graph.rs +++ /dev/null @@ -1,432 +0,0 @@ -use std::vec::IntoIter as VecIntoIter; - -use hashbrown::{HashMap, HashSet}; - -use crate::component::storage::archetype::{Archetype, Id as ArchetypeId}; -use crate::uid::{Kind as UidKind, Uid}; -use crate::util::{BorrowedOrOwned, StreamingIterator}; - -#[derive(Debug, Default)] -pub struct Graph -{ - nodes: Vec, - archetype_index_lookup: HashMap, -} - -impl Graph -{ - pub fn create_node(&mut self, id: ArchetypeId, component_ids: &impl AsRef<[Uid]>) - { - debug_assert!(!self.contains_archetype(id)); - - let _ = self.get_or_create_node(id, component_ids); - } - - pub fn get_or_create_node( - &mut self, - id: ArchetypeId, - component_ids: &impl AsRef<[Uid]>, - ) -> &mut ArchetypeNode - { - let exists_before = self.archetype_index_lookup.contains_key(&id); - - let index = *self.archetype_index_lookup.entry(id).or_insert_with(|| { - self.nodes.push(ArchetypeNode { - archetype: Archetype::new(id, component_ids.as_ref()), - edges: HashMap::new(), - }); - - self.nodes.len() - 1 - }); - - if !exists_before { - self.create_missing_edges(id); - } - - self.nodes - .get_mut(index) - .expect("Archetype index from lookup is out of bounds") - } - - pub fn contains_archetype(&self, id: ArchetypeId) -> bool - { - self.archetype_index_lookup.contains_key(&id) - } - - pub fn get_node_by_id(&self, id: ArchetypeId) -> Option<&ArchetypeNode> - { - let index = self.archetype_index_lookup.get(&id)?; - - Some(self.nodes.get(*index).unwrap_or_else(|| { - panic!("In invalid state! Index of archetype with ID {id:?} is out of bounds") - })) - } - - pub fn get_node_by_id_mut(&mut self, id: ArchetypeId) -> Option<&mut ArchetypeNode> - { - let index = self.archetype_index_lookup.get(&id)?; - - Some(self.nodes.get_mut(*index).unwrap_or_else(|| { - panic!("In invalid state! Index of archetype with ID {id:?} is out of bounds") - })) - } - - #[cfg(feature = "vizoxide")] - pub fn iter_nodes(&self) -> impl Iterator - { - self.nodes.iter() - } - - pub fn dfs_archetype_add_edges( - &self, - archetype_id: ArchetypeId, - ) -> Option> - { - let node = self.get_node_by_id(archetype_id)?; - - Some(ArchetypeAddEdgeDfsIter { - graph: self, - stack: vec![( - BorrowedOrOwned::Borrowned(node.archetype()), - node.edges - .iter() - .map(|(comp_id, edges)| (*comp_id, edges.clone())) - .collect::>() - .into_iter(), - )], - visited: HashSet::new(), - }) - } - - fn create_missing_edges(&mut self, archetype_id: ArchetypeId) - { - let archetype_node_index = *self - .archetype_index_lookup - .get(&archetype_id) - .expect("Archetype should exist"); - - let (nodes_before, nodes_rest) = self.nodes.split_at_mut(archetype_node_index); - - let ([archetype_node], nodes_after) = nodes_rest.split_at_mut(1) else { - unreachable!(); - }; - - for other_archetype_node in nodes_before.iter_mut().chain(nodes_after.iter_mut()) - { - if archetype_node.archetype().component_cnt() - > other_archetype_node.archetype().component_cnt() - && other_archetype_node - .archetype() - .is_subset(archetype_node.archetype()) - { - Self::create_missing_subset_node_edges( - archetype_node, - other_archetype_node, - ); - - continue; - } - - if other_archetype_node - .archetype() - .is_superset(archetype_node.archetype()) - { - Self::create_missing_superset_node_edges( - archetype_node, - other_archetype_node, - ); - } - } - } - - fn create_missing_subset_node_edges( - target_node: &mut ArchetypeNode, - subset_node: &mut ArchetypeNode, - ) - { - let uniq_comp_id = target_node - .archetype() - .component_ids_sorted() - .find(|id| { - !subset_node - .archetype() - .contains_component_with_exact_id(*id) - }) - .unwrap(); - - subset_node - .get_or_insert_edges(uniq_comp_id, ArchetypeEdges::default) - .add = Some(subset_node.make_add_edge(uniq_comp_id).0); - - if target_node.archetype().component_cnt() - == subset_node.archetype().component_cnt() + 1 - { - target_node - .get_or_insert_edges(uniq_comp_id, ArchetypeEdges::default) - .remove = Some(subset_node.archetype().id()); - } - } - - fn create_missing_superset_node_edges( - target_node: &mut ArchetypeNode, - superset_node: &mut ArchetypeNode, - ) - { - if superset_node.archetype().component_cnt() - > target_node.archetype().component_cnt() + 1 - { - let first_unique_comp_id = superset_node - .archetype() - .component_ids_sorted() - .find(|other_archetype_comp_id| { - !target_node - .archetype() - .contains_component_with_exact_id(*other_archetype_comp_id) - }) - .or_else(|| { - if target_node.archetype().component_cnt() != 0 { - return None; - } - - superset_node.archetype().component_ids_sorted().next() - }) - .expect("Not possible"); - - target_node - .get_or_insert_edges(first_unique_comp_id, ArchetypeEdges::default) - .add = Some(target_node.make_add_edge(first_unique_comp_id).0); - - return; - } - - if superset_node.archetype().component_cnt() - != target_node.archetype().component_cnt() + 1 - { - return; - } - - let extra_comp_id = superset_node - .archetype() - .component_ids_unsorted() - .find(|comp_id| { - !target_node - .archetype() - .contains_component_with_exact_id(*comp_id) - }) - .expect("Archetype should contain one extra component ID"); - - superset_node - .get_or_insert_edges(extra_comp_id, ArchetypeEdges::default) - .remove = Some(target_node.archetype().id()); - - target_node - .get_or_insert_edges(extra_comp_id, ArchetypeEdges::default) - .add = Some(superset_node.archetype().id()); - } -} - -#[derive(Debug)] -pub struct ArchetypeNode -{ - archetype: Archetype, - edges: HashMap, -} - -impl ArchetypeNode -{ - pub fn archetype(&self) -> &Archetype - { - &self.archetype - } - - pub fn archetype_mut(&mut self) -> &mut Archetype - { - &mut self.archetype - } - - pub fn get_or_insert_edges( - &mut self, - component_id: Uid, - insert_fn: impl FnOnce() -> ArchetypeEdges, - ) -> &mut ArchetypeEdges - { - debug_assert!(matches!( - component_id.kind(), - UidKind::Component | UidKind::Pair - )); - - self.edges.entry(component_id).or_insert_with(insert_fn) - } - - #[cfg(feature = "vizoxide")] - pub fn iter_edges(&self) -> impl Iterator - { - self.edges.iter() - } - - pub fn make_add_edge(&self, component_id: Uid) -> (ArchetypeId, Vec) - { - let mut edge_comp_ids = self - .archetype() - .component_ids_unsorted() - .chain([component_id]) - .collect::>(); - - edge_comp_ids.sort(); - - let add_edge_id = ArchetypeId::new(&edge_comp_ids); - - (add_edge_id, edge_comp_ids) - } - - pub fn make_remove_edge(&self, component_id: Uid) -> (ArchetypeId, Vec) - { - let mut edge_comp_ids = self - .archetype() - .component_ids_unsorted() - .filter(|id| *id != component_id) - .collect::>(); - - edge_comp_ids.sort(); - - let remove_edge_id = ArchetypeId::new(&edge_comp_ids); - - (remove_edge_id, edge_comp_ids) - } -} - -#[derive(Debug, Default, Clone)] -pub struct ArchetypeEdges -{ - pub add: Option, - pub remove: Option, -} - -type ArchetypeAddEdgeDfsIterStackElem<'graph> = ( - BorrowedOrOwned<'graph, Archetype>, - VecIntoIter<(Uid, ArchetypeEdges)>, -); - -#[derive(Debug)] -pub struct ArchetypeAddEdgeDfsIter<'graph> -{ - graph: &'graph Graph, - stack: Vec>, - visited: HashSet, -} - -impl<'graph> ArchetypeAddEdgeDfsIter<'graph> -{ - pub fn new(graph: &'graph Graph, start_nodes: &[ArchetypeId]) -> Self - { - Self { - graph, - stack: start_nodes - .iter() - .map(|start_node_id| { - let start_node = graph - .get_node_by_id(*start_node_id) - .expect("Start node does not exist"); - - ( - BorrowedOrOwned::Borrowned(start_node.archetype()), - start_node - .edges - .iter() - .map(|(comp_id, edges)| (*comp_id, edges.clone())) - .collect::>() - .into_iter(), - ) - }) - .collect(), - visited: start_nodes.iter().copied().collect::>(), - } - } - - pub fn push( - &mut self, - item: ( - BorrowedOrOwned<'graph, Archetype>, - VecIntoIter<(Uid, ArchetypeEdges)>, - ), - ) - { - self.stack.push(item); - } - - pub fn pop(&mut self) - { - self.stack.pop(); - } -} - -impl<'graph> StreamingIterator for ArchetypeAddEdgeDfsIter<'graph> -{ - type Item<'a> - = ArchetypeAddEdgeDfsIterResult<'graph, 'a> - where - Self: 'a; - - fn streaming_next(&mut self) -> Option> - { - let (_, edges_iter) = self.stack.last_mut()?; - - let Some((component_id, edges)) = edges_iter.next() else { - self.stack.pop(); - - return Some(ArchetypeAddEdgeDfsIterResult::NoEdgesLeftForArchetype); - }; - - let Some(add_edge) = edges.add else { - return Some(ArchetypeAddEdgeDfsIterResult::NoAddEdge); - }; - - if self.visited.contains(&add_edge) { - return Some(ArchetypeAddEdgeDfsIterResult::AddEdgeAlreadyVisited); - } - - self.visited.insert(add_edge); - - let Some(add_edge_archetype) = self.graph.get_node_by_id(add_edge) else { - return Some(ArchetypeAddEdgeDfsIterResult::AddEdgeArchetypeNotFound { - archetype: &self.stack.last().unwrap().0, - add_edge_archetype_id: add_edge, - add_edge_component_id: component_id, - }); - }; - - self.stack.push(( - BorrowedOrOwned::Borrowned(add_edge_archetype.archetype()), - add_edge_archetype - .edges - .iter() - .map(|(comp_id, edges)| (*comp_id, edges.clone())) - .collect::>() - .into_iter(), - )); - - Some(ArchetypeAddEdgeDfsIterResult::AddEdge { - add_edge_archetype_id: add_edge, - add_edge_component_id: component_id, - }) - } -} - -#[derive(Debug)] -pub enum ArchetypeAddEdgeDfsIterResult<'graph, 'iter> -{ - AddEdge - { - add_edge_archetype_id: ArchetypeId, - add_edge_component_id: Uid, - }, - NoEdgesLeftForArchetype, - NoAddEdge, - AddEdgeAlreadyVisited, - AddEdgeArchetypeNotFound - { - archetype: &'iter BorrowedOrOwned<'graph, Archetype>, - add_edge_archetype_id: ArchetypeId, - add_edge_component_id: Uid, - }, -} diff --git a/ecs/src/entity.rs b/ecs/src/entity.rs deleted file mode 100644 index ad9f179..0000000 --- a/ecs/src/entity.rs +++ /dev/null @@ -1,295 +0,0 @@ -use std::any::type_name; -use std::ops::Deref; -use std::sync::LazyLock; - -use crate::component::storage::archetype::{ - Archetype, - Entity as ArchetypeEntity, - MatchingComponentIter as ArchetypeMatchingComponentIter, -}; -use crate::component::{ - Component, - Handle as ComponentHandle, - HandleMut as ComponentHandleMut, -}; -use crate::pair::{ - ComponentOrWildcard, - MultipleWithWildcard as PairMultipleWithWildcard, - Pair, - WithWildcard as PairWithWildcard, -}; -use crate::uid::{Kind as UidKind, Uid}; -use crate::{EntityComponentRef, World}; - -pub mod obtainer; - -/// A handle to a entity. -#[derive(Debug, Clone)] -pub struct Handle<'a> -{ - archetype: &'a Archetype, - entity: &'a ArchetypeEntity, - world: &'a World, -} - -impl<'a> Handle<'a> -{ - /// Returns the [`Uid`] of this entity. - #[inline] - #[must_use] - pub fn uid(&self) -> Uid - { - self.entity.uid() - } - - /// Returns a reference to the specified component in this entity. `None` is - /// returned if the component isn't found in the entity. - /// - /// # Panics - /// Will panic if: - /// - The component's ID is not a component ID - /// - The component is mutably borrowed elsewhere - #[must_use] - pub fn get(&self) -> Option> - { - assert_eq!(ComponentT::id().kind(), UidKind::Component); - - let component = self.get_matching_components(ComponentT::id()).next()?; - - Some( - ComponentHandle::from_entity_component_ref(&component).unwrap_or_else( - |err| { - panic!( - "Creating handle to component {} failed: {err}", - type_name::() - ); - }, - ), - ) - } - - /// Returns a mutable reference to the specified component in this entity. `None` is - /// returned if the component isn't found in the entity. - /// - /// # Panics - /// Will panic if: - /// - The component's ID is not a component ID - /// - The component is borrowed elsewhere - #[must_use] - pub fn get_mut( - &self, - ) -> Option> - { - assert_eq!(ComponentT::id().kind(), UidKind::Component); - - let component = self.get_matching_components(ComponentT::id()).next()?; - - Some( - ComponentHandleMut::from_entity_component_ref(&component, self.world) - .unwrap_or_else(|err| { - panic!( - "Creating handle to component {} failed: {err}", - type_name::() - ); - }), - ) - } - - /// 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( - &self, - id: Uid, - ) -> Option> - { - 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::() - ); - }, - ), - ) - } - - /// 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( - &self, - id: Uid, - ) -> Option> - { - 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::() - ); - }), - ) - } - - #[must_use] - pub fn get_first_wildcard_pair_match( - &self, - ) -> Option> - where - Relation: ComponentOrWildcard, - Target: ComponentOrWildcard, - { - let mut matching_comps = self.get_matching_components( - Pair::builder() - .relation_id(Relation::uid()) - .target_id(Target::uid()) - .build() - .id(), - ); - - Some(PairWithWildcard::new(self.world, matching_comps.next()?)) - } - - #[must_use] - pub fn get_wildcard_pair_matches( - &self, - ) -> PairMultipleWithWildcard<'a, Relation, Target> - where - Relation: ComponentOrWildcard, - Target: ComponentOrWildcard, - { - PairMultipleWithWildcard::new(self.world, self.clone()) - } - - #[inline] - #[must_use] - pub fn get_matching_components(&self, component_uid: Uid) - -> MatchingComponentIter<'a> - { - MatchingComponentIter { - inner: self.archetype.get_matching_component_indices(component_uid), - entity: self.entity, - } - } - - /// 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) - } - - /// Returns the `Uids`s of the components this entity has. - pub fn component_ids(&self) -> impl Iterator + '_ - { - self.archetype.component_ids_sorted() - } - - pub(crate) fn new( - archetype: &'a Archetype, - entity: &'a ArchetypeEntity, - world: &'a World, - ) -> Self - { - Self { archetype, entity, world } - } -} - -#[derive(Debug)] -pub struct MatchingComponentIter<'a> -{ - inner: ArchetypeMatchingComponentIter<'a>, - entity: &'a ArchetypeEntity, -} - -impl<'a> Iterator for MatchingComponentIter<'a> -{ - type Item = EntityComponentRef<'a>; - - fn next(&mut self) -> Option - { - let (matching_component_id, index) = self.inner.next()?; - - 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, - 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! declare_entity { - ($visibility: vis $ident: ident, $components: expr) => { - $visibility static $ident: $crate::entity::Declaration = - $crate::entity::Declaration::new(|world| { - world.create_entity_with_uid(*$ident, $components); - }); - } -} diff --git a/ecs/src/entity/obtainer.rs b/ecs/src/entity/obtainer.rs deleted file mode 100644 index 6c2ea96..0000000 --- a/ecs/src/entity/obtainer.rs +++ /dev/null @@ -1,29 +0,0 @@ -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> - { - self.world.get_entity(entity_id) - } -} diff --git a/ecs/src/error.rs b/ecs/src/error.rs deleted file mode 100644 index 185b706..0000000 --- a/ecs/src/error.rs +++ /dev/null @@ -1,270 +0,0 @@ -use std::fmt::{Debug, Display, Write as _}; - -use backtrace::Backtrace; - -#[macro_export] -macro_rules! error { - ($lit: literal) => { - $crate::error::Error::from($lit) - }; - - ($lit: literal, $($tt: tt)*) => { - $crate::error::Error::from(std::format!($lit, $($tt)*)) - }; - - ($err: expr) => { - $crate::error::Error::from($err) - }; -} - -pub struct Error -{ - inner: Box, - backtrace: Backtrace, -} - -impl Error -{ - pub fn resolve_backtrace(&mut self) - { - self.backtrace.resolve(); - } - - fn is_backtrace_resolved(&self) -> bool - { - let Some(first_frame) = self.backtrace.frames().first() else { - return false; - }; - - !first_frame.symbols().is_empty() - } -} - -impl Debug for Error -{ - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - let error = &*self.inner; - - write!(formatter, "{error}")?; - - if let Some(cause) = error.source() { - write!(formatter, "\n\nCaused by:")?; - let multiple = cause.source().is_some(); - for (n, error) in anyhow::Chain::new(cause).enumerate() { - writeln!(formatter)?; - - let mut indented = Indented { - inner: formatter, - number: if multiple { Some(n) } else { None }, - started: false, - }; - write!(indented, "{error}")?; - } - } - - if std::env::var_os("ENGINE_ECS_BACKTRACE") - .is_none_or(|backtrace_enabled| backtrace_enabled != "1") - { - write!( - formatter, - concat!( - "\n\nnote: run with `ENGINE_ECS_BACKTRACE=1` environment variable ", - "to display a engine backtrace" - ) - )?; - - return Ok(()); - } - - let mut cloned_backtrace; - - let backtrace = if self.is_backtrace_resolved() { - &self.backtrace - } else { - cloned_backtrace = self.backtrace.clone(); - cloned_backtrace.resolve(); - &cloned_backtrace - }; - - write!( - formatter, - "\n\nStack backtrace:\n{:?}", - std::fmt::from_fn(|backtrace_formatter| fmt_backtrace( - backtrace, - backtrace_formatter - )) - )?; - - Ok(()) - } -} - -impl Display for Error -{ - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - let error = &*self.inner; - - write!(formatter, "{error}")?; - - if formatter.alternate() { - let chain = anyhow::Chain::new(error); - for cause in chain.skip(1) { - write!(formatter, ": {}", cause)?; - } - } - - Ok(()) - } -} - -impl From for Error -where - Box: From, -{ - fn from(err: Err) -> Self - { - Self { - inner: err.into(), - backtrace: Backtrace::new_unresolved(), - } - } -} - -pub type ErrorHandler = fn(Error, Metadata); - -/// Error metadata. -#[derive(Debug)] -pub struct Metadata -{ - pub source_name: &'static str, - pub source_kind: SourceKind, -} - -/// Error source kind. -#[derive(Debug)] -#[non_exhaustive] -pub enum SourceKind -{ - System, - Observer, -} - -impl Display for SourceKind -{ - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - match self { - SourceKind::System => formatter.write_str("system"), - SourceKind::Observer => formatter.write_str("observer"), - } - } -} - -pub fn err_handler_panic(mut err: Error, err_metadata: Metadata) -{ - err.resolve_backtrace(); - - panic!( - "Error occurred in {} '{}': {err:?}", - err_metadata.source_kind, err_metadata.source_name - ); -} - -pub fn err_handler_log_error(err: Error, err_metadata: Metadata) -{ - tracing::error!( - "Error occurred in {} '{}': {err:#}", - err_metadata.source_kind, - err_metadata.source_name - ); -} - -fn fmt_backtrace( - backtrace: &Backtrace, - fmt: &mut std::fmt::Formatter<'_>, -) -> std::fmt::Result -{ - let style = if fmt.alternate() { - backtrace::PrintFmt::Full - } else { - backtrace::PrintFmt::Short - }; - - // When printing paths we try to strip the cwd if it exists, otherwise - // we just print the path as-is. Note that we also only do this for the - // short format, because if it's full we presumably want to print - // everything. - let cwd = std::env::current_dir(); - let mut print_path = - move |fmt: &mut std::fmt::Formatter<'_>, - path: backtrace::BytesOrWideString<'_>| { - let path = path.into_path_buf(); - if style != backtrace::PrintFmt::Full { - if let Ok(cwd) = &cwd { - if let Ok(suffix) = path.strip_prefix(cwd) { - return std::fmt::Display::fmt(&suffix.display(), fmt); - } - } - } - std::fmt::Display::fmt(&path.display(), fmt) - }; - - let mut f = backtrace::BacktraceFmt::new(fmt, style, &mut print_path); - - f.add_context()?; - - for frame in backtrace.frames() { - if frame.symbols().iter().all(|symbol| { - symbol.name().is_some_and(|symbol_name| { - let symbol_name = symbol_name.to_string(); - - symbol_name - .contains(">::from") - }) - }) { - continue; - } - - f.frame().backtrace_frame(frame)?; - } - f.finish()?; - Ok(()) -} - -struct Indented<'a, D> -{ - inner: &'a mut D, - number: Option, - started: bool, -} - -impl std::fmt::Write for Indented<'_, T> -where - T: std::fmt::Write, -{ - fn write_str(&mut self, s: &str) -> std::fmt::Result - { - for (i, line) in s.split('\n').enumerate() { - if !self.started { - self.started = true; - match self.number { - Some(number) => write!(self.inner, "{: >5}: ", number)?, - None => self.inner.write_str(" ")?, - } - } else if i > 0 { - self.inner.write_char('\n')?; - if self.number.is_some() { - self.inner.write_str(" ")?; - } else { - self.inner.write_str(" ")?; - } - } - - self.inner.write_str(line)?; - } - - Ok(()) - } -} diff --git a/ecs/src/event.rs b/ecs/src/event.rs deleted file mode 100644 index 15455b6..0000000 --- a/ecs/src/event.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::lock::Lock; -use crate::pair::Pair; -use crate::uid::{Kind as UidKind, Uid}; -use crate::util::VecExt; - -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, -} - -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, 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(new_events: &'world Lock) -> Self - { - Self { new_events } - } -} - -#[derive(Debug, Default)] -pub(crate) struct NewEvents -{ - events: Vec<(Uid, Matches)>, -} - -impl NewEvents -{ - pub fn push_event_match(&mut self, event: &Pair, 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_part_pt_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) - } - - pub fn is_empty(&self) -> bool - { - self.events.is_empty() - } -} - -#[derive(Debug)] -pub(crate) struct Matches -{ - pub match_ids: Vec, -} - -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_part_pt_by_key(match_id, |other_match_id| other_match_id); - } -} diff --git a/ecs/src/event/component.rs b/ecs/src/event/component.rs deleted file mode 100644 index 70ea3e5..0000000 --- a/ecs/src/event/component.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! Component events. - -use std::convert::Infallible; - -use crate::Component; -use crate::component::{Handle as ComponentHandle, HandleMut as ComponentHandleMut}; -use crate::entity::Handle as EntityHandle; -use crate::pair::Pair; -use crate::system::observer::{EventMatch, Observed}; - -/// Implemented by the relations of component event pairs -pub trait EventRelation: 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); - -impl EventRelation for Added {} - -/// Pair relation for events emitted **before**: -/// 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); - -impl EventRelation for Removed {} - -#[derive(Debug, Component)] -pub struct Changed(Infallible); - -impl EventRelation for Changed {} - -/// [`EventMatch`] extension trait for component event matches. -pub trait EventMatchExt: sealed::Sealed -{ - #[must_use] - fn get_entity(&self) -> EntityHandle<'_>; - - #[must_use] - fn get_ent_target_comp(&self) -> ComponentHandle<'_, Target> - where - Target: Component; - - #[must_use] - fn get_ent_target_comp_mut(&self) -> ComponentHandleMut<'_, Target> - where - Target: Component; -} - -impl EventMatchExt - for EventMatch<'_, Pair> -where - Pair: Observed, -{ - fn get_entity(&self) -> EntityHandle<'_> - { - let Some(ent) = self.try_get_entity() else { - unreachable!(); - }; - - ent - } - - fn get_ent_target_comp(&self) -> ComponentHandle<'_, Target> - where - Target: Component, - { - let ent = self.get_entity(); - - let Some(comp) = ent.get::() else { - unreachable!(); - }; - - comp - } - - fn get_ent_target_comp_mut(&self) -> ComponentHandleMut<'_, Target> - where - Target: Component, - { - let ent = self.get_entity(); - - let Some(comp) = ent.get_mut::() else { - unreachable!(); - }; - - comp - } -} - -impl sealed::Sealed - for EventMatch<'_, Pair> -where - Pair: Observed, -{ -} - -mod sealed -{ - pub trait Sealed {} -} diff --git a/ecs/src/extension.rs b/ecs/src/extension.rs deleted file mode 100644 index 9c6614b..0000000 --- a/ecs/src/extension.rs +++ /dev/null @@ -1,72 +0,0 @@ -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}; - -/// A collection of systems, entities & soles that can be added to a [`World`]. -pub trait Extension -{ - fn collect(self, collector: Collector<'_>); -} - -/// Passed to a [`Extension`] to collects it's systems, entities & soles. -pub struct Collector<'world> -{ - world: &'world mut World, -} - -impl<'world> Collector<'world> -{ - /// Returns a new `Collector` for the given [`World`]. - pub fn new(world: &'world mut World) -> Self - { - Self { world } - } - - /// Adds a system to the [`World`]. - pub fn add_system<'this, SystemImpl>( - &'this mut self, - phase_euid: Uid, - system: impl System<'this, SystemImpl>, - ) - { - 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(&mut self, components: Comps) - where - Comps: ComponentSequence, - { - 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 - /// Returns `Err` if this [`Sole`] has already been added. - pub fn add_sole(&mut self, sole: SoleT) -> Result<(), SoleAlreadyExistsError> - where - SoleT: Sole, - { - self.world.add_sole(sole) - } -} diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs deleted file mode 100644 index 667aa0e..0000000 --- a/ecs/src/lib.rs +++ /dev/null @@ -1,773 +0,0 @@ -#![deny(clippy::all, clippy::pedantic)] - -use std::any::{Any, TypeId, type_name}; -use std::fmt::Debug; -use std::hint::cold_path; -use std::mem::ManuallyDrop; -use std::rc::Rc; -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; - -use hashbrown::HashMap; - -use crate::actions::Action; -use crate::component::storage::archetype::EntityComponent as ArchetypeEntityComponent; -use crate::component::storage::{EntityAlreadyExistsError, Storage as ComponentStorage}; -use crate::component::{ - Component, - IntoParts as IntoComponentParts, - Parts as ComponentParts, - Sequence as ComponentSequence, -}; -use crate::entity::{Declaration as EntityDeclaration, Handle as EntityHandle}; -use crate::error::{ - ErrorHandler, - Metadata as ErrorMetadata, - SourceKind as ErrorSourceKind, - err_handler_panic, -}; -use crate::event::component::Added; -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, Pair, Wildcard}; -use crate::phase::{ - HasSystem as PhaseHasSystem, - POST_UPDATE as POST_UPDATE_PHASE, - PRE_UPDATE as PRE_UPDATE_PHASE, - Phase, - START as START_PHASE, - UPDATE as UPDATE_PHASE, -}; -use crate::query::flexible::Query as FlexibleQuery; -use crate::query::{ - MAX_TERM_CNT as QUERY_MAX_TERM_CNT, - TermWithFieldTuple as QueryTermWithFieldTuple, - TermWithoutFieldTuple as QueryTermWithoutFieldTuple, - Terms as QueryTerms, - TermsBuilderInterface, -}; -use crate::sole::{Single, Sole}; -use crate::stats::Stats; -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; -pub mod entity; -pub mod error; -pub mod event; -pub mod extension; -pub mod pair; -pub mod phase; -pub mod query; -pub mod sole; -pub mod stats; -pub mod system; -pub mod tuple; -pub mod uid; -pub mod util; - -mod lock; - -pub use ecs_macros::{Component, Sole}; - -pub use crate::query::Query; - -#[derive(Debug)] -pub struct World -{ - data: WorldData, - stop: AtomicBool, - is_first_tick: AtomicBool, - error_handler: ErrorHandler, -} - -impl World -{ - #[must_use] - pub fn new() -> Self - { - let mut world = Self { - data: WorldData::default(), - stop: AtomicBool::new(false), - is_first_tick: AtomicBool::new(false), - error_handler: err_handler_panic, - }; - - crate::phase::spawn_entities(&mut world); - - world.add_sole(Stats::default()).ok(); - - world - } - - pub fn set_err_handler(&mut self, err_handler: ErrorHandler) - { - self.error_handler = err_handler; - } - - /// Creates a entity with the given components. A new unique [`Uid`] will be generated - /// for this entity. - pub fn create_entity(&mut self, components: Comps) -> Uid - where - Comps: ComponentSequence, - { - let entity_uid = Uid::new_unique(UidKind::Entity); - - 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)] - pub fn create_entity_with_uid(&mut self, entity_uid: Uid, components: Comps) - where - Comps: ComponentSequence, - { - self.create_ent(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, - &EventSubmitter::new(&self.data.new_events), - ); - } - - pub fn create_declared_entity(&mut self, entity_decl: &EntityDeclaration) - { - entity_decl.create(self); - } - - /// Adds a globally shared singleton value. - /// - /// # Errors - /// Returns `Err` if this [`Sole`] has already been added. - pub fn add_sole(&mut self, sole: SoleT) -> Result<(), SoleAlreadyExistsError> - where - SoleT: Sole, - { - self.data.sole_storage.insert(sole) - } - - pub fn register_observer<'this, SystemImpl, ObserverT>( - &'this mut self, - observer: ObserverT, - ) where - ObserverT: Observer<'this, SystemImpl>, - { - 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_system<'this, SystemImpl>( - &'this mut self, - phase_euid: Uid, - system: impl System<'this, SystemImpl>, - ) - { - let (type_erased_system, mut system_callbacks) = system.finish(); - - let system_ent_id = - self.create_entity((SystemComponent { system: type_erased_system },)); - - system_callbacks.on_created(self, SystemMetadata { ent_id: system_ent_id }); - - self.create_entity_with_uid( - phase_euid, - (Pair::builder() - .relation::() - .target_id(system_ent_id) - .build(),), - ); - } - - /// Adds a extensions. - pub fn add_extension(&mut self, extension: impl Extension) - { - let extension_collector = ExtensionCollector::new(self); - - extension.collect(extension_collector); - } - - pub fn query( - &self, - ) -> Query<'_, FieldTerms, FieldlessTerms> - where - FieldTerms: QueryTermWithFieldTuple, - FieldlessTerms: QueryTermWithoutFieldTuple, - { - Query::new(self) - } - - pub fn flexible_query( - &self, - terms: QueryTerms, - ) -> FlexibleQuery<'_, MAX_TERM_CNT> - { - FlexibleQuery::new(self, terms) - } - - pub fn get_entity(&self, entity_id: Uid) -> Option> - { - 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(&self) -> Option> - { - Some(Single::new(self.data.sole_storage.get::()?)) - } - - pub fn event_submitter(&self) -> EventSubmitter<'_> - { - EventSubmitter::new(&self.data.new_events) - } - - /// Performs a single tick. - /// # Panics - /// Will panic if mutable internal lock cannot be acquired. - pub fn step(&mut self) -> StepResult - { - if self.stop.load(Ordering::Relaxed) { - return StepResult::Stop; - } - - if self - .is_first_tick - .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) - .is_ok() - { - let Some(start_phase_entity) = self.get_entity(*START_PHASE) else { - unreachable!(); - }; - - self.run_phase_systems(&start_phase_entity); - } - - self.perform_phases(); - - self.emit_new_events(); - - self.data.component_storage.create_imaginary_archetypes(); - - self.perform_queued_actions(); - - if self.stop.load(Ordering::Relaxed) { - return StepResult::Stop; - } - - let Some(mut stats) = self.get_sole::() else { - unreachable!(); // Reason: is added in World::new - }; - - stats.current_tick += 1; - - StepResult::Continue - } - - /// Starts a loop which calls [`Self::step`] until the world is stopped. - pub fn start_loop(&mut self) - { - while let StepResult::Continue = self.step() {} - } - - #[cfg(feature = "vizoxide")] - pub fn create_vizoxide_archetype_graph( - &self, - name: impl AsRef, - ) -> Result - { - use std::borrow::Cow; - - use crate::component::storage::{ - VizoxideArchetypeGraphEdgeKind, - VizoxideArchetypeGraphParams, - }; - - self.data.component_storage.create_vizoxide_archetype_graph( - name, - VizoxideArchetypeGraphParams { - create_node_name: |archetype, _| { - Cow::Owned(format!( - "[{}]", - archetype - .component_ids_sorted() - .into_iter() - .map(|comp_id| comp_id.to_string()) - .collect::>() - .join(", ") - )) - }, - create_node_cb: |_archetype, archetype_metadata, node_builder| { - if archetype_metadata.is_imaginary { - return node_builder.attribute("shape", "ellipse"); - } - - node_builder.attribute("shape", "box") - }, - create_edge_cb: |_, _, edge_kind, edge_builder| { - edge_builder.attribute( - "color", - match edge_kind { - VizoxideArchetypeGraphEdgeKind::Add => "green", - VizoxideArchetypeGraphEdgeKind::Remove => "red", - }, - ) - }, - }, - ) - } - - #[tracing::instrument(skip_all)] - fn create_ent( - &mut self, - entity_uid: Uid, - components: impl IntoIterator, - ) - { - debug_assert_eq!(entity_uid.kind(), UidKind::Entity); - - if let Err(EntityAlreadyExistsError) = - self.data.component_storage.create_entity(entity_uid) - { - // This is fine - } - - Self::add_entity_components( - entity_uid, - components, - &mut self.data.component_storage, - &EventSubmitter::new(&self.data.new_events), - ); - } - - fn run_phase_systems(&self, phase_entity: &EntityHandle<'_>) - { - // The phase's systems are retrieved this way so that the order they are - // run is the same order as they were registered, even if they have local - // components. - for system_entity in phase_entity - .get_wildcard_pair_matches::() - .into_iter() - .filter_map(|phase_has_system| phase_has_system.get_target_ent()) - { - let Some(system) = system_entity.get::() else { - cold_path(); - continue; - }; - - // SAFETY: The world lives long enough - if let Err(err) = unsafe { - system - .system - .run(self, SystemMetadata { ent_id: system_entity.uid() }) - } { - cold_path(); - - (self.error_handler)( - err, - ErrorMetadata { - source_name: system.system.name(), - source_kind: ErrorSourceKind::System, - }, - ) - } - } - } - - fn perform_child_phases(&self, parent_phase_euid: Uid) - { - let phase_query = self.flexible_query( - QueryTerms::<2>::builder() - .with_required([ - Phase::id(), - Pair::builder() - .relation::() - .target_id(parent_phase_euid) - .build() - .id(), - ]) - .build(), - ); - - for child_phase_entity in &phase_query { - self.run_phase_systems(&child_phase_entity); - self.perform_child_phases(child_phase_entity.uid()); - } - } - - fn perform_single_phase(&self, phase_entity_id: Uid) - { - let Some(phase_entity) = self.get_entity(phase_entity_id) else { - unreachable!(); - }; - - self.run_phase_systems(&phase_entity); - self.perform_child_phases(phase_entity_id); - } - - fn perform_phases(&self) - { - self.perform_single_phase(*PRE_UPDATE_PHASE); - self.perform_single_phase(*UPDATE_PHASE); - self.perform_single_phase(*POST_UPDATE_PHASE); - } - - fn emit_new_events(&self) - { - loop { - let new_events = { - let mut new_events_lock = self - .data - .new_events - .write_nonblock() - .expect("Failed to acquire read-write lock to new events"); - - if new_events_lock.is_empty() { - break; - } - - new_events_lock.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, - }, - ); - } - } - } - - #[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 action_queue_lock.drain(..) { - match action { - Action::Spawn(new_entity_uid, components) => { - if let Err(err) = - self.data.component_storage.create_entity(new_entity_uid) - { - tracing::warn!("Failed to create entity: {err}"); - continue; - } - - Self::add_entity_components( - new_entity_uid, - components, - &mut self.data.component_storage, - &EventSubmitter::new(&self.data.new_events), - ); - } - Action::Despawn(entity_uid) => { - if let Err(err) = - self.data.component_storage.remove_entity(entity_uid) - { - tracing::error!("Failed to despawn entity: {err}"); - } - } - Action::AddComponents(entity_uid, components) => { - Self::add_entity_components( - entity_uid, - components, - &mut self.data.component_storage, - &EventSubmitter::new(&self.data.new_events), - ); - } - Action::RemoveComponents(entity_uid, component_ids) => { - Self::remove_entity_components( - entity_uid, - component_ids, - &mut self.data.component_storage, - ); - } - Action::Stop => { - self.stop.store(true, Ordering::Relaxed); - } - } - } - } - - fn add_entity_components( - entity_uid: Uid, - components: impl IntoIterator, - component_storage: &mut ComponentStorage, - event_submitter: &EventSubmitter<'_>, - ) - { - let component_iter = components.into_iter(); - - for component_parts in component_iter { - let comp_id = component_parts.id(); - - let comp_name = component_parts.name(); - - if let Err(err) = component_storage.add_entity_component( - entity_uid, - (comp_id, comp_name, component_parts.into_data()), - ) { - tracing::error!("Failed to add component {comp_name} to entity: {err}"); - continue; - } - - if comp_id.kind() == UidKind::Pair { - continue; - } - - event_submitter.submit_event( - &Pair::builder() - .relation::() - .target_id(comp_id) - .build(), - entity_uid, - ); - } - } - - fn remove_entity_components( - entity_uid: Uid, - component_ids: impl IntoIterator, - component_storage: &mut ComponentStorage, - ) - { - let component_id_iter = component_ids.into_iter(); - - for component_id in component_id_iter { - if let Err(err) = - component_storage.remove_entity_component(entity_uid, component_id) - { - tracing::error!("Failed to remove component to entity: {err}"); - } - } - } - - fn emit_event_observers(&self, event_id: Uid, emitted_event: &EmittedEvent<'_>) - { - assert_eq!(event_id.kind(), UidKind::Pair); - - let query = Query::<(&ObserverWrapperComponent,)>::from_flexible_query( - self.flexible_query( - QueryTerms::::builder() - .with_required([ObserverWrapperComponent::id(), event_id]) - .build(), - ), - ); - - for (observer_ent_id, (observer,)) in query.iter_with_euids() { - if let Err(err) = unsafe { - observer.run( - self, - SystemMetadata { ent_id: observer_ent_id }, - emitted_event.clone(), - ) - } { - cold_path(); - - (self.error_handler)( - err, - ErrorMetadata { - source_name: observer.name(), - source_kind: ErrorSourceKind::Observer, - }, - ) - } - } - } -} - -impl Default for World -{ - fn default() -> Self - { - Self::new() - } -} - -/// The result of calling [`World::step`]. -pub enum StepResult -{ - /// Another step can be made. - Continue, - - /// The world have been stopped so no step can be made again. - Stop, -} - -#[derive(Debug, Default)] -struct WorldData -{ - component_storage: ComponentStorage, - sole_storage: SoleStorage, - action_queue: Rc, - new_events: Lock, -} - -#[derive(Debug, Clone)] -pub struct EntityComponentRef<'a> -{ - component_id: Uid, - component: &'a ArchetypeEntityComponent, - entity_id: Uid, -} - -impl<'a> EntityComponentRef<'a> -{ - fn component(&self) -> &'a Lock> - { - self.component.component() - } - - #[must_use] - pub fn id(&self) -> Uid - { - self.component_id - } - - #[must_use] - pub fn entity_id(&self) -> Uid - { - self.entity_id - } - - fn new(component_id: Uid, comp: &'a ArchetypeEntityComponent, entity_id: Uid) - -> Self - { - Self { - component_id, - component: comp, - entity_id, - } - } -} - -#[derive(Debug, Default)] -struct ActionQueue -{ - queue: Lock>, -} - -impl ActionQueue -{ - fn push(&self, action: Action) - { - self.queue - .write_nonblock() - .expect("Failed to aquire read-write lock to action queue") - .push(action); - } -} - -#[derive(Debug, thiserror::Error)] -#[error("Sole {0} already exists")] -pub struct SoleAlreadyExistsError(pub &'static str); - -#[derive(Debug)] -struct StoredSole -{ - sole: Arc>>, - drop_last: bool, -} - -#[derive(Debug, Default)] -struct SoleStorage -{ - storage: HashMap>, -} - -impl SoleStorage -{ - fn get(&self) -> Option<&Arc>>> - { - self.storage - .get(&TypeId::of::()) - .map(|sole| &sole.sole) - } - - fn insert(&mut self, sole: SoleT) -> Result<(), SoleAlreadyExistsError> - { - let sole_type_id = TypeId::of::(); - - if self.storage.contains_key(&sole_type_id) { - return Err(SoleAlreadyExistsError(type_name::())); - } - - let drop_last = sole.drop_last(); - - // TODO: Reconsider this maybe? - #[allow(clippy::arc_with_non_send_sync)] - self.storage.insert( - sole_type_id, - ManuallyDrop::new(StoredSole { - sole: Arc::new(Lock::new(Box::new(sole), type_name::())), - drop_last, - }), - ); - - Ok(()) - } -} - -impl Drop for SoleStorage -{ - fn drop(&mut self) - { - let mut soles_to_drop_last = Vec::new(); - - for sole in self.storage.values_mut() { - if sole.drop_last { - soles_to_drop_last.push(sole); - continue; - } - - unsafe { - ManuallyDrop::drop(sole); - } - } - - for sole in &mut soles_to_drop_last { - unsafe { - ManuallyDrop::drop(sole); - } - } - } -} diff --git a/ecs/src/lock.rs b/ecs/src/lock.rs deleted file mode 100644 index fe4e08b..0000000 --- a/ecs/src/lock.rs +++ /dev/null @@ -1,259 +0,0 @@ -use std::any::type_name; -use std::mem::forget; -use std::ops::{Deref, DerefMut}; - -use parking_lot::{ - MappedRwLockReadGuard, - MappedRwLockWriteGuard, - RwLock, - RwLockReadGuard, - RwLockWriteGuard, -}; - -#[derive(Debug)] -pub struct Lock -{ - inner: RwLock, - value_type_name: &'static str, -} - -impl Lock -{ - pub fn new(value: Value, value_type_name: &'static str) -> Self - { - Self { - inner: RwLock::new(value), - value_type_name, - } - } - - /// Tries to a acquire a handle to the resource with read access. - /// - /// # Errors - /// Returns `Err` if unavailable (A mutable handle is hold). - pub fn read_nonblock(&self) -> Result, Error> - { - let guard = self.inner.try_read().ok_or(Error::ReadUnavailable)?; - - tracing::trace!("Acquired lock to value of type {}", self.value_type_name); - - Ok(ReadGuard { - inner: guard, - value_type_name: self.value_type_name, - }) - } - - /// Tries to a acquire a handle to the resource with mutable access. - /// - /// # Errors - /// Returns `Err` if unavailable (A mutable or immutable handle is hold). - pub fn write_nonblock(&self) -> Result, Error> - { - let guard = self.inner.try_write().ok_or(Error::WriteUnavailable)?; - - tracing::trace!( - "Acquired mutable lock to value of type {}", - self.value_type_name - ); - - Ok(WriteGuard { - inner: guard, - value_type_name: self.value_type_name, - }) - } -} - -impl Default for Lock -{ - fn default() -> Self - { - Self::new(Value::default(), type_name::()) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error -{ - #[error("Lock is unavailable for reading")] - ReadUnavailable, - - #[error("Lock is unavailable for writing")] - WriteUnavailable, -} - -#[derive(Debug)] -pub struct ReadGuard<'guard, Value> -{ - inner: RwLockReadGuard<'guard, Value>, - value_type_name: &'static str, -} - -impl<'guard, Value> ReadGuard<'guard, Value> -{ - pub fn try_map( - this: Self, - func: impl FnOnce(&Value) -> Option<&NewValue>, - ) -> Result, Self> - { - 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(&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, - }), - } - } -} - -impl Deref for ReadGuard<'_, Value> -{ - type Target = Value; - - fn deref(&self) -> &Self::Target - { - &self.inner - } -} - -impl Drop for ReadGuard<'_, Value> -{ - fn drop(&mut self) - { - tracing::trace!("Dropped lock to value of type {}", self.value_type_name); - } -} - -#[derive(Debug)] -pub struct MappedReadGuard<'guard, Value> -{ - inner: MappedRwLockReadGuard<'guard, Value>, - value_type_name: &'static str, -} - -impl Deref for MappedReadGuard<'_, Value> -{ - type Target = Value; - - fn deref(&self) -> &Self::Target - { - &self.inner - } -} - -impl Drop for MappedReadGuard<'_, Value> -{ - fn drop(&mut self) - { - tracing::trace!( - "Dropped mapped lock to value of type {}", - self.value_type_name - ); - } -} - -#[derive(Debug)] -pub struct WriteGuard<'guard, Value> -{ - inner: RwLockWriteGuard<'guard, Value>, - value_type_name: &'static str, -} - -impl<'guard, Value> WriteGuard<'guard, Value> -{ - pub fn try_map( - this: Self, - func: impl FnOnce(&mut Value) -> Option<&mut NewValue>, - ) -> Result, Self> - { - 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(&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, - }), - } - } -} - -impl Deref for WriteGuard<'_, Value> -{ - type Target = Value; - - fn deref(&self) -> &Self::Target - { - &self.inner - } -} - -impl DerefMut for WriteGuard<'_, Value> -{ - fn deref_mut(&mut self) -> &mut Self::Target - { - &mut self.inner - } -} - -impl Drop for WriteGuard<'_, Value> -{ - fn drop(&mut self) - { - tracing::trace!( - "Dropped mutable lock to value of type {}", - self.value_type_name - ); - } -} - -#[derive(Debug)] -pub struct MappedWriteGuard<'guard, Value> -{ - inner: MappedRwLockWriteGuard<'guard, Value>, - value_type_name: &'static str, -} - -impl Deref for MappedWriteGuard<'_, Value> -{ - type Target = Value; - - fn deref(&self) -> &Self::Target - { - &self.inner - } -} - -impl DerefMut for MappedWriteGuard<'_, Value> -{ - fn deref_mut(&mut self) -> &mut Self::Target - { - &mut self.inner - } -} - -impl Drop for MappedWriteGuard<'_, Value> -{ - fn drop(&mut self) - { - tracing::trace!( - "Dropped mapped mutable lock to value of type {}", - self.value_type_name - ); - } -} diff --git a/ecs/src/pair.rs b/ecs/src/pair.rs deleted file mode 100644 index 0d353e3..0000000 --- a/ecs/src/pair.rs +++ /dev/null @@ -1,687 +0,0 @@ -use std::any::type_name; -use std::convert::Infallible; -use std::marker::PhantomData; - -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, -}; -use crate::query::{ - TermWithField as QueryTermWithField, - TermsBuilder as QueryTermsBuilder, - TermsBuilderInterface, -}; -use crate::uid::{Kind as UidKind, PairParams as UidPairParams, Uid, With as WithUid}; -use crate::util::impl_multiple; -use crate::{Component, EntityComponentRef, World}; - -/// Pair builder. -#[derive(Debug)] -pub struct Builder -{ - relation: Relation, - target: Target, -} - -impl Builder -{ - pub fn relation(self) -> Builder - { - Builder { - relation: NewRelation::id(), - target: self.target, - } - } - - pub fn relation_id(self, id: Uid) -> Builder - { - Builder { relation: id, target: self.target } - } - - pub fn target(self) -> Builder - { - Builder { - relation: self.relation, - target: NewTarget::id(), - } - } - - pub fn target_id(self, id: Uid) -> Builder - { - Builder { relation: self.relation, target: id } - } -} - -impl_multiple!( - Builder, - (impl _<>, impl _<><(), Target>) - cb=(type_params=(ty_param_1, ty_param_2)) => { - pub fn target_as_data( - self, - data: NewTarget, - ) -> Builder<$ty_param_1, NewTarget> - { - Builder { - relation: self.relation, - target: data, - } - } - } -); - -impl_multiple!( - Builder, - (impl _<>, impl _<>) - cb=(type_params=(ty_param_1, ty_param_2)) => { - pub fn relation_as_data( - self, - data: NewRelation, - ) -> Builder - { - Builder { - relation: data, - target: self.target, - } - } - } -); - -impl_multiple!( - Builder, - ( - impl _<>, - impl _<>, - impl _<>, - impl _<> - ) - cb=(type_params=(ty_param_1, ty_param_2)) => { - #[must_use] - pub fn build(self) -> Pair<$ty_param_1, $ty_param_2> - { - Pair { - relation: self.relation, - target: self.target - } - } - } -); - -impl Default for Builder<(), ()> -{ - fn default() -> Self - { - Self { relation: (), target: () } - } -} - -#[derive(Debug)] -pub struct Pair -{ - relation: Relation, - target: Target, -} - -impl Pair<(), ()> -{ - #[must_use] - pub fn builder() -> Builder<(), ()> - { - Builder { relation: (), target: () } - } -} - -impl Pair -{ - #[must_use] - pub fn id(&self) -> Uid - { - Uid::new_pair(&UidPairParams { - relation: self.relation, - target: self.target, - }) - } -} - -impl IntoComponentParts for Pair -{ - fn into_parts(self) -> ComponentParts - { - ComponentParts::builder().name("Pair").build(self.id(), ()) - } -} - -impl IntoComponentParts for Pair -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 IntoComponentParts for Pair -where - Relation: Component, -{ - fn into_parts(self) -> ComponentParts - { - let id = Uid::new_pair(&UidPairParams { - relation: Relation::id(), - target: self.target, - }); - - ComponentParts::builder() - .name("Pair") - .build(id, self.relation) - } -} - -impl QueryTermWithField for Pair -where - Relation: Component, - Target: Component, -{ - type Field<'a> = ComponentHandle<'a, Target>; - - fn apply_to_terms_builder( - terms_builder: &mut QueryTermsBuilder, - ) - { - terms_builder.with_required([Pair::::uid()]); - } - - fn get_field<'world>( - entity_handle: &EntityHandle<'world>, - _world: &'world World, - ) -> Self::Field<'world> - { - let target_component = entity_handle - .get_matching_components(Pair::::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::() - ); - }) - } -} - -impl QueryTermWithField for Pair -where - Relation: Component, - Target: Component, -{ - type Field<'a> = ComponentHandleMut<'a, Target>; - - fn apply_to_terms_builder( - terms_builder: &mut QueryTermsBuilder, - ) - { - terms_builder.with_required([Pair::::uid()]); - } - - fn get_field<'world>( - entity_handle: &EntityHandle<'world>, - world: &'world World, - ) -> Self::Field<'world> - { - let target_component = entity_handle - .get_matching_components(Pair::::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::() - ); - }, - ) - } -} - -// TODO: implement QueryTermWithField for Pair<&Relation, Target> (or equivalent) -// TODO: implement QueryTermWithField for Pair<&mut Relation, Target> (or equivalent) - -impl QueryTermWithField for Pair -where - Relation: Component, -{ - type Field<'a> = WithWildcard<'a, Relation, Wildcard>; - - fn apply_to_terms_builder( - terms_builder: &mut QueryTermsBuilder, - ) - { - terms_builder.with_required([Self::uid()]); - } - - fn get_field<'world>( - entity_handle: &EntityHandle<'world>, - world: &'world World, - ) -> Self::Field<'world> - { - let first_matching_comp = entity_handle - .get_matching_components(Self::uid()) - .next() - .expect("Not possible"); - - WithWildcard { - world, - component_ref: first_matching_comp, - _pd: PhantomData, - } - } -} - -impl WithUid for Pair -where - Relation: Component, - Target: Component, -{ - fn uid() -> Uid - { - Uid::new_pair(&UidPairParams { - relation: Relation::id(), - target: Target::id(), - }) - } -} - -impl WithUid for Pair -where - Relation: Component, -{ - fn uid() -> Uid - { - Uid::new_pair(&UidPairParams { - relation: Relation::id(), - target: Wildcard::uid(), - }) - } -} - -impl QueryTermWithField for &'_ [Pair] -where - Relation: Component, -{ - type Field<'a> = MultipleWithWildcard<'a, Relation, Wildcard>; - - fn apply_to_terms_builder( - _terms_builder: &mut QueryTermsBuilder, - ) - { - } - - fn get_field<'world>( - entity_handle: &EntityHandle<'world>, - world: &'world World, - ) -> Self::Field<'world> - { - MultipleWithWildcard { - entity_handle: entity_handle.clone(), - world, - _pd: PhantomData, - } - } -} - -/// Reference to a pair with a wildcard relation/target. -#[derive(Debug)] -pub struct WithWildcard<'world, Relation, Target> -{ - world: &'world World, - component_ref: EntityComponentRef<'world>, - _pd: PhantomData<(Relation, Target)>, -} - -impl<'world, Relation, Target> WithWildcard<'world, Relation, Target> -{ - /// Returns a new `WithWildcard`. - /// - /// # Panics - /// This function will panic if: - /// - The given component's ID is not a pair ID. - /// - `Relation::uid()` is not wildcard and does not equal to the relation of the - /// given component's ID - /// - `Target::uid()` is not wildcard and does not equal to the target of the given - /// component's ID - /// - Both `Relation::uid()` and `Target::uid()` are wildcards - /// - Neither `Relation::uid()` or `Target::uid()` are wildcards - pub fn new(world: &'world World, component_ref: EntityComponentRef<'world>) -> Self - where - Relation: ComponentOrWildcard, - Target: ComponentOrWildcard, - { - let component_id = component_ref.id(); - - assert!(component_id.kind() == UidKind::Pair); - - assert!( - Relation::uid() == Wildcard::uid() - || component_id.relation_component() == Relation::uid() - ); - - assert!( - Target::uid() == Wildcard::uid() - || component_id.target_component() == Target::uid() - ); - - assert!(Relation::uid() == Wildcard::uid() || Target::uid() == Wildcard::uid()); - - assert!( - !(Relation::uid() == Wildcard::uid() && Target::uid() == Wildcard::uid()) - ); - - WithWildcard { - world, - component_ref, - _pd: PhantomData, - } - } - - /// Returns the [`Uid`] of the pair. - #[must_use] - pub fn id(&self) -> Uid - { - self.component_ref.id() - } - - /// Attempts to get the component data of this pair, returning `None` if the `Data` - /// type is incorrect. - /// - /// # Panics - /// Will panic if the component data is mutably borrowed elsewhere. - #[must_use] - pub fn get_data(&self) -> Option> - where - Data: 'static, - { - ComponentHandle::::from_entity_component_ref(&self.component_ref) - .map_or_else( - |err| match err { - ComponentHandleError::IncorrectType => None, - err @ ComponentHandleError::AcquireLockFailed(_) => { - panic!( - "Creating handle to pair data as component {} failed: {err}", - type_name::() - ); - } - }, - Some, - ) - } - - /// Attempts to get the component data of this pair, returning `None` if the `Data` - /// type is incorrect. - /// - /// # Panics - /// Will panic if the component data is borrowed elsewhere. - #[must_use] - pub fn get_data_mut(&self) -> Option> - where - Data: 'static, - { - ComponentHandleMut::::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 pair data as component {} failed: {err}", - type_name::() - ); - } - }, - Some, - ) - } -} - -impl<'world, Relation> WithWildcard<'world, Relation, Wildcard> -where - Relation: Component, -{ - /// Attempts to retrieve the target as a entity, returning `None` if not found. - #[must_use] - pub fn get_target_ent(&self) -> Option> - { - let archetype = self - .world - .data - .component_storage - .get_entity_archetype(self.component_ref.id().target_entity())?; - - let Some(archetype_entity) = - archetype.get_entity_by_id(self.component_ref.id().target_entity()) - else { - unreachable!(); - }; - - Some(EntityHandle::new(archetype, archetype_entity, self.world)) - } - - /// Attempts to get the component data of this pair, returning `None` if the - /// `Relation` type is incorrect. - /// - /// # Panics - /// Will panic if the component data is mutably borrowed elsewhere. - #[must_use] - pub fn get_data_as_relation(&self) -> Option> - { - ComponentHandle::::from_entity_component_ref(&self.component_ref) - .map_or_else( - |err| match err { - ComponentHandleError::IncorrectType => None, - err @ ComponentHandleError::AcquireLockFailed(_) => { - panic!( - "Creating handle to pair data as component {} failed: {err}", - type_name::() - ); - } - }, - Some, - ) - } - - /// Attempts to get the component data of this pair, returning `None` if the - /// `Relation` type is incorrect. - /// - /// # Panics - /// Will panic if the component data is borrowed elsewhere. - #[must_use] - pub fn get_data_as_relation_mut(&self) -> Option> - { - ComponentHandleMut::::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 pair data as component {} failed: {err}", - type_name::() - ); - } - }, - Some, - ) - } -} - -/// Used to access matching pairs in a entity containing zero or more matching pairs. -#[derive(Debug)] -pub struct MultipleWithWildcard<'a, Relation, Target> -{ - entity_handle: EntityHandle<'a>, - world: &'a World, - _pd: PhantomData<(Relation, Target)>, -} - -impl<'world, Relation, Target> MultipleWithWildcard<'world, Relation, Target> -{ - /// Returns a new `MultipleWithWildcard`. - /// - /// # Panics - /// This function will panic if: - /// - Both `Relation::uid()` and `Target::uid()` are wildcards - /// - Neither `Relation::uid()` or `Target::uid()` are wildcards - pub fn new(world: &'world World, entity_handle: EntityHandle<'world>) -> Self - where - Relation: ComponentOrWildcard, - Target: ComponentOrWildcard, - { - assert!(Relation::uid() == Wildcard::uid() || Target::uid() == Wildcard::uid()); - - assert!( - !(Relation::uid() == Wildcard::uid() && Target::uid() == Wildcard::uid()) - ); - - MultipleWithWildcard { - entity_handle, - world, - _pd: PhantomData, - } - } -} - -impl<'a, Relation: Component> MultipleWithWildcard<'a, Relation, Wildcard> -{ - #[must_use] - pub fn get_with_target_id( - &self, - target_id: Uid, - ) -> Option> - { - Some(WithWildcard { - world: self.world, - component_ref: self - .entity_handle - .get_matching_components( - Pair::builder() - .relation::() - .target_id(target_id) - .build() - .id(), - ) - .next()?, - _pd: PhantomData, - }) - } -} - -impl<'a, Relation: Component> IntoIterator - for MultipleWithWildcard<'a, Relation, Wildcard> -{ - type IntoIter = WithWildcardIter<'a, Relation, Wildcard>; - type Item = ::Item; - - fn into_iter(self) -> Self::IntoIter - { - WithWildcardIter { - inner: self - .entity_handle - .get_matching_components(Pair::::uid()), - world: self.world, - _pd: PhantomData, - } - } -} - -/// Iterator of matching pairs in a entity. -pub struct WithWildcardIter<'a, Relation, Target> -{ - inner: EntityMatchingComponentIter<'a>, - world: &'a World, - _pd: PhantomData<(Relation, Target)>, -} - -impl<'a, Relation, Target> Iterator for WithWildcardIter<'a, Relation, Target> -{ - type Item = WithWildcard<'a, Relation, Target>; - - fn next(&mut self) -> Option - { - let matching_comp = self.inner.next()?; - - Some(WithWildcard { - world: self.world, - component_ref: matching_comp, - _pd: PhantomData, - }) - } -} - -/// Relation denoting a dependency to another entity -#[derive(Debug, Default, Clone, Copy, Component)] -pub struct DependsOn; - -/// Relation denoting being the child of another entity. -#[derive(Debug, Default, Clone, Copy, Component)] -pub struct ChildOf; - -#[derive(Debug)] -pub struct Wildcard(Infallible); - -impl Wildcard -{ - #[must_use] - pub fn uid() -> Uid - { - Uid::wildcard() - } -} - -pub trait ComponentOrWildcard: sealed::Sealed -{ - fn uid() -> Uid; -} - -impl ComponentOrWildcard for ComponentT -{ - fn uid() -> Uid - { - ComponentT::id() - } -} - -impl sealed::Sealed for ComponentT {} - -impl ComponentOrWildcard for Wildcard -{ - fn uid() -> Uid - { - Wildcard::uid() - } -} - -impl sealed::Sealed for Wildcard {} - -mod sealed -{ - pub trait Sealed {} -} diff --git a/ecs/src/phase.rs b/ecs/src/phase.rs deleted file mode 100644 index e8d9b71..0000000 --- a/ecs/src/phase.rs +++ /dev/null @@ -1,22 +0,0 @@ -use ecs_macros::Component; - -use crate::{World, declare_entity}; - -#[derive(Debug, Default, Clone, Copy, Component)] -pub struct Phase; - -declare_entity!(pub START, (Phase,)); -declare_entity!(pub PRE_UPDATE, (Phase,)); -declare_entity!(pub UPDATE, (Phase,)); -declare_entity!(pub POST_UPDATE, (Phase,)); - -pub(crate) fn spawn_entities(world: &mut World) -{ - world.create_declared_entity(&START); - world.create_declared_entity(&PRE_UPDATE); - world.create_declared_entity(&UPDATE); - world.create_declared_entity(&POST_UPDATE); -} - -#[derive(Debug, Component)] -pub(crate) struct HasSystem; diff --git a/ecs/src/query.rs b/ecs/src/query.rs deleted file mode 100644 index 5f13579..0000000 --- a/ecs/src/query.rs +++ /dev/null @@ -1,569 +0,0 @@ -use std::any::type_name; -use std::marker::PhantomData; - -use seq_macro::seq; - -use crate::component::{ - Component, - Handle as ComponentHandle, - HandleMut as ComponentHandleMut, -}; -use crate::entity::Handle as EntityHandle; -use crate::query::flexible::{Iter as FlexibleQueryIter, Query as FlexibleQuery}; -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; -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, -{ - inner: FlexibleQuery<'world, MAX_TERM_CNT>, - _pd: PhantomData<(FieldTerms, FieldlessTerms)>, -} - -impl<'world, FieldTerms, FieldlessTerms> Query<'world, FieldTerms, FieldlessTerms> -where - FieldTerms: TermWithFieldTuple, - FieldlessTerms: TermWithoutFieldTuple, -{ - /// Iterates over the entities matching this query, the iterator item being the entity - /// components. - #[must_use] - pub fn iter<'query>( - &'query self, - ) -> Iter<'query, 'world, FieldTerms, FlexibleQueryIter<'query>> - { - tracing::trace!("Searching for {}", std::any::type_name::()); - - Iter { - world: self.inner.world(), - inner: self.inner.iter(), - comps_pd: PhantomData, - } - } - - /// Iterates over the entities matching this query, the iterator item being the entity - /// [`Uid`] and the matching entity components. - #[must_use] - pub fn iter_with_euids<'query>( - &'query self, - ) -> ComponentAndEuidIter<'query, 'world, FieldTerms, FlexibleQueryIter<'query>> - { - tracing::trace!("Searching for {}", std::any::type_name::()); - - ComponentAndEuidIter { - world: self.inner.world(), - iter: self.inner.iter(), - comps_pd: PhantomData, - } - } - - /// Iterates over the entities matching this query using the iterator returned by - /// `func`. - /// - /// This function exists so that a custom [`EntityHandle`] iterator can be given to - /// [`Iter`] without giving the user access to a reference to the [`World`]. - #[must_use] - pub fn iter_with<'query, OutIter>( - &'query self, - func: impl FnOnce(FlexibleQueryIter<'query>) -> OutIter, - ) -> Iter<'query, 'world, FieldTerms, OutIter> - where - OutIter: Iterator>, - { - tracing::trace!("Searching for {}", std::any::type_name::()); - - Iter { - world: self.inner.world(), - inner: func(self.inner.iter()), - comps_pd: PhantomData, - } - } - - /// Returns the UID of the entity at the given query iteration index. - #[must_use] - pub fn get_entity_uid(&self, entity_index: usize) -> Option - { - 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(); - - FieldTerms::apply_terms_to_builder(&mut terms_builder); - FieldlessTerms::apply_terms_to_builder(&mut terms_builder); - - Self { - inner: world.flexible_query(terms_builder.build()), - _pd: PhantomData, - } - } -} - -impl<'query, 'world, FieldTerms, FieldlessTerms> IntoIterator - for &'query Query<'world, FieldTerms, FieldlessTerms> -where - FieldTerms: TermWithFieldTuple, - FieldlessTerms: TermWithoutFieldTuple, -{ - type IntoIter = Iter<'query, 'world, FieldTerms, FlexibleQueryIter<'query>>; - type Item = FieldTerms::Fields<'query>; - - fn into_iter(self) -> Self::IntoIter - { - self.iter() - } -} - -impl<'world, FieldTerms, FieldlessTerms> SystemParam<'world> - for Query<'world, FieldTerms, FieldlessTerms> -where - FieldTerms: TermWithFieldTuple, - FieldlessTerms: TermWithoutFieldTuple, -{ - type Input = (); - - fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self - { - Self::new(world) - } -} - -#[derive(Debug)] -pub struct Terms -{ - required_components: ArrayVec, - excluded_components: ArrayVec, -} - -impl Terms -{ - pub fn builder() -> TermsBuilder - { - TermsBuilder::default() - } -} - -#[derive(Debug, Default)] -#[must_use] -pub struct TermsBuilder -{ - required_components: ArrayVec, - excluded_components: ArrayVec, -} - -#[allow(clippy::return_self_not_must_use)] -pub trait TermsBuilderInterface -{ - fn with(self) -> Self; - - fn without(self) -> Self; - - fn with_required(self, ids: impl Array) -> Self; - - fn without_ids(self, ids: impl Array) -> Self; -} - -macro_rules! impl_terms_builder { - ($($impl_content: tt)*) => { - impl - TermsBuilderInterface for TermsBuilder - { - $($impl_content)* - } - - impl - TermsBuilderInterface for &mut TermsBuilder - { - $($impl_content)* - } - }; -} - -impl_terms_builder! { - #[allow(unused_mut)] - fn with(mut self) -> Self - { - let insert_index = self.required_components - .partition_point(|id| *id <= WithUidT::uid()); - - self.required_components - .insert(insert_index, WithUidT::uid()); - - self - } - - #[allow(unused_mut)] - fn without(mut self) -> Self - { - let insert_index = self.excluded_components - .partition_point(|id| *id <= WithUidT::uid()); - - self.excluded_components - .insert(insert_index, WithUidT::uid()); - - self - } - - #[allow(unused_mut)] - fn with_required(mut self, mut ids: impl Array) -> Self - { - if !ids.as_ref().is_sorted() { - ids.as_mut().sort(); - } - - if self.required_components.is_empty() { - self.required_components.extend(ids); - return self; - } - - let mut id_iter = ids.into_iter(); - - while let Some(id) = id_iter.next() { - let insert_index = self.required_components - .partition_point(|other_id| *other_id <= id); - - if insert_index == self.required_components.len() { - self.required_components.extend([id].into_iter().chain(id_iter)); - - return self; - } - - self.required_components - .insert(insert_index, id); - - } - - self - } - - #[allow(unused_mut)] - fn without_ids(mut self, mut ids: impl Array) -> Self - { - if !ids.as_ref().is_sorted() { - ids.as_mut().sort(); - } - - if self.excluded_components.is_empty() { - self.excluded_components.extend(ids); - return self; - } - - let mut id_iter = ids.into_iter(); - - while let Some(id) = id_iter.next() { - let insert_index = self.excluded_components - .partition_point(|other_id| *other_id <= id); - - if insert_index == self.excluded_components.len() { - self.excluded_components.extend([id].into_iter().chain(id_iter)); - - return self; - } - - self.excluded_components - .insert(insert_index, id); - - } - - self - } -} - -impl TermsBuilder -{ - #[must_use] - pub fn build(self) -> Terms - { - debug_assert!(self.required_components.is_sorted()); - debug_assert!(self.excluded_components.is_sorted()); - - Terms { - required_components: self.required_components, - excluded_components: self.excluded_components, - } - } -} - -pub trait TermWithoutField -{ - fn apply_to_terms_builder( - terms_builder: &mut TermsBuilder, - ); -} - -pub trait TermWithField -{ - type Field<'a>; - - fn apply_to_terms_builder( - terms_builder: &mut TermsBuilder, - ); - - fn get_field<'world>( - entity_handle: &EntityHandle<'world>, - world: &'world World, - ) -> Self::Field<'world>; -} - -impl TermWithField for &ComponentT -{ - type Field<'a> = ComponentHandle<'a, ComponentT>; - - fn apply_to_terms_builder( - terms_builder: &mut TermsBuilder, - ) - { - terms_builder.with::(); - } - - fn get_field<'world>( - entity_handle: &EntityHandle<'world>, - _world: &'world World, - ) -> Self::Field<'world> - { - assert_eq!(ComponentT::id().kind(), UidKind::Component); - - 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::(), - entity_handle.uid() - ); - }; - - Self::Field::from_entity_component_ref(&component).unwrap_or_else(|err| { - panic!( - "Creating handle to component {} failed: {err}", - type_name::() - ); - }) - } -} - -impl TermWithField for &mut ComponentT -{ - type Field<'a> = ComponentHandleMut<'a, ComponentT>; - - fn apply_to_terms_builder( - terms_builder: &mut TermsBuilder, - ) - { - terms_builder.with::(); - } - - fn get_field<'world>( - entity_handle: &EntityHandle<'world>, - world: &'world World, - ) -> Self::Field<'world> - { - assert_eq!(ComponentT::id().kind(), UidKind::Component); - - 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::(), - entity_handle.uid() - ); - }; - - Self::Field::from_entity_component_ref(&component, world).unwrap_or_else(|err| { - panic!( - "Creating handle to component {} failed: {err}", - type_name::() - ); - }) - } -} - -pub trait TermWithoutFieldTuple -{ - fn apply_terms_to_builder( - terms_builder: &mut TermsBuilder, - ); -} - -pub trait TermWithFieldTuple -{ - type Fields<'component>; - - fn apply_terms_to_builder( - terms_builder: &mut TermsBuilder, - ); - - fn get_fields<'component>( - entity_handle: &EntityHandle<'component>, - world: &'component World, - ) -> Self::Fields<'component>; -} - -pub struct Iter<'query, 'world, FieldTerms, EntityHandleIter> -where - FieldTerms: TermWithFieldTuple, - EntityHandleIter: Iterator>, -{ - world: &'world World, - inner: EntityHandleIter, - comps_pd: PhantomData, -} - -impl<'query, 'world, FieldTerms, EntityHandleIter> Iterator - for Iter<'query, 'world, FieldTerms, EntityHandleIter> -where - FieldTerms: TermWithFieldTuple, - EntityHandleIter: Iterator>, - 'world: 'query, -{ - type Item = FieldTerms::Fields<'query>; - - fn next(&mut self) -> Option - { - let entity_handle = self.inner.next()?; - - Some(FieldTerms::get_fields(&entity_handle, self.world)) - } -} - -pub struct ComponentAndEuidIter<'query, 'world, FieldTerms, EntityHandleIter> -where - FieldTerms: TermWithFieldTuple, - EntityHandleIter: Iterator>, -{ - world: &'world World, - iter: EntityHandleIter, - comps_pd: PhantomData, -} - -impl<'query, 'world, FieldTerms, EntityHandleIter> Iterator - for ComponentAndEuidIter<'query, 'world, FieldTerms, EntityHandleIter> -where - FieldTerms: TermWithFieldTuple, - EntityHandleIter: Iterator>, - 'world: 'query, -{ - type Item = (Uid, FieldTerms::Fields<'query>); - - fn next(&mut self) -> Option - { - let entity_handle = self.iter.next()?; - - Some(( - entity_handle.uid(), - FieldTerms::get_fields(&entity_handle, self.world), - )) - } -} - -macro_rules! impl_term_sequence { - ($c: tt) => { - seq!(I in 0..=$c { - impl<#(Term~I: TermWithoutField,)*> TermWithoutFieldTuple for (#(Term~I,)*) - { - fn apply_terms_to_builder( - terms_builder: &mut TermsBuilder - ) - { - #( - Term~I::apply_to_terms_builder(terms_builder); - )* - } - } - - impl<#(Term~I: TermWithField,)*> TermWithFieldTuple for (#(Term~I,)*) - { - type Fields<'component> = (#(Term~I::Field<'component>,)*); - - fn apply_terms_to_builder( - terms_builder: &mut TermsBuilder - ) - { - #( - Term~I::apply_to_terms_builder(terms_builder); - )* - } - - fn get_fields<'component>( - entity_handle: &EntityHandle<'component>, - world: &'component World, - ) -> Self::Fields<'component> - { - (#(Term~I::get_field(entity_handle, world),)*) - } - } - }); - }; -} - -seq!(C in 0..=16 { - impl_term_sequence!(C); -}); - -impl TermWithoutFieldTuple for () -{ - fn apply_terms_to_builder( - _terms_builder: &mut TermsBuilder, - ) - { - } -} - -impl TermWithFieldTuple for () -{ - type Fields<'component> = (); - - fn apply_terms_to_builder( - _terms_builder: &mut TermsBuilder, - ) - { - } - - fn get_fields<'component>( - _entity_handle: &EntityHandle<'_>, - _world: &'component World, - ) -> Self::Fields<'component> - { - } -} diff --git a/ecs/src/query/flexible.rs b/ecs/src/query/flexible.rs deleted file mode 100644 index 936ab82..0000000 --- a/ecs/src/query/flexible.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! Low-level querying. -use std::iter::{repeat_n, FlatMap, RepeatN, Zip}; - -use crate::component::storage::archetype::{Archetype, EntityIter}; -use crate::component::storage::{ArchetypeRefIter, ArchetypeSearchTerms}; -use crate::entity::Handle as EntityHandle; -use crate::query::Terms; -use crate::World; - -/// Low-level entity query structure. -#[derive(Debug)] -pub struct Query<'world, const MAX_TERM_CNT: usize> -{ - world: &'world World, - terms: Terms, -} - -impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT> -{ - /// Iterates over the entities matching this query. - #[must_use] - pub fn iter(&self) -> Iter<'_> - { - Iter { - iter: self - .world - .data - .component_storage - .search_archetypes(ArchetypeSearchTerms { - required_components: &self.terms.required_components, - excluded_components: &self.terms.excluded_components, - }) - .flat_map( - (|archetype| { - repeat_n(archetype, archetype.entity_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) -> Self - { - Self { world, terms } - } -} - -impl<'query, const MAX_TERM_CNT: usize> IntoIterator for &'query Query<'_, MAX_TERM_CNT> -{ - type IntoIter = Iter<'query>; - type Item = EntityHandle<'query>; - - fn into_iter(self) -> Self::IntoIter - { - self.iter() - } -} - -pub struct Iter<'query> -{ - iter: QueryEntityIter<'query>, - world: &'query World, -} - -impl<'query> Iterator for Iter<'query> -{ - type Item = EntityHandle<'query>; - - fn next(&mut self) -> Option - { - let (archetype, entity) = self.iter.next()?; - - Some(EntityHandle::new(archetype, entity, self.world)) - } -} - -type ComponentIterMapFnOutput<'a> = Zip, EntityIter<'a>>; - -type ComponentIterMapFn = for<'a> fn(&'a Archetype) -> ComponentIterMapFnOutput<'a>; - -type QueryEntityIter<'query> = FlatMap< - ArchetypeRefIter<'query, 'query>, - ComponentIterMapFnOutput<'query>, - ComponentIterMapFn, ->; diff --git a/ecs/src/query/term.rs b/ecs/src/query/term.rs deleted file mode 100644 index 0683918..0000000 --- a/ecs/src/query/term.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::any::type_name; -use std::marker::PhantomData; - -use crate::component::{ - Component, - Handle as ComponentHandle, - HandleMut as ComponentHandleMut, -}; -use crate::query::{ - TermWithField, - TermWithoutField, - TermsBuilder, - TermsBuilderInterface, -}; -use crate::uid::With as WithUid; - -pub struct With -where - WithUidT: WithUid, -{ - _pd: PhantomData, -} - -impl TermWithoutField for With -where - WithUidT: WithUid, -{ - fn apply_to_terms_builder( - terms_builder: &mut TermsBuilder, - ) - { - terms_builder.with::(); - } -} - -pub struct Without -where - WithUidT: WithUid, -{ - _pd: PhantomData, -} - -impl TermWithoutField for Without -where - WithUidT: WithUid, -{ - fn apply_to_terms_builder( - terms_builder: &mut TermsBuilder, - ) - { - terms_builder.without::(); - } -} - -impl TermWithField for Option<&ComponentT> -{ - type Field<'a> = Option>; - - fn apply_to_terms_builder( - _terms_builder: &mut TermsBuilder, - ) - { - } - - fn get_field<'world>( - entity_handle: &crate::entity::Handle<'world>, - _world: &'world crate::World, - ) -> Self::Field<'world> - { - Some( - ComponentHandle::<'world, ComponentT>::from_entity_component_ref( - &entity_handle - .get_matching_components(ComponentT::id()) - .next()?, - ) - .unwrap_or_else(|err| { - panic!( - "Creating handle to component {} failed: {err}", - type_name::() - ); - }), - ) - } -} - -impl TermWithField for Option<&mut ComponentT> -{ - type Field<'a> = Option>; - - fn apply_to_terms_builder( - _terms_builder: &mut TermsBuilder, - ) - { - } - - fn get_field<'world>( - entity_handle: &crate::entity::Handle<'world>, - world: &'world crate::World, - ) -> Self::Field<'world> - { - Some( - ComponentHandleMut::<'world, ComponentT>::from_entity_component_ref( - &entity_handle - .get_matching_components(ComponentT::id()) - .next()?, - world, - ) - .unwrap_or_else(|err| { - panic!( - "Creating handle to component {} failed: {err}", - type_name::() - ); - }), - ) - } -} diff --git a/ecs/src/sole.rs b/ecs/src/sole.rs deleted file mode 100644 index 82e5e0f..0000000 --- a/ecs/src/sole.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::any::{Any, type_name}; -use std::fmt::Debug; -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::sync::Arc; - -use crate::World; -use crate::lock::{Lock, WriteGuard}; -use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; - -/// A type which has a single instance and is shared globally. -pub trait Sole: Any -{ - fn drop_last(&self) -> bool; - - fn as_any_mut(&mut self) -> &mut dyn Any; - - fn as_any(&self) -> &dyn Any; -} - -impl dyn Sole -{ - pub fn downcast_mut(&mut self) -> Option<&mut Real> - { - self.as_any_mut().downcast_mut() - } - - pub fn downcast_ref(&self) -> Option<&Real> - { - self.as_any().downcast_ref() - } -} - -impl Debug for dyn Sole -{ - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - formatter.debug_struct("Sole").finish_non_exhaustive() - } -} - -/// Holds a reference to a globally shared singleton value. -#[derive(Debug)] -pub struct Single<'world, SoleT: Sole> -{ - sole: WriteGuard<'world, Box>, - _ph: PhantomData, -} - -impl<'world, SoleT> Single<'world, SoleT> -where - SoleT: Sole, -{ - pub(crate) fn new(sole: &'world Arc>>) -> Self - { - Self { - sole: sole.write_nonblock().unwrap_or_else(|_| { - panic!( - "Failed to aquire read-write lock to single component {}", - type_name::() - ) - }), - _ph: PhantomData, - } - } -} - -impl<'world, SoleT> SystemParam<'world> for Single<'world, SoleT> -where - SoleT: Sole, -{ - type Input = (); - - fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self - { - let sole = world.data.sole_storage.get::().unwrap_or_else(|| { - panic!("Sole {} was not found in world", type_name::()) - }); - - Self::new(sole) - } -} - -impl Deref for Single<'_, SoleT> -where - SoleT: Sole, -{ - type Target = SoleT; - - fn deref(&self) -> &Self::Target - { - self.sole.downcast_ref().unwrap() - } -} - -impl DerefMut for Single<'_, SoleT> -where - SoleT: Sole, -{ - fn deref_mut(&mut self) -> &mut Self::Target - { - self.sole.downcast_mut().unwrap() - } -} diff --git a/ecs/src/stats.rs b/ecs/src/stats.rs deleted file mode 100644 index 56a5c32..0000000 --- a/ecs/src/stats.rs +++ /dev/null @@ -1,8 +0,0 @@ -use ecs_macros::Sole; - -#[derive(Debug, Default, Sole)] -#[non_exhaustive] -pub struct Stats -{ - pub current_tick: u64, -} diff --git a/ecs/src/system.rs b/ecs/src/system.rs deleted file mode 100644 index 5d3e0bf..0000000 --- a/ecs/src/system.rs +++ /dev/null @@ -1,158 +0,0 @@ -use std::fmt::Debug; - -use ecs_macros::Component; -use seq_macro::seq; - -use crate::World; -use crate::error::Error; -use crate::uid::Uid; - -pub mod initializable; -pub mod observer; -pub mod stateful; - -/// Metadata of a system. -#[derive(Debug)] -#[non_exhaustive] -pub struct Metadata -{ - pub ent_id: Uid, -} - -pub trait System<'world, Impl>: 'static -{ - type Callbacks: Callbacks; - - fn finish(self) -> (TypeErased, Self::Callbacks); -} - -macro_rules! impl_system { - ($c: tt) => { - seq!(I in 0..$c { - impl<'world, Func, Ret, #(TParam~I,)*> System<'world, fn(#(TParam~I,)*) -> Ret> - for Func - where - Func: Fn(#(TParam~I,)*) -> Ret + 'static, - Ret: ReturnValue, - #(TParam~I: Param<'world, Input = ()>,)* - { - type Callbacks = NoCallbacks; - - fn finish(self) -> (TypeErased, Self::Callbacks) - { - #![allow(unused)] - - 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) }; - - self(#({ - TParam~I::new(world, &metadata) - },)*).into_result() - }), - name: std::any::type_name::() - }; - - (type_erased, NoCallbacks) - } - } - }); - }; -} - -seq!(C in 0..16 { - impl_system!(C); -}); - -pub trait Into<'world, Impl> -{ - type System; - - fn into_system(self) -> Self::System; -} - -pub struct TypeErased -{ - run: Box, - name: &'static str, -} - -impl TypeErased -{ - /// Runs the system. - /// - /// # Safety - /// `world_data` must live at least as long as the [`World`] the system belongs to. - pub unsafe fn run(&self, world: &World, metadata: Metadata) -> Result<(), Error> - { - (self.run)(world, metadata) - } - - pub fn name(&self) -> &'static str - { - self.name - } -} - -impl Debug for TypeErased -{ - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { - formatter.debug_struct("TypeErased").finish_non_exhaustive() - } -} - -pub trait ReturnValue -{ - fn into_result(self) -> Result<(), Error>; -} - -impl ReturnValue for () -{ - fn into_result(self) -> Result<(), Error> - { - Ok(()) - } -} - -impl ReturnValue for Result<(), Error> -{ - fn into_result(self) -> Result<(), Error> - { - self - } -} - -/// A parameter to a [`System`]. -pub trait Param<'world> -{ - type Input; - - fn new(world: &'world World, system_metadata: &Metadata) -> Self; -} - -/// A type which can be used as input to a [`System`]. -pub trait Input: 'static {} - -pub trait Callbacks -{ - fn on_created(&mut self, world: &mut World, metadata: Metadata); -} - -pub struct NoCallbacks; - -impl Callbacks for NoCallbacks -{ - fn on_created(&mut self, _world: &mut World, _metadata: Metadata) {} -} - -#[derive(Debug, Component)] -pub(crate) struct SystemComponent -{ - pub(crate) system: TypeErased, -} - -/// Function in [`TypeErased`] used to run the system. -type TypeErasedRunFn = dyn Fn(&World, Metadata) -> Result<(), Error>; diff --git a/ecs/src/system/initializable.rs b/ecs/src/system/initializable.rs deleted file mode 100644 index b6ec8e8..0000000 --- a/ecs/src/system/initializable.rs +++ /dev/null @@ -1,131 +0,0 @@ -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> for ParamT -where - ParamT: SystemParam< - 'world, - Input: AppendInitializableParam<'world, Accumulator, ParamT, SystemT>, - >, - Accumulator: Tuple, -{ - type Return = >::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; -} - -impl - 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, - ) { - #( - >::initialize( - system, - inputs.I - ); - )* - } - } - }); - }; -} - -seq!(C in 1..16 { - impl_initializable_param_tuple!(C); -}); - -impl 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, Out: ParamTuple<'world, SystemT>>, -{ - type Inputs = >::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 deleted file mode 100644 index 6893b0f..0000000 --- a/ecs/src/system/observer.rs +++ /dev/null @@ -1,280 +0,0 @@ -use std::any::type_name; -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::World; -use crate::component::Component; -use crate::entity::Handle as EntityHandle; -use crate::error::Error; -use crate::event::Emitted as EmittedEvent; -use crate::pair::Pair; -use crate::system::{ - Metadata, - NoCallbacks, - Param, - ReturnValue as SystemReturnValue, - System, - TypeErased as TypeErasedSystem, -}; -use crate::uid::Uid; -use crate::util::Array; - -pub trait Observed -{ - type Events: Array>; - - fn events() -> Self::Events; -} - -impl Observed for Pair -where - Relation: Component, - Target: Component, -{ - type Events = [Pair; 1]; - - fn events() -> Self::Events - { - [Pair::builder() - .relation::() - .target::() - .build()] - } -} - -/// Observer system. -pub trait Observer<'world, Impl>: System<'world, Impl> -{ - type ObservedEvents: Array>; - - fn observed_events() -> Self::ObservedEvents; - - fn finish_observer(self) -> (WrapperComponent, Self::Callbacks); -} - -pub struct Observe<'world, ObservedT: Observed> -{ - _pd: PhantomData, - 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 Observe<'_, ObservedT> -{ - #[must_use] - pub fn iter(&self) -> ObserveIter<'_, ObservedT> - { - ObserveIter { - world: self.world, - inner: self.emitted_event.match_ids.iter(), - _pd: PhantomData, - } - } -} - -impl<'a, ObservedT: Observed> IntoIterator for &'a Observe<'_, ObservedT> -{ - type IntoIter = ObserveIter<'a, ObservedT>; - type Item = ::Item; - - fn into_iter(self) -> Self::IntoIter - { - self.iter() - } -} - -pub struct ObserveIter<'observe, ObservedT: Observed> -{ - world: &'observe World, - inner: SliceIter<'observe, Uid>, - _pd: PhantomData, -} - -impl<'observe, ObservedT: Observed> Iterator for ObserveIter<'observe, ObservedT> -{ - type Item = EventMatch<'observe, ObservedT>; - - fn next(&mut self) -> Option - { - let match_id = *self.inner.next()?; - - Some(EventMatch { - world: self.world, - id: match_id, - _pd: PhantomData, - }) - } -} - -/// A event match. -#[derive(Debug)] -pub struct EventMatch<'world, ObservedT: Observed> -{ - world: &'world World, - id: Uid, - _pd: PhantomData, -} - -impl<'world, ObservedT: Observed> EventMatch<'world, ObservedT> -{ - #[must_use] - pub fn entity_id(&self) -> Uid - { - self.id - } - - /// Attempts to get the entity with the id of this match. - #[must_use] - pub fn try_get_entity(&self) -> Option> - { - self.world.get_entity(self.id) - } -} - -macro_rules! impl_observer { - ($c: tt) => { - seq!(I in 0..$c { - impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> System< - 'world, - fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret - > for Func - where - ObservedT: Observed, - Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static, - Ret: SystemReturnValue, - #(TParam~I: Param<'world, Input = ()>,)* - { - type Callbacks = NoCallbacks; - - fn finish(self) -> (TypeErasedSystem, NoCallbacks) - { - const { - panic!("Observers cannot be used as regular systems"); - } - } - } - - impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> Observer< - 'world, - fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret - > for Func - where - ObservedT: Observed, - Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static, - Ret: SystemReturnValue, - #(TParam~I: Param<'world, Input = ()>,)* - { - type ObservedEvents = ObservedT::Events; - - fn observed_events() -> Self::ObservedEvents - { - ObservedT::events() - } - - fn finish_observer(self) -> (WrapperComponent, NoCallbacks) - { - #[allow(unused)] - - let wrapper_comp = WrapperComponent::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<'_>>( - emitted_event - ) - }; - - self(Observe::new(world, emitted_event), #({ - TParam~I::new(world, &metadata) - },)*).into_result() - }, - type_name::() - ); - - (wrapper_comp, NoCallbacks) - } - } - }); - }; -} - -seq!(C in 0..16 { - impl_observer!(C); -}); - -#[derive(Component)] -pub struct WrapperComponent -{ - run: Box, - name: &'static str, -} - -impl WrapperComponent -{ - pub fn new( - run: impl Fn(&World, Metadata, EmittedEvent<'_>) -> Result<(), Error> + 'static, - name: &'static str, - ) -> Self - { - Self { run: Box::new(run), name } - } - - /// 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<'_>, - ) -> Result<(), Error> - { - (self.run)(world, metadata, emitted_event) - } - - pub fn name(&self) -> &'static str - { - self.name - } -} - -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<'_>) -> Result<(), Error>; diff --git a/ecs/src/system/stateful.rs b/ecs/src/system/stateful.rs deleted file mode 100644 index 3e0076a..0000000 --- a/ecs/src/system/stateful.rs +++ /dev/null @@ -1,269 +0,0 @@ -use std::any::type_name; -use std::mem::transmute; - -use seq_macro::seq; - -use crate::World; -use crate::component::Parts as ComponentParts; -use crate::component::local::SystemWithLocalComponents; -use crate::event::Emitted as EmittedEvent; -use crate::system::initializable::{Initializable, MaybeInitializableParamTuple}; -use crate::system::observer::{ - Observe, - Observed, - Observer, - WrapperComponent as ObserverWrapperComponent, -}; -use crate::system::{ - Into as IntoSystem, - Metadata, - Param, - ReturnValue, - System, - TypeErased, -}; - -/// A stateful system. -pub struct Stateful -{ - func: Func, - local_components: Vec, -} - -macro_rules! impl_system { - ($c: tt) => { - seq!(I in 0..$c { - impl<'world, Func, Ret, #(TParam~I,)*> - System<'world, fn(#(TParam~I,)*) -> Ret> for Stateful - where - Func: Fn(#(TParam~I,)*) -> Ret + 'static, - Ret: ReturnValue, - #(TParam~I: Param<'world, Input: 'static>,)* - { - type Callbacks = Callbacks; - - fn finish(self) -> (TypeErased, Self::Callbacks) - { - let Self { func, local_components } = 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) - },)*); - - Ok(()) - }), - name: type_name::() - }; - - - (type_erased, callbacks) - } - } - - impl<'world, Func, Ret, #(TParam~I,)*> - Initializable<'world, fn(#(TParam~I,)*) -> Ret> for Stateful - where - Func: Fn(#(TParam~I,)*) -> Ret + 'static, - Ret: ReturnValue, - #(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 - { - init_initializable_params::<_, (#(TParam~I,)*)>(&mut self, inputs); - - self - } - } - - impl<'world, Func, Ret, #(TParam~I,)*> - IntoSystem<'world, fn(#(TParam~I,)*) -> Ret> for Func - where - Func: Fn(#(TParam~I,)*) -> Ret + 'static, - Ret: ReturnValue, - #(TParam~I: Param<'world>,)* - { - type System = Stateful; - - fn into_system(self) -> Self::System - { - Self::System { - func: self, - local_components: Vec::new(), // TODO: Use Vec::with_capacity - } - } - } - }); - }; -} - -seq!(C in 1..16 { - impl_system!(C); -}); - -impl SystemWithLocalComponents for Stateful -{ - fn add_local_component(&mut self, component_parts: ComponentParts) - { - self.local_components.push(component_parts); - } -} - -#[derive(Debug)] -pub struct Callbacks -{ - local_components: Vec, -} - -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, Ret, #(TParam~I,)*> System< - 'world, - fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret - > for Stateful - where - ObservedT: Observed, - Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static, - Ret: ReturnValue, - #(TParam~I: Param<'world>,)* - { - type Callbacks = Callbacks; - - fn finish(self) -> (TypeErased, Callbacks) - { - unimplemented!(); - } - } - - impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> Initializable< - 'world, - fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret - > for Stateful - where - ObservedT: Observed, - Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static, - Ret: ReturnValue, - #(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 - { - init_initializable_params::<_, (#(TParam~I,)*)>(&mut self, inputs); - - self - } - } - - impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> Observer< - 'world, - fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret - > for Stateful - where - ObservedT: Observed, - Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static, - Ret: ReturnValue, - #(TParam~I: Param<'world>,)* - { - type ObservedEvents = ObservedT::Events; - - fn observed_events() -> Self::ObservedEvents - { - ObservedT::events() - } - - fn finish_observer(self) -> (ObserverWrapperComponent, Callbacks) - { - #![allow(unused)] - - 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<'_>>( - emitted_event - ) - }; - - func(Observe::new(world, emitted_event), #({ - TParam~I::new(world, &metadata) - },)*).into_result() - }, - type_name::() - ); - - (wrapper_comp, callbacks) - } - } - - impl<'world, Func, Ret, ObservedT, #(TParam~I,)*> IntoSystem< - 'world, - fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret - > for Func - where - ObservedT: Observed, - Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static, - Ret: ReturnValue, - #(TParam~I: Param<'world>,)* - { - type System = Stateful; - - fn into_system(self) -> Stateful - { - Stateful { - func: self, - local_components: Vec::new(), // TODO: Use Vec::with_capacity - } - } - } - }); - }; -} - -seq!(C in 0..16 { - impl_observer!(C); -}); diff --git a/ecs/src/tuple.rs b/ecs/src/tuple.rs deleted file mode 100644 index def25a0..0000000 --- a/ecs/src/tuple.rs +++ /dev/null @@ -1,238 +0,0 @@ -use std::any::TypeId; - -use paste::paste; -use seq_macro::seq; -use util_macros::sub; - -pub trait Tuple: sealed::Sealed -{ - /// `Self` with the given type added as the last element. - /// - /// `(String, i32, u8)::WithElementAtEnd = (String, i32, u8, Path)` - /// - /// # Important note - /// If `Self` has 16 elements, this will be `()`. The reason for this is that the - /// `Tuple` trait is only implemented for tuples with up to and including 16 elements. - type WithElementAtEnd: Tuple; - - /// `Self` without the last element. - /// - /// `(u16, AtomicU8)::WithoutLastElement = (u16,)` - type WithoutLastElement: Tuple; - - /// The last element of `Self`. - type LastElement; - - /// Self with all elements wrapped in [`Option`]. - type InOptions: Tuple; - - /// Pops the last element from this tuple, returning the new tuple and the popped - /// element. - fn pop_last(self) -> (Self::WithoutLastElement, Self::LastElement); - - /// Converts this tuple so that all elements are wrapped in [`Option`]. - fn into_in_options(self) -> Self::InOptions; -} - -/// A tuple with element types that all have the lifetime `'static`. -pub trait WithAllElemLtStatic: Tuple + sealed::Sealed -{ - /// Returns the element at the given index. - fn get_mut(&mut self, index: usize) -> Option<&mut Element>; -} - -/// Using the type system, reduces the elements of a tuple to a single one. Each element -/// determines itself how it is handled. -pub trait Reduce -{ - type Out; -} - -pub trait ReduceElement -{ - type Return; -} - -macro_rules! tuple_reduce_elem_tuple { - (overflow) => { - () - }; - - ($index: tt) => { - paste! { - []::Return - } - }; -} - -macro_rules! elem_type_by_index { - (overflow) => { - () - }; - - ($index: tt) => { - paste! { - [] - } - }; -} - -macro_rules! elem_by_index { - (overflow) => { - () - }; - - ($index: tt, $self: ident) => { - $self.$index - }; -} - -macro_rules! all_except_last { - (start $($rest: tt)*) => { - all_except_last!(@[] $($rest)*) - }; - - (@[$($included_elem: ident,)*] $elem: ident $($rest: tt)+) => { - all_except_last!(@[$($included_elem,)* $elem,] $($rest)*) - }; - - (@[$($included_elem: expr,)*] ($elem: expr) $($rest: tt)+) => { - all_except_last!(@[$($included_elem,)* $elem,] $($rest)*) - }; - - (@[$($included_elem: ident,)*] $elem: ident) => { - ($($included_elem,)*) - }; - - (@[$($included_elem: expr,)*] $elem: expr) => { - ($($included_elem,)*) - }; - - (@[]) => { - () - }; -} - -macro_rules! impl_tuple_traits { - ($cnt: tt) => { - seq!(I in 0..$cnt { - impl<#(Elem~I,)*> Tuple for (#(Elem~I,)*) - { - type WithElementAtEnd = (#(Elem~I,)* NewElem,); - - type WithoutLastElement = all_except_last!(start #(Elem~I)*); - - type LastElement = sub!($cnt - 1, elem_type_by_index); - - type InOptions = (#(Option,)*); - - fn pop_last(self) -> (Self::WithoutLastElement, Self::LastElement) - { - ( - all_except_last!(start #((self.I))*), - sub!($cnt - 1, elem_by_index, (self)) - ) - } - - fn into_in_options(self) -> Self::InOptions - { - #![allow(clippy::unused_unit)] - (#(Some(self.I),)*) - } - } - - impl<#(Elem~I: 'static,)*> WithAllElemLtStatic for (#(Elem~I,)*) - { - fn get_mut(&mut self, index: usize) -> Option<&mut Element> - { - match index { - #( - I => { - assert!(TypeId::of::() == TypeId::of::()); - - // SAFETY: It is checked above that the type is correct - Some(unsafe { &mut *(&raw mut self.I).cast::() }) - } - )* - _ => None - } - } - } - - impl<#(Elem~I,)*> sealed::Sealed for (#(Elem~I,)*) - { - } - - paste! { - impl Reduce for (#(Elem~I,)*) - where - #( - Elem~I: ReduceElement< - sub!(I - 1, tuple_reduce_elem_tuple), Operation - >, - )* - { - type Out = sub!($cnt - 1, tuple_reduce_elem_tuple); - } - } - }); - }; -} - -seq!(N in 0..16 { - impl_tuple_traits!(N); -}); - -seq!(I in 0..16 { - impl<#(Elem~I,)*> Tuple for (#(Elem~I,)*) - { - type WithElementAtEnd = (); - - type WithoutLastElement = all_except_last!(start #(Elem~I)*); - - type LastElement = Elem15; - - type InOptions = (#(Option,)*); - - fn pop_last(self) -> (Self::WithoutLastElement, Self::LastElement) - { - ( - all_except_last!(start #((self.I))*), - self.15 - ) - } - - fn into_in_options(self) -> Self::InOptions - { - #![allow(clippy::unused_unit)] - (#(Some(self.I),)*) - } - } - - impl<#(Elem~I: 'static,)*> WithAllElemLtStatic for (#(Elem~I,)*) - { - fn get_mut(&mut self, index: usize) -> Option<&mut Element> - { - match index { - #( - I => { - assert!(TypeId::of::() == TypeId::of::()); - - // SAFETY: It is checked above that the type is correct - Some(unsafe { &mut *(&raw mut self.I).cast::() }) - } - )* - _ => None - } - } - } - - impl<#(Elem~I,)*> sealed::Sealed for (#(Elem~I,)*) - { - } -}); - -mod sealed -{ - pub trait Sealed {} -} diff --git a/ecs/src/uid.rs b/ecs/src/uid.rs deleted file mode 100644 index bb393a1..0000000 --- a/ecs/src/uid.rs +++ /dev/null @@ -1,261 +0,0 @@ -use std::fmt::{Debug, Display, Formatter}; -use std::mem::transmute; -use std::sync::atomic::{AtomicU32, Ordering}; - -use seq_macro::seq; - -use crate::component::Component; -use crate::util::{gen_mask_64, Array, BitMask, NumberExt}; - -static NEXT: AtomicU32 = AtomicU32::new(Uid::FIRST_UNIQUE_ID); - -static WILDCARD_ID: u32 = 1; - -const ID_BITS: BitMask = BitMask::new(gen_mask_64!(32..=63)); -const RELATION_BITS: BitMask = BitMask::new(gen_mask_64!(6..=31)); -const KIND_BITS: BitMask = BitMask::new(gen_mask_64!(0..=1)); - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u8)] -pub enum Kind -{ - Pair = 3, - Entity = 2, - Component = 1, -} - -/// A unique identifier. -#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct Uid -{ - inner: u64, -} - -impl Uid -{ - /// The id part of the first unique `Uid`. The ids `0..Uid::FIRST_UNIQUE_ID` are - /// reserved. - pub const FIRST_UNIQUE_ID: u32 = 5; - - /// Returns a new unique entity/component ID. - pub fn new_unique(kind: Kind) -> Self - { - let id = NEXT.fetch_add(1, Ordering::Relaxed); - - Self { - inner: ID_BITS.field_prep(u64::from(id)) | KIND_BITS.field_prep(kind as u64), - } - } - - #[must_use] - pub fn wildcard() -> Self - { - Self { - inner: ID_BITS.field_prep(u64::from(WILDCARD_ID)) - | KIND_BITS.field_prep(Kind::Component as u64), - } - } - - /// Returns a new pair UID. - /// - /// # Panics - /// Will panic if either the given relation or target is a pair UID. - #[must_use] - pub fn new_pair(params: &PairParams) -> Self - { - assert_ne!( - params.relation.kind(), - Kind::Pair, - "Pair relation cannot be a pair" - ); - - assert_ne!( - params.target.kind(), - Kind::Pair, - "Pair target cannot be a pair" - ); - - Self { - inner: ID_BITS.field_prep(u64::from(params.target.id())) - | RELATION_BITS.field_prep(u64::from(params.relation.id())) - | KIND_BITS.field_prep(Kind::Pair as u64), - } - } - - #[must_use] - pub fn id(&self) -> u32 - { - let Ok(id) = u32::try_from(self.inner.field_get(ID_BITS)) else { - unreachable!("Uid id does not fit in u32"); - }; - - id - } - - #[must_use] - pub fn kind(&self) -> Kind - { - let Ok(kind) = u8::try_from(self.inner.field_get(KIND_BITS)) else { - unreachable!("Uid kind does not fit in u8"); - }; - - // SAFETY: The kind bits cannot be invalid since they are set using the Kind enum - // in the new_unique function - unsafe { transmute::(kind) } - } - - /// If this `Uid` is a pair, returns the relation as a component `Uid`. - /// - /// # Panics - /// Will panic if this `Uid` is not a pair. - #[must_use] - pub fn relation_component(&self) -> Self - { - assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair"); - - Self { - inner: ID_BITS.field_prep(u64::from(self.relation())) - | KIND_BITS.field_prep(Kind::Component as u64), - } - } - - #[must_use] - pub fn has_same_relation_as(&self, other: Self) -> bool - { - self.relation() == other.relation() - } - - /// If this `Uid` is a pair, returns the relation as a entity `Uid`. - /// - /// # Panics - /// Will panic if this `Uid` is not a pair. - #[must_use] - pub fn relation_entity(&self) -> Self - { - assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair"); - - Self { - inner: ID_BITS.field_prep(u64::from(self.relation())) - | KIND_BITS.field_prep(Kind::Entity as u64), - } - } - - /// If this `Uid` is a pair, returns the target as a component `Uid`. - /// - /// # Panics - /// Will panic if this `Uid` is not a pair. - #[must_use] - pub fn target_component(&self) -> Self - { - assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair"); - - Self { - inner: ID_BITS.field_prep(u64::from(self.id())) - | KIND_BITS.field_prep(Kind::Component as u64), - } - } - - /// If this `Uid` is a pair, returns the target as a entity `Uid`. - /// - /// # Panics - /// Will panic if this `Uid` is not a pair. - #[must_use] - pub fn target_entity(&self) -> Self - { - assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair"); - - Self { - inner: ID_BITS.field_prep(u64::from(self.id())) - | KIND_BITS.field_prep(Kind::Entity as u64), - } - } - - fn relation(self) -> u32 - { - let Ok(relation) = u32::try_from(self.inner.field_get(RELATION_BITS)) else { - unreachable!("Uid relation does not fit in u32"); - }; - - relation - } -} - -impl Debug for Uid -{ - fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result - { - formatter - .debug_struct("Uid") - .field("id", &self.id()) - .field("kind", &self.kind()) - .finish_non_exhaustive() - } -} - -impl Display for Uid -{ - fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result - { - if self.kind() == Kind::Pair { - return write!( - formatter, - "({}, {})", - self.relation(), - self.target_component() - ); - } - - if *self == Uid::wildcard() { - return write!(formatter, "*"); - } - - write!(formatter, "{}", self.id()) - } -} - -#[derive(Debug, Clone)] -pub struct PairParams -{ - pub relation: Uid, - pub target: Uid, -} - -pub trait With -{ - fn uid() -> Uid; -} - -impl With for ComponentT -{ - fn uid() -> Uid - { - Self::id() - } -} - -pub trait WithUidTuple -{ - type UidsArray: Array; - - fn uids() -> Self::UidsArray; -} - -macro_rules! impl_with_uid_tuple { - ($c: tt) => { - seq!(I in 0..=$c { - impl<#(WithUid~I: With,)*> WithUidTuple for (#(WithUid~I,)*) - { - type UidsArray = [Uid; $c + 1]; - - fn uids() -> Self::UidsArray - { - [#(WithUid~I::uid(),)*] - } - } - }); - }; -} - -seq!(C in 0..=16 { - impl_with_uid_tuple!(C); -}); diff --git a/ecs/src/util.rs b/ecs/src/util.rs deleted file mode 100644 index 27e9748..0000000 --- a/ecs/src/util.rs +++ /dev/null @@ -1,415 +0,0 @@ -use std::hash::Hash; -use std::mem::transmute; -use std::ops::{BitAnd, Deref}; - -use hashbrown::HashMap; - -pub(crate) mod array_vec; - -pub trait VecExt -{ - fn insert_at_part_pt_by_key( - &mut self, - item: Item, - func: impl FnMut(&Item) -> &Key, - ) where - Key: Ord; -} - -impl VecExt for Vec -{ - fn insert_at_part_pt_by_key( - &mut self, - item: Item, - mut func: impl FnMut(&Item) -> &Key, - ) where - Key: Ord, - { - let key = func(&item); - - let insert_index = self.partition_point(|other_item| func(other_item) <= key); - - self.insert(insert_index, item); - } -} - -pub trait StreamingIterator -{ - type Item<'a> - where - Self: 'a; - - fn streaming_next(&mut self) -> Option>; - - fn streaming_map(self, func: Func) -> StreamingMap - where - Self: Sized, - Func: FnMut(Self::Item<'_>) -> NewItem, - { - StreamingMap { iter: self, func } - } - - fn streaming_find<'this, Predicate>( - &'this mut self, - mut predicate: Predicate, - ) -> Option> - where - Self: Sized, - Predicate: FnMut(&Self::Item<'this>) -> bool, - { - while let Some(item) = unsafe { - transmute::>, Option>>( - self.streaming_next(), - ) - } { - if predicate(&item) { - return Some(item); - } - } - - None - } -} - -pub struct StreamingMap -{ - iter: Iter, - func: Func, -} - -impl StreamingIterator for StreamingMap -where - Iter: StreamingIterator, - Func: FnMut(Iter::Item<'_>) -> Item, -{ - type Item<'a> - = Item - where - Iter: 'a, - Func: 'a; - - fn streaming_next(&mut self) -> Option> - { - Some((self.func)(self.iter.streaming_next()?)) - } -} - -#[derive(Debug)] -pub enum BorrowedOrOwned<'a, Value> -{ - Borrowned(&'a Value), - Owned(Value), -} - -impl Deref for BorrowedOrOwned<'_, Value> -{ - type Target = Value; - - fn deref(&self) -> &Self::Target - { - match self { - Self::Borrowned(value) => value, - Self::Owned(value) => value, - } - } -} - -#[derive(Debug, Clone)] -pub enum Either -{ - A(A), - B(B), -} - -impl Iterator for Either -where - A: Iterator, - B: Iterator, -{ - type Item = A::Item; - - fn next(&mut self) -> Option - { - match self { - Self::A(a) => a.next(), - Self::B(b) => b.next(), - } - } -} - -pub trait HashMapExt -{ - /// Returns true if the keys are a subset of another [`HashMap`]'s keys, i.e., `other` - /// contains at least all the keys in `self`. - fn keys_is_subset(&self, other: &Self) -> bool; - - /// Returns true if the keys are a superset of another [`HashMap`]'s keys, i.e., - /// `self` contains at least all the keys in `other`. - fn keys_is_superset(&self, other: &Self) -> bool; -} - -impl HashMapExt for HashMap -where - Key: Eq + Hash, -{ - fn keys_is_subset(&self, other: &Self) -> bool - { - if self.len() <= other.len() { - self.keys().all(|key| other.contains_key(key)) - } else { - false - } - } - - fn keys_is_superset(&self, other: &Self) -> bool - { - other.keys_is_subset(self) - } -} - -pub trait Array: - AsRef<[Item]> - + AsMut<[Item]> - + IntoIterator - + Into> - + Sortable - + sealed::Sealed -{ -} - -impl Array for [Item; CNT] {} - -impl sealed::Sealed for [Item; CNT] {} - -pub trait Sortable -{ - type Item; - - fn sort_by_key_b(&mut self, func: Func) - where - Func: FnMut(&Self::Item) -> Key, - Key: Ord; -} - -impl Sortable for [Item] -{ - type Item = Item; - - fn sort_by_key_b(&mut self, func: Func) - where - Func: FnMut(&Self::Item) -> Key, - Key: Ord, - { - self.sort_by_key(func); - } -} - -impl Sortable for [Item; LENGTH] -{ - type Item = Item; - - fn sort_by_key_b(&mut self, func: Func) - where - Func: FnMut(&Self::Item) -> Key, - Key: Ord, - { - self.sort_by_key(func); - } -} - -impl Sortable for Vec -{ - type Item = Item; - - fn sort_by_key_b(&mut self, func: Func) - where - Func: FnMut(&Self::Item) -> Key, - Key: Ord, - { - self.sort_by_key(func); - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct BitMask -{ - mask: Value, -} - -impl BitMask -{ - #[must_use] - pub const fn new(mask: u64) -> Self - { - Self { mask } - } - - #[must_use] - pub const fn value(self) -> u64 - { - self.mask - } - - /// Prepares a bitfield value in the range of bits specified by this `BitMask`. - #[must_use] - pub const fn field_prep(self, field_value: u64) -> u64 - { - debug_assert!(field_value < 1 << self.mask.count_ones()); - - ((field_value) << self.mask.trailing_zeros()) & (self.mask) - } -} - -impl BitAnd for BitMask -{ - type Output = u64; - - fn bitand(self, rhs: u64) -> Self::Output - { - self.mask & rhs - } -} - -pub trait NumberExt: Sized -{ - /// Returns a range of bits (field) specified by the provided [`BitMask`]. - #[must_use] - fn field_get(self, field_mask: BitMask) -> Self; -} - -impl NumberExt for u64 -{ - fn field_get(self, field_mask: BitMask) -> Self - { - (field_mask & self) >> field_mask.value().trailing_zeros() - } -} - -macro_rules! gen_mask_64 { - ($low: literal..=$high: literal) => { - const { - if $high <= $low { - panic!("High bit index cannot be less than or equal to low bit index"); - } - - (((!0u64) - (1u64 << ($low)) + 1) - & (!0u64 >> (u64::BITS as u64 - 1 - ($high)))) - } - }; -} - -pub(crate) use gen_mask_64; - -macro_rules! impl_multiple { - ( - $type: ident, - ($( - impl$(<$($generic: tt$(: $bound: ident)?),*>)? - _<$($lt_param: lifetime),*><$($type_param: ty),*> - $(($($extra_cb_arg: expr),*))? - ),*) - cb=( - type_params=($($ty_param_matcher: ident),*) - $(, $($extra_matcher: ident),+)? - ) => { - $($item_tt: tt)* - } - ) => { - const _: () = { - $crate::util::impl_multiple!( - @(make_gen_item_macro) - _gen_multiple_impl_item, - ($($ty_param_matcher),*), - ($($($extra_matcher),+)?), - ($($item_tt)*) - ); - - $( - impl $(<$($generic$(: $bound)?,)*>)? $type<$($lt_param,)* $($type_param),*> - { - _gen_multiple_impl_item!( - type_params=($($type_param),*), - $($($extra_cb_arg),*)? - ); - } - )* - }; - }; - - ( - @(make_gen_item_macro) - $name: ident, - ($($ty_param_matcher: ident),*), - ($($extra_matcher: ident),*), - ($($transcriber: tt)*) - ) => { - $crate::util::impl_multiple!( - @(make_gen_item_macro) - ($), - $name, - ($($ty_param_matcher),*), - ($($extra_matcher),*), - ($($transcriber)*) - ); - }; - - ( - @(make_gen_item_macro) - ($dollar: tt), - $name: ident, - ($($ty_param_matcher: ident),*), - ($($extra_matcher: ident),*), - ($($transcriber: tt)*) - ) => { - $crate::util::impl_multiple!( - @(make_gen_item_macro) - $name, - ( - type_params=($($dollar$ty_param_matcher: ty),*), - $($dollar$extra_matcher: expr),* - ) => { - $($transcriber)* - } - ); - }; - - (@(make_gen_item_macro) $name: ident, $($rule: tt)*) => { - macro_rules! $name { - $($rule)* - } - }; -} - -pub(crate) use impl_multiple; - -mod sealed -{ - pub trait Sealed {} -} - -#[cfg(test)] -mod tests -{ - - use super::BitMask; - use crate::util::NumberExt; - - #[test] - fn field_get_works() - { - assert_eq!(0b11011u64.field_get(BitMask::new(0b11100)), 0b00110); - } - - #[test] - fn bitmask_field_prep_works() - { - assert_eq!(BitMask::new(0b11000).field_prep(3), 0b11000); - } - - #[test] - #[should_panic] - fn bitmask_field_prep_too_large_value_panics() - { - let _ = BitMask::new(0b001110).field_prep(9); - } -} diff --git a/ecs/src/util/array_vec.rs b/ecs/src/util/array_vec.rs deleted file mode 100644 index 5d0aac9..0000000 --- a/ecs/src/util/array_vec.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::mem::MaybeUninit; -use std::ops::{Deref, DerefMut}; - -#[derive(Debug)] -pub struct ArrayVec -{ - items: [MaybeUninit; CAPACITY], - len: usize, -} - -impl ArrayVec -{ - #[inline] - #[must_use] - pub fn len(&self) -> usize - { - self.len - } - - #[inline] - #[must_use] - pub fn is_empty(&self) -> bool - { - self.len == 0 - } - - pub fn push(&mut self, item: Item) - { - assert!(self.len < CAPACITY); - - self.items[self.len].write(item); - - self.len += 1; - } - - pub fn insert(&mut self, index: usize, item: Item) - { - assert!(index <= self.len); - assert!(self.len < CAPACITY); - - if index == self.len { - self.push(item); - return; - } - - unsafe { - std::ptr::copy( - &raw const self.items[index], - &raw mut self.items[index + 1], - self.len - index, - ); - } - - self.items[index].write(item); - - self.len += 1; - } -} - -impl Extend for ArrayVec -{ - fn extend>(&mut self, iter: IntoIter) - { - for item in iter { - self.push(item); - } - } -} - -impl AsRef<[Item]> for ArrayVec -{ - fn as_ref(&self) -> &[Item] - { - let ptr = &raw const self.items[..self.len]; - - unsafe { &*(ptr as *const [Item]) } - } -} - -impl AsMut<[Item]> for ArrayVec -{ - fn as_mut(&mut self) -> &mut [Item] - { - let ptr = &raw mut self.items[..self.len]; - - unsafe { &mut *(ptr as *mut [Item]) } - } -} - -impl Deref for ArrayVec -{ - type Target = [Item]; - - fn deref(&self) -> &Self::Target - { - self.as_ref() - } -} - -impl DerefMut for ArrayVec -{ - fn deref_mut(&mut self) -> &mut Self::Target - { - self.as_mut() - } -} - -impl Default for ArrayVec -{ - fn default() -> Self - { - Self { - items: [const { MaybeUninit::uninit() }; CAPACITY], - len: 0, - } - } -} - -impl Drop for ArrayVec -{ - fn drop(&mut self) - { - for item in &mut self.items[..self.len] { - // SAFETY: The items from index 0 to the length index will always be - // initialized and satisfy all the invariants of the Item type. - unsafe { - item.assume_init_drop(); - } - } - } -} diff --git a/ecs/tests/phase.rs b/ecs/tests/phase.rs deleted file mode 100644 index af2646b..0000000 --- a/ecs/tests/phase.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::sync::atomic::{AtomicUsize, Ordering}; - -use ecs::component::local::Local; -use ecs::phase::UPDATE; -use ecs::system::Into; -use ecs::system::initializable::Initializable; -use ecs::{Component, World}; - -#[derive(Component)] -struct Thing; - -#[test] -fn system_run_order_correct_when_one_has_local_comp() -{ - static COUNTER: AtomicUsize = AtomicUsize::new(0); - - fn first_system(_thing: Local) - { - assert_eq!(COUNTER.fetch_add(1, Ordering::Relaxed), 0); - } - - fn second_system() - { - assert_eq!(COUNTER.fetch_add(1, Ordering::Relaxed), 1); - } - - let mut world = World::new(); - - world.register_system(*UPDATE, first_system.into_system().initialize((Thing,))); - - world.register_system(*UPDATE, second_system); - - world.step(); - - assert_eq!(COUNTER.load(Ordering::Relaxed), 2); -} diff --git a/ecs/tests/query.rs b/ecs/tests/query.rs deleted file mode 100644 index 7b218e3..0000000 --- a/ecs/tests/query.rs +++ /dev/null @@ -1,413 +0,0 @@ -use ecs::component::Component; -use ecs::pair::{Pair, Wildcard}; -use ecs::query::term::Without; -use ecs::query::{ - TermWithFieldTuple as QueryTermWithFieldTuple, - TermWithoutFieldTuple as QueryTermWithoutFieldTuple, -}; -use ecs::uid::Uid; -use ecs::{Component, Query, World}; -use parking_lot::{Mutex, Once}; - -pub static SETUP: Once = Once::new(); - -pub static TEST_LOCK: Mutex<()> = Mutex::new(()); - -#[derive(Component)] -struct A; - -#[derive(Component)] -struct B; - -#[derive(Component)] -struct C; - -#[derive(Component)] -struct D; - -#[derive(Component)] -struct E; - -#[derive(Component)] -struct F; - -#[derive(Component)] -struct G; - -fn setup() -{ - SETUP.call_once_force(|_| { - assert_eq!(A::id().id(), Uid::FIRST_UNIQUE_ID); - assert_eq!(B::id().id(), Uid::FIRST_UNIQUE_ID + 1); - assert_eq!(C::id().id(), Uid::FIRST_UNIQUE_ID + 2); - assert_eq!(D::id().id(), Uid::FIRST_UNIQUE_ID + 3); - assert_eq!(E::id().id(), Uid::FIRST_UNIQUE_ID + 4); - assert_eq!(F::id().id(), Uid::FIRST_UNIQUE_ID + 5); - assert_eq!(G::id().id(), Uid::FIRST_UNIQUE_ID + 6); - }); -} - -fn assert_query_finds_ents( - query: Query<'_, QueryFieldTerms, QueryFieldlessTerms>, - mut expected_ent_ids: Vec, -) where - QueryFieldTerms: QueryTermWithFieldTuple, - QueryFieldlessTerms: QueryTermWithoutFieldTuple, -{ - assert!( - query.iter_with_euids().all(|(ent_id, _)| { - let Some(index) = expected_ent_ids - .iter() - .position(|expected_id| *expected_id == ent_id) - else { - return false; - }; - - expected_ent_ids.remove(index); - - true - }), - "Unexpected entity was found. Expected entities left: {expected_ent_ids:?}" - ); - - assert_eq!( - expected_ent_ids.len(), - 0, - concat!( - "Not all entities expected to be found was found. ", - "Expected entities left: {:?}" - ), - expected_ent_ids - ); -} - -#[test] -fn query_archetype_exists_with_edges_to_next_archetypes() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - let ent_1_id = world.create_entity((A, B, C)); - let ent_2_id = world.create_entity((A, B, C, D, E)); - let ent_3_id = world.create_entity((A, B, C, E)); - let ent_4_id = world.create_entity((A, B, C, G, F)); - - assert_query_finds_ents( - world.query::<(&A, &B, &C), ()>(), - vec![ent_1_id, ent_2_id, ent_3_id, ent_4_id], - ); -} - -#[test] -fn query_archetype_exists_with_2_comps_diff_to_next_archetype() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - let ent_1_id = world.create_entity((A, B, C, D, F)); - - let ent_2_id = world.create_entity((A, B, F)); - - assert_query_finds_ents(world.query::<(&A, &B, &F), ()>(), vec![ent_1_id, ent_2_id]); -} - -#[test] -fn query_archetype_exists_with_2_comps_diff_to_next_archetype_rev() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - let ent_1_id = world.create_entity((A, B, F)); - - let ent_2_id = world.create_entity((A, B, C, D, F)); - - assert_query_finds_ents(world.query::<(&A, &B, &F), ()>(), vec![ent_1_id, ent_2_id]); -} - -#[test] -fn query_archetype_exists_with_3_comps_diff_to_next_archetype() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - let ent_1_id = world.create_entity((A, B, C, D, E, F)); - - let ent_2_id = world.create_entity((A, B, F)); - - assert_query_finds_ents(world.query::<(&A, &B, &F), ()>(), vec![ent_1_id, ent_2_id]); -} - -#[test] -fn query_archetype_exists_with_3_comps_diff_to_next_archetype_rev() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - let ent_1_id = world.create_entity((A, B, F)); - - let ent_2_id = world.create_entity((A, B, C, D, E, F)); - - assert_query_finds_ents(world.query::<(&A, &B, &F), ()>(), vec![ent_1_id, ent_2_id]); -} - -#[test] -fn query_archetype_exists_with_4_comps_diff_to_next_archetype() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - let ent_1_id = world.create_entity((A, B, C, D, E, F, G)); - - let ent_2_id = world.create_entity((A, B, G)); - - assert_query_finds_ents(world.query::<(&A, &B, &G), ()>(), vec![ent_1_id, ent_2_id]); -} - -#[test] -fn query_archetype_exists_with_4_comps_diff_to_next_archetype_rev() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - let ent_1_id = world.create_entity((A, B, G)); - - let ent_2_id = world.create_entity((A, B, C, D, E, F, G)); - - assert_query_finds_ents(world.query::<(&A, &B, &G), ()>(), vec![ent_1_id, ent_2_id]); -} - -#[test] -fn query_archetype_exists_with_4_comps_diff_to_next_archetype_and_opt_comp() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - let ent_1_id = world.create_entity((A, B, C, D, E, F, G)); - - let ent_2_id = world.create_entity((A, B, G)); - - assert_query_finds_ents( - world.query::<(&A, Option<&E>, &G), ()>(), - vec![ent_1_id, ent_2_id], - ); -} - -#[test] -fn query_archetype_nonexistant() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - world.create_entity((A, B, C)); - - let ent_2_id = world.create_entity((A, B, C, D, E)); - let ent_3_id = world.create_entity((A, B, C, E)); - - world.create_entity((A, B, C, G, F)); - - assert_query_finds_ents(world.query::<(&A, &E), ()>(), vec![ent_2_id, ent_3_id]); -} - -#[test] -fn query_archetype_nonexistant_and_opt_comp() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - world.create_entity((A, B, C)); - let ent_2_id = world.create_entity((A, B, C, D, E)); - let ent_3_id = world.create_entity((A, B, C, E)); - world.create_entity((A, B, C, G, F)); - - assert_query_finds_ents( - world.query::<(&A, &E, Option<&D>), ()>(), - vec![ent_2_id, ent_3_id], - ); -} - -#[test] -fn query_without_comp_and_archetype_exists() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - let ent_1_id = world.create_entity((A, B, C)); - - world.create_entity((A, B, C, E)); - world.create_entity((A, B, C, F, E)); - - let ent_2_id = world.create_entity((A, B, C, G)); - let ent_3_id = world.create_entity((A, B, C, G, F)); - - assert_query_finds_ents( - world.query::<(&A, &B, &C), (Without,)>(), - vec![ent_1_id, ent_2_id, ent_3_id], - ); -} - -#[test] -fn query_without_required_comp_and_archetype_exists() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - world.create_entity((A, B, C)); - - world.create_entity((A, B, C, E)); - world.create_entity((A, B, C, F, E)); - - world.create_entity((A, B, C, G)); - world.create_entity((A, B, C, G, F)); - - assert_query_finds_ents(world.query::<(&A, &B), (Without,)>(), vec![]); -} - -#[test] -fn query_without_comp_and_archetype_nonexistant() -{ - setup(); - - let _test_lock = TEST_LOCK.lock(); - - let mut world = World::new(); - - world.create_entity((A, B, C)); - - let ent_1_id = world.create_entity((A, B, C, E)); - - world.create_entity((A, B, C, F, E)); - - let ent_2_id = world.create_entity((A, B, C, G, E)); - world.create_entity((A, B, C, G, F, E)); - - assert_query_finds_ents( - world.query::<(&A, &E), (Without,)>(), - 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::builder().relation::().target_id(ent_1_id).build(), - )); - - world.create_entity(( - B, - Pair::builder().relation::().target_id(ent_1_id).build(), - )); - world.create_entity(( - B, - A, - C, - Pair::builder().relation::().target_id(ent_1_id).build(), - )); - - let ent_3_id = world.create_entity(( - B, - Pair::builder().relation::().target_id(ent_2_id).build(), - )); - - let ent_4_id = world.create_entity(( - B, - E, - Pair::builder().relation::().target_as_data(D).build(), - )); - - assert_query_finds_ents( - world.query::<(&B, Pair), ()>(), - 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::builder().relation::().target_id(ent_1_id).build(), - )); - - world.create_entity(( - B, - Pair::builder().relation::().target_id(ent_1_id).build(), - )); - world.create_entity(( - B, - A, - C, - Pair::builder().relation::().target_id(ent_1_id).build(), - )); - - let ent_2_id = world - .create_entity((B, Pair::builder().relation::().target_as_data(F).build())); - - let ent_3_id = world.create_entity(( - B, - E, - Pair::builder().relation::().target_as_data(F).build(), - )); - - assert_query_finds_ents( - world.query::<(&B, Pair), ()>(), - vec![ent_2_id, ent_3_id], - ); -} diff --git a/engine-ecs-macros/Cargo.toml b/engine-ecs-macros/Cargo.toml new file mode 100644 index 0000000..3a2e27b --- /dev/null +++ b/engine-ecs-macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "engine-ecs-macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0.35" +syn = { version = "2.0.51", features = ["full"] } +proc-macro2 = "1.0.78" +toml = "0.8.12" + diff --git a/engine-ecs-macros/src/lib.rs b/engine-ecs-macros/src/lib.rs new file mode 100644 index 0000000..bdbba25 --- /dev/null +++ b/engine-ecs-macros/src/lib.rs @@ -0,0 +1,316 @@ +#![deny(clippy::all, clippy::pedantic)] +use std::path::PathBuf as FsPathBuf; + +use proc_macro::TokenStream; +use quote::{ToTokens, format_ident, quote}; +use syn::spanned::Spanned; +use syn::{ + Attribute, + Generics, + Ident, + Item, + ItemEnum, + ItemStruct, + ItemUnion, + Path, + parse, +}; +use toml::value::{Table as TomlTable, Value as TomlValue}; + +macro_rules! syn_path { + ($first_segment: ident $(::$segment: ident)*) => { + ::syn::Path { + leading_colon: None, + segments: ::syn::punctuated::Punctuated::from_iter([ + syn_path_segment!($first_segment), + $(syn_path_segment!($segment),)* + ]) + } + }; +} + +macro_rules! syn_path_segment { + ($segment: ident) => { + ::syn::PathSegment { + ident: ::proc_macro2::Ident::new( + stringify!($segment), + ::proc_macro2::Span::call_site(), + ), + arguments: ::syn::PathArguments::None, + } + }; +} + +/// Generates a `Component` implementation. +/// +/// # Panics +/// Will panic if: +/// - 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)] +pub fn component_derive(input: TokenStream) -> TokenStream +{ + let item: TypeItem = parse::(input).unwrap().try_into().unwrap(); + + let item_ident = item.ident(); + + let (impl_generics, type_generics, where_clause) = item.generics().split_for_impl(); + + let ecs_path = find_engine_ecs_crate_path().unwrap_or_else(|| syn_path!(ecs)); + + assert!( + item.generics().params.is_empty(), + "Generic types are not supported as components" + ); + + let id_var_ident = format_ident!("{}_ID", item_ident.to_string().to_uppercase()); + + let id_var = quote! { + static #id_var_ident: LazyLock = LazyLock::new(|| { + Uid::new_unique(UidKind::Component) + }); + }; + + let mod_ident = format_ident!( + "__ecs_priv_component_impl_{}", + item_ident.to_string().to_lowercase() + ); + + quote! { + mod #mod_ident { + use ::std::any::{Any, TypeId}; + use ::std::sync::{LazyLock, Mutex}; + + use #ecs_path::component::Component; + use #ecs_path::uid::{Uid, Kind as UidKind}; + use #ecs_path::system::Input as SystemInput; + + use super::*; + + #id_var + + impl #impl_generics Component for #item_ident #type_generics + #where_clause + { + fn id() -> Uid + { + *#id_var_ident + } + + fn name(&self) -> &'static str + { + std::any::type_name::() + } + } + + impl #impl_generics SystemInput for #item_ident #type_generics + #where_clause + { + } + } + } + .into() +} + +/// Generates a `Sole` implementation. +/// +/// # Panics +/// Will panic if not attributed to a type item or if parsing the user crate's +/// `Cargo.toml` file fails. +#[proc_macro_derive(Sole, attributes(sole))] +pub fn sole_derive(input: TokenStream) -> TokenStream +{ + let item: TypeItem = parse::(input).unwrap().try_into().unwrap(); + + let item_ident = item.ident(); + + let sole_attr = item.attribute::("sole").unwrap_or_default(); + + let drop_last = sole_attr.drop_last; + + let (impl_generics, type_generics, where_clause) = item.generics().split_for_impl(); + + let ecs_path = find_engine_ecs_crate_path().unwrap_or_else(|| syn_path!(ecs)); + + quote! { + impl #impl_generics #ecs_path::sole::Sole for #item_ident #type_generics + #where_clause + { + fn drop_last(&self) -> bool + { + #drop_last + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any + { + self + } + + fn as_any(&self) -> &dyn std::any::Any + { + self + } + } + } + .into() +} + +trait FromAttribute: Sized +{ + fn from_attribute(attribute: &Attribute) -> Result; +} + +enum TypeItem +{ + Struct(ItemStruct), + Enum(ItemEnum), + Union(ItemUnion), +} + +impl TypeItem +{ + fn ident(&self) -> &Ident + { + match self { + Self::Struct(struct_item) => &struct_item.ident, + Self::Enum(enum_item) => &enum_item.ident, + Self::Union(union_item) => &union_item.ident, + } + } + + fn attribute(&self, attr_ident: &str) -> Option + { + let item_attrs = match &self { + Self::Struct(struct_item) => &struct_item.attrs, + &Self::Enum(enum_item) => &enum_item.attrs, + &Self::Union(union_item) => &union_item.attrs, + }; + + let mut attr: Option<&Attribute> = None; + + for item_attr in item_attrs { + assert!(attr.is_none(), "Expected only one {attr_ident} attribute"); + + if item_attr.path().get_ident()? == attr_ident { + attr = Some(item_attr); + } + } + + Some(Attr::from_attribute(attr?).unwrap()) + } + + fn generics(&self) -> &Generics + { + match self { + Self::Struct(struct_item) => &struct_item.generics, + Self::Enum(enum_item) => &enum_item.generics, + Self::Union(union_item) => &union_item.generics, + } + } +} + +impl TryFrom for TypeItem +{ + type Error = syn::Error; + + fn try_from(item: Item) -> Result + { + match item { + Item::Struct(struct_item) => Ok(Self::Struct(struct_item)), + Item::Enum(enum_item) => Ok(Self::Enum(enum_item)), + Item::Union(union_item) => Ok(Self::Union(union_item)), + _ => Err(syn::Error::new( + item.span(), + "Expected a struct, a enum or a union", + )), + } + } +} + +impl ToTokens for TypeItem +{ + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) + { + match self { + Self::Struct(struct_item) => struct_item.to_tokens(tokens), + Self::Enum(enum_item) => enum_item.to_tokens(tokens), + Self::Union(union_item) => union_item.to_tokens(tokens), + } + } +} + +#[derive(Debug, Default)] +struct SoleAttribute +{ + drop_last: bool, +} + +impl FromAttribute for SoleAttribute +{ + fn from_attribute(attribute: &Attribute) -> Result + { + let mut drop_last = false; + + attribute.parse_nested_meta(|meta| { + if meta + .path + .get_ident() + .is_some_and(|flag| flag == "drop_last") + { + drop_last = true; + + return Ok(()); + } + + Err(meta.error("Unrecognized token")) + })?; + + Ok(Self { drop_last }) + } +} + +fn find_engine_ecs_crate_path() -> Option +{ + let cargo_manifest_dir = FsPathBuf::from(std::env::var("CARGO_MANIFEST_DIR").ok()?); + + let cargo_crate_name = std::env::var("CARGO_CRATE_NAME").ok()?; + let cargo_pkg_name = std::env::var("CARGO_PKG_NAME").ok()?; + + if cargo_pkg_name == "engine-ecs" && cargo_crate_name != "engine_ecs" { + // Macro is used by a engine-ecs crate example/test/benchmark + return Some(syn_path!(engine_ecs)); + } + + let crate_manifest = std::fs::read_to_string(cargo_manifest_dir.join("Cargo.toml")) + .ok()? + .parse::() + .expect("Failed to parse crate manifest file"); + + let package = match crate_manifest.get("package")? { + TomlValue::Table(package) => Some(package), + _ => None, + }?; + + let package_name = match package.get("name")? { + TomlValue::String(package_name) => Some(package_name), + _ => None, + }?; + + if package_name == "engine-ecs" { + return Some(syn_path!(crate)); + } + + let crate_dependencies = match crate_manifest.get("dependencies")? { + TomlValue::Table(dependencies) => Some(dependencies), + _ => None, + }?; + + crate_dependencies.iter().find_map(|(crate_dep_name, _)| { + if crate_dep_name == "engine" { + return Some(syn_path!(engine::ecs)); + } + + None + }) +} diff --git a/engine-ecs/Cargo.toml b/engine-ecs/Cargo.toml new file mode 100644 index 0000000..724aaea --- /dev/null +++ b/engine-ecs/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "engine-ecs" +version = "0.1.0" +edition = "2021" + +[features] +vizoxide = ["dep:vizoxide"] + +[dependencies] +seq-macro = "0.3.5" +paste = "1.0.14" +thiserror = "1.0.49" +tracing = "0.1.39" +hashbrown = "0.15.2" +parking_lot = "0.12.3" +anyhow = "1.0.102" +backtrace = "0.3.76" +engine-ecs-macros = { workspace = true } +util-macros = { workspace = true } +vizoxide = { version = "1.0.5", optional = true } + +[dev-dependencies.criterion] +version = "0.5.1" +default-features = false +features = ["cargo_bench_support"] + +[dev-dependencies.tracing-subscriber] +version = "0.3.17" +default-features = false +features = ["std", "ansi", "fmt", "smallvec", "env-filter", "chrono"] + +[[bench]] +name = "query" +harness = false diff --git a/engine-ecs/benches/query.rs b/engine-ecs/benches/query.rs new file mode 100644 index 0000000..bbc50ab --- /dev/null +++ b/engine-ecs/benches/query.rs @@ -0,0 +1,141 @@ +use std::hint::black_box; +use std::path::PathBuf; + +use criterion::{criterion_group, criterion_main, Criterion}; +use engine_ecs::{Component, World}; + +#[derive(Component)] +struct Foo +{ + _text: String, +} + +#[derive(Component)] +struct Bar +{ + _path: PathBuf, + _num: u64, +} + +#[derive(Component)] +struct Position +{ + _x: f32, + _y: f32, + _z: f32, +} + +#[derive(Component)] +struct PosA +{ + _x: f32, + _y: f32, + _z: f32, +} + +#[derive(Component)] +struct PosB +{ + _x: f32, + _y: f32, + _z: f32, +} + +#[derive(Component)] +struct PosC +{ + _x: f32, + _y: f32, + _z: f32, +} + +#[derive(Component)] +struct MoreText +{ + _more_text: String, +} + +#[derive(Component)] +struct EvenMoreText +{ + _even_more_text: String, +} + +fn spawn_1000_entities(world: &mut World) +{ + for _ in 0..300 { + world.create_entity(( + Bar { + _path: "/dev/zero".into(), + _num: 65789, + }, + Position { _x: 13.98, _y: 27.0, _z: 0.2 }, + Foo { _text: "Hello there".to_string() }, + PosA { + _x: 1183.98, + _y: 272628.0, + _z: 3306.2, + }, + PosB { + _x: 171183.98, + _y: 28.0, + _z: 336.2901, + }, + PosC { _x: 8273.98, _y: 28.0, _z: 336.2901 }, + MoreText { + _more_text: "Lorem ipsum".to_string(), + }, + EvenMoreText { + _even_more_text: "Wow so much text".to_string(), + }, + )); + } + + for _ in 0..700 { + world.create_entity(( + Bar { + _path: "/dev/null".into(), + _num: 65789, + }, + Position { _x: 88.11, _y: 9.0, _z: 36.11 }, + Foo { _text: "Hey".to_string() }, + PosA { + _x: 118310.98, + _y: 272628.0, + _z: 3306.2, + }, + PosB { _x: 11323.98, _y: 28.0, _z: 336.2901 }, + PosC { + _x: 8273.98, + _y: 21818.0, + _z: 336.2901, + }, + MoreText { + _more_text: "Lorem ipsum".to_string(), + }, + EvenMoreText { + _even_more_text: "Wow much text".to_string(), + }, + )); + } +} + +fn benchbark(criterion: &mut Criterion) +{ + criterion.bench_function("Iterate 1000 entities", |bencher| { + let mut world = World::new(); + + spawn_1000_entities(&mut world); + + let query = world.query::<(&Bar, &Position, &Foo), ()>(); + + bencher.iter(|| { + for comps in query.iter() { + black_box(comps); + } + }) + }); +} + +criterion_group!(benches, benchbark); +criterion_main!(benches); diff --git a/engine-ecs/examples/component_changed_event.rs b/engine-ecs/examples/component_changed_event.rs new file mode 100644 index 0000000..2788505 --- /dev/null +++ b/engine-ecs/examples/component_changed_event.rs @@ -0,0 +1,78 @@ +use engine_ecs::event::component::{Changed, EventMatchExt}; +use engine_ecs::pair::Pair; +use engine_ecs::phase::UPDATE as UPDATE_PHASE; +use engine_ecs::system::observer::Observe; +use engine_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>) +{ + println!("\nChanged greetings:"); + + for evt_match in &observe { + let greeting = evt_match.get_ent_target_comp(); + + 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/engine-ecs/examples/component_events.rs b/engine-ecs/examples/component_events.rs new file mode 100644 index 0000000..7c65630 --- /dev/null +++ b/engine-ecs/examples/component_events.rs @@ -0,0 +1,64 @@ +use engine_ecs::actions::Actions; +use engine_ecs::component::Component; +use engine_ecs::event::component::{Changed, EventMatchExt, Removed}; +use engine_ecs::pair::Pair; +use engine_ecs::phase::UPDATE; +use engine_ecs::system::observer::Observe; +use engine_ecs::{Component, Query, World}; + +#[derive(Debug, Component)] +struct CheeseCrumbs +{ + cnt: usize, +} + +#[derive(Debug, Component)] +struct Cheese +{ + name: &'static str, +} + +fn eat_cheese(query: Query<(&Cheese, &mut CheeseCrumbs)>, mut actions: Actions) +{ + for (cheese_ent_id, (_, mut cheese_crumbs)) in query.iter_with_euids() { + println!("Eating cheese!"); + + cheese_crumbs.cnt += 40; + cheese_crumbs.set_changed(); + + actions.remove_components(cheese_ent_id, [Cheese::id()]); + } +} + +fn on_cheese_removed(observe: Observe>) +{ + for evt_match in &observe { + let cheese = evt_match.get_ent_target_comp(); + + println!("{} cheese was eaten", cheese.name); + } +} + +fn on_cheese_crumbs_changed(observe: Observe>) +{ + for evt_match in &observe { + let cheese_crumbs = evt_match.get_ent_target_comp(); + + println!("Cheese crumbs count changed to {}", cheese_crumbs.cnt); + } +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*UPDATE, eat_cheese); + world.register_observer(on_cheese_removed); + world.register_observer(on_cheese_crumbs_changed); + + world.create_entity((Cheese { name: "Brie" }, CheeseCrumbs { cnt: 0 })); + world.create_entity((Cheese { name: "Parmesan" }, CheeseCrumbs { cnt: 0 })); + world.create_entity((Cheese { name: "Gouda" }, CheeseCrumbs { cnt: 0 })); + + world.step(); +} diff --git a/engine-ecs/examples/component_relationship.rs b/engine-ecs/examples/component_relationship.rs new file mode 100644 index 0000000..0f7b514 --- /dev/null +++ b/engine-ecs/examples/component_relationship.rs @@ -0,0 +1,65 @@ +use engine_ecs::pair::Pair; +use engine_ecs::phase::START as START_PHASE; +use engine_ecs::{Component, Query, World}; + +#[derive(Component)] +struct Person +{ + name: String, +} + +fn print_dog_likers(query: Query<(&Person, Pair)>) +{ + 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::builder() + .relation::() + .target_as_data(Dogs { large: true }) + .build(), + )); + + world.create_entity(( + Person { name: "Mark".to_string() }, + Pair::builder() + .relation::() + .target_as_data(Cats) + .build(), + )); + + world.create_entity(( + Person { name: "Helena".to_string() }, + Pair::builder() + .relation::() + .target_as_data(Dogs { large: false }) + .build(), + )); + + world.step(); +} diff --git a/engine-ecs/examples/component_removed_event.rs b/engine-ecs/examples/component_removed_event.rs new file mode 100644 index 0000000..b15c2c3 --- /dev/null +++ b/engine-ecs/examples/component_removed_event.rs @@ -0,0 +1,46 @@ +use engine_ecs::actions::Actions; +use engine_ecs::component::Component; +use engine_ecs::event::component::{EventMatchExt, Removed}; +use engine_ecs::pair::Pair; +use engine_ecs::phase::UPDATE; +use engine_ecs::system::observer::Observe; +use engine_ecs::{Component, Query, World}; + +#[derive(Debug, Component)] +struct Cheese +{ + name: &'static str, +} + +fn eat_cheese(query: Query<(&Cheese,)>, mut actions: Actions) +{ + for (cheese_ent_id, (_,)) in query.iter_with_euids() { + println!("Eating cheese!"); + + actions.remove_components(cheese_ent_id, [Cheese::id()]); + } +} + +fn on_cheese_removed(observe: Observe>) +{ + for evt_match in &observe { + let cheese = evt_match.get_ent_target_comp(); + + println!("{} cheese was eaten", cheese.name); + } +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*UPDATE, eat_cheese); + world.register_observer(on_cheese_removed); + + world.create_entity((Cheese { name: "Brie" },)); + world.create_entity((Cheese { name: "Parmesan" },)); + world.create_entity((Cheese { name: "Gouda" },)); + + world.step(); + world.step(); +} diff --git a/engine-ecs/examples/error_handling.rs b/engine-ecs/examples/error_handling.rs new file mode 100644 index 0000000..3d57778 --- /dev/null +++ b/engine-ecs/examples/error_handling.rs @@ -0,0 +1,79 @@ +use engine_ecs::error::Error; +use engine_ecs::event::component::{Changed, EventMatchExt}; +use engine_ecs::pair::Pair; +use engine_ecs::phase::UPDATE; +use engine_ecs::query::Query; +use engine_ecs::system::observer::Observe; +use engine_ecs::{Component, World, error}; +use tracing::level_filters::LevelFilter; +use tracing_subscriber::EnvFilter; +use tracing_subscriber::fmt::time::ChronoLocal; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; + +#[derive(Component)] +struct State +{ + value: u32, +} + +fn do_something_fallible(query: Query<(&mut State,)>) -> Result<(), Error> +{ + for (mut state,) in &query { + state.value += 1; + + state.set_changed(); + + if state.value > 3 { + return Err(error!("Invalid state value {}", state.value)); + } + } + + Ok(()) +} + +fn handle_state_changed(observe: Observe>) -> Result<(), Error> +{ + for evt_match in &observe { + let state = evt_match.get_ent_target_comp(); + + if state.value > 3 { + return Err(error!("Invalid state value {}", state.value)); + } + + tracing::info!("State has valid value {}", state.value); + } + + Ok(()) +} + +fn main() +{ + tracing_subscriber::registry() + .with( + tracing_subscriber::fmt::layer() + .with_timer(ChronoLocal::new("%T%.6f".to_string())), + ) + .with( + EnvFilter::builder() + .with_default_directive(LevelFilter::DEBUG.into()) + .from_env() + .unwrap(), + ) + .init(); + + let mut world = World::new(); + + world.set_err_handler(engine_ecs::error::err_handler_log_error); + + world.create_entity((State { value: 0 },)); + + world.register_system(*UPDATE, do_something_fallible); + + world.register_observer(handle_state_changed); + + world.step(); + world.step(); + world.step(); + world.step(); +} diff --git a/engine-ecs/examples/event_loop.rs b/engine-ecs/examples/event_loop.rs new file mode 100644 index 0000000..62d0876 --- /dev/null +++ b/engine-ecs/examples/event_loop.rs @@ -0,0 +1,120 @@ +use engine_ecs::actions::Actions; +use engine_ecs::pair::{ChildOf, Pair}; +use engine_ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; +use engine_ecs::{declare_entity, Component, Query, World}; + +#[derive(Component)] +struct Wool +{ + remaining: u32, +} + +#[derive(Component)] +struct Health +{ + health: u32, +} + +#[derive(Component)] +struct Name +{ + name: &'static str, +} + +fn sheer(query: Query<(&mut Wool, &Name)>) +{ + for (mut wool, name) in &query { + if wool.remaining == 0 { + println!("{} Has no wool left", name.name); + + continue; + } + + // Sheer the whool + wool.remaining -= 5; + + println!("Sheered 5 wool from {}", name.name); + } +} + +fn feed(query: Query<(&mut Health, &Name)>) +{ + for (mut health, name) in &query { + health.health += 1; + + println!("Feeded {} which gained 1 health", name.name); + } +} + +fn age(query: Query<(&mut Health, &Name)>, mut actions: Actions) +{ + for (mut health, name) in &query { + if health.health <= 2 { + health.health = 0; + + println!("{} passed away", name.name); + + actions.stop(); + + continue; + } + + health.health -= 2; + + println!("{} aged and lost 2 health", name.name); + } +} + +declare_entity!( + SHEER_PHASE, + ( + Phase, + Pair::builder() + .relation::() + .target_id(*UPDATE_PHASE) + .build() + ) +); + +declare_entity!( + FEED_PHASE, + ( + Phase, + Pair::builder() + .relation::() + .target_id(*SHEER_PHASE) + .build() + ) +); + +declare_entity!( + AGE_PHASE, + ( + Phase, + Pair::builder() + .relation::() + .target_id(*FEED_PHASE) + .build() + ) +); + +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); + + world.create_entity(( + Wool { remaining: 30 }, + Health { health: 3 }, + Name { name: "Bessy" }, + )); + + world.start_loop(); +} diff --git a/engine-ecs/examples/extension.rs b/engine-ecs/examples/extension.rs new file mode 100644 index 0000000..a96c1a7 --- /dev/null +++ b/engine-ecs/examples/extension.rs @@ -0,0 +1,70 @@ +use engine_ecs::actions::Actions; +use engine_ecs::extension::{Collector as ExtensionCollector, Extension}; +use engine_ecs::phase::UPDATE as UPDATE_PHASE; +use engine_ecs::{Component, Query, World}; + +#[derive(Debug, Component)] +struct Position +{ + x: u32, + y: u32, +} + +#[derive(Debug, Component)] +struct EnemySpawnSource; + +#[derive(Debug, Component)] +enum EvilnessLevel +{ + Medium, +} + +fn spawn_enemies( + spawner_query: Query<(&EnemySpawnSource, &Position)>, + enemies_query: Query<(&EvilnessLevel,)>, + mut actions: Actions, +) +{ + let Some((_, enemy_spawner_position)) = spawner_query.iter().next() else { + return; + }; + + let enemy_cnt = enemies_query.iter().count(); + + if enemy_cnt > 3 { + return; + } + + actions.spawn(( + EvilnessLevel::Medium, + Position { + x: enemy_spawner_position.x * enemy_cnt as u32, + y: enemy_spawner_position.y, + }, + )); + + println!("Spawned enemy with medium evilness and 45 strength"); +} + +struct EnemySpawningExtension; + +impl Extension for EnemySpawningExtension +{ + fn collect(self, mut collector: ExtensionCollector<'_>) + { + collector.add_system(*UPDATE_PHASE, spawn_enemies); + + collector.add_entity((Position { x: 187, y: 30 }, EnemySpawnSource)); + } +} + +fn main() +{ + let mut world = World::new(); + + world.add_extension(EnemySpawningExtension); + + for _ in 0..7 { + world.step(); + } +} diff --git a/engine-ecs/examples/multiple_queries.rs b/engine-ecs/examples/multiple_queries.rs new file mode 100644 index 0000000..1a4aaad --- /dev/null +++ b/engine-ecs/examples/multiple_queries.rs @@ -0,0 +1,85 @@ +use std::fmt::Display; + +use engine_ecs::phase::START as START_PHASE; +use engine_ecs::{Component, Query, World}; + +#[derive(Component)] +struct Health +{ + health: u32, +} + +#[derive(Component)] +enum AttackStrength +{ + Strong, + Weak, +} + +#[derive(Component)] +struct EnemyName +{ + name: String, +} + +impl Display for EnemyName +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + self.name.fmt(formatter) + } +} + +fn do_attacks( + attacker_query: Query<(&AttackStrength,)>, + enemy_query: Query<(&mut Health, &EnemyName)>, +) +{ + for (attack_strength,) in &attacker_query { + for (mut health, enemy_name) in &enemy_query { + let damage = match *attack_strength { + AttackStrength::Strong => 20, + AttackStrength::Weak => 10, + }; + + if health.health <= damage { + println!("Enemy '{}' died", *enemy_name); + + health.health = 0; + + continue; + } + + health.health -= damage; + + println!("Enemy '{}' took {damage} damage", *enemy_name); + } + } +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*START_PHASE, do_attacks); + + world.create_entity(( + Health { health: 100 }, + EnemyName { name: "Big spider".to_string() }, + )); + + world.create_entity(( + Health { health: 30 }, + EnemyName { name: "Small goblin".to_string() }, + )); + + world.create_entity(( + Health { health: 30 }, + EnemyName { name: "Headcrab".to_string() }, + )); + + world.create_entity((AttackStrength::Strong,)); + world.create_entity((AttackStrength::Weak,)); + + world.step(); +} diff --git a/engine-ecs/examples/optional_component.rs b/engine-ecs/examples/optional_component.rs new file mode 100644 index 0000000..79650b9 --- /dev/null +++ b/engine-ecs/examples/optional_component.rs @@ -0,0 +1,81 @@ +use engine_ecs::phase::UPDATE as UPDATE_PHASE; +use engine_ecs::{Component, Query, World}; + +#[derive(Debug, Component)] +struct PettingCapacity +{ + capacity_left: u32, +} + +#[derive(Debug, Clone, Copy, Component)] +enum Aggressivity +{ + High, + Medium, + Low, +} + +#[derive(Debug, Component)] +pub struct CatName +{ + name: String, +} + +fn pet_cats(query: Query<(&CatName, &mut PettingCapacity, Option<&Aggressivity>)>) +{ + for (cat_name, mut petting_capacity, aggressivity) in &query { + let Some(aggressivity) = aggressivity else { + println!("Aggressivity of cat {} is unknown. Skipping", cat_name.name); + continue; + }; + + if let Aggressivity::High = *aggressivity { + println!("Cat {} is aggressive. Skipping", cat_name.name); + continue; + } + + if petting_capacity.capacity_left == 0 { + println!( + "Cat {} have had enough of being petted. Skipping", + cat_name.name + ); + continue; + } + + println!("Petting cat {}", cat_name.name); + + petting_capacity.capacity_left -= 1; + } +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*UPDATE_PHASE, pet_cats); + + world.create_entity(( + CatName { name: "Jasper".to_string() }, + Aggressivity::Medium, + PettingCapacity { capacity_left: 5 }, + )); + + world.create_entity(( + CatName { name: "Otto".to_string() }, + PettingCapacity { capacity_left: 9 }, + )); + + world.create_entity(( + CatName { name: "Carrie".to_string() }, + PettingCapacity { capacity_left: 2 }, + Aggressivity::High, + )); + + world.create_entity(( + CatName { name: "Tommy".to_string() }, + PettingCapacity { capacity_left: 1 }, + Aggressivity::Low, + )); + + world.step(); +} diff --git a/engine-ecs/examples/relationship.rs b/engine-ecs/examples/relationship.rs new file mode 100644 index 0000000..749c202 --- /dev/null +++ b/engine-ecs/examples/relationship.rs @@ -0,0 +1,56 @@ +use engine_ecs::pair::{Pair, Wildcard}; +use engine_ecs::phase::START as START_PHASE; +use engine_ecs::{Component, Query, World}; + +#[derive(Component)] +struct Sword +{ + attack_strength: u32, +} + +#[derive(Component)] +struct Player; + +#[derive(Component)] +struct Health +{ + health: u32, +} + +#[derive(Component)] +struct Holding; + +fn print_player_stats(player_query: Query<(&Player, &Health, Pair)>) +{ + for (_, health, target_sword) in &player_query { + println!("Player health: {}", health.health); + + if let Some(sword_ent) = target_sword.get_target_ent() { + let sword = sword_ent + .get::() + .expect("Sword entity is missing sword component"); + + println!("Player sword attack strength: {}", sword.attack_strength); + } + } +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*START_PHASE, print_player_stats); + + let sword_uid = world.create_entity((Sword { attack_strength: 17 },)); + + world.create_entity(( + Player, + Health { health: 180 }, + Pair::builder() + .relation::() + .target_id(sword_uid) + .build(), + )); + + world.step(); +} diff --git a/engine-ecs/examples/simple.rs b/engine-ecs/examples/simple.rs new file mode 100644 index 0000000..e03c003 --- /dev/null +++ b/engine-ecs/examples/simple.rs @@ -0,0 +1,42 @@ +use engine_ecs::phase::START as START_PHASE; +use engine_ecs::{Component, Query, World}; + +#[derive(Component)] +struct SomeData +{ + num: u64, +} + +#[derive(Component)] +struct Greeting +{ + greeting: String, +} + +fn say_hello(query: Query<(&SomeData, &Greeting)>) +{ + for (data, greeting) in &query { + println!("{}: {}", greeting.greeting, data.num); + } +} + +fn main() +{ + let mut world = World::new(); + + world.register_system(*START_PHASE, say_hello); + + 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(); +} diff --git a/engine-ecs/examples/with_local.rs b/engine-ecs/examples/with_local.rs new file mode 100644 index 0000000..2c04f26 --- /dev/null +++ b/engine-ecs/examples/with_local.rs @@ -0,0 +1,70 @@ +use engine_ecs::component::local::Local; +use engine_ecs::phase::UPDATE as UPDATE_PHASE; +use engine_ecs::system::initializable::Initializable; +use engine_ecs::system::Into; +use engine_ecs::{Component, Query, World}; + +#[derive(Component)] +struct SomeData +{ + num: u64, +} + +#[derive(Component)] +struct Name +{ + name: String, +} + +#[derive(Component)] +struct SayHelloState +{ + cnt: usize, +} + +fn say_hello(query: Query<(&SomeData,)>, mut state: Local) +{ + for (data,) in &query { + println!("Hello there. Count {}: {}", state.cnt, data.num); + + state.cnt += 1; + } +} + +fn say_whats_up(query: Query<(&SomeData, &Name)>, mut state: Local) +{ + for (data, name) in &query { + println!( + "Whats up, {}. Number is {}. Count {}", + name.name, data.num, state.cnt + ); + + state.cnt += 1; + } +} + +fn main() +{ + let mut world = World::new(); + + world.register_system( + *UPDATE_PHASE, + say_hello + .into_system() + .initialize((SayHelloState { cnt: 0 },)), + ); + + world.register_system( + *UPDATE_PHASE, + say_whats_up + .into_system() + .initialize((SayHelloState { cnt: 0 },)), + ); + + world.create_entity((SomeData { num: 987_654 }, Name { name: "Bob".to_string() })); + + world.create_entity((SomeData { num: 345 },)); + + world.step(); + world.step(); +} diff --git a/engine-ecs/examples/with_sole.rs b/engine-ecs/examples/with_sole.rs new file mode 100644 index 0000000..4b2fa44 --- /dev/null +++ b/engine-ecs/examples/with_sole.rs @@ -0,0 +1,61 @@ +use engine_ecs::pair::{ChildOf, Pair}; +use engine_ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; +use engine_ecs::sole::Single; +use engine_ecs::{declare_entity, Component, Query, Sole, World}; + +#[derive(Component)] +struct Ammo +{ + ammo_left: u32, +} + +#[derive(Sole, Default)] +struct AmmoCounter +{ + counter: u32, +} + +fn count_ammo(query: Query<(&Ammo,)>, mut ammo_counter: Single) +{ + for (ammo,) in &query { + println!("Found {} ammo", ammo.ammo_left); + + ammo_counter.counter += ammo.ammo_left; + } +} + +fn print_total_ammo_count(ammo_counter: Single) +{ + println!("Total ammo count: {}", ammo_counter.counter); + + assert_eq!(ammo_counter.counter, 19); +} + +declare_entity!( + PRINT_AMMO_COUNT_PHASE, + ( + Phase, + Pair::builder() + .relation::() + .target_id(*UPDATE_PHASE) + .build() + ) +); + +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); + + world.create_entity((Ammo { ammo_left: 4 },)); + world.create_entity((Ammo { ammo_left: 7 },)); + world.create_entity((Ammo { ammo_left: 8 },)); + + world.add_sole(AmmoCounter::default()).unwrap(); + + world.step(); +} diff --git a/engine-ecs/src/actions.rs b/engine-ecs/src/actions.rs new file mode 100644 index 0000000..3d8afe6 --- /dev/null +++ b/engine-ecs/src/actions.rs @@ -0,0 +1,174 @@ +use crate::component::{Parts as ComponentParts, Sequence as ComponentSequence}; +use crate::event::component::Removed; +use crate::pair::Pair; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; +use crate::uid::{Kind as UidKind, Uid, WithUidTuple}; +use crate::{ActionQueue, World}; + +/// Used to to queue up actions for a [`World`] to perform. +#[derive(Debug)] +pub struct Actions<'world> +{ + action_queue: &'world ActionQueue, + world: Option<&'world World>, +} + +impl Actions<'_> +{ + /// Queues up a entity to spawn at the end of the current tick, returning the [`Uid`] + /// that the entity will have. + pub fn spawn(&mut self, components: Comps) -> Uid + { + let new_entity_uid = Uid::new_unique(UidKind::Entity); + + self.action_queue.push(Action::Spawn( + new_entity_uid, + components.into_parts_array().into(), + )); + + new_entity_uid + } + + /// Queues up despawning a entity at the end of the current tick. + pub fn despawn(&mut self, entity_uid: Uid) + { + debug_assert_eq!(entity_uid.kind(), UidKind::Entity); + + let Some(world) = self.world else { + self.action_queue.push(Action::Despawn(entity_uid)); + return; + }; + + let Some(ent) = world.get_entity(entity_uid) else { + tracing::warn!("Cannot entity that doesn't exist"); + return; + }; + + // TODO: Submit all events with a single function call to reduce overhead + for comp_id in ent.component_ids() { + if comp_id.kind() == UidKind::Pair { + continue; + } + + world.event_submitter().submit_event( + &Pair::builder() + .relation::() + .target_id(comp_id) + .build(), + entity_uid, + ); + } + + self.action_queue.push(Action::Despawn(entity_uid)); + } + + /// Queues up adding component(s) to a entity at the end of the current tick. + pub fn add_components(&mut self, entity_uid: Uid, components: Comps) + where + Comps: ComponentSequence, + { + debug_assert_eq!(entity_uid.kind(), UidKind::Entity); + + if Comps::COUNT == 0 { + return; + } + + self.action_queue.push(Action::AddComponents( + entity_uid, + components.into_parts_array().into(), + )); + } + + /// Queues up removing component(s) from a entity at the end of the current tick. + #[tracing::instrument(skip(self, component_ids))] + pub fn remove_components( + &mut self, + entity_uid: Uid, + component_ids: impl IntoIterator, + ) + { + debug_assert_eq!(entity_uid.kind(), UidKind::Entity); + + let mut component_ids = component_ids.into_iter().peekable(); + + if component_ids.peek().is_none() { + return; + } + + let Some(world) = self.world else { + self.action_queue.push(Action::RemoveComponents( + entity_uid, + component_ids.collect(), + )); + return; + }; + + let Some(ent) = world.get_entity(entity_uid) else { + tracing::warn!("Cannot remove components from entity that doesn't exist"); + return; + }; + + let component_ids = component_ids + .filter(|comp_id| ent.has_component(*comp_id)) + .collect::>(); + + if component_ids.is_empty() { + return; + } + + // TODO: Submit all events with a single function call to reduce overhead + for comp_id in &component_ids { + if comp_id.kind() == UidKind::Pair { + continue; + } + + world.event_submitter().submit_event( + &Pair::builder() + .relation::() + .target_id(*comp_id) + .build(), + entity_uid, + ); + } + + self.action_queue + .push(Action::RemoveComponents(entity_uid, component_ids)); + } + + /// Queues up removing component(s) from a entity at the end of the current tick. + pub fn remove_comps(&mut self, entity_uid: Uid) + { + self.remove_components(entity_uid, Ids::uids()); + } + + /// Stops the [`World`]. The world will finish the current tick and that tick will be + /// the last. + pub fn stop(&mut self) + { + self.action_queue.push(Action::Stop); + } +} + +impl<'world> SystemParam<'world> for Actions<'world> +{ + type Input = (); + + fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self + { + Self { + action_queue: &world.data.action_queue, + world: Some(world), + } + } +} + +/// A action for a [`System`] to perform. +#[derive(Debug)] +pub(crate) enum Action +{ + Spawn(Uid, Vec), + Despawn(Uid), + AddComponents(Uid, Vec), + RemoveComponents(Uid, Vec), + Stop, +} diff --git a/engine-ecs/src/component.rs b/engine-ecs/src/component.rs new file mode 100644 index 0000000..17b279b --- /dev/null +++ b/engine-ecs/src/component.rs @@ -0,0 +1,324 @@ +use std::any::{type_name, Any}; +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, + MappedWriteGuard, + ReadGuard, + WriteGuard, +}; +use crate::pair::Pair; +use crate::system::Input as SystemInput; +use crate::uid::Uid; +use crate::util::Array; +use crate::{EntityComponentRef, World}; + +pub mod local; + +pub(crate) mod storage; + +pub trait Component: SystemInput + Any +{ + /// Returns the ID of this component. + fn id() -> Uid + where + Self: Sized; + + /// Returns the name of this component. + fn name(&self) -> &'static str; +} + +impl dyn Component +{ + pub fn downcast_mut(&mut self) -> Option<&mut Real> + { + (self as &mut dyn Any).downcast_mut() + } + + pub fn downcast_ref(&self) -> Option<&Real> + { + (self as &dyn Any).downcast_ref() + } + + pub fn is(&self) -> bool + { + (self as &dyn Any).is::() + } +} + +impl Debug for dyn Component +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + formatter.debug_struct("Component").finish_non_exhaustive() + } +} + +/// A sequence of components. +pub trait Sequence +{ + /// The number of components in this component sequence. + const COUNT: usize; + + type PartsArray: Array; + + fn into_parts_array(self) -> Self::PartsArray; +} + +#[derive(Debug)] +pub struct Handle<'a, DataT: 'static> +{ + inner: MappedReadGuard<'a, DataT>, +} + +impl<'comp, DataT: 'static> Handle<'comp, DataT> +{ + /// 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>, + ) -> Result + { + Self::new( + entity_component_ref + .component() + .read_nonblock() + .map_err(AcquireLockError)?, + ) + } + + fn new(inner: ReadGuard<'comp, Box>) -> Result + { + Ok(Self { + inner: ReadGuard::try_map(inner, |component| { + component.downcast_ref::() + }) + .map_err(|_| HandleError::IncorrectType)?, + }) + } +} + +impl Deref for Handle<'_, DataT> +{ + type Target = DataT; + + fn deref(&self) -> &Self::Target + { + &self.inner + } +} + +#[derive(Debug)] +pub struct HandleMut<'a, DataT: 'static> +{ + entity_component_ref: EntityComponentRef<'a>, + inner: MappedWriteGuard<'a, DataT>, + event_submitter: EventSubmitter<'a>, +} + +impl<'comp, DataT: 'static> HandleMut<'comp, DataT> +{ + /// 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 + { + 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::() + }) + .map_err(|_| HandleError::IncorrectType)?, + event_submitter: world.event_submitter(), + }) + } + + pub fn set_changed(&self) + { + self.event_submitter.submit_event( + &Pair::builder() + .relation::() + .target_id(self.entity_component_ref.id()) + .build(), + self.entity_component_ref.entity_id(), + ); + } +} + +impl Deref for HandleMut<'_, DataT> +{ + type Target = DataT; + + fn deref(&self) -> &Self::Target + { + &self.inner + } +} + +impl DerefMut for HandleMut<'_, DataT> +{ + fn deref_mut(&mut self) -> &mut Self::Target + { + &mut self.inner + } +} + +#[derive(Debug, thiserror::Error)] +pub enum HandleError +{ + #[error(transparent)] + AcquireLockFailed(#[from] AcquireLockError), + + #[error("Incorrect component type")] + IncorrectType, +} + +#[derive(Debug, thiserror::Error)] +#[error("Failed to acquire component lock")] +pub struct AcquireLockError(#[source] LockError); + +macro_rules! inner { + ($c: tt) => { + seq!(I in 0..=$c { + impl<#(IntoCompParts~I: IntoParts,)*> Sequence for (#(IntoCompParts~I,)*) + { + const COUNT: usize = $c + 1; + + type PartsArray = [Parts; $c + 1]; + + fn into_parts_array(self) -> Self::PartsArray + { + [#({ + self.I.into_parts() + },)*] + } + } + }); + }; +} + +seq!(C in 0..=16 { + inner!(C); +}); + +impl Sequence for () +{ + type PartsArray = [Parts; 0]; + + const COUNT: usize = 0; + + fn into_parts_array(self) -> Self::PartsArray + { + [] + } +} + +pub trait IntoParts +{ + fn into_parts(self) -> Parts; +} + +impl IntoParts for ComponentT +where + ComponentT: Component, +{ + fn into_parts(self) -> Parts + { + Parts::builder() + .name(type_name::()) + .build(Self::id(), self) + } +} + +/// The parts of a component. +#[derive(Debug)] +#[non_exhaustive] +pub struct Parts +{ + id: Uid, + name: &'static str, + data: Box, +} + +impl Parts +{ + #[must_use] + pub fn id(&self) -> Uid + { + self.id + } + + #[must_use] + pub fn name(&self) -> &'static str + { + self.name + } + + #[must_use] + pub fn builder() -> PartsBuilder + { + PartsBuilder::default() + } + + pub(crate) fn into_data(self) -> Box + { + self.data + } +} + +#[derive(Debug)] +pub struct PartsBuilder +{ + name: &'static str, +} + +impl PartsBuilder +{ + #[must_use] + pub fn name(mut self, name: &'static str) -> Self + { + self.name = name; + self + } + + #[must_use] + pub fn build(self, id: Uid, data: Data) -> Parts + { + Parts { + id, + name: self.name, + data: Box::new(data), + } + } + + #[must_use] + pub fn build_with_any_data(self, id: Uid, data: Box) -> Parts + { + Parts { id, name: self.name, data } + } +} + +impl Default for PartsBuilder +{ + fn default() -> Self + { + Self { name: "(unspecified)" } + } +} diff --git a/engine-ecs/src/component/local.rs b/engine-ecs/src/component/local.rs new file mode 100644 index 0000000..29afcee --- /dev/null +++ b/engine-ecs/src/component/local.rs @@ -0,0 +1,101 @@ +use std::any::type_name; +use std::ops::{Deref, DerefMut}; + +use crate::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::{Component, World}; + +/// Holds a component which is local to a single system. +#[derive(Debug)] +pub struct Local<'world, LocalComponent: Component> +{ + local_component: ComponentHandleMut<'world, LocalComponent>, +} + +impl<'world, LocalComponent> SystemParam<'world> for Local<'world, LocalComponent> +where + LocalComponent: Component, +{ + type Input = LocalComponent; + + fn new(world: &'world World, system_metadata: &SystemMetadata) -> Self + { + let Some(system_ent) = world.get_entity(system_metadata.ent_id) else { + panic!( + "System entity with ID {} does not exist", + system_metadata.ent_id + ); + }; + + let Some(local_component) = system_ent.get_with_id_mut::( + Pair::builder() + .relation::() + .target::() + .build() + .id(), + ) else { + panic!( + "Local component {} of system with ID {} is uninitialized", + type_name::(), + 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::builder() + .relation::() + .target_as_data(input) + .build() + .into_parts(), + ); + } +} + +impl Deref for Local<'_, LocalComponent> +where + LocalComponent: Component, +{ + type Target = LocalComponent; + + fn deref(&self) -> &Self::Target + { + &self.local_component + } +} + +impl DerefMut for Local<'_, LocalComponent> +where + LocalComponent: Component, +{ + fn deref_mut(&mut self) -> &mut Self::Target + { + &mut self.local_component + } +} + +pub trait SystemWithLocalComponents +{ + fn add_local_component(&mut self, component_parts: ComponentParts); +} + +#[derive(Component)] +struct IsLocalComponent; diff --git a/engine-ecs/src/component/storage.rs b/engine-ecs/src/component/storage.rs new file mode 100644 index 0000000..dc38b6a --- /dev/null +++ b/engine-ecs/src/component/storage.rs @@ -0,0 +1,795 @@ +use std::any::Any; +use std::array::IntoIter as ArrayIter; +use std::cell::RefCell; +use std::vec::IntoIter as VecIntoIter; + +use hashbrown::HashMap; + +use crate::component::storage::archetype::{ + Archetype, + Entity as ArchetypeEntity, + EntityComponent as ArchetypeEntityComponent, + Id as ArchetypeId, +}; +use crate::component::storage::graph::{ + ArchetypeAddEdgeDfsIter, + ArchetypeAddEdgeDfsIterResult, + ArchetypeEdges, + Graph, +}; +use crate::uid::{Kind as UidKind, Uid}; +use crate::util::{BorrowedOrOwned, Either, StreamingIterator, VecExt}; + +pub mod archetype; + +mod graph; + +#[derive(Debug)] +pub struct ArchetypeSearchTerms<'a> +{ + pub required_components: &'a [Uid], + pub excluded_components: &'a [Uid], +} + +impl ArchetypeSearchTerms<'_> +{ + fn excluded_contains(&self, comp_id: Uid) -> bool + { + let comp_id_kind = comp_id.kind(); + + debug_assert!( + comp_id_kind == UidKind::Component + || (comp_id_kind == UidKind::Pair + && comp_id.target_component() != Uid::wildcard()) + ); + + let is_found = self.excluded_components.binary_search(&comp_id).is_ok(); + + if !is_found && comp_id_kind == UidKind::Pair { + return self.excluded_components.iter().any(|excluded_comp_id| { + excluded_comp_id.kind() == UidKind::Pair + && excluded_comp_id.has_same_relation_as(comp_id) + && excluded_comp_id.target_component() == Uid::wildcard() + }); + } + + is_found + } + + fn contains_conflicting(&self) -> bool + { + self.excluded_components.iter().any(|excluded_comp_id| { + self.required_components + .binary_search(excluded_comp_id) + .is_ok() + }) + } + + fn archetype_contains_all_required(&self, archetype: &Archetype) -> bool + { + self.required_components + .iter() + .all(|comp_id| archetype.contains_matching_component(*comp_id)) + } +} + +#[derive(Debug, Default)] +pub struct Storage +{ + graph: Graph, + entity_archetype_lookup: HashMap, + imaginary_archetypes: RefCell>, +} + +impl Storage +{ + pub fn search_archetypes<'search_terms>( + &self, + search_terms: ArchetypeSearchTerms<'search_terms>, + ) -> ArchetypeRefIter<'_, 'search_terms> + { + let archetype_id = ArchetypeId::new(search_terms.required_components); + + if search_terms.contains_conflicting() { + return ArchetypeRefIter { + storage: self, + pre_iter: Either::B(Vec::new().into_iter()), + dfs_iter: ArchetypeAddEdgeDfsIter::new(&self.graph, &[]), + search_terms, + }; + } + + let Some(add_edge_recursive_iter) = + self.graph.dfs_archetype_add_edges(archetype_id) + else { + self.imaginary_archetypes + .borrow_mut() + .push(ImaginaryArchetype { + id: ArchetypeId::new(search_terms.required_components.iter().filter( + |required_comp_id| { + required_comp_id.kind() != UidKind::Pair + || required_comp_id.target_component() != Uid::wildcard() + }, + )), + component_ids: search_terms + .required_components + .iter() + .copied() + .filter(|required_comp_id| { + required_comp_id.kind() != UidKind::Pair + || required_comp_id.target_component() != Uid::wildcard() + }) + .collect(), + }); + + let found_archetypes = self.find_all_archetype_with_comps(&search_terms); + + return ArchetypeRefIter { + storage: self, + pre_iter: Either::B(found_archetypes.clone().into_iter()), + dfs_iter: ArchetypeAddEdgeDfsIter::new(&self.graph, &found_archetypes), + search_terms, + }; + }; + + ArchetypeRefIter { + storage: self, + pre_iter: Either::A([archetype_id].into_iter()), + dfs_iter: add_edge_recursive_iter, + search_terms, + } + } + + pub fn get_archetype_by_id(&self, id: ArchetypeId) -> Option<&Archetype> + { + Some(self.graph.get_node_by_id(id)?.archetype()) + } + + pub fn create_entity(&mut self, uid: Uid) -> Result<(), EntityAlreadyExistsError> + { + debug_assert_eq!(uid.kind(), UidKind::Entity); + + if self.entity_archetype_lookup.contains_key(&uid) { + return Err(EntityAlreadyExistsError); + } + + let empty_archetype_id = ArchetypeId::new_empty(); + + let archetype_node = self.graph.get_or_create_node(empty_archetype_id, &[]); + + archetype_node + .archetype_mut() + .push_entity(ArchetypeEntity::new(uid, [])); + + self.entity_archetype_lookup.insert(uid, empty_archetype_id); + + Ok(()) + } + + pub fn remove_entity(&mut self, entity_uid: Uid) -> Result + { + let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { + return Err(Error::EntityDoesNotExist(entity_uid)); + }; + + let archetype_node = self + .graph + .get_node_by_id_mut(*archetype_id) + .expect("Archetype should exist"); + + let entity = archetype_node + .archetype_mut() + .remove_entity(entity_uid) + .expect("Entity should exist in archetype"); + + self.entity_archetype_lookup.remove(&entity_uid); + + Ok(entity) + } + + pub fn get_entity_archetype(&self, entity_uid: Uid) -> Option<&Archetype> + { + let archetype_id = self.entity_archetype_lookup.get(&entity_uid)?; + + self.get_archetype_by_id(*archetype_id) + } + + pub fn add_entity_component( + &mut self, + entity_uid: Uid, + (component_id, component_name, component): (Uid, &'static str, Box), + ) -> Result<(), Error> + { + let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { + return Err(Error::EntityDoesNotExist(entity_uid)); + }; + + let archetype_id = *archetype_id; + + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + if archetype_node + .archetype() + .contains_component_with_exact_id(component_id) + { + return Err(Error::ComponentAlreadyInEntity { + entity: entity_uid, + component: component_id, + }); + } + + let add_edge_archetype_id = if let Some(add_edge_id) = archetype_node + .get_or_insert_edges(component_id, ArchetypeEdges::default) + .add + { + if !self.graph.contains_archetype(add_edge_id) { + let (_, add_edge_comp_ids) = self + .graph + .get_node_by_id(archetype_id) + .expect("Archetype should exist") + .make_add_edge(component_id); + + self.graph.create_node(add_edge_id, &add_edge_comp_ids); + } + + add_edge_id + } else { + let archetype_node = self + .graph + .get_node_by_id(archetype_id) + .expect("Archetype should exist"); + + let (add_edge_id, add_edge_comp_ids) = + archetype_node.make_add_edge(component_id); + + if !self.graph.contains_archetype(add_edge_id) { + self.graph.create_node(add_edge_id, &add_edge_comp_ids); + } + + add_edge_id + }; + + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + let mut entity = archetype_node + .archetype_mut() + .remove_entity(entity_uid) + .expect("Entity should exist in archetype"); + + let add_edge_archetype = self + .graph + .get_node_by_id_mut(add_edge_archetype_id) + .expect("Add edge archetype should exist") + .archetype_mut(); + + entity.insert_component( + component_id, + ArchetypeEntityComponent::new(component, component_name), + add_edge_archetype, + ); + + add_edge_archetype.push_entity(entity); + + self.entity_archetype_lookup + .insert(entity_uid, add_edge_archetype_id); + + Ok(()) + } + + pub fn remove_entity_component( + &mut self, + entity_uid: Uid, + component_id: Uid, + ) -> Result<(), Error> + { + let Some(archetype_id) = self.entity_archetype_lookup.get(&entity_uid) else { + return Err(Error::EntityDoesNotExist(entity_uid)); + }; + + let archetype_id = *archetype_id; + + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + if !archetype_node + .archetype() + .contains_component_with_exact_id(component_id) + { + return Err(Error::ComponentNotFoundInEntity { + entity: entity_uid, + component: component_id, + }); + } + + let remove_edge_id = archetype_node + .get_or_insert_edges(component_id, ArchetypeEdges::default) + .remove + .unwrap_or_else(|| { + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + let (remove_edge_id, remove_edge_comp_ids) = + archetype_node.make_remove_edge(component_id); + + if !self.graph.contains_archetype(remove_edge_id) { + self.graph + .create_node(remove_edge_id, &remove_edge_comp_ids); + } + + remove_edge_id + }); + + let archetype_node = self + .graph + .get_node_by_id_mut(archetype_id) + .expect("Archetype should exist"); + + let mut entity = archetype_node + .archetype_mut() + .remove_entity(entity_uid) + .expect("Entity should exist in archetype"); + + let removed_component = + entity.remove_component(component_id, archetype_node.archetype()); + + self.graph + .get_node_by_id_mut(remove_edge_id) + .expect("Remove edge archetype should exist") + .archetype_mut() + .push_entity(entity); + + self.entity_archetype_lookup + .insert(entity_uid, remove_edge_id); + + tracing::debug!( + entity_id = %entity_uid, + component_id = %component_id, + component_name = removed_component.name(), + "Removed component from entity" + ); + + Ok(()) + } + + pub fn create_imaginary_archetypes(&mut self) + { + for imaginary_archetype in self.imaginary_archetypes.get_mut().drain(..) { + if self.graph.contains_archetype(imaginary_archetype.id) { + continue; + } + + self.graph + .create_node(imaginary_archetype.id, &imaginary_archetype.component_ids); + } + } + + fn find_all_archetype_with_comps( + &self, + search_terms: &ArchetypeSearchTerms<'_>, + ) -> Vec + { + let Some(mut search_iter) = + self.graph.dfs_archetype_add_edges(ArchetypeId::new_empty()) + else { + // If the root archetype doesn't exist, no other archetype can exist either + // + // TODO: The above comment is not true. Cases where imaginary archetypes have + // been created should be handled as well + return Vec::new(); + }; + + let mut found = Vec::::new(); + + while let Some(node_id) = search_iter.streaming_next() { + let ArchetypeAddEdgeDfsIterResult::AddEdge { + add_edge_archetype_id: node_id, + add_edge_component_id, + } = node_id + else { + continue; + }; + + if search_terms.excluded_contains(add_edge_component_id) { + search_iter.pop(); + continue; + } + + let node = self + .graph + .get_node_by_id(node_id) + .expect("Graph node found through DFS doesn't exist"); + + if node.archetype().component_cnt() < search_terms.required_components.len() { + continue; + } + + if !search_terms.archetype_contains_all_required(node.archetype()) { + continue; + } + + found.push(node.archetype().id()); + + search_iter.pop(); + } + + found + } +} + +#[cfg(feature = "vizoxide")] +impl Storage +{ + pub fn create_vizoxide_archetype_graph( + &self, + graph_name: impl AsRef, + params: VizoxideArchetypeGraphParams, + ) -> Result + { + let viz_graph = vizoxide::Graph::builder(graph_name.as_ref()) + .strict(true) + .directed(true) + .build()?; + + let mut viz_node_lookup = HashMap::new(); + + for node in self.graph.iter_nodes() { + let id = node.archetype().id(); + + if !viz_node_lookup.contains_key(&id) { + let node = self.graph.get_node_by_id(id).unwrap(); + + let viz_node = (params.create_node_cb)( + node.archetype(), + ArchetypeMetadata { is_imaginary: false }, + viz_graph.create_node(&(params.create_node_name)( + node.archetype(), + ArchetypeMetadata { is_imaginary: false }, + )), + ) + .build()?; + + viz_node_lookup.insert(id, viz_node); + } + + for (edge_comp_id, edges) in node.iter_edges() { + if let Some(add_edge) = edges.add { + if !viz_node_lookup.contains_key(&add_edge) { + let viz_node = self.create_vizoxide_archetype_graph_edge_node( + &viz_graph, + node, + add_edge, + *edge_comp_id, + ¶ms, + )?; + + viz_node_lookup.insert(add_edge, viz_node); + } + + (params.create_edge_cb)( + node.archetype(), + *edge_comp_id, + VizoxideArchetypeGraphEdgeKind::Add, + viz_graph.create_edge( + viz_node_lookup.get(&id).unwrap(), + viz_node_lookup.get(&add_edge).unwrap(), + Some(&format!("Add {}", edge_comp_id.id())), + ), + ) + .build()?; + } + + if let Some(remove_edge) = edges.remove { + if !viz_node_lookup.contains_key(&remove_edge) { + let viz_node = self.create_vizoxide_archetype_graph_edge_node( + &viz_graph, + node, + remove_edge, + *edge_comp_id, + ¶ms, + )?; + + viz_node_lookup.insert(remove_edge, viz_node); + } + + (params.create_edge_cb)( + node.archetype(), + *edge_comp_id, + VizoxideArchetypeGraphEdgeKind::Remove, + viz_graph.create_edge( + viz_node_lookup.get(&id).unwrap(), + viz_node_lookup.get(&remove_edge).unwrap(), + Some(&format!("Remove {}", edge_comp_id.id())), + ), + ) + .build()?; + } + } + } + + drop(viz_node_lookup); + + Ok(viz_graph) + } + + fn create_vizoxide_archetype_graph_edge_node<'vizoxide_graph>( + &self, + viz_graph: &'vizoxide_graph vizoxide::Graph, + node: &graph::ArchetypeNode, + edge_id: ArchetypeId, + edge_comp_id: Uid, + params: &VizoxideArchetypeGraphParams, + ) -> Result, vizoxide::GraphvizError> + { + match self.graph.get_node_by_id(edge_id) { + Some(edge_node) => (params.create_node_cb)( + edge_node.archetype(), + ArchetypeMetadata { is_imaginary: false }, + viz_graph.create_node(&(params.create_node_name)( + edge_node.archetype(), + ArchetypeMetadata { is_imaginary: false }, + )), + ) + .build(), + None => { + let mut comp_ids = + node.archetype().component_ids_sorted().collect::>(); + + let insert_index = comp_ids.partition_point(|cid| *cid <= edge_comp_id); + + comp_ids.insert(insert_index, edge_comp_id); + + let imaginary_edge_archetype = Archetype::new(edge_id, comp_ids); + + (params.create_node_cb)( + &imaginary_edge_archetype, + ArchetypeMetadata { is_imaginary: true }, + viz_graph.create_node(&(params.create_node_name)( + &imaginary_edge_archetype, + ArchetypeMetadata { is_imaginary: true }, + )), + ) + .build() + } + } + } +} + +#[cfg(feature = "vizoxide")] +pub struct VizoxideArchetypeGraphParams +{ + pub create_node_name: fn(&Archetype, ArchetypeMetadata) -> std::borrow::Cow<'_, str>, + pub create_node_cb: for<'storage, 'graph> fn( + &'storage Archetype, + ArchetypeMetadata, + vizoxide::NodeBuilder<'graph>, + ) -> vizoxide::NodeBuilder<'graph>, + pub create_edge_cb: for<'storage, 'graph> fn( + &'storage Archetype, + Uid, + VizoxideArchetypeGraphEdgeKind, + vizoxide::EdgeBuilder<'graph>, + ) -> vizoxide::EdgeBuilder<'graph>, +} + +#[cfg(feature = "vizoxide")] +#[derive(Debug, Clone)] +pub struct ArchetypeMetadata +{ + pub is_imaginary: bool, +} + +#[cfg(feature = "vizoxide")] +#[derive(Debug, Clone, Copy)] +pub enum VizoxideArchetypeGraphEdgeKind +{ + Add, + Remove, +} + +#[derive(Debug)] +pub struct ArchetypeRefIter<'storage, 'search_terms> +{ + storage: &'storage Storage, + pre_iter: Either, VecIntoIter>, + dfs_iter: ArchetypeAddEdgeDfsIter<'storage>, + search_terms: ArchetypeSearchTerms<'search_terms>, +} + +impl<'component_storage> Iterator for ArchetypeRefIter<'component_storage, '_> +{ + type Item = &'component_storage Archetype; + + fn next(&mut self) -> Option + { + if let Some(pre_iter_archetype_id) = self.pre_iter.next() { + return Some( + self.storage + .get_archetype_by_id(pre_iter_archetype_id) + .expect("Archetype should exist"), + ); + } + + let archetype_id = loop { + match self.dfs_iter.streaming_find(|res| { + matches!( + res, + ArchetypeAddEdgeDfsIterResult::AddEdge { .. } + | ArchetypeAddEdgeDfsIterResult::AddEdgeArchetypeNotFound { .. } + ) + })? { + ArchetypeAddEdgeDfsIterResult::AddEdge { + add_edge_archetype_id, + add_edge_component_id, + } => { + if self.search_terms.excluded_contains(add_edge_component_id) { + self.dfs_iter.pop(); + continue; + } + + break add_edge_archetype_id; + } + ArchetypeAddEdgeDfsIterResult::AddEdgeArchetypeNotFound { + archetype, + add_edge_archetype_id, + add_edge_component_id, + } => { + if self.search_terms.excluded_contains(add_edge_component_id) { + continue; + } + + let mut add_edge_archetype_comps = + archetype.component_ids_sorted().collect::>(); + + add_edge_archetype_comps + .insert_at_part_pt_by_key(add_edge_component_id, |comp_id| { + comp_id + }); + + self.storage.imaginary_archetypes.borrow_mut().push( + ImaginaryArchetype { + id: add_edge_archetype_id, + component_ids: add_edge_archetype_comps.clone(), + }, + ); + + let found = + self.find_edges_of_imaginary_archetype(&add_edge_archetype_comps); + + self.dfs_iter.push(( + BorrowedOrOwned::Owned(Archetype::new( + add_edge_archetype_id, + add_edge_archetype_comps.clone(), + )), + found.into_iter(), + )); + } + _ => { + unreachable!(); + } + } + }; + + Some( + self.storage + .get_archetype_by_id(archetype_id) + .expect("Archetype should exist"), + ) + } +} + +impl ArchetypeRefIter<'_, '_> +{ + fn find_edges_of_imaginary_archetype( + &self, + imaginary_archetype_comps: &[Uid], + ) -> Vec<(Uid, ArchetypeEdges)> + { + self.storage + .find_all_archetype_with_comps(&ArchetypeSearchTerms { + required_components: imaginary_archetype_comps, + excluded_components: &[], + }) + .into_iter() + .filter_map(|found_id| { + let found_archetype = self.storage.get_archetype_by_id(found_id).unwrap(); + + if found_archetype.component_cnt() < imaginary_archetype_comps.len() + 1 { + return None; + } + + let unique_comp_id = found_archetype + .component_ids_sorted() + .find(|found_archetype_comp_id| { + !imaginary_archetype_comps.iter().any( + |imaginary_archetype_comp_id| { + *imaginary_archetype_comp_id == *found_archetype_comp_id + }, + ) + }) + .expect("Oh noooo"); + + let mut add_edge_comp_ids = imaginary_archetype_comps.to_vec(); + + add_edge_comp_ids.insert_at_part_pt_by_key(unique_comp_id, |id| id); + + let add_edge = ArchetypeId::new(&add_edge_comp_ids); + + Some(( + unique_comp_id, + ArchetypeEdges { add: Some(add_edge), remove: None }, + )) + }) + .collect::>() + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Entity with ID {0:?} does not exist")] + EntityDoesNotExist(Uid), + + #[error("Entity with ID {entity:?} already has component with ID {component:?}")] + ComponentAlreadyInEntity + { + entity: Uid, component: Uid + }, + + #[error("Entity with ID {entity:?} does not have component with ID {component:?}")] + ComponentNotFoundInEntity + { + entity: Uid, component: Uid + }, +} + +#[derive(Debug, thiserror::Error)] +#[error("Entity with already exists")] +pub struct EntityAlreadyExistsError; + +#[derive(Debug)] +struct ImaginaryArchetype +{ + id: ArchetypeId, + component_ids: Vec, +} + +#[cfg(test)] +mod tests +{ + use crate::component::storage::Storage; + use crate::component::storage::archetype::Id as ArchetypeId; + use crate::uid::{Kind as UidKind, Uid}; + + #[test] + fn create_entity_works() + { + let mut new_storage = Storage::default(); + + let uid = Uid::new_unique(UidKind::Entity); + + new_storage.create_entity(uid).expect("Expected Ok"); + + let archetype_node = new_storage + .graph + .get_node_by_id(ArchetypeId::new_empty()) + .expect("Archetype for entities with no component doesn't exist"); + + assert_eq!(archetype_node.archetype().component_cnt(), 0); + assert_eq!(archetype_node.archetype().entity_cnt(), 1); + + assert_eq!( + new_storage.entity_archetype_lookup.get(&uid).copied(), + Some(ArchetypeId::new_empty()) + ); + } +} diff --git a/engine-ecs/src/component/storage/archetype.rs b/engine-ecs/src/component/storage/archetype.rs new file mode 100644 index 0000000..a7fe7ed --- /dev/null +++ b/engine-ecs/src/component/storage/archetype.rs @@ -0,0 +1,385 @@ +use std::any::Any; +use std::array::IntoIter as ArrayIntoIter; +use std::hash::{DefaultHasher, Hash, Hasher}; +use std::iter::{Enumerate, Filter, Map, RepeatN, Zip}; +use std::option::IntoIter as OptionIntoIter; +use std::slice::Iter as SliceIter; + +use hashbrown::HashMap; + +use crate::lock::Lock; +use crate::uid::{Kind as UidKind, Uid}; +use crate::util::{Either, HashMapExt}; + +#[derive(Debug)] +pub struct Archetype +{ + id: Id, + entities: Vec, + entity_index_lookup: HashMap, + component_index_lookup: HashMap, + component_ids: Vec, +} + +impl Archetype +{ + pub fn new(id: Id, component_ids: impl AsRef<[Uid]>) -> Self + { + Self { + id, + entities: Vec::new(), + entity_index_lookup: HashMap::new(), + component_index_lookup: component_ids + .as_ref() + .iter() + .enumerate() + .map(|(index, id)| (*id, index)) + .collect(), + component_ids: component_ids.as_ref().to_vec(), + } + } + + pub fn id(&self) -> Id + { + self.id + } + + pub fn is_superset(&self, other: &Self) -> bool + { + self.component_index_lookup + .keys_is_superset(&other.component_index_lookup) + } + + pub fn is_subset(&self, other: &Self) -> bool + { + self.component_index_lookup + .keys_is_subset(&other.component_index_lookup) + } + + pub fn get_entity_by_id(&self, entity_uid: Uid) -> Option<&Entity> + { + let index = *self.entity_index_lookup.get(&entity_uid)?; + + Some(self.entities.get(index).unwrap_or_else(|| { + panic!( + "In invalid state! Index of entity with ID {entity_uid:?} is out of bounds" + ); + })) + } + + pub fn push_entity(&mut self, entity: Entity) + { + self.entity_index_lookup + .insert(entity.uid, self.entities.len()); + + self.entities.push(entity); + } + + pub fn remove_entity(&mut self, entity_uid: Uid) -> Option + { + //debug_assert_eq!(entity_uid.kind(), UidKind::Entity); + + let entity_index = self.entity_index_lookup.remove(&entity_uid)?; + + if self.entities.len() == 1 { + return Some(self.entities.remove(entity_index)); + } + + let last_entity_uid = self + .entities + .last() + .expect(concat!( + "Invalid state. No entities in archetype but entry was ", + "removed successfully from entity index lookup" + )) + .uid; + + // By using swap_remove, no memory reallocation occurs and only one index in the + // entity lookup needs to be updated + let removed_entity = self.entities.swap_remove(entity_index); + + self.entity_index_lookup + .insert(last_entity_uid, entity_index); + + Some(removed_entity) + } + + pub fn entities(&self) -> EntityIter<'_> + { + EntityIter { iter: self.entities.iter() } + } + + pub fn entity_cnt(&self) -> usize + { + self.entities.len() + } + + pub fn component_cnt(&self) -> usize + { + self.component_index_lookup.len() + } + + pub fn get_matching_component_indices( + &self, + component_id: Uid, + ) -> MatchingComponentIter<'_> + { + assert!( + component_id.kind() == UidKind::Component + || component_id.kind() == UidKind::Pair + ); + + if component_id.kind() == UidKind::Pair + && component_id.target_component() == Uid::wildcard() + { + return MatchingComponentIter { + inner: Either::A( + self.component_ids + .iter() + .enumerate() + .zip(std::iter::repeat_n(component_id, self.component_ids.len())) + .filter( + (|((_, other_comp_id), component_id)| { + other_comp_id.kind() == UidKind::Pair + && other_comp_id.has_same_relation_as(*component_id) + }) + as MatchingComponentIterFilterFn, + ) + .map(|((index, other_comp_id), _)| (*other_comp_id, index)), + ), + }; + } + + MatchingComponentIter { + inner: Either::B( + [component_id] + .into_iter() + .zip(self.get_index_for_component(component_id)), + ), + } + } + + pub fn get_index_for_component(&self, component_id: Uid) -> Option + { + assert!( + component_id.kind() == UidKind::Component + || (component_id.kind() == UidKind::Pair + && component_id.target_component() != Uid::wildcard()) + ); + + self.component_index_lookup.get(&component_id).copied() + } + + pub fn component_ids_unsorted(&self) -> impl Iterator + '_ + { + self.component_index_lookup.keys().copied() + } + + pub fn component_ids_sorted(&self) -> impl Iterator + '_ + { + self.component_ids.iter().copied() + } + + pub fn contains_matching_component(&self, component_id: Uid) -> bool + { + let component_id_kind = component_id.kind(); + + debug_assert!( + component_id_kind == UidKind::Component || component_id_kind == UidKind::Pair + ); + + if component_id.kind() == UidKind::Pair + && component_id.target_component() == Uid::wildcard() + { + return self.component_ids.iter().any(|other_comp_id| { + other_comp_id.kind() == UidKind::Pair + && other_comp_id.has_same_relation_as(component_id) + }); + } + + self.contains_component_with_exact_id(component_id) + } + + pub fn contains_component_with_exact_id(&self, component_id: Uid) -> bool + { + let component_id_kind = component_id.kind(); + + debug_assert!( + component_id_kind == UidKind::Component + || (component_id_kind == UidKind::Pair + && component_id.target_component() != Uid::wildcard()) + ); + + self.component_index_lookup.contains_key(&component_id) + } +} + +type MatchingComponentIterFilterFn = fn(&((usize, &Uid), Uid)) -> bool; + +type MatchingComponentIterMapFn = fn(((usize, &Uid), Uid)) -> (Uid, usize); + +type InnerMatchingComponentIterA<'archetype> = Map< + Filter< + Zip>, RepeatN>, + MatchingComponentIterFilterFn, + >, + MatchingComponentIterMapFn, +>; + +type InnerMatchingComponentIterB = Zip, OptionIntoIter>; + +#[derive(Debug)] +pub struct MatchingComponentIter<'archetype> +{ + inner: Either, InnerMatchingComponentIterB>, +} + +impl Iterator for MatchingComponentIter<'_> +{ + type Item = (Uid, usize); + + fn next(&mut self) -> Option + { + self.inner.next() + } +} + +#[derive(Debug)] +pub struct EntityIter<'archetype> +{ + iter: SliceIter<'archetype, Entity>, +} + +impl<'archetype> Iterator for EntityIter<'archetype> +{ + type Item = &'archetype Entity; + + fn next(&mut self) -> Option + { + self.iter.next() + } +} + +#[derive(Debug)] +pub struct Entity +{ + uid: Uid, + components: Vec, +} + +impl Entity +{ + pub fn new(uid: Uid, components: impl IntoIterator) -> Self + { + Self { + uid, + components: components.into_iter().collect(), + } + } + + pub fn uid(&self) -> Uid + { + self.uid + } + + pub fn components(&self) -> &[EntityComponent] + { + &self.components + } + + pub fn remove_component( + &mut self, + component_id: Uid, + archetype: &Archetype, + ) -> EntityComponent + { + let index = archetype + .get_index_for_component(component_id) + .expect("Archetype should contain component"); + + self.components.remove(index) + } + + pub fn insert_component( + &mut self, + component_id: Uid, + component: EntityComponent, + archetype: &Archetype, + ) + { + let index = archetype + .get_index_for_component(component_id) + .expect("Archetype should contain component"); + + self.components.insert(index, component); + } +} + +#[derive(Debug)] +pub struct EntityComponent +{ + component: Lock>, + name: &'static str, +} + +impl EntityComponent +{ + pub fn new(component: Box, component_name: &'static str) -> Self + { + Self { + component: Lock::new(component, component_name), + name: component_name, + } + } + + pub fn component(&self) -> &Lock> + { + &self.component + } + + pub fn name(&self) -> &str + { + self.name + } +} + +/// Archetype ID. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Id +{ + hash: u64, +} + +impl Id +{ + pub fn new_empty() -> Self + { + Self { hash: 0 } + } + + pub fn new<'a>(component_ids: impl IntoIterator) -> Self + { + let mut hasher = DefaultHasher::new(); + + let mut prev_component_id: Option = None; + + let mut component_id_iter = component_ids.into_iter().peekable(); + + if component_id_iter.peek().is_none() { + return Self::new_empty(); + } + + for comp_id in component_id_iter { + 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); + + comp_id.hash(&mut hasher); + } + + Self { hash: hasher.finish() } + } +} diff --git a/engine-ecs/src/component/storage/graph.rs b/engine-ecs/src/component/storage/graph.rs new file mode 100644 index 0000000..76200f9 --- /dev/null +++ b/engine-ecs/src/component/storage/graph.rs @@ -0,0 +1,432 @@ +use std::vec::IntoIter as VecIntoIter; + +use hashbrown::{HashMap, HashSet}; + +use crate::component::storage::archetype::{Archetype, Id as ArchetypeId}; +use crate::uid::{Kind as UidKind, Uid}; +use crate::util::{BorrowedOrOwned, StreamingIterator}; + +#[derive(Debug, Default)] +pub struct Graph +{ + nodes: Vec, + archetype_index_lookup: HashMap, +} + +impl Graph +{ + pub fn create_node(&mut self, id: ArchetypeId, component_ids: &impl AsRef<[Uid]>) + { + debug_assert!(!self.contains_archetype(id)); + + let _ = self.get_or_create_node(id, component_ids); + } + + pub fn get_or_create_node( + &mut self, + id: ArchetypeId, + component_ids: &impl AsRef<[Uid]>, + ) -> &mut ArchetypeNode + { + let exists_before = self.archetype_index_lookup.contains_key(&id); + + let index = *self.archetype_index_lookup.entry(id).or_insert_with(|| { + self.nodes.push(ArchetypeNode { + archetype: Archetype::new(id, component_ids.as_ref()), + edges: HashMap::new(), + }); + + self.nodes.len() - 1 + }); + + if !exists_before { + self.create_missing_edges(id); + } + + self.nodes + .get_mut(index) + .expect("Archetype index from lookup is out of bounds") + } + + pub fn contains_archetype(&self, id: ArchetypeId) -> bool + { + self.archetype_index_lookup.contains_key(&id) + } + + pub fn get_node_by_id(&self, id: ArchetypeId) -> Option<&ArchetypeNode> + { + let index = self.archetype_index_lookup.get(&id)?; + + Some(self.nodes.get(*index).unwrap_or_else(|| { + panic!("In invalid state! Index of archetype with ID {id:?} is out of bounds") + })) + } + + pub fn get_node_by_id_mut(&mut self, id: ArchetypeId) -> Option<&mut ArchetypeNode> + { + let index = self.archetype_index_lookup.get(&id)?; + + Some(self.nodes.get_mut(*index).unwrap_or_else(|| { + panic!("In invalid state! Index of archetype with ID {id:?} is out of bounds") + })) + } + + #[cfg(feature = "vizoxide")] + pub fn iter_nodes(&self) -> impl Iterator + { + self.nodes.iter() + } + + pub fn dfs_archetype_add_edges( + &self, + archetype_id: ArchetypeId, + ) -> Option> + { + let node = self.get_node_by_id(archetype_id)?; + + Some(ArchetypeAddEdgeDfsIter { + graph: self, + stack: vec![( + BorrowedOrOwned::Borrowned(node.archetype()), + node.edges + .iter() + .map(|(comp_id, edges)| (*comp_id, edges.clone())) + .collect::>() + .into_iter(), + )], + visited: HashSet::new(), + }) + } + + fn create_missing_edges(&mut self, archetype_id: ArchetypeId) + { + let archetype_node_index = *self + .archetype_index_lookup + .get(&archetype_id) + .expect("Archetype should exist"); + + let (nodes_before, nodes_rest) = self.nodes.split_at_mut(archetype_node_index); + + let ([archetype_node], nodes_after) = nodes_rest.split_at_mut(1) else { + unreachable!(); + }; + + for other_archetype_node in nodes_before.iter_mut().chain(nodes_after.iter_mut()) + { + if archetype_node.archetype().component_cnt() + > other_archetype_node.archetype().component_cnt() + && other_archetype_node + .archetype() + .is_subset(archetype_node.archetype()) + { + Self::create_missing_subset_node_edges( + archetype_node, + other_archetype_node, + ); + + continue; + } + + if other_archetype_node + .archetype() + .is_superset(archetype_node.archetype()) + { + Self::create_missing_superset_node_edges( + archetype_node, + other_archetype_node, + ); + } + } + } + + fn create_missing_subset_node_edges( + target_node: &mut ArchetypeNode, + subset_node: &mut ArchetypeNode, + ) + { + let uniq_comp_id = target_node + .archetype() + .component_ids_sorted() + .find(|id| { + !subset_node + .archetype() + .contains_component_with_exact_id(*id) + }) + .unwrap(); + + subset_node + .get_or_insert_edges(uniq_comp_id, ArchetypeEdges::default) + .add = Some(subset_node.make_add_edge(uniq_comp_id).0); + + if target_node.archetype().component_cnt() + == subset_node.archetype().component_cnt() + 1 + { + target_node + .get_or_insert_edges(uniq_comp_id, ArchetypeEdges::default) + .remove = Some(subset_node.archetype().id()); + } + } + + fn create_missing_superset_node_edges( + target_node: &mut ArchetypeNode, + superset_node: &mut ArchetypeNode, + ) + { + if superset_node.archetype().component_cnt() + > target_node.archetype().component_cnt() + 1 + { + let first_unique_comp_id = superset_node + .archetype() + .component_ids_sorted() + .find(|other_archetype_comp_id| { + !target_node + .archetype() + .contains_component_with_exact_id(*other_archetype_comp_id) + }) + .or_else(|| { + if target_node.archetype().component_cnt() != 0 { + return None; + } + + superset_node.archetype().component_ids_sorted().next() + }) + .expect("Not possible"); + + target_node + .get_or_insert_edges(first_unique_comp_id, ArchetypeEdges::default) + .add = Some(target_node.make_add_edge(first_unique_comp_id).0); + + return; + } + + if superset_node.archetype().component_cnt() + != target_node.archetype().component_cnt() + 1 + { + return; + } + + let extra_comp_id = superset_node + .archetype() + .component_ids_unsorted() + .find(|comp_id| { + !target_node + .archetype() + .contains_component_with_exact_id(*comp_id) + }) + .expect("Archetype should contain one extra component ID"); + + superset_node + .get_or_insert_edges(extra_comp_id, ArchetypeEdges::default) + .remove = Some(target_node.archetype().id()); + + target_node + .get_or_insert_edges(extra_comp_id, ArchetypeEdges::default) + .add = Some(superset_node.archetype().id()); + } +} + +#[derive(Debug)] +pub struct ArchetypeNode +{ + archetype: Archetype, + edges: HashMap, +} + +impl ArchetypeNode +{ + pub fn archetype(&self) -> &Archetype + { + &self.archetype + } + + pub fn archetype_mut(&mut self) -> &mut Archetype + { + &mut self.archetype + } + + pub fn get_or_insert_edges( + &mut self, + component_id: Uid, + insert_fn: impl FnOnce() -> ArchetypeEdges, + ) -> &mut ArchetypeEdges + { + debug_assert!(matches!( + component_id.kind(), + UidKind::Component | UidKind::Pair + )); + + self.edges.entry(component_id).or_insert_with(insert_fn) + } + + #[cfg(feature = "vizoxide")] + pub fn iter_edges(&self) -> impl Iterator + { + self.edges.iter() + } + + pub fn make_add_edge(&self, component_id: Uid) -> (ArchetypeId, Vec) + { + let mut edge_comp_ids = self + .archetype() + .component_ids_unsorted() + .chain([component_id]) + .collect::>(); + + edge_comp_ids.sort(); + + let add_edge_id = ArchetypeId::new(&edge_comp_ids); + + (add_edge_id, edge_comp_ids) + } + + pub fn make_remove_edge(&self, component_id: Uid) -> (ArchetypeId, Vec) + { + let mut edge_comp_ids = self + .archetype() + .component_ids_unsorted() + .filter(|id| *id != component_id) + .collect::>(); + + edge_comp_ids.sort(); + + let remove_edge_id = ArchetypeId::new(&edge_comp_ids); + + (remove_edge_id, edge_comp_ids) + } +} + +#[derive(Debug, Default, Clone)] +pub struct ArchetypeEdges +{ + pub add: Option, + pub remove: Option, +} + +type ArchetypeAddEdgeDfsIterStackElem<'graph> = ( + BorrowedOrOwned<'graph, Archetype>, + VecIntoIter<(Uid, ArchetypeEdges)>, +); + +#[derive(Debug)] +pub struct ArchetypeAddEdgeDfsIter<'graph> +{ + graph: &'graph Graph, + stack: Vec>, + visited: HashSet, +} + +impl<'graph> ArchetypeAddEdgeDfsIter<'graph> +{ + pub fn new(graph: &'graph Graph, start_nodes: &[ArchetypeId]) -> Self + { + Self { + graph, + stack: start_nodes + .iter() + .map(|start_node_id| { + let start_node = graph + .get_node_by_id(*start_node_id) + .expect("Start node does not exist"); + + ( + BorrowedOrOwned::Borrowned(start_node.archetype()), + start_node + .edges + .iter() + .map(|(comp_id, edges)| (*comp_id, edges.clone())) + .collect::>() + .into_iter(), + ) + }) + .collect(), + visited: start_nodes.iter().copied().collect::>(), + } + } + + pub fn push( + &mut self, + item: ( + BorrowedOrOwned<'graph, Archetype>, + VecIntoIter<(Uid, ArchetypeEdges)>, + ), + ) + { + self.stack.push(item); + } + + pub fn pop(&mut self) + { + self.stack.pop(); + } +} + +impl<'graph> StreamingIterator for ArchetypeAddEdgeDfsIter<'graph> +{ + type Item<'a> + = ArchetypeAddEdgeDfsIterResult<'graph, 'a> + where + Self: 'a; + + fn streaming_next(&mut self) -> Option> + { + let (_, edges_iter) = self.stack.last_mut()?; + + let Some((component_id, edges)) = edges_iter.next() else { + self.stack.pop(); + + return Some(ArchetypeAddEdgeDfsIterResult::NoEdgesLeftForArchetype); + }; + + let Some(add_edge) = edges.add else { + return Some(ArchetypeAddEdgeDfsIterResult::NoAddEdge); + }; + + if self.visited.contains(&add_edge) { + return Some(ArchetypeAddEdgeDfsIterResult::AddEdgeAlreadyVisited); + } + + self.visited.insert(add_edge); + + let Some(add_edge_archetype) = self.graph.get_node_by_id(add_edge) else { + return Some(ArchetypeAddEdgeDfsIterResult::AddEdgeArchetypeNotFound { + archetype: &self.stack.last().unwrap().0, + add_edge_archetype_id: add_edge, + add_edge_component_id: component_id, + }); + }; + + self.stack.push(( + BorrowedOrOwned::Borrowned(add_edge_archetype.archetype()), + add_edge_archetype + .edges + .iter() + .map(|(comp_id, edges)| (*comp_id, edges.clone())) + .collect::>() + .into_iter(), + )); + + Some(ArchetypeAddEdgeDfsIterResult::AddEdge { + add_edge_archetype_id: add_edge, + add_edge_component_id: component_id, + }) + } +} + +#[derive(Debug)] +pub enum ArchetypeAddEdgeDfsIterResult<'graph, 'iter> +{ + AddEdge + { + add_edge_archetype_id: ArchetypeId, + add_edge_component_id: Uid, + }, + NoEdgesLeftForArchetype, + NoAddEdge, + AddEdgeAlreadyVisited, + AddEdgeArchetypeNotFound + { + archetype: &'iter BorrowedOrOwned<'graph, Archetype>, + add_edge_archetype_id: ArchetypeId, + add_edge_component_id: Uid, + }, +} diff --git a/engine-ecs/src/entity.rs b/engine-ecs/src/entity.rs new file mode 100644 index 0000000..ad9f179 --- /dev/null +++ b/engine-ecs/src/entity.rs @@ -0,0 +1,295 @@ +use std::any::type_name; +use std::ops::Deref; +use std::sync::LazyLock; + +use crate::component::storage::archetype::{ + Archetype, + Entity as ArchetypeEntity, + MatchingComponentIter as ArchetypeMatchingComponentIter, +}; +use crate::component::{ + Component, + Handle as ComponentHandle, + HandleMut as ComponentHandleMut, +}; +use crate::pair::{ + ComponentOrWildcard, + MultipleWithWildcard as PairMultipleWithWildcard, + Pair, + WithWildcard as PairWithWildcard, +}; +use crate::uid::{Kind as UidKind, Uid}; +use crate::{EntityComponentRef, World}; + +pub mod obtainer; + +/// A handle to a entity. +#[derive(Debug, Clone)] +pub struct Handle<'a> +{ + archetype: &'a Archetype, + entity: &'a ArchetypeEntity, + world: &'a World, +} + +impl<'a> Handle<'a> +{ + /// Returns the [`Uid`] of this entity. + #[inline] + #[must_use] + pub fn uid(&self) -> Uid + { + self.entity.uid() + } + + /// Returns a reference to the specified component in this entity. `None` is + /// returned if the component isn't found in the entity. + /// + /// # Panics + /// Will panic if: + /// - The component's ID is not a component ID + /// - The component is mutably borrowed elsewhere + #[must_use] + pub fn get(&self) -> Option> + { + assert_eq!(ComponentT::id().kind(), UidKind::Component); + + let component = self.get_matching_components(ComponentT::id()).next()?; + + Some( + ComponentHandle::from_entity_component_ref(&component).unwrap_or_else( + |err| { + panic!( + "Creating handle to component {} failed: {err}", + type_name::() + ); + }, + ), + ) + } + + /// Returns a mutable reference to the specified component in this entity. `None` is + /// returned if the component isn't found in the entity. + /// + /// # Panics + /// Will panic if: + /// - The component's ID is not a component ID + /// - The component is borrowed elsewhere + #[must_use] + pub fn get_mut( + &self, + ) -> Option> + { + assert_eq!(ComponentT::id().kind(), UidKind::Component); + + let component = self.get_matching_components(ComponentT::id()).next()?; + + Some( + ComponentHandleMut::from_entity_component_ref(&component, self.world) + .unwrap_or_else(|err| { + panic!( + "Creating handle to component {} failed: {err}", + type_name::() + ); + }), + ) + } + + /// 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( + &self, + id: Uid, + ) -> Option> + { + 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::() + ); + }, + ), + ) + } + + /// 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( + &self, + id: Uid, + ) -> Option> + { + 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::() + ); + }), + ) + } + + #[must_use] + pub fn get_first_wildcard_pair_match( + &self, + ) -> Option> + where + Relation: ComponentOrWildcard, + Target: ComponentOrWildcard, + { + let mut matching_comps = self.get_matching_components( + Pair::builder() + .relation_id(Relation::uid()) + .target_id(Target::uid()) + .build() + .id(), + ); + + Some(PairWithWildcard::new(self.world, matching_comps.next()?)) + } + + #[must_use] + pub fn get_wildcard_pair_matches( + &self, + ) -> PairMultipleWithWildcard<'a, Relation, Target> + where + Relation: ComponentOrWildcard, + Target: ComponentOrWildcard, + { + PairMultipleWithWildcard::new(self.world, self.clone()) + } + + #[inline] + #[must_use] + pub fn get_matching_components(&self, component_uid: Uid) + -> MatchingComponentIter<'a> + { + MatchingComponentIter { + inner: self.archetype.get_matching_component_indices(component_uid), + entity: self.entity, + } + } + + /// 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) + } + + /// Returns the `Uids`s of the components this entity has. + pub fn component_ids(&self) -> impl Iterator + '_ + { + self.archetype.component_ids_sorted() + } + + pub(crate) fn new( + archetype: &'a Archetype, + entity: &'a ArchetypeEntity, + world: &'a World, + ) -> Self + { + Self { archetype, entity, world } + } +} + +#[derive(Debug)] +pub struct MatchingComponentIter<'a> +{ + inner: ArchetypeMatchingComponentIter<'a>, + entity: &'a ArchetypeEntity, +} + +impl<'a> Iterator for MatchingComponentIter<'a> +{ + type Item = EntityComponentRef<'a>; + + fn next(&mut self) -> Option + { + let (matching_component_id, index) = self.inner.next()?; + + 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, + 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! declare_entity { + ($visibility: vis $ident: ident, $components: expr) => { + $visibility static $ident: $crate::entity::Declaration = + $crate::entity::Declaration::new(|world| { + world.create_entity_with_uid(*$ident, $components); + }); + } +} diff --git a/engine-ecs/src/entity/obtainer.rs b/engine-ecs/src/entity/obtainer.rs new file mode 100644 index 0000000..6c2ea96 --- /dev/null +++ b/engine-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> + { + self.world.get_entity(entity_id) + } +} diff --git a/engine-ecs/src/error.rs b/engine-ecs/src/error.rs new file mode 100644 index 0000000..185b706 --- /dev/null +++ b/engine-ecs/src/error.rs @@ -0,0 +1,270 @@ +use std::fmt::{Debug, Display, Write as _}; + +use backtrace::Backtrace; + +#[macro_export] +macro_rules! error { + ($lit: literal) => { + $crate::error::Error::from($lit) + }; + + ($lit: literal, $($tt: tt)*) => { + $crate::error::Error::from(std::format!($lit, $($tt)*)) + }; + + ($err: expr) => { + $crate::error::Error::from($err) + }; +} + +pub struct Error +{ + inner: Box, + backtrace: Backtrace, +} + +impl Error +{ + pub fn resolve_backtrace(&mut self) + { + self.backtrace.resolve(); + } + + fn is_backtrace_resolved(&self) -> bool + { + let Some(first_frame) = self.backtrace.frames().first() else { + return false; + }; + + !first_frame.symbols().is_empty() + } +} + +impl Debug for Error +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + let error = &*self.inner; + + write!(formatter, "{error}")?; + + if let Some(cause) = error.source() { + write!(formatter, "\n\nCaused by:")?; + let multiple = cause.source().is_some(); + for (n, error) in anyhow::Chain::new(cause).enumerate() { + writeln!(formatter)?; + + let mut indented = Indented { + inner: formatter, + number: if multiple { Some(n) } else { None }, + started: false, + }; + write!(indented, "{error}")?; + } + } + + if std::env::var_os("ENGINE_ECS_BACKTRACE") + .is_none_or(|backtrace_enabled| backtrace_enabled != "1") + { + write!( + formatter, + concat!( + "\n\nnote: run with `ENGINE_ECS_BACKTRACE=1` environment variable ", + "to display a engine backtrace" + ) + )?; + + return Ok(()); + } + + let mut cloned_backtrace; + + let backtrace = if self.is_backtrace_resolved() { + &self.backtrace + } else { + cloned_backtrace = self.backtrace.clone(); + cloned_backtrace.resolve(); + &cloned_backtrace + }; + + write!( + formatter, + "\n\nStack backtrace:\n{:?}", + std::fmt::from_fn(|backtrace_formatter| fmt_backtrace( + backtrace, + backtrace_formatter + )) + )?; + + Ok(()) + } +} + +impl Display for Error +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + let error = &*self.inner; + + write!(formatter, "{error}")?; + + if formatter.alternate() { + let chain = anyhow::Chain::new(error); + for cause in chain.skip(1) { + write!(formatter, ": {}", cause)?; + } + } + + Ok(()) + } +} + +impl From for Error +where + Box: From, +{ + fn from(err: Err) -> Self + { + Self { + inner: err.into(), + backtrace: Backtrace::new_unresolved(), + } + } +} + +pub type ErrorHandler = fn(Error, Metadata); + +/// Error metadata. +#[derive(Debug)] +pub struct Metadata +{ + pub source_name: &'static str, + pub source_kind: SourceKind, +} + +/// Error source kind. +#[derive(Debug)] +#[non_exhaustive] +pub enum SourceKind +{ + System, + Observer, +} + +impl Display for SourceKind +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + match self { + SourceKind::System => formatter.write_str("system"), + SourceKind::Observer => formatter.write_str("observer"), + } + } +} + +pub fn err_handler_panic(mut err: Error, err_metadata: Metadata) +{ + err.resolve_backtrace(); + + panic!( + "Error occurred in {} '{}': {err:?}", + err_metadata.source_kind, err_metadata.source_name + ); +} + +pub fn err_handler_log_error(err: Error, err_metadata: Metadata) +{ + tracing::error!( + "Error occurred in {} '{}': {err:#}", + err_metadata.source_kind, + err_metadata.source_name + ); +} + +fn fmt_backtrace( + backtrace: &Backtrace, + fmt: &mut std::fmt::Formatter<'_>, +) -> std::fmt::Result +{ + let style = if fmt.alternate() { + backtrace::PrintFmt::Full + } else { + backtrace::PrintFmt::Short + }; + + // When printing paths we try to strip the cwd if it exists, otherwise + // we just print the path as-is. Note that we also only do this for the + // short format, because if it's full we presumably want to print + // everything. + let cwd = std::env::current_dir(); + let mut print_path = + move |fmt: &mut std::fmt::Formatter<'_>, + path: backtrace::BytesOrWideString<'_>| { + let path = path.into_path_buf(); + if style != backtrace::PrintFmt::Full { + if let Ok(cwd) = &cwd { + if let Ok(suffix) = path.strip_prefix(cwd) { + return std::fmt::Display::fmt(&suffix.display(), fmt); + } + } + } + std::fmt::Display::fmt(&path.display(), fmt) + }; + + let mut f = backtrace::BacktraceFmt::new(fmt, style, &mut print_path); + + f.add_context()?; + + for frame in backtrace.frames() { + if frame.symbols().iter().all(|symbol| { + symbol.name().is_some_and(|symbol_name| { + let symbol_name = symbol_name.to_string(); + + symbol_name + .contains(">::from") + }) + }) { + continue; + } + + f.frame().backtrace_frame(frame)?; + } + f.finish()?; + Ok(()) +} + +struct Indented<'a, D> +{ + inner: &'a mut D, + number: Option, + started: bool, +} + +impl std::fmt::Write for Indented<'_, T> +where + T: std::fmt::Write, +{ + fn write_str(&mut self, s: &str) -> std::fmt::Result + { + for (i, line) in s.split('\n').enumerate() { + if !self.started { + self.started = true; + match self.number { + Some(number) => write!(self.inner, "{: >5}: ", number)?, + None => self.inner.write_str(" ")?, + } + } else if i > 0 { + self.inner.write_char('\n')?; + if self.number.is_some() { + self.inner.write_str(" ")?; + } else { + self.inner.write_str(" ")?; + } + } + + self.inner.write_str(line)?; + } + + Ok(()) + } +} diff --git a/engine-ecs/src/event.rs b/engine-ecs/src/event.rs new file mode 100644 index 0000000..15455b6 --- /dev/null +++ b/engine-ecs/src/event.rs @@ -0,0 +1,105 @@ +use crate::lock::Lock; +use crate::pair::Pair; +use crate::uid::{Kind as UidKind, Uid}; +use crate::util::VecExt; + +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, +} + +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, 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(new_events: &'world Lock) -> Self + { + Self { new_events } + } +} + +#[derive(Debug, Default)] +pub(crate) struct NewEvents +{ + events: Vec<(Uid, Matches)>, +} + +impl NewEvents +{ + pub fn push_event_match(&mut self, event: &Pair, 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_part_pt_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) + } + + pub fn is_empty(&self) -> bool + { + self.events.is_empty() + } +} + +#[derive(Debug)] +pub(crate) struct Matches +{ + pub match_ids: Vec, +} + +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_part_pt_by_key(match_id, |other_match_id| other_match_id); + } +} diff --git a/engine-ecs/src/event/component.rs b/engine-ecs/src/event/component.rs new file mode 100644 index 0000000..70ea3e5 --- /dev/null +++ b/engine-ecs/src/event/component.rs @@ -0,0 +1,103 @@ +//! Component events. + +use std::convert::Infallible; + +use crate::Component; +use crate::component::{Handle as ComponentHandle, HandleMut as ComponentHandleMut}; +use crate::entity::Handle as EntityHandle; +use crate::pair::Pair; +use crate::system::observer::{EventMatch, Observed}; + +/// Implemented by the relations of component event pairs +pub trait EventRelation: 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); + +impl EventRelation for Added {} + +/// Pair relation for events emitted **before**: +/// 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); + +impl EventRelation for Removed {} + +#[derive(Debug, Component)] +pub struct Changed(Infallible); + +impl EventRelation for Changed {} + +/// [`EventMatch`] extension trait for component event matches. +pub trait EventMatchExt: sealed::Sealed +{ + #[must_use] + fn get_entity(&self) -> EntityHandle<'_>; + + #[must_use] + fn get_ent_target_comp(&self) -> ComponentHandle<'_, Target> + where + Target: Component; + + #[must_use] + fn get_ent_target_comp_mut(&self) -> ComponentHandleMut<'_, Target> + where + Target: Component; +} + +impl EventMatchExt + for EventMatch<'_, Pair> +where + Pair: Observed, +{ + fn get_entity(&self) -> EntityHandle<'_> + { + let Some(ent) = self.try_get_entity() else { + unreachable!(); + }; + + ent + } + + fn get_ent_target_comp(&self) -> ComponentHandle<'_, Target> + where + Target: Component, + { + let ent = self.get_entity(); + + let Some(comp) = ent.get::() else { + unreachable!(); + }; + + comp + } + + fn get_ent_target_comp_mut(&self) -> ComponentHandleMut<'_, Target> + where + Target: Component, + { + let ent = self.get_entity(); + + let Some(comp) = ent.get_mut::() else { + unreachable!(); + }; + + comp + } +} + +impl sealed::Sealed + for EventMatch<'_, Pair> +where + Pair: Observed, +{ +} + +mod sealed +{ + pub trait Sealed {} +} diff --git a/engine-ecs/src/extension.rs b/engine-ecs/src/extension.rs new file mode 100644 index 0000000..9c6614b --- /dev/null +++ b/engine-ecs/src/extension.rs @@ -0,0 +1,72 @@ +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}; + +/// A collection of systems, entities & soles that can be added to a [`World`]. +pub trait Extension +{ + fn collect(self, collector: Collector<'_>); +} + +/// Passed to a [`Extension`] to collects it's systems, entities & soles. +pub struct Collector<'world> +{ + world: &'world mut World, +} + +impl<'world> Collector<'world> +{ + /// Returns a new `Collector` for the given [`World`]. + pub fn new(world: &'world mut World) -> Self + { + Self { world } + } + + /// Adds a system to the [`World`]. + pub fn add_system<'this, SystemImpl>( + &'this mut self, + phase_euid: Uid, + system: impl System<'this, SystemImpl>, + ) + { + 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(&mut self, components: Comps) + where + Comps: ComponentSequence, + { + 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 + /// Returns `Err` if this [`Sole`] has already been added. + pub fn add_sole(&mut self, sole: SoleT) -> Result<(), SoleAlreadyExistsError> + where + SoleT: Sole, + { + self.world.add_sole(sole) + } +} diff --git a/engine-ecs/src/lib.rs b/engine-ecs/src/lib.rs new file mode 100644 index 0000000..6450587 --- /dev/null +++ b/engine-ecs/src/lib.rs @@ -0,0 +1,773 @@ +#![deny(clippy::all, clippy::pedantic)] + +use std::any::{Any, TypeId, type_name}; +use std::fmt::Debug; +use std::hint::cold_path; +use std::mem::ManuallyDrop; +use std::rc::Rc; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; + +use hashbrown::HashMap; + +use crate::actions::Action; +use crate::component::storage::archetype::EntityComponent as ArchetypeEntityComponent; +use crate::component::storage::{EntityAlreadyExistsError, Storage as ComponentStorage}; +use crate::component::{ + Component, + IntoParts as IntoComponentParts, + Parts as ComponentParts, + Sequence as ComponentSequence, +}; +use crate::entity::{Declaration as EntityDeclaration, Handle as EntityHandle}; +use crate::error::{ + ErrorHandler, + Metadata as ErrorMetadata, + SourceKind as ErrorSourceKind, + err_handler_panic, +}; +use crate::event::component::Added; +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, Pair, Wildcard}; +use crate::phase::{ + HasSystem as PhaseHasSystem, + POST_UPDATE as POST_UPDATE_PHASE, + PRE_UPDATE as PRE_UPDATE_PHASE, + Phase, + START as START_PHASE, + UPDATE as UPDATE_PHASE, +}; +use crate::query::flexible::Query as FlexibleQuery; +use crate::query::{ + MAX_TERM_CNT as QUERY_MAX_TERM_CNT, + TermWithFieldTuple as QueryTermWithFieldTuple, + TermWithoutFieldTuple as QueryTermWithoutFieldTuple, + Terms as QueryTerms, + TermsBuilderInterface, +}; +use crate::sole::{Single, Sole}; +use crate::stats::Stats; +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; +pub mod entity; +pub mod error; +pub mod event; +pub mod extension; +pub mod pair; +pub mod phase; +pub mod query; +pub mod sole; +pub mod stats; +pub mod system; +pub mod tuple; +pub mod uid; +pub mod util; + +mod lock; + +pub use engine_ecs_macros::{Component, Sole}; + +pub use crate::query::Query; + +#[derive(Debug)] +pub struct World +{ + data: WorldData, + stop: AtomicBool, + is_first_tick: AtomicBool, + error_handler: ErrorHandler, +} + +impl World +{ + #[must_use] + pub fn new() -> Self + { + let mut world = Self { + data: WorldData::default(), + stop: AtomicBool::new(false), + is_first_tick: AtomicBool::new(false), + error_handler: err_handler_panic, + }; + + crate::phase::spawn_entities(&mut world); + + world.add_sole(Stats::default()).ok(); + + world + } + + pub fn set_err_handler(&mut self, err_handler: ErrorHandler) + { + self.error_handler = err_handler; + } + + /// Creates a entity with the given components. A new unique [`Uid`] will be generated + /// for this entity. + pub fn create_entity(&mut self, components: Comps) -> Uid + where + Comps: ComponentSequence, + { + let entity_uid = Uid::new_unique(UidKind::Entity); + + 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)] + pub fn create_entity_with_uid(&mut self, entity_uid: Uid, components: Comps) + where + Comps: ComponentSequence, + { + self.create_ent(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, + &EventSubmitter::new(&self.data.new_events), + ); + } + + pub fn create_declared_entity(&mut self, entity_decl: &EntityDeclaration) + { + entity_decl.create(self); + } + + /// Adds a globally shared singleton value. + /// + /// # Errors + /// Returns `Err` if this [`Sole`] has already been added. + pub fn add_sole(&mut self, sole: SoleT) -> Result<(), SoleAlreadyExistsError> + where + SoleT: Sole, + { + self.data.sole_storage.insert(sole) + } + + pub fn register_observer<'this, SystemImpl, ObserverT>( + &'this mut self, + observer: ObserverT, + ) where + ObserverT: Observer<'this, SystemImpl>, + { + 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_system<'this, SystemImpl>( + &'this mut self, + phase_euid: Uid, + system: impl System<'this, SystemImpl>, + ) + { + let (type_erased_system, mut system_callbacks) = system.finish(); + + let system_ent_id = + self.create_entity((SystemComponent { system: type_erased_system },)); + + system_callbacks.on_created(self, SystemMetadata { ent_id: system_ent_id }); + + self.create_entity_with_uid( + phase_euid, + (Pair::builder() + .relation::() + .target_id(system_ent_id) + .build(),), + ); + } + + /// Adds a extensions. + pub fn add_extension(&mut self, extension: impl Extension) + { + let extension_collector = ExtensionCollector::new(self); + + extension.collect(extension_collector); + } + + pub fn query( + &self, + ) -> Query<'_, FieldTerms, FieldlessTerms> + where + FieldTerms: QueryTermWithFieldTuple, + FieldlessTerms: QueryTermWithoutFieldTuple, + { + Query::new(self) + } + + pub fn flexible_query( + &self, + terms: QueryTerms, + ) -> FlexibleQuery<'_, MAX_TERM_CNT> + { + FlexibleQuery::new(self, terms) + } + + pub fn get_entity(&self, entity_id: Uid) -> Option> + { + 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(&self) -> Option> + { + Some(Single::new(self.data.sole_storage.get::()?)) + } + + pub fn event_submitter(&self) -> EventSubmitter<'_> + { + EventSubmitter::new(&self.data.new_events) + } + + /// Performs a single tick. + /// # Panics + /// Will panic if mutable internal lock cannot be acquired. + pub fn step(&mut self) -> StepResult + { + if self.stop.load(Ordering::Relaxed) { + return StepResult::Stop; + } + + if self + .is_first_tick + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + let Some(start_phase_entity) = self.get_entity(*START_PHASE) else { + unreachable!(); + }; + + self.run_phase_systems(&start_phase_entity); + } + + self.perform_phases(); + + self.emit_new_events(); + + self.data.component_storage.create_imaginary_archetypes(); + + self.perform_queued_actions(); + + if self.stop.load(Ordering::Relaxed) { + return StepResult::Stop; + } + + let Some(mut stats) = self.get_sole::() else { + unreachable!(); // Reason: is added in World::new + }; + + stats.current_tick += 1; + + StepResult::Continue + } + + /// Starts a loop which calls [`Self::step`] until the world is stopped. + pub fn start_loop(&mut self) + { + while let StepResult::Continue = self.step() {} + } + + #[cfg(feature = "vizoxide")] + pub fn create_vizoxide_archetype_graph( + &self, + name: impl AsRef, + ) -> Result + { + use std::borrow::Cow; + + use crate::component::storage::{ + VizoxideArchetypeGraphEdgeKind, + VizoxideArchetypeGraphParams, + }; + + self.data.component_storage.create_vizoxide_archetype_graph( + name, + VizoxideArchetypeGraphParams { + create_node_name: |archetype, _| { + Cow::Owned(format!( + "[{}]", + archetype + .component_ids_sorted() + .into_iter() + .map(|comp_id| comp_id.to_string()) + .collect::>() + .join(", ") + )) + }, + create_node_cb: |_archetype, archetype_metadata, node_builder| { + if archetype_metadata.is_imaginary { + return node_builder.attribute("shape", "ellipse"); + } + + node_builder.attribute("shape", "box") + }, + create_edge_cb: |_, _, edge_kind, edge_builder| { + edge_builder.attribute( + "color", + match edge_kind { + VizoxideArchetypeGraphEdgeKind::Add => "green", + VizoxideArchetypeGraphEdgeKind::Remove => "red", + }, + ) + }, + }, + ) + } + + #[tracing::instrument(skip_all)] + fn create_ent( + &mut self, + entity_uid: Uid, + components: impl IntoIterator, + ) + { + debug_assert_eq!(entity_uid.kind(), UidKind::Entity); + + if let Err(EntityAlreadyExistsError) = + self.data.component_storage.create_entity(entity_uid) + { + // This is fine + } + + Self::add_entity_components( + entity_uid, + components, + &mut self.data.component_storage, + &EventSubmitter::new(&self.data.new_events), + ); + } + + fn run_phase_systems(&self, phase_entity: &EntityHandle<'_>) + { + // The phase's systems are retrieved this way so that the order they are + // run is the same order as they were registered, even if they have local + // components. + for system_entity in phase_entity + .get_wildcard_pair_matches::() + .into_iter() + .filter_map(|phase_has_system| phase_has_system.get_target_ent()) + { + let Some(system) = system_entity.get::() else { + cold_path(); + continue; + }; + + // SAFETY: The world lives long enough + if let Err(err) = unsafe { + system + .system + .run(self, SystemMetadata { ent_id: system_entity.uid() }) + } { + cold_path(); + + (self.error_handler)( + err, + ErrorMetadata { + source_name: system.system.name(), + source_kind: ErrorSourceKind::System, + }, + ) + } + } + } + + fn perform_child_phases(&self, parent_phase_euid: Uid) + { + let phase_query = self.flexible_query( + QueryTerms::<2>::builder() + .with_required([ + Phase::id(), + Pair::builder() + .relation::() + .target_id(parent_phase_euid) + .build() + .id(), + ]) + .build(), + ); + + for child_phase_entity in &phase_query { + self.run_phase_systems(&child_phase_entity); + self.perform_child_phases(child_phase_entity.uid()); + } + } + + fn perform_single_phase(&self, phase_entity_id: Uid) + { + let Some(phase_entity) = self.get_entity(phase_entity_id) else { + unreachable!(); + }; + + self.run_phase_systems(&phase_entity); + self.perform_child_phases(phase_entity_id); + } + + fn perform_phases(&self) + { + self.perform_single_phase(*PRE_UPDATE_PHASE); + self.perform_single_phase(*UPDATE_PHASE); + self.perform_single_phase(*POST_UPDATE_PHASE); + } + + fn emit_new_events(&self) + { + loop { + let new_events = { + let mut new_events_lock = self + .data + .new_events + .write_nonblock() + .expect("Failed to acquire read-write lock to new events"); + + if new_events_lock.is_empty() { + break; + } + + new_events_lock.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, + }, + ); + } + } + } + + #[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 action_queue_lock.drain(..) { + match action { + Action::Spawn(new_entity_uid, components) => { + if let Err(err) = + self.data.component_storage.create_entity(new_entity_uid) + { + tracing::warn!("Failed to create entity: {err}"); + continue; + } + + Self::add_entity_components( + new_entity_uid, + components, + &mut self.data.component_storage, + &EventSubmitter::new(&self.data.new_events), + ); + } + Action::Despawn(entity_uid) => { + if let Err(err) = + self.data.component_storage.remove_entity(entity_uid) + { + tracing::error!("Failed to despawn entity: {err}"); + } + } + Action::AddComponents(entity_uid, components) => { + Self::add_entity_components( + entity_uid, + components, + &mut self.data.component_storage, + &EventSubmitter::new(&self.data.new_events), + ); + } + Action::RemoveComponents(entity_uid, component_ids) => { + Self::remove_entity_components( + entity_uid, + component_ids, + &mut self.data.component_storage, + ); + } + Action::Stop => { + self.stop.store(true, Ordering::Relaxed); + } + } + } + } + + fn add_entity_components( + entity_uid: Uid, + components: impl IntoIterator, + component_storage: &mut ComponentStorage, + event_submitter: &EventSubmitter<'_>, + ) + { + let component_iter = components.into_iter(); + + for component_parts in component_iter { + let comp_id = component_parts.id(); + + let comp_name = component_parts.name(); + + if let Err(err) = component_storage.add_entity_component( + entity_uid, + (comp_id, comp_name, component_parts.into_data()), + ) { + tracing::error!("Failed to add component {comp_name} to entity: {err}"); + continue; + } + + if comp_id.kind() == UidKind::Pair { + continue; + } + + event_submitter.submit_event( + &Pair::builder() + .relation::() + .target_id(comp_id) + .build(), + entity_uid, + ); + } + } + + fn remove_entity_components( + entity_uid: Uid, + component_ids: impl IntoIterator, + component_storage: &mut ComponentStorage, + ) + { + let component_id_iter = component_ids.into_iter(); + + for component_id in component_id_iter { + if let Err(err) = + component_storage.remove_entity_component(entity_uid, component_id) + { + tracing::error!("Failed to remove component to entity: {err}"); + } + } + } + + fn emit_event_observers(&self, event_id: Uid, emitted_event: &EmittedEvent<'_>) + { + assert_eq!(event_id.kind(), UidKind::Pair); + + let query = Query::<(&ObserverWrapperComponent,)>::from_flexible_query( + self.flexible_query( + QueryTerms::::builder() + .with_required([ObserverWrapperComponent::id(), event_id]) + .build(), + ), + ); + + for (observer_ent_id, (observer,)) in query.iter_with_euids() { + if let Err(err) = unsafe { + observer.run( + self, + SystemMetadata { ent_id: observer_ent_id }, + emitted_event.clone(), + ) + } { + cold_path(); + + (self.error_handler)( + err, + ErrorMetadata { + source_name: observer.name(), + source_kind: ErrorSourceKind::Observer, + }, + ) + } + } + } +} + +impl Default for World +{ + fn default() -> Self + { + Self::new() + } +} + +/// The result of calling [`World::step`]. +pub enum StepResult +{ + /// Another step can be made. + Continue, + + /// The world have been stopped so no step can be made again. + Stop, +} + +#[derive(Debug, Default)] +struct WorldData +{ + component_storage: ComponentStorage, + sole_storage: SoleStorage, + action_queue: Rc, + new_events: Lock, +} + +#[derive(Debug, Clone)] +pub struct EntityComponentRef<'a> +{ + component_id: Uid, + component: &'a ArchetypeEntityComponent, + entity_id: Uid, +} + +impl<'a> EntityComponentRef<'a> +{ + fn component(&self) -> &'a Lock> + { + self.component.component() + } + + #[must_use] + pub fn id(&self) -> Uid + { + self.component_id + } + + #[must_use] + pub fn entity_id(&self) -> Uid + { + self.entity_id + } + + fn new(component_id: Uid, comp: &'a ArchetypeEntityComponent, entity_id: Uid) + -> Self + { + Self { + component_id, + component: comp, + entity_id, + } + } +} + +#[derive(Debug, Default)] +struct ActionQueue +{ + queue: Lock>, +} + +impl ActionQueue +{ + fn push(&self, action: Action) + { + self.queue + .write_nonblock() + .expect("Failed to aquire read-write lock to action queue") + .push(action); + } +} + +#[derive(Debug, thiserror::Error)] +#[error("Sole {0} already exists")] +pub struct SoleAlreadyExistsError(pub &'static str); + +#[derive(Debug)] +struct StoredSole +{ + sole: Arc>>, + drop_last: bool, +} + +#[derive(Debug, Default)] +struct SoleStorage +{ + storage: HashMap>, +} + +impl SoleStorage +{ + fn get(&self) -> Option<&Arc>>> + { + self.storage + .get(&TypeId::of::()) + .map(|sole| &sole.sole) + } + + fn insert(&mut self, sole: SoleT) -> Result<(), SoleAlreadyExistsError> + { + let sole_type_id = TypeId::of::(); + + if self.storage.contains_key(&sole_type_id) { + return Err(SoleAlreadyExistsError(type_name::())); + } + + let drop_last = sole.drop_last(); + + // TODO: Reconsider this maybe? + #[allow(clippy::arc_with_non_send_sync)] + self.storage.insert( + sole_type_id, + ManuallyDrop::new(StoredSole { + sole: Arc::new(Lock::new(Box::new(sole), type_name::())), + drop_last, + }), + ); + + Ok(()) + } +} + +impl Drop for SoleStorage +{ + fn drop(&mut self) + { + let mut soles_to_drop_last = Vec::new(); + + for sole in self.storage.values_mut() { + if sole.drop_last { + soles_to_drop_last.push(sole); + continue; + } + + unsafe { + ManuallyDrop::drop(sole); + } + } + + for sole in &mut soles_to_drop_last { + unsafe { + ManuallyDrop::drop(sole); + } + } + } +} diff --git a/engine-ecs/src/lock.rs b/engine-ecs/src/lock.rs new file mode 100644 index 0000000..fe4e08b --- /dev/null +++ b/engine-ecs/src/lock.rs @@ -0,0 +1,259 @@ +use std::any::type_name; +use std::mem::forget; +use std::ops::{Deref, DerefMut}; + +use parking_lot::{ + MappedRwLockReadGuard, + MappedRwLockWriteGuard, + RwLock, + RwLockReadGuard, + RwLockWriteGuard, +}; + +#[derive(Debug)] +pub struct Lock +{ + inner: RwLock, + value_type_name: &'static str, +} + +impl Lock +{ + pub fn new(value: Value, value_type_name: &'static str) -> Self + { + Self { + inner: RwLock::new(value), + value_type_name, + } + } + + /// Tries to a acquire a handle to the resource with read access. + /// + /// # Errors + /// Returns `Err` if unavailable (A mutable handle is hold). + pub fn read_nonblock(&self) -> Result, Error> + { + let guard = self.inner.try_read().ok_or(Error::ReadUnavailable)?; + + tracing::trace!("Acquired lock to value of type {}", self.value_type_name); + + Ok(ReadGuard { + inner: guard, + value_type_name: self.value_type_name, + }) + } + + /// Tries to a acquire a handle to the resource with mutable access. + /// + /// # Errors + /// Returns `Err` if unavailable (A mutable or immutable handle is hold). + pub fn write_nonblock(&self) -> Result, Error> + { + let guard = self.inner.try_write().ok_or(Error::WriteUnavailable)?; + + tracing::trace!( + "Acquired mutable lock to value of type {}", + self.value_type_name + ); + + Ok(WriteGuard { + inner: guard, + value_type_name: self.value_type_name, + }) + } +} + +impl Default for Lock +{ + fn default() -> Self + { + Self::new(Value::default(), type_name::()) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Lock is unavailable for reading")] + ReadUnavailable, + + #[error("Lock is unavailable for writing")] + WriteUnavailable, +} + +#[derive(Debug)] +pub struct ReadGuard<'guard, Value> +{ + inner: RwLockReadGuard<'guard, Value>, + value_type_name: &'static str, +} + +impl<'guard, Value> ReadGuard<'guard, Value> +{ + pub fn try_map( + this: Self, + func: impl FnOnce(&Value) -> Option<&NewValue>, + ) -> Result, Self> + { + 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(&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, + }), + } + } +} + +impl Deref for ReadGuard<'_, Value> +{ + type Target = Value; + + fn deref(&self) -> &Self::Target + { + &self.inner + } +} + +impl Drop for ReadGuard<'_, Value> +{ + fn drop(&mut self) + { + tracing::trace!("Dropped lock to value of type {}", self.value_type_name); + } +} + +#[derive(Debug)] +pub struct MappedReadGuard<'guard, Value> +{ + inner: MappedRwLockReadGuard<'guard, Value>, + value_type_name: &'static str, +} + +impl Deref for MappedReadGuard<'_, Value> +{ + type Target = Value; + + fn deref(&self) -> &Self::Target + { + &self.inner + } +} + +impl Drop for MappedReadGuard<'_, Value> +{ + fn drop(&mut self) + { + tracing::trace!( + "Dropped mapped lock to value of type {}", + self.value_type_name + ); + } +} + +#[derive(Debug)] +pub struct WriteGuard<'guard, Value> +{ + inner: RwLockWriteGuard<'guard, Value>, + value_type_name: &'static str, +} + +impl<'guard, Value> WriteGuard<'guard, Value> +{ + pub fn try_map( + this: Self, + func: impl FnOnce(&mut Value) -> Option<&mut NewValue>, + ) -> Result, Self> + { + 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(&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, + }), + } + } +} + +impl Deref for WriteGuard<'_, Value> +{ + type Target = Value; + + fn deref(&self) -> &Self::Target + { + &self.inner + } +} + +impl DerefMut for WriteGuard<'_, Value> +{ + fn deref_mut(&mut self) -> &mut Self::Target + { + &mut self.inner + } +} + +impl Drop for WriteGuard<'_, Value> +{ + fn drop(&mut self) + { + tracing::trace!( + "Dropped mutable lock to value of type {}", + self.value_type_name + ); + } +} + +#[derive(Debug)] +pub struct MappedWriteGuard<'guard, Value> +{ + inner: MappedRwLockWriteGuard<'guard, Value>, + value_type_name: &'static str, +} + +impl Deref for MappedWriteGuard<'_, Value> +{ + type Target = Value; + + fn deref(&self) -> &Self::Target + { + &self.inner + } +} + +impl DerefMut for MappedWriteGuard<'_, Value> +{ + fn deref_mut(&mut self) -> &mut Self::Target + { + &mut self.inner + } +} + +impl Drop for MappedWriteGuard<'_, Value> +{ + fn drop(&mut self) + { + tracing::trace!( + "Dropped mapped mutable lock to value of type {}", + self.value_type_name + ); + } +} diff --git a/engine-ecs/src/pair.rs b/engine-ecs/src/pair.rs new file mode 100644 index 0000000..0d353e3 --- /dev/null +++ b/engine-ecs/src/pair.rs @@ -0,0 +1,687 @@ +use std::any::type_name; +use std::convert::Infallible; +use std::marker::PhantomData; + +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, +}; +use crate::query::{ + TermWithField as QueryTermWithField, + TermsBuilder as QueryTermsBuilder, + TermsBuilderInterface, +}; +use crate::uid::{Kind as UidKind, PairParams as UidPairParams, Uid, With as WithUid}; +use crate::util::impl_multiple; +use crate::{Component, EntityComponentRef, World}; + +/// Pair builder. +#[derive(Debug)] +pub struct Builder +{ + relation: Relation, + target: Target, +} + +impl Builder +{ + pub fn relation(self) -> Builder + { + Builder { + relation: NewRelation::id(), + target: self.target, + } + } + + pub fn relation_id(self, id: Uid) -> Builder + { + Builder { relation: id, target: self.target } + } + + pub fn target(self) -> Builder + { + Builder { + relation: self.relation, + target: NewTarget::id(), + } + } + + pub fn target_id(self, id: Uid) -> Builder + { + Builder { relation: self.relation, target: id } + } +} + +impl_multiple!( + Builder, + (impl _<>, impl _<><(), Target>) + cb=(type_params=(ty_param_1, ty_param_2)) => { + pub fn target_as_data( + self, + data: NewTarget, + ) -> Builder<$ty_param_1, NewTarget> + { + Builder { + relation: self.relation, + target: data, + } + } + } +); + +impl_multiple!( + Builder, + (impl _<>, impl _<>) + cb=(type_params=(ty_param_1, ty_param_2)) => { + pub fn relation_as_data( + self, + data: NewRelation, + ) -> Builder + { + Builder { + relation: data, + target: self.target, + } + } + } +); + +impl_multiple!( + Builder, + ( + impl _<>, + impl _<>, + impl _<>, + impl _<> + ) + cb=(type_params=(ty_param_1, ty_param_2)) => { + #[must_use] + pub fn build(self) -> Pair<$ty_param_1, $ty_param_2> + { + Pair { + relation: self.relation, + target: self.target + } + } + } +); + +impl Default for Builder<(), ()> +{ + fn default() -> Self + { + Self { relation: (), target: () } + } +} + +#[derive(Debug)] +pub struct Pair +{ + relation: Relation, + target: Target, +} + +impl Pair<(), ()> +{ + #[must_use] + pub fn builder() -> Builder<(), ()> + { + Builder { relation: (), target: () } + } +} + +impl Pair +{ + #[must_use] + pub fn id(&self) -> Uid + { + Uid::new_pair(&UidPairParams { + relation: self.relation, + target: self.target, + }) + } +} + +impl IntoComponentParts for Pair +{ + fn into_parts(self) -> ComponentParts + { + ComponentParts::builder().name("Pair").build(self.id(), ()) + } +} + +impl IntoComponentParts for Pair +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 IntoComponentParts for Pair +where + Relation: Component, +{ + fn into_parts(self) -> ComponentParts + { + let id = Uid::new_pair(&UidPairParams { + relation: Relation::id(), + target: self.target, + }); + + ComponentParts::builder() + .name("Pair") + .build(id, self.relation) + } +} + +impl QueryTermWithField for Pair +where + Relation: Component, + Target: Component, +{ + type Field<'a> = ComponentHandle<'a, Target>; + + fn apply_to_terms_builder( + terms_builder: &mut QueryTermsBuilder, + ) + { + terms_builder.with_required([Pair::::uid()]); + } + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + _world: &'world World, + ) -> Self::Field<'world> + { + let target_component = entity_handle + .get_matching_components(Pair::::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::() + ); + }) + } +} + +impl QueryTermWithField for Pair +where + Relation: Component, + Target: Component, +{ + type Field<'a> = ComponentHandleMut<'a, Target>; + + fn apply_to_terms_builder( + terms_builder: &mut QueryTermsBuilder, + ) + { + terms_builder.with_required([Pair::::uid()]); + } + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + world: &'world World, + ) -> Self::Field<'world> + { + let target_component = entity_handle + .get_matching_components(Pair::::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::() + ); + }, + ) + } +} + +// TODO: implement QueryTermWithField for Pair<&Relation, Target> (or equivalent) +// TODO: implement QueryTermWithField for Pair<&mut Relation, Target> (or equivalent) + +impl QueryTermWithField for Pair +where + Relation: Component, +{ + type Field<'a> = WithWildcard<'a, Relation, Wildcard>; + + fn apply_to_terms_builder( + terms_builder: &mut QueryTermsBuilder, + ) + { + terms_builder.with_required([Self::uid()]); + } + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + world: &'world World, + ) -> Self::Field<'world> + { + let first_matching_comp = entity_handle + .get_matching_components(Self::uid()) + .next() + .expect("Not possible"); + + WithWildcard { + world, + component_ref: first_matching_comp, + _pd: PhantomData, + } + } +} + +impl WithUid for Pair +where + Relation: Component, + Target: Component, +{ + fn uid() -> Uid + { + Uid::new_pair(&UidPairParams { + relation: Relation::id(), + target: Target::id(), + }) + } +} + +impl WithUid for Pair +where + Relation: Component, +{ + fn uid() -> Uid + { + Uid::new_pair(&UidPairParams { + relation: Relation::id(), + target: Wildcard::uid(), + }) + } +} + +impl QueryTermWithField for &'_ [Pair] +where + Relation: Component, +{ + type Field<'a> = MultipleWithWildcard<'a, Relation, Wildcard>; + + fn apply_to_terms_builder( + _terms_builder: &mut QueryTermsBuilder, + ) + { + } + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + world: &'world World, + ) -> Self::Field<'world> + { + MultipleWithWildcard { + entity_handle: entity_handle.clone(), + world, + _pd: PhantomData, + } + } +} + +/// Reference to a pair with a wildcard relation/target. +#[derive(Debug)] +pub struct WithWildcard<'world, Relation, Target> +{ + world: &'world World, + component_ref: EntityComponentRef<'world>, + _pd: PhantomData<(Relation, Target)>, +} + +impl<'world, Relation, Target> WithWildcard<'world, Relation, Target> +{ + /// Returns a new `WithWildcard`. + /// + /// # Panics + /// This function will panic if: + /// - The given component's ID is not a pair ID. + /// - `Relation::uid()` is not wildcard and does not equal to the relation of the + /// given component's ID + /// - `Target::uid()` is not wildcard and does not equal to the target of the given + /// component's ID + /// - Both `Relation::uid()` and `Target::uid()` are wildcards + /// - Neither `Relation::uid()` or `Target::uid()` are wildcards + pub fn new(world: &'world World, component_ref: EntityComponentRef<'world>) -> Self + where + Relation: ComponentOrWildcard, + Target: ComponentOrWildcard, + { + let component_id = component_ref.id(); + + assert!(component_id.kind() == UidKind::Pair); + + assert!( + Relation::uid() == Wildcard::uid() + || component_id.relation_component() == Relation::uid() + ); + + assert!( + Target::uid() == Wildcard::uid() + || component_id.target_component() == Target::uid() + ); + + assert!(Relation::uid() == Wildcard::uid() || Target::uid() == Wildcard::uid()); + + assert!( + !(Relation::uid() == Wildcard::uid() && Target::uid() == Wildcard::uid()) + ); + + WithWildcard { + world, + component_ref, + _pd: PhantomData, + } + } + + /// Returns the [`Uid`] of the pair. + #[must_use] + pub fn id(&self) -> Uid + { + self.component_ref.id() + } + + /// Attempts to get the component data of this pair, returning `None` if the `Data` + /// type is incorrect. + /// + /// # Panics + /// Will panic if the component data is mutably borrowed elsewhere. + #[must_use] + pub fn get_data(&self) -> Option> + where + Data: 'static, + { + ComponentHandle::::from_entity_component_ref(&self.component_ref) + .map_or_else( + |err| match err { + ComponentHandleError::IncorrectType => None, + err @ ComponentHandleError::AcquireLockFailed(_) => { + panic!( + "Creating handle to pair data as component {} failed: {err}", + type_name::() + ); + } + }, + Some, + ) + } + + /// Attempts to get the component data of this pair, returning `None` if the `Data` + /// type is incorrect. + /// + /// # Panics + /// Will panic if the component data is borrowed elsewhere. + #[must_use] + pub fn get_data_mut(&self) -> Option> + where + Data: 'static, + { + ComponentHandleMut::::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 pair data as component {} failed: {err}", + type_name::() + ); + } + }, + Some, + ) + } +} + +impl<'world, Relation> WithWildcard<'world, Relation, Wildcard> +where + Relation: Component, +{ + /// Attempts to retrieve the target as a entity, returning `None` if not found. + #[must_use] + pub fn get_target_ent(&self) -> Option> + { + let archetype = self + .world + .data + .component_storage + .get_entity_archetype(self.component_ref.id().target_entity())?; + + let Some(archetype_entity) = + archetype.get_entity_by_id(self.component_ref.id().target_entity()) + else { + unreachable!(); + }; + + Some(EntityHandle::new(archetype, archetype_entity, self.world)) + } + + /// Attempts to get the component data of this pair, returning `None` if the + /// `Relation` type is incorrect. + /// + /// # Panics + /// Will panic if the component data is mutably borrowed elsewhere. + #[must_use] + pub fn get_data_as_relation(&self) -> Option> + { + ComponentHandle::::from_entity_component_ref(&self.component_ref) + .map_or_else( + |err| match err { + ComponentHandleError::IncorrectType => None, + err @ ComponentHandleError::AcquireLockFailed(_) => { + panic!( + "Creating handle to pair data as component {} failed: {err}", + type_name::() + ); + } + }, + Some, + ) + } + + /// Attempts to get the component data of this pair, returning `None` if the + /// `Relation` type is incorrect. + /// + /// # Panics + /// Will panic if the component data is borrowed elsewhere. + #[must_use] + pub fn get_data_as_relation_mut(&self) -> Option> + { + ComponentHandleMut::::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 pair data as component {} failed: {err}", + type_name::() + ); + } + }, + Some, + ) + } +} + +/// Used to access matching pairs in a entity containing zero or more matching pairs. +#[derive(Debug)] +pub struct MultipleWithWildcard<'a, Relation, Target> +{ + entity_handle: EntityHandle<'a>, + world: &'a World, + _pd: PhantomData<(Relation, Target)>, +} + +impl<'world, Relation, Target> MultipleWithWildcard<'world, Relation, Target> +{ + /// Returns a new `MultipleWithWildcard`. + /// + /// # Panics + /// This function will panic if: + /// - Both `Relation::uid()` and `Target::uid()` are wildcards + /// - Neither `Relation::uid()` or `Target::uid()` are wildcards + pub fn new(world: &'world World, entity_handle: EntityHandle<'world>) -> Self + where + Relation: ComponentOrWildcard, + Target: ComponentOrWildcard, + { + assert!(Relation::uid() == Wildcard::uid() || Target::uid() == Wildcard::uid()); + + assert!( + !(Relation::uid() == Wildcard::uid() && Target::uid() == Wildcard::uid()) + ); + + MultipleWithWildcard { + entity_handle, + world, + _pd: PhantomData, + } + } +} + +impl<'a, Relation: Component> MultipleWithWildcard<'a, Relation, Wildcard> +{ + #[must_use] + pub fn get_with_target_id( + &self, + target_id: Uid, + ) -> Option> + { + Some(WithWildcard { + world: self.world, + component_ref: self + .entity_handle + .get_matching_components( + Pair::builder() + .relation::() + .target_id(target_id) + .build() + .id(), + ) + .next()?, + _pd: PhantomData, + }) + } +} + +impl<'a, Relation: Component> IntoIterator + for MultipleWithWildcard<'a, Relation, Wildcard> +{ + type IntoIter = WithWildcardIter<'a, Relation, Wildcard>; + type Item = ::Item; + + fn into_iter(self) -> Self::IntoIter + { + WithWildcardIter { + inner: self + .entity_handle + .get_matching_components(Pair::::uid()), + world: self.world, + _pd: PhantomData, + } + } +} + +/// Iterator of matching pairs in a entity. +pub struct WithWildcardIter<'a, Relation, Target> +{ + inner: EntityMatchingComponentIter<'a>, + world: &'a World, + _pd: PhantomData<(Relation, Target)>, +} + +impl<'a, Relation, Target> Iterator for WithWildcardIter<'a, Relation, Target> +{ + type Item = WithWildcard<'a, Relation, Target>; + + fn next(&mut self) -> Option + { + let matching_comp = self.inner.next()?; + + Some(WithWildcard { + world: self.world, + component_ref: matching_comp, + _pd: PhantomData, + }) + } +} + +/// Relation denoting a dependency to another entity +#[derive(Debug, Default, Clone, Copy, Component)] +pub struct DependsOn; + +/// Relation denoting being the child of another entity. +#[derive(Debug, Default, Clone, Copy, Component)] +pub struct ChildOf; + +#[derive(Debug)] +pub struct Wildcard(Infallible); + +impl Wildcard +{ + #[must_use] + pub fn uid() -> Uid + { + Uid::wildcard() + } +} + +pub trait ComponentOrWildcard: sealed::Sealed +{ + fn uid() -> Uid; +} + +impl ComponentOrWildcard for ComponentT +{ + fn uid() -> Uid + { + ComponentT::id() + } +} + +impl sealed::Sealed for ComponentT {} + +impl ComponentOrWildcard for Wildcard +{ + fn uid() -> Uid + { + Wildcard::uid() + } +} + +impl sealed::Sealed for Wildcard {} + +mod sealed +{ + pub trait Sealed {} +} diff --git a/engine-ecs/src/phase.rs b/engine-ecs/src/phase.rs new file mode 100644 index 0000000..976de1b --- /dev/null +++ b/engine-ecs/src/phase.rs @@ -0,0 +1,20 @@ +use crate::{Component, World, declare_entity}; + +#[derive(Debug, Default, Clone, Copy, Component)] +pub struct Phase; + +declare_entity!(pub START, (Phase,)); +declare_entity!(pub PRE_UPDATE, (Phase,)); +declare_entity!(pub UPDATE, (Phase,)); +declare_entity!(pub POST_UPDATE, (Phase,)); + +pub(crate) fn spawn_entities(world: &mut World) +{ + world.create_declared_entity(&START); + world.create_declared_entity(&PRE_UPDATE); + world.create_declared_entity(&UPDATE); + world.create_declared_entity(&POST_UPDATE); +} + +#[derive(Debug, Component)] +pub(crate) struct HasSystem; diff --git a/engine-ecs/src/query.rs b/engine-ecs/src/query.rs new file mode 100644 index 0000000..5f13579 --- /dev/null +++ b/engine-ecs/src/query.rs @@ -0,0 +1,569 @@ +use std::any::type_name; +use std::marker::PhantomData; + +use seq_macro::seq; + +use crate::component::{ + Component, + Handle as ComponentHandle, + HandleMut as ComponentHandleMut, +}; +use crate::entity::Handle as EntityHandle; +use crate::query::flexible::{Iter as FlexibleQueryIter, Query as FlexibleQuery}; +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; +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, +{ + inner: FlexibleQuery<'world, MAX_TERM_CNT>, + _pd: PhantomData<(FieldTerms, FieldlessTerms)>, +} + +impl<'world, FieldTerms, FieldlessTerms> Query<'world, FieldTerms, FieldlessTerms> +where + FieldTerms: TermWithFieldTuple, + FieldlessTerms: TermWithoutFieldTuple, +{ + /// Iterates over the entities matching this query, the iterator item being the entity + /// components. + #[must_use] + pub fn iter<'query>( + &'query self, + ) -> Iter<'query, 'world, FieldTerms, FlexibleQueryIter<'query>> + { + tracing::trace!("Searching for {}", std::any::type_name::()); + + Iter { + world: self.inner.world(), + inner: self.inner.iter(), + comps_pd: PhantomData, + } + } + + /// Iterates over the entities matching this query, the iterator item being the entity + /// [`Uid`] and the matching entity components. + #[must_use] + pub fn iter_with_euids<'query>( + &'query self, + ) -> ComponentAndEuidIter<'query, 'world, FieldTerms, FlexibleQueryIter<'query>> + { + tracing::trace!("Searching for {}", std::any::type_name::()); + + ComponentAndEuidIter { + world: self.inner.world(), + iter: self.inner.iter(), + comps_pd: PhantomData, + } + } + + /// Iterates over the entities matching this query using the iterator returned by + /// `func`. + /// + /// This function exists so that a custom [`EntityHandle`] iterator can be given to + /// [`Iter`] without giving the user access to a reference to the [`World`]. + #[must_use] + pub fn iter_with<'query, OutIter>( + &'query self, + func: impl FnOnce(FlexibleQueryIter<'query>) -> OutIter, + ) -> Iter<'query, 'world, FieldTerms, OutIter> + where + OutIter: Iterator>, + { + tracing::trace!("Searching for {}", std::any::type_name::()); + + Iter { + world: self.inner.world(), + inner: func(self.inner.iter()), + comps_pd: PhantomData, + } + } + + /// Returns the UID of the entity at the given query iteration index. + #[must_use] + pub fn get_entity_uid(&self, entity_index: usize) -> Option + { + 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(); + + FieldTerms::apply_terms_to_builder(&mut terms_builder); + FieldlessTerms::apply_terms_to_builder(&mut terms_builder); + + Self { + inner: world.flexible_query(terms_builder.build()), + _pd: PhantomData, + } + } +} + +impl<'query, 'world, FieldTerms, FieldlessTerms> IntoIterator + for &'query Query<'world, FieldTerms, FieldlessTerms> +where + FieldTerms: TermWithFieldTuple, + FieldlessTerms: TermWithoutFieldTuple, +{ + type IntoIter = Iter<'query, 'world, FieldTerms, FlexibleQueryIter<'query>>; + type Item = FieldTerms::Fields<'query>; + + fn into_iter(self) -> Self::IntoIter + { + self.iter() + } +} + +impl<'world, FieldTerms, FieldlessTerms> SystemParam<'world> + for Query<'world, FieldTerms, FieldlessTerms> +where + FieldTerms: TermWithFieldTuple, + FieldlessTerms: TermWithoutFieldTuple, +{ + type Input = (); + + fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self + { + Self::new(world) + } +} + +#[derive(Debug)] +pub struct Terms +{ + required_components: ArrayVec, + excluded_components: ArrayVec, +} + +impl Terms +{ + pub fn builder() -> TermsBuilder + { + TermsBuilder::default() + } +} + +#[derive(Debug, Default)] +#[must_use] +pub struct TermsBuilder +{ + required_components: ArrayVec, + excluded_components: ArrayVec, +} + +#[allow(clippy::return_self_not_must_use)] +pub trait TermsBuilderInterface +{ + fn with(self) -> Self; + + fn without(self) -> Self; + + fn with_required(self, ids: impl Array) -> Self; + + fn without_ids(self, ids: impl Array) -> Self; +} + +macro_rules! impl_terms_builder { + ($($impl_content: tt)*) => { + impl + TermsBuilderInterface for TermsBuilder + { + $($impl_content)* + } + + impl + TermsBuilderInterface for &mut TermsBuilder + { + $($impl_content)* + } + }; +} + +impl_terms_builder! { + #[allow(unused_mut)] + fn with(mut self) -> Self + { + let insert_index = self.required_components + .partition_point(|id| *id <= WithUidT::uid()); + + self.required_components + .insert(insert_index, WithUidT::uid()); + + self + } + + #[allow(unused_mut)] + fn without(mut self) -> Self + { + let insert_index = self.excluded_components + .partition_point(|id| *id <= WithUidT::uid()); + + self.excluded_components + .insert(insert_index, WithUidT::uid()); + + self + } + + #[allow(unused_mut)] + fn with_required(mut self, mut ids: impl Array) -> Self + { + if !ids.as_ref().is_sorted() { + ids.as_mut().sort(); + } + + if self.required_components.is_empty() { + self.required_components.extend(ids); + return self; + } + + let mut id_iter = ids.into_iter(); + + while let Some(id) = id_iter.next() { + let insert_index = self.required_components + .partition_point(|other_id| *other_id <= id); + + if insert_index == self.required_components.len() { + self.required_components.extend([id].into_iter().chain(id_iter)); + + return self; + } + + self.required_components + .insert(insert_index, id); + + } + + self + } + + #[allow(unused_mut)] + fn without_ids(mut self, mut ids: impl Array) -> Self + { + if !ids.as_ref().is_sorted() { + ids.as_mut().sort(); + } + + if self.excluded_components.is_empty() { + self.excluded_components.extend(ids); + return self; + } + + let mut id_iter = ids.into_iter(); + + while let Some(id) = id_iter.next() { + let insert_index = self.excluded_components + .partition_point(|other_id| *other_id <= id); + + if insert_index == self.excluded_components.len() { + self.excluded_components.extend([id].into_iter().chain(id_iter)); + + return self; + } + + self.excluded_components + .insert(insert_index, id); + + } + + self + } +} + +impl TermsBuilder +{ + #[must_use] + pub fn build(self) -> Terms + { + debug_assert!(self.required_components.is_sorted()); + debug_assert!(self.excluded_components.is_sorted()); + + Terms { + required_components: self.required_components, + excluded_components: self.excluded_components, + } + } +} + +pub trait TermWithoutField +{ + fn apply_to_terms_builder( + terms_builder: &mut TermsBuilder, + ); +} + +pub trait TermWithField +{ + type Field<'a>; + + fn apply_to_terms_builder( + terms_builder: &mut TermsBuilder, + ); + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + world: &'world World, + ) -> Self::Field<'world>; +} + +impl TermWithField for &ComponentT +{ + type Field<'a> = ComponentHandle<'a, ComponentT>; + + fn apply_to_terms_builder( + terms_builder: &mut TermsBuilder, + ) + { + terms_builder.with::(); + } + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + _world: &'world World, + ) -> Self::Field<'world> + { + assert_eq!(ComponentT::id().kind(), UidKind::Component); + + 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::(), + entity_handle.uid() + ); + }; + + Self::Field::from_entity_component_ref(&component).unwrap_or_else(|err| { + panic!( + "Creating handle to component {} failed: {err}", + type_name::() + ); + }) + } +} + +impl TermWithField for &mut ComponentT +{ + type Field<'a> = ComponentHandleMut<'a, ComponentT>; + + fn apply_to_terms_builder( + terms_builder: &mut TermsBuilder, + ) + { + terms_builder.with::(); + } + + fn get_field<'world>( + entity_handle: &EntityHandle<'world>, + world: &'world World, + ) -> Self::Field<'world> + { + assert_eq!(ComponentT::id().kind(), UidKind::Component); + + 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::(), + entity_handle.uid() + ); + }; + + Self::Field::from_entity_component_ref(&component, world).unwrap_or_else(|err| { + panic!( + "Creating handle to component {} failed: {err}", + type_name::() + ); + }) + } +} + +pub trait TermWithoutFieldTuple +{ + fn apply_terms_to_builder( + terms_builder: &mut TermsBuilder, + ); +} + +pub trait TermWithFieldTuple +{ + type Fields<'component>; + + fn apply_terms_to_builder( + terms_builder: &mut TermsBuilder, + ); + + fn get_fields<'component>( + entity_handle: &EntityHandle<'component>, + world: &'component World, + ) -> Self::Fields<'component>; +} + +pub struct Iter<'query, 'world, FieldTerms, EntityHandleIter> +where + FieldTerms: TermWithFieldTuple, + EntityHandleIter: Iterator>, +{ + world: &'world World, + inner: EntityHandleIter, + comps_pd: PhantomData, +} + +impl<'query, 'world, FieldTerms, EntityHandleIter> Iterator + for Iter<'query, 'world, FieldTerms, EntityHandleIter> +where + FieldTerms: TermWithFieldTuple, + EntityHandleIter: Iterator>, + 'world: 'query, +{ + type Item = FieldTerms::Fields<'query>; + + fn next(&mut self) -> Option + { + let entity_handle = self.inner.next()?; + + Some(FieldTerms::get_fields(&entity_handle, self.world)) + } +} + +pub struct ComponentAndEuidIter<'query, 'world, FieldTerms, EntityHandleIter> +where + FieldTerms: TermWithFieldTuple, + EntityHandleIter: Iterator>, +{ + world: &'world World, + iter: EntityHandleIter, + comps_pd: PhantomData, +} + +impl<'query, 'world, FieldTerms, EntityHandleIter> Iterator + for ComponentAndEuidIter<'query, 'world, FieldTerms, EntityHandleIter> +where + FieldTerms: TermWithFieldTuple, + EntityHandleIter: Iterator>, + 'world: 'query, +{ + type Item = (Uid, FieldTerms::Fields<'query>); + + fn next(&mut self) -> Option + { + let entity_handle = self.iter.next()?; + + Some(( + entity_handle.uid(), + FieldTerms::get_fields(&entity_handle, self.world), + )) + } +} + +macro_rules! impl_term_sequence { + ($c: tt) => { + seq!(I in 0..=$c { + impl<#(Term~I: TermWithoutField,)*> TermWithoutFieldTuple for (#(Term~I,)*) + { + fn apply_terms_to_builder( + terms_builder: &mut TermsBuilder + ) + { + #( + Term~I::apply_to_terms_builder(terms_builder); + )* + } + } + + impl<#(Term~I: TermWithField,)*> TermWithFieldTuple for (#(Term~I,)*) + { + type Fields<'component> = (#(Term~I::Field<'component>,)*); + + fn apply_terms_to_builder( + terms_builder: &mut TermsBuilder + ) + { + #( + Term~I::apply_to_terms_builder(terms_builder); + )* + } + + fn get_fields<'component>( + entity_handle: &EntityHandle<'component>, + world: &'component World, + ) -> Self::Fields<'component> + { + (#(Term~I::get_field(entity_handle, world),)*) + } + } + }); + }; +} + +seq!(C in 0..=16 { + impl_term_sequence!(C); +}); + +impl TermWithoutFieldTuple for () +{ + fn apply_terms_to_builder( + _terms_builder: &mut TermsBuilder, + ) + { + } +} + +impl TermWithFieldTuple for () +{ + type Fields<'component> = (); + + fn apply_terms_to_builder( + _terms_builder: &mut TermsBuilder, + ) + { + } + + fn get_fields<'component>( + _entity_handle: &EntityHandle<'_>, + _world: &'component World, + ) -> Self::Fields<'component> + { + } +} diff --git a/engine-ecs/src/query/flexible.rs b/engine-ecs/src/query/flexible.rs new file mode 100644 index 0000000..936ab82 --- /dev/null +++ b/engine-ecs/src/query/flexible.rs @@ -0,0 +1,92 @@ +//! Low-level querying. +use std::iter::{repeat_n, FlatMap, RepeatN, Zip}; + +use crate::component::storage::archetype::{Archetype, EntityIter}; +use crate::component::storage::{ArchetypeRefIter, ArchetypeSearchTerms}; +use crate::entity::Handle as EntityHandle; +use crate::query::Terms; +use crate::World; + +/// Low-level entity query structure. +#[derive(Debug)] +pub struct Query<'world, const MAX_TERM_CNT: usize> +{ + world: &'world World, + terms: Terms, +} + +impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT> +{ + /// Iterates over the entities matching this query. + #[must_use] + pub fn iter(&self) -> Iter<'_> + { + Iter { + iter: self + .world + .data + .component_storage + .search_archetypes(ArchetypeSearchTerms { + required_components: &self.terms.required_components, + excluded_components: &self.terms.excluded_components, + }) + .flat_map( + (|archetype| { + repeat_n(archetype, archetype.entity_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) -> Self + { + Self { world, terms } + } +} + +impl<'query, const MAX_TERM_CNT: usize> IntoIterator for &'query Query<'_, MAX_TERM_CNT> +{ + type IntoIter = Iter<'query>; + type Item = EntityHandle<'query>; + + fn into_iter(self) -> Self::IntoIter + { + self.iter() + } +} + +pub struct Iter<'query> +{ + iter: QueryEntityIter<'query>, + world: &'query World, +} + +impl<'query> Iterator for Iter<'query> +{ + type Item = EntityHandle<'query>; + + fn next(&mut self) -> Option + { + let (archetype, entity) = self.iter.next()?; + + Some(EntityHandle::new(archetype, entity, self.world)) + } +} + +type ComponentIterMapFnOutput<'a> = Zip, EntityIter<'a>>; + +type ComponentIterMapFn = for<'a> fn(&'a Archetype) -> ComponentIterMapFnOutput<'a>; + +type QueryEntityIter<'query> = FlatMap< + ArchetypeRefIter<'query, 'query>, + ComponentIterMapFnOutput<'query>, + ComponentIterMapFn, +>; diff --git a/engine-ecs/src/query/term.rs b/engine-ecs/src/query/term.rs new file mode 100644 index 0000000..0683918 --- /dev/null +++ b/engine-ecs/src/query/term.rs @@ -0,0 +1,116 @@ +use std::any::type_name; +use std::marker::PhantomData; + +use crate::component::{ + Component, + Handle as ComponentHandle, + HandleMut as ComponentHandleMut, +}; +use crate::query::{ + TermWithField, + TermWithoutField, + TermsBuilder, + TermsBuilderInterface, +}; +use crate::uid::With as WithUid; + +pub struct With +where + WithUidT: WithUid, +{ + _pd: PhantomData, +} + +impl TermWithoutField for With +where + WithUidT: WithUid, +{ + fn apply_to_terms_builder( + terms_builder: &mut TermsBuilder, + ) + { + terms_builder.with::(); + } +} + +pub struct Without +where + WithUidT: WithUid, +{ + _pd: PhantomData, +} + +impl TermWithoutField for Without +where + WithUidT: WithUid, +{ + fn apply_to_terms_builder( + terms_builder: &mut TermsBuilder, + ) + { + terms_builder.without::(); + } +} + +impl TermWithField for Option<&ComponentT> +{ + type Field<'a> = Option>; + + fn apply_to_terms_builder( + _terms_builder: &mut TermsBuilder, + ) + { + } + + fn get_field<'world>( + entity_handle: &crate::entity::Handle<'world>, + _world: &'world crate::World, + ) -> Self::Field<'world> + { + Some( + ComponentHandle::<'world, ComponentT>::from_entity_component_ref( + &entity_handle + .get_matching_components(ComponentT::id()) + .next()?, + ) + .unwrap_or_else(|err| { + panic!( + "Creating handle to component {} failed: {err}", + type_name::() + ); + }), + ) + } +} + +impl TermWithField for Option<&mut ComponentT> +{ + type Field<'a> = Option>; + + fn apply_to_terms_builder( + _terms_builder: &mut TermsBuilder, + ) + { + } + + fn get_field<'world>( + entity_handle: &crate::entity::Handle<'world>, + world: &'world crate::World, + ) -> Self::Field<'world> + { + Some( + ComponentHandleMut::<'world, ComponentT>::from_entity_component_ref( + &entity_handle + .get_matching_components(ComponentT::id()) + .next()?, + world, + ) + .unwrap_or_else(|err| { + panic!( + "Creating handle to component {} failed: {err}", + type_name::() + ); + }), + ) + } +} diff --git a/engine-ecs/src/sole.rs b/engine-ecs/src/sole.rs new file mode 100644 index 0000000..82e5e0f --- /dev/null +++ b/engine-ecs/src/sole.rs @@ -0,0 +1,104 @@ +use std::any::{Any, type_name}; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; + +use crate::World; +use crate::lock::{Lock, WriteGuard}; +use crate::system::{Metadata as SystemMetadata, Param as SystemParam}; + +/// A type which has a single instance and is shared globally. +pub trait Sole: Any +{ + fn drop_last(&self) -> bool; + + fn as_any_mut(&mut self) -> &mut dyn Any; + + fn as_any(&self) -> &dyn Any; +} + +impl dyn Sole +{ + pub fn downcast_mut(&mut self) -> Option<&mut Real> + { + self.as_any_mut().downcast_mut() + } + + pub fn downcast_ref(&self) -> Option<&Real> + { + self.as_any().downcast_ref() + } +} + +impl Debug for dyn Sole +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + formatter.debug_struct("Sole").finish_non_exhaustive() + } +} + +/// Holds a reference to a globally shared singleton value. +#[derive(Debug)] +pub struct Single<'world, SoleT: Sole> +{ + sole: WriteGuard<'world, Box>, + _ph: PhantomData, +} + +impl<'world, SoleT> Single<'world, SoleT> +where + SoleT: Sole, +{ + pub(crate) fn new(sole: &'world Arc>>) -> Self + { + Self { + sole: sole.write_nonblock().unwrap_or_else(|_| { + panic!( + "Failed to aquire read-write lock to single component {}", + type_name::() + ) + }), + _ph: PhantomData, + } + } +} + +impl<'world, SoleT> SystemParam<'world> for Single<'world, SoleT> +where + SoleT: Sole, +{ + type Input = (); + + fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self + { + let sole = world.data.sole_storage.get::().unwrap_or_else(|| { + panic!("Sole {} was not found in world", type_name::()) + }); + + Self::new(sole) + } +} + +impl Deref for Single<'_, SoleT> +where + SoleT: Sole, +{ + type Target = SoleT; + + fn deref(&self) -> &Self::Target + { + self.sole.downcast_ref().unwrap() + } +} + +impl DerefMut for Single<'_, SoleT> +where + SoleT: Sole, +{ + fn deref_mut(&mut self) -> &mut Self::Target + { + self.sole.downcast_mut().unwrap() + } +} diff --git a/engine-ecs/src/stats.rs b/engine-ecs/src/stats.rs new file mode 100644 index 0000000..2fe731b --- /dev/null +++ b/engine-ecs/src/stats.rs @@ -0,0 +1,8 @@ +use crate::Sole; + +#[derive(Debug, Default, Sole)] +#[non_exhaustive] +pub struct Stats +{ + pub current_tick: u64, +} diff --git a/engine-ecs/src/system.rs b/engine-ecs/src/system.rs new file mode 100644 index 0000000..38e480d --- /dev/null +++ b/engine-ecs/src/system.rs @@ -0,0 +1,157 @@ +use std::fmt::Debug; + +use seq_macro::seq; + +use crate::error::Error; +use crate::uid::Uid; +use crate::{Component, World}; + +pub mod initializable; +pub mod observer; +pub mod stateful; + +/// Metadata of a system. +#[derive(Debug)] +#[non_exhaustive] +pub struct Metadata +{ + pub ent_id: Uid, +} + +pub trait System<'world, Impl>: 'static +{ + type Callbacks: Callbacks; + + fn finish(self) -> (TypeErased, Self::Callbacks); +} + +macro_rules! impl_system { + ($c: tt) => { + seq!(I in 0..$c { + impl<'world, Func, Ret, #(TParam~I,)*> System<'world, fn(#(TParam~I,)*) -> Ret> + for Func + where + Func: Fn(#(TParam~I,)*) -> Ret + 'static, + Ret: ReturnValue, + #(TParam~I: Param<'world, Input = ()>,)* + { + type Callbacks = NoCallbacks; + + fn finish(self) -> (TypeErased, Self::Callbacks) + { + #![allow(unused)] + + 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) }; + + self(#({ + TParam~I::new(world, &metadata) + },)*).into_result() + }), + name: std::any::type_name::() + }; + + (type_erased, NoCallbacks) + } + } + }); + }; +} + +seq!(C in 0..16 { + impl_system!(C); +}); + +pub trait Into<'world, Impl> +{ + type System; + + fn into_system(self) -> Self::System; +} + +pub struct TypeErased +{ + run: Box, + name: &'static str, +} + +impl TypeErased +{ + /// Runs the system. + /// + /// # Safety + /// `world_data` must live at least as long as the [`World`] the system belongs to. + pub unsafe fn run(&self, world: &World, metadata: Metadata) -> Result<(), Error> + { + (self.run)(world, metadata) + } + + pub fn name(&self) -> &'static str + { + self.name + } +} + +impl Debug for TypeErased +{ + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + formatter.debug_struct("TypeErased").finish_non_exhaustive() + } +} + +pub trait ReturnValue +{ + fn into_result(self) -> Result<(), Error>; +} + +impl ReturnValue for () +{ + fn into_result(self) -> Result<(), Error> + { + Ok(()) + } +} + +impl ReturnValue for Result<(), Error> +{ + fn into_result(self) -> Result<(), Error> + { + self + } +} + +/// A parameter to a [`System`]. +pub trait Param<'world> +{ + type Input; + + fn new(world: &'world World, system_metadata: &Metadata) -> Self; +} + +/// A type which can be used as input to a [`System`]. +pub trait Input: 'static {} + +pub trait Callbacks +{ + fn on_created(&mut self, world: &mut World, metadata: Metadata); +} + +pub struct NoCallbacks; + +impl Callbacks for NoCallbacks +{ + fn on_created(&mut self, _world: &mut World, _metadata: Metadata) {} +} + +#[derive(Debug, Component)] +pub(crate) struct SystemComponent +{ + pub(crate) system: TypeErased, +} + +/// Function in [`TypeErased`] used to run the system. +type TypeErasedRunFn = dyn Fn(&World, Metadata) -> Result<(), Error>; diff --git a/engine-ecs/src/system/initializable.rs b/engine-ecs/src/system/initializable.rs new file mode 100644 index 0000000..b6ec8e8 --- /dev/null +++ b/engine-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> for ParamT +where + ParamT: SystemParam< + 'world, + Input: AppendInitializableParam<'world, Accumulator, ParamT, SystemT>, + >, + Accumulator: Tuple, +{ + type Return = >::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; +} + +impl + 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, + ) { + #( + >::initialize( + system, + inputs.I + ); + )* + } + } + }); + }; +} + +seq!(C in 1..16 { + impl_initializable_param_tuple!(C); +}); + +impl 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, Out: ParamTuple<'world, SystemT>>, +{ + type Inputs = >::Inputs; + + fn init_initializable(system: &mut SystemT, inputs: Self::Inputs) + { + Params::Out::initialize_all(system, inputs); + } +} diff --git a/engine-ecs/src/system/observer.rs b/engine-ecs/src/system/observer.rs new file mode 100644 index 0000000..1ad7496 --- /dev/null +++ b/engine-ecs/src/system/observer.rs @@ -0,0 +1,278 @@ +use std::any::type_name; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::mem::transmute; +use std::slice::Iter as SliceIter; + +use seq_macro::seq; + +use crate::entity::Handle as EntityHandle; +use crate::error::Error; +use crate::event::Emitted as EmittedEvent; +use crate::pair::Pair; +use crate::system::{ + Metadata, + NoCallbacks, + Param, + ReturnValue as SystemReturnValue, + System, + TypeErased as TypeErasedSystem, +}; +use crate::uid::Uid; +use crate::util::Array; +use crate::{Component, World}; + +pub trait Observed +{ + type Events: Array>; + + fn events() -> Self::Events; +} + +impl Observed for Pair +where + Relation: Component, + Target: Component, +{ + type Events = [Pair; 1]; + + fn events() -> Self::Events + { + [Pair::builder() + .relation::() + .target::() + .build()] + } +} + +/// Observer system. +pub trait Observer<'world, Impl>: System<'world, Impl> +{ + type ObservedEvents: Array>; + + fn observed_events() -> Self::ObservedEvents; + + fn finish_observer(self) -> (WrapperComponent, Self::Callbacks); +} + +pub struct Observe<'world, ObservedT: Observed> +{ + _pd: PhantomData, + 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 Observe<'_, ObservedT> +{ + #[must_use] + pub fn iter(&self) -> ObserveIter<'_, ObservedT> + { + ObserveIter { + world: self.world, + inner: self.emitted_event.match_ids.iter(), + _pd: PhantomData, + } + } +} + +impl<'a, ObservedT: Observed> IntoIterator for &'a Observe<'_, ObservedT> +{ + type IntoIter = ObserveIter<'a, ObservedT>; + type Item = ::Item; + + fn into_iter(self) -> Self::IntoIter + { + self.iter() + } +} + +pub struct ObserveIter<'observe, ObservedT: Observed> +{ + world: &'observe World, + inner: SliceIter<'observe, Uid>, + _pd: PhantomData, +} + +impl<'observe, ObservedT: Observed> Iterator for ObserveIter<'observe, ObservedT> +{ + type Item = EventMatch<'observe, ObservedT>; + + fn next(&mut self) -> Option + { + let match_id = *self.inner.next()?; + + Some(EventMatch { + world: self.world, + id: match_id, + _pd: PhantomData, + }) + } +} + +/// A event match. +#[derive(Debug)] +pub struct EventMatch<'world, ObservedT: Observed> +{ + world: &'world World, + id: Uid, + _pd: PhantomData, +} + +impl<'world, ObservedT: Observed> EventMatch<'world, ObservedT> +{ + #[must_use] + pub fn entity_id(&self) -> Uid + { + self.id + } + + /// Attempts to get the entity with the id of this match. + #[must_use] + pub fn try_get_entity(&self) -> Option> + { + self.world.get_entity(self.id) + } +} + +macro_rules! impl_observer { + ($c: tt) => { + seq!(I in 0..$c { + impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> System< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + > for Func + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static, + Ret: SystemReturnValue, + #(TParam~I: Param<'world, Input = ()>,)* + { + type Callbacks = NoCallbacks; + + fn finish(self) -> (TypeErasedSystem, NoCallbacks) + { + const { + panic!("Observers cannot be used as regular systems"); + } + } + } + + impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> Observer< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + > for Func + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static, + Ret: SystemReturnValue, + #(TParam~I: Param<'world, Input = ()>,)* + { + type ObservedEvents = ObservedT::Events; + + fn observed_events() -> Self::ObservedEvents + { + ObservedT::events() + } + + fn finish_observer(self) -> (WrapperComponent, NoCallbacks) + { + #[allow(unused)] + + let wrapper_comp = WrapperComponent::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<'_>>( + emitted_event + ) + }; + + self(Observe::new(world, emitted_event), #({ + TParam~I::new(world, &metadata) + },)*).into_result() + }, + type_name::() + ); + + (wrapper_comp, NoCallbacks) + } + } + }); + }; +} + +seq!(C in 0..16 { + impl_observer!(C); +}); + +#[derive(Component)] +pub struct WrapperComponent +{ + run: Box, + name: &'static str, +} + +impl WrapperComponent +{ + pub fn new( + run: impl Fn(&World, Metadata, EmittedEvent<'_>) -> Result<(), Error> + 'static, + name: &'static str, + ) -> Self + { + Self { run: Box::new(run), name } + } + + /// 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<'_>, + ) -> Result<(), Error> + { + (self.run)(world, metadata, emitted_event) + } + + pub fn name(&self) -> &'static str + { + self.name + } +} + +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<'_>) -> Result<(), Error>; diff --git a/engine-ecs/src/system/stateful.rs b/engine-ecs/src/system/stateful.rs new file mode 100644 index 0000000..3e0076a --- /dev/null +++ b/engine-ecs/src/system/stateful.rs @@ -0,0 +1,269 @@ +use std::any::type_name; +use std::mem::transmute; + +use seq_macro::seq; + +use crate::World; +use crate::component::Parts as ComponentParts; +use crate::component::local::SystemWithLocalComponents; +use crate::event::Emitted as EmittedEvent; +use crate::system::initializable::{Initializable, MaybeInitializableParamTuple}; +use crate::system::observer::{ + Observe, + Observed, + Observer, + WrapperComponent as ObserverWrapperComponent, +}; +use crate::system::{ + Into as IntoSystem, + Metadata, + Param, + ReturnValue, + System, + TypeErased, +}; + +/// A stateful system. +pub struct Stateful +{ + func: Func, + local_components: Vec, +} + +macro_rules! impl_system { + ($c: tt) => { + seq!(I in 0..$c { + impl<'world, Func, Ret, #(TParam~I,)*> + System<'world, fn(#(TParam~I,)*) -> Ret> for Stateful + where + Func: Fn(#(TParam~I,)*) -> Ret + 'static, + Ret: ReturnValue, + #(TParam~I: Param<'world, Input: 'static>,)* + { + type Callbacks = Callbacks; + + fn finish(self) -> (TypeErased, Self::Callbacks) + { + let Self { func, local_components } = 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) + },)*); + + Ok(()) + }), + name: type_name::() + }; + + + (type_erased, callbacks) + } + } + + impl<'world, Func, Ret, #(TParam~I,)*> + Initializable<'world, fn(#(TParam~I,)*) -> Ret> for Stateful + where + Func: Fn(#(TParam~I,)*) -> Ret + 'static, + Ret: ReturnValue, + #(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 + { + init_initializable_params::<_, (#(TParam~I,)*)>(&mut self, inputs); + + self + } + } + + impl<'world, Func, Ret, #(TParam~I,)*> + IntoSystem<'world, fn(#(TParam~I,)*) -> Ret> for Func + where + Func: Fn(#(TParam~I,)*) -> Ret + 'static, + Ret: ReturnValue, + #(TParam~I: Param<'world>,)* + { + type System = Stateful; + + fn into_system(self) -> Self::System + { + Self::System { + func: self, + local_components: Vec::new(), // TODO: Use Vec::with_capacity + } + } + } + }); + }; +} + +seq!(C in 1..16 { + impl_system!(C); +}); + +impl SystemWithLocalComponents for Stateful +{ + fn add_local_component(&mut self, component_parts: ComponentParts) + { + self.local_components.push(component_parts); + } +} + +#[derive(Debug)] +pub struct Callbacks +{ + local_components: Vec, +} + +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, Ret, #(TParam~I,)*> System< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + > for Stateful + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static, + Ret: ReturnValue, + #(TParam~I: Param<'world>,)* + { + type Callbacks = Callbacks; + + fn finish(self) -> (TypeErased, Callbacks) + { + unimplemented!(); + } + } + + impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> Initializable< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + > for Stateful + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static, + Ret: ReturnValue, + #(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 + { + init_initializable_params::<_, (#(TParam~I,)*)>(&mut self, inputs); + + self + } + } + + impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> Observer< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + > for Stateful + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static, + Ret: ReturnValue, + #(TParam~I: Param<'world>,)* + { + type ObservedEvents = ObservedT::Events; + + fn observed_events() -> Self::ObservedEvents + { + ObservedT::events() + } + + fn finish_observer(self) -> (ObserverWrapperComponent, Callbacks) + { + #![allow(unused)] + + 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<'_>>( + emitted_event + ) + }; + + func(Observe::new(world, emitted_event), #({ + TParam~I::new(world, &metadata) + },)*).into_result() + }, + type_name::() + ); + + (wrapper_comp, callbacks) + } + } + + impl<'world, Func, Ret, ObservedT, #(TParam~I,)*> IntoSystem< + 'world, + fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + > for Func + where + ObservedT: Observed, + Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static, + Ret: ReturnValue, + #(TParam~I: Param<'world>,)* + { + type System = Stateful; + + fn into_system(self) -> Stateful + { + Stateful { + func: self, + local_components: Vec::new(), // TODO: Use Vec::with_capacity + } + } + } + }); + }; +} + +seq!(C in 0..16 { + impl_observer!(C); +}); diff --git a/engine-ecs/src/tuple.rs b/engine-ecs/src/tuple.rs new file mode 100644 index 0000000..def25a0 --- /dev/null +++ b/engine-ecs/src/tuple.rs @@ -0,0 +1,238 @@ +use std::any::TypeId; + +use paste::paste; +use seq_macro::seq; +use util_macros::sub; + +pub trait Tuple: sealed::Sealed +{ + /// `Self` with the given type added as the last element. + /// + /// `(String, i32, u8)::WithElementAtEnd = (String, i32, u8, Path)` + /// + /// # Important note + /// If `Self` has 16 elements, this will be `()`. The reason for this is that the + /// `Tuple` trait is only implemented for tuples with up to and including 16 elements. + type WithElementAtEnd: Tuple; + + /// `Self` without the last element. + /// + /// `(u16, AtomicU8)::WithoutLastElement = (u16,)` + type WithoutLastElement: Tuple; + + /// The last element of `Self`. + type LastElement; + + /// Self with all elements wrapped in [`Option`]. + type InOptions: Tuple; + + /// Pops the last element from this tuple, returning the new tuple and the popped + /// element. + fn pop_last(self) -> (Self::WithoutLastElement, Self::LastElement); + + /// Converts this tuple so that all elements are wrapped in [`Option`]. + fn into_in_options(self) -> Self::InOptions; +} + +/// A tuple with element types that all have the lifetime `'static`. +pub trait WithAllElemLtStatic: Tuple + sealed::Sealed +{ + /// Returns the element at the given index. + fn get_mut(&mut self, index: usize) -> Option<&mut Element>; +} + +/// Using the type system, reduces the elements of a tuple to a single one. Each element +/// determines itself how it is handled. +pub trait Reduce +{ + type Out; +} + +pub trait ReduceElement +{ + type Return; +} + +macro_rules! tuple_reduce_elem_tuple { + (overflow) => { + () + }; + + ($index: tt) => { + paste! { + []::Return + } + }; +} + +macro_rules! elem_type_by_index { + (overflow) => { + () + }; + + ($index: tt) => { + paste! { + [] + } + }; +} + +macro_rules! elem_by_index { + (overflow) => { + () + }; + + ($index: tt, $self: ident) => { + $self.$index + }; +} + +macro_rules! all_except_last { + (start $($rest: tt)*) => { + all_except_last!(@[] $($rest)*) + }; + + (@[$($included_elem: ident,)*] $elem: ident $($rest: tt)+) => { + all_except_last!(@[$($included_elem,)* $elem,] $($rest)*) + }; + + (@[$($included_elem: expr,)*] ($elem: expr) $($rest: tt)+) => { + all_except_last!(@[$($included_elem,)* $elem,] $($rest)*) + }; + + (@[$($included_elem: ident,)*] $elem: ident) => { + ($($included_elem,)*) + }; + + (@[$($included_elem: expr,)*] $elem: expr) => { + ($($included_elem,)*) + }; + + (@[]) => { + () + }; +} + +macro_rules! impl_tuple_traits { + ($cnt: tt) => { + seq!(I in 0..$cnt { + impl<#(Elem~I,)*> Tuple for (#(Elem~I,)*) + { + type WithElementAtEnd = (#(Elem~I,)* NewElem,); + + type WithoutLastElement = all_except_last!(start #(Elem~I)*); + + type LastElement = sub!($cnt - 1, elem_type_by_index); + + type InOptions = (#(Option,)*); + + fn pop_last(self) -> (Self::WithoutLastElement, Self::LastElement) + { + ( + all_except_last!(start #((self.I))*), + sub!($cnt - 1, elem_by_index, (self)) + ) + } + + fn into_in_options(self) -> Self::InOptions + { + #![allow(clippy::unused_unit)] + (#(Some(self.I),)*) + } + } + + impl<#(Elem~I: 'static,)*> WithAllElemLtStatic for (#(Elem~I,)*) + { + fn get_mut(&mut self, index: usize) -> Option<&mut Element> + { + match index { + #( + I => { + assert!(TypeId::of::() == TypeId::of::()); + + // SAFETY: It is checked above that the type is correct + Some(unsafe { &mut *(&raw mut self.I).cast::() }) + } + )* + _ => None + } + } + } + + impl<#(Elem~I,)*> sealed::Sealed for (#(Elem~I,)*) + { + } + + paste! { + impl Reduce for (#(Elem~I,)*) + where + #( + Elem~I: ReduceElement< + sub!(I - 1, tuple_reduce_elem_tuple), Operation + >, + )* + { + type Out = sub!($cnt - 1, tuple_reduce_elem_tuple); + } + } + }); + }; +} + +seq!(N in 0..16 { + impl_tuple_traits!(N); +}); + +seq!(I in 0..16 { + impl<#(Elem~I,)*> Tuple for (#(Elem~I,)*) + { + type WithElementAtEnd = (); + + type WithoutLastElement = all_except_last!(start #(Elem~I)*); + + type LastElement = Elem15; + + type InOptions = (#(Option,)*); + + fn pop_last(self) -> (Self::WithoutLastElement, Self::LastElement) + { + ( + all_except_last!(start #((self.I))*), + self.15 + ) + } + + fn into_in_options(self) -> Self::InOptions + { + #![allow(clippy::unused_unit)] + (#(Some(self.I),)*) + } + } + + impl<#(Elem~I: 'static,)*> WithAllElemLtStatic for (#(Elem~I,)*) + { + fn get_mut(&mut self, index: usize) -> Option<&mut Element> + { + match index { + #( + I => { + assert!(TypeId::of::() == TypeId::of::()); + + // SAFETY: It is checked above that the type is correct + Some(unsafe { &mut *(&raw mut self.I).cast::() }) + } + )* + _ => None + } + } + } + + impl<#(Elem~I,)*> sealed::Sealed for (#(Elem~I,)*) + { + } +}); + +mod sealed +{ + pub trait Sealed {} +} diff --git a/engine-ecs/src/uid.rs b/engine-ecs/src/uid.rs new file mode 100644 index 0000000..bb393a1 --- /dev/null +++ b/engine-ecs/src/uid.rs @@ -0,0 +1,261 @@ +use std::fmt::{Debug, Display, Formatter}; +use std::mem::transmute; +use std::sync::atomic::{AtomicU32, Ordering}; + +use seq_macro::seq; + +use crate::component::Component; +use crate::util::{gen_mask_64, Array, BitMask, NumberExt}; + +static NEXT: AtomicU32 = AtomicU32::new(Uid::FIRST_UNIQUE_ID); + +static WILDCARD_ID: u32 = 1; + +const ID_BITS: BitMask = BitMask::new(gen_mask_64!(32..=63)); +const RELATION_BITS: BitMask = BitMask::new(gen_mask_64!(6..=31)); +const KIND_BITS: BitMask = BitMask::new(gen_mask_64!(0..=1)); + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[repr(u8)] +pub enum Kind +{ + Pair = 3, + Entity = 2, + Component = 1, +} + +/// A unique identifier. +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Uid +{ + inner: u64, +} + +impl Uid +{ + /// The id part of the first unique `Uid`. The ids `0..Uid::FIRST_UNIQUE_ID` are + /// reserved. + pub const FIRST_UNIQUE_ID: u32 = 5; + + /// Returns a new unique entity/component ID. + pub fn new_unique(kind: Kind) -> Self + { + let id = NEXT.fetch_add(1, Ordering::Relaxed); + + Self { + inner: ID_BITS.field_prep(u64::from(id)) | KIND_BITS.field_prep(kind as u64), + } + } + + #[must_use] + pub fn wildcard() -> Self + { + Self { + inner: ID_BITS.field_prep(u64::from(WILDCARD_ID)) + | KIND_BITS.field_prep(Kind::Component as u64), + } + } + + /// Returns a new pair UID. + /// + /// # Panics + /// Will panic if either the given relation or target is a pair UID. + #[must_use] + pub fn new_pair(params: &PairParams) -> Self + { + assert_ne!( + params.relation.kind(), + Kind::Pair, + "Pair relation cannot be a pair" + ); + + assert_ne!( + params.target.kind(), + Kind::Pair, + "Pair target cannot be a pair" + ); + + Self { + inner: ID_BITS.field_prep(u64::from(params.target.id())) + | RELATION_BITS.field_prep(u64::from(params.relation.id())) + | KIND_BITS.field_prep(Kind::Pair as u64), + } + } + + #[must_use] + pub fn id(&self) -> u32 + { + let Ok(id) = u32::try_from(self.inner.field_get(ID_BITS)) else { + unreachable!("Uid id does not fit in u32"); + }; + + id + } + + #[must_use] + pub fn kind(&self) -> Kind + { + let Ok(kind) = u8::try_from(self.inner.field_get(KIND_BITS)) else { + unreachable!("Uid kind does not fit in u8"); + }; + + // SAFETY: The kind bits cannot be invalid since they are set using the Kind enum + // in the new_unique function + unsafe { transmute::(kind) } + } + + /// If this `Uid` is a pair, returns the relation as a component `Uid`. + /// + /// # Panics + /// Will panic if this `Uid` is not a pair. + #[must_use] + pub fn relation_component(&self) -> Self + { + assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair"); + + Self { + inner: ID_BITS.field_prep(u64::from(self.relation())) + | KIND_BITS.field_prep(Kind::Component as u64), + } + } + + #[must_use] + pub fn has_same_relation_as(&self, other: Self) -> bool + { + self.relation() == other.relation() + } + + /// If this `Uid` is a pair, returns the relation as a entity `Uid`. + /// + /// # Panics + /// Will panic if this `Uid` is not a pair. + #[must_use] + pub fn relation_entity(&self) -> Self + { + assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair"); + + Self { + inner: ID_BITS.field_prep(u64::from(self.relation())) + | KIND_BITS.field_prep(Kind::Entity as u64), + } + } + + /// If this `Uid` is a pair, returns the target as a component `Uid`. + /// + /// # Panics + /// Will panic if this `Uid` is not a pair. + #[must_use] + pub fn target_component(&self) -> Self + { + assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair"); + + Self { + inner: ID_BITS.field_prep(u64::from(self.id())) + | KIND_BITS.field_prep(Kind::Component as u64), + } + } + + /// If this `Uid` is a pair, returns the target as a entity `Uid`. + /// + /// # Panics + /// Will panic if this `Uid` is not a pair. + #[must_use] + pub fn target_entity(&self) -> Self + { + assert_eq!(self.kind(), Kind::Pair, "Uid is not a pair"); + + Self { + inner: ID_BITS.field_prep(u64::from(self.id())) + | KIND_BITS.field_prep(Kind::Entity as u64), + } + } + + fn relation(self) -> u32 + { + let Ok(relation) = u32::try_from(self.inner.field_get(RELATION_BITS)) else { + unreachable!("Uid relation does not fit in u32"); + }; + + relation + } +} + +impl Debug for Uid +{ + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result + { + formatter + .debug_struct("Uid") + .field("id", &self.id()) + .field("kind", &self.kind()) + .finish_non_exhaustive() + } +} + +impl Display for Uid +{ + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result + { + if self.kind() == Kind::Pair { + return write!( + formatter, + "({}, {})", + self.relation(), + self.target_component() + ); + } + + if *self == Uid::wildcard() { + return write!(formatter, "*"); + } + + write!(formatter, "{}", self.id()) + } +} + +#[derive(Debug, Clone)] +pub struct PairParams +{ + pub relation: Uid, + pub target: Uid, +} + +pub trait With +{ + fn uid() -> Uid; +} + +impl With for ComponentT +{ + fn uid() -> Uid + { + Self::id() + } +} + +pub trait WithUidTuple +{ + type UidsArray: Array; + + fn uids() -> Self::UidsArray; +} + +macro_rules! impl_with_uid_tuple { + ($c: tt) => { + seq!(I in 0..=$c { + impl<#(WithUid~I: With,)*> WithUidTuple for (#(WithUid~I,)*) + { + type UidsArray = [Uid; $c + 1]; + + fn uids() -> Self::UidsArray + { + [#(WithUid~I::uid(),)*] + } + } + }); + }; +} + +seq!(C in 0..=16 { + impl_with_uid_tuple!(C); +}); diff --git a/engine-ecs/src/util.rs b/engine-ecs/src/util.rs new file mode 100644 index 0000000..27e9748 --- /dev/null +++ b/engine-ecs/src/util.rs @@ -0,0 +1,415 @@ +use std::hash::Hash; +use std::mem::transmute; +use std::ops::{BitAnd, Deref}; + +use hashbrown::HashMap; + +pub(crate) mod array_vec; + +pub trait VecExt +{ + fn insert_at_part_pt_by_key( + &mut self, + item: Item, + func: impl FnMut(&Item) -> &Key, + ) where + Key: Ord; +} + +impl VecExt for Vec +{ + fn insert_at_part_pt_by_key( + &mut self, + item: Item, + mut func: impl FnMut(&Item) -> &Key, + ) where + Key: Ord, + { + let key = func(&item); + + let insert_index = self.partition_point(|other_item| func(other_item) <= key); + + self.insert(insert_index, item); + } +} + +pub trait StreamingIterator +{ + type Item<'a> + where + Self: 'a; + + fn streaming_next(&mut self) -> Option>; + + fn streaming_map(self, func: Func) -> StreamingMap + where + Self: Sized, + Func: FnMut(Self::Item<'_>) -> NewItem, + { + StreamingMap { iter: self, func } + } + + fn streaming_find<'this, Predicate>( + &'this mut self, + mut predicate: Predicate, + ) -> Option> + where + Self: Sized, + Predicate: FnMut(&Self::Item<'this>) -> bool, + { + while let Some(item) = unsafe { + transmute::>, Option>>( + self.streaming_next(), + ) + } { + if predicate(&item) { + return Some(item); + } + } + + None + } +} + +pub struct StreamingMap +{ + iter: Iter, + func: Func, +} + +impl StreamingIterator for StreamingMap +where + Iter: StreamingIterator, + Func: FnMut(Iter::Item<'_>) -> Item, +{ + type Item<'a> + = Item + where + Iter: 'a, + Func: 'a; + + fn streaming_next(&mut self) -> Option> + { + Some((self.func)(self.iter.streaming_next()?)) + } +} + +#[derive(Debug)] +pub enum BorrowedOrOwned<'a, Value> +{ + Borrowned(&'a Value), + Owned(Value), +} + +impl Deref for BorrowedOrOwned<'_, Value> +{ + type Target = Value; + + fn deref(&self) -> &Self::Target + { + match self { + Self::Borrowned(value) => value, + Self::Owned(value) => value, + } + } +} + +#[derive(Debug, Clone)] +pub enum Either +{ + A(A), + B(B), +} + +impl Iterator for Either +where + A: Iterator, + B: Iterator, +{ + type Item = A::Item; + + fn next(&mut self) -> Option + { + match self { + Self::A(a) => a.next(), + Self::B(b) => b.next(), + } + } +} + +pub trait HashMapExt +{ + /// Returns true if the keys are a subset of another [`HashMap`]'s keys, i.e., `other` + /// contains at least all the keys in `self`. + fn keys_is_subset(&self, other: &Self) -> bool; + + /// Returns true if the keys are a superset of another [`HashMap`]'s keys, i.e., + /// `self` contains at least all the keys in `other`. + fn keys_is_superset(&self, other: &Self) -> bool; +} + +impl HashMapExt for HashMap +where + Key: Eq + Hash, +{ + fn keys_is_subset(&self, other: &Self) -> bool + { + if self.len() <= other.len() { + self.keys().all(|key| other.contains_key(key)) + } else { + false + } + } + + fn keys_is_superset(&self, other: &Self) -> bool + { + other.keys_is_subset(self) + } +} + +pub trait Array: + AsRef<[Item]> + + AsMut<[Item]> + + IntoIterator + + Into> + + Sortable + + sealed::Sealed +{ +} + +impl Array for [Item; CNT] {} + +impl sealed::Sealed for [Item; CNT] {} + +pub trait Sortable +{ + type Item; + + fn sort_by_key_b(&mut self, func: Func) + where + Func: FnMut(&Self::Item) -> Key, + Key: Ord; +} + +impl Sortable for [Item] +{ + type Item = Item; + + fn sort_by_key_b(&mut self, func: Func) + where + Func: FnMut(&Self::Item) -> Key, + Key: Ord, + { + self.sort_by_key(func); + } +} + +impl Sortable for [Item; LENGTH] +{ + type Item = Item; + + fn sort_by_key_b(&mut self, func: Func) + where + Func: FnMut(&Self::Item) -> Key, + Key: Ord, + { + self.sort_by_key(func); + } +} + +impl Sortable for Vec +{ + type Item = Item; + + fn sort_by_key_b(&mut self, func: Func) + where + Func: FnMut(&Self::Item) -> Key, + Key: Ord, + { + self.sort_by_key(func); + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct BitMask +{ + mask: Value, +} + +impl BitMask +{ + #[must_use] + pub const fn new(mask: u64) -> Self + { + Self { mask } + } + + #[must_use] + pub const fn value(self) -> u64 + { + self.mask + } + + /// Prepares a bitfield value in the range of bits specified by this `BitMask`. + #[must_use] + pub const fn field_prep(self, field_value: u64) -> u64 + { + debug_assert!(field_value < 1 << self.mask.count_ones()); + + ((field_value) << self.mask.trailing_zeros()) & (self.mask) + } +} + +impl BitAnd for BitMask +{ + type Output = u64; + + fn bitand(self, rhs: u64) -> Self::Output + { + self.mask & rhs + } +} + +pub trait NumberExt: Sized +{ + /// Returns a range of bits (field) specified by the provided [`BitMask`]. + #[must_use] + fn field_get(self, field_mask: BitMask) -> Self; +} + +impl NumberExt for u64 +{ + fn field_get(self, field_mask: BitMask) -> Self + { + (field_mask & self) >> field_mask.value().trailing_zeros() + } +} + +macro_rules! gen_mask_64 { + ($low: literal..=$high: literal) => { + const { + if $high <= $low { + panic!("High bit index cannot be less than or equal to low bit index"); + } + + (((!0u64) - (1u64 << ($low)) + 1) + & (!0u64 >> (u64::BITS as u64 - 1 - ($high)))) + } + }; +} + +pub(crate) use gen_mask_64; + +macro_rules! impl_multiple { + ( + $type: ident, + ($( + impl$(<$($generic: tt$(: $bound: ident)?),*>)? + _<$($lt_param: lifetime),*><$($type_param: ty),*> + $(($($extra_cb_arg: expr),*))? + ),*) + cb=( + type_params=($($ty_param_matcher: ident),*) + $(, $($extra_matcher: ident),+)? + ) => { + $($item_tt: tt)* + } + ) => { + const _: () = { + $crate::util::impl_multiple!( + @(make_gen_item_macro) + _gen_multiple_impl_item, + ($($ty_param_matcher),*), + ($($($extra_matcher),+)?), + ($($item_tt)*) + ); + + $( + impl $(<$($generic$(: $bound)?,)*>)? $type<$($lt_param,)* $($type_param),*> + { + _gen_multiple_impl_item!( + type_params=($($type_param),*), + $($($extra_cb_arg),*)? + ); + } + )* + }; + }; + + ( + @(make_gen_item_macro) + $name: ident, + ($($ty_param_matcher: ident),*), + ($($extra_matcher: ident),*), + ($($transcriber: tt)*) + ) => { + $crate::util::impl_multiple!( + @(make_gen_item_macro) + ($), + $name, + ($($ty_param_matcher),*), + ($($extra_matcher),*), + ($($transcriber)*) + ); + }; + + ( + @(make_gen_item_macro) + ($dollar: tt), + $name: ident, + ($($ty_param_matcher: ident),*), + ($($extra_matcher: ident),*), + ($($transcriber: tt)*) + ) => { + $crate::util::impl_multiple!( + @(make_gen_item_macro) + $name, + ( + type_params=($($dollar$ty_param_matcher: ty),*), + $($dollar$extra_matcher: expr),* + ) => { + $($transcriber)* + } + ); + }; + + (@(make_gen_item_macro) $name: ident, $($rule: tt)*) => { + macro_rules! $name { + $($rule)* + } + }; +} + +pub(crate) use impl_multiple; + +mod sealed +{ + pub trait Sealed {} +} + +#[cfg(test)] +mod tests +{ + + use super::BitMask; + use crate::util::NumberExt; + + #[test] + fn field_get_works() + { + assert_eq!(0b11011u64.field_get(BitMask::new(0b11100)), 0b00110); + } + + #[test] + fn bitmask_field_prep_works() + { + assert_eq!(BitMask::new(0b11000).field_prep(3), 0b11000); + } + + #[test] + #[should_panic] + fn bitmask_field_prep_too_large_value_panics() + { + let _ = BitMask::new(0b001110).field_prep(9); + } +} diff --git a/engine-ecs/src/util/array_vec.rs b/engine-ecs/src/util/array_vec.rs new file mode 100644 index 0000000..5d0aac9 --- /dev/null +++ b/engine-ecs/src/util/array_vec.rs @@ -0,0 +1,131 @@ +use std::mem::MaybeUninit; +use std::ops::{Deref, DerefMut}; + +#[derive(Debug)] +pub struct ArrayVec +{ + items: [MaybeUninit; CAPACITY], + len: usize, +} + +impl ArrayVec +{ + #[inline] + #[must_use] + pub fn len(&self) -> usize + { + self.len + } + + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool + { + self.len == 0 + } + + pub fn push(&mut self, item: Item) + { + assert!(self.len < CAPACITY); + + self.items[self.len].write(item); + + self.len += 1; + } + + pub fn insert(&mut self, index: usize, item: Item) + { + assert!(index <= self.len); + assert!(self.len < CAPACITY); + + if index == self.len { + self.push(item); + return; + } + + unsafe { + std::ptr::copy( + &raw const self.items[index], + &raw mut self.items[index + 1], + self.len - index, + ); + } + + self.items[index].write(item); + + self.len += 1; + } +} + +impl Extend for ArrayVec +{ + fn extend>(&mut self, iter: IntoIter) + { + for item in iter { + self.push(item); + } + } +} + +impl AsRef<[Item]> for ArrayVec +{ + fn as_ref(&self) -> &[Item] + { + let ptr = &raw const self.items[..self.len]; + + unsafe { &*(ptr as *const [Item]) } + } +} + +impl AsMut<[Item]> for ArrayVec +{ + fn as_mut(&mut self) -> &mut [Item] + { + let ptr = &raw mut self.items[..self.len]; + + unsafe { &mut *(ptr as *mut [Item]) } + } +} + +impl Deref for ArrayVec +{ + type Target = [Item]; + + fn deref(&self) -> &Self::Target + { + self.as_ref() + } +} + +impl DerefMut for ArrayVec +{ + fn deref_mut(&mut self) -> &mut Self::Target + { + self.as_mut() + } +} + +impl Default for ArrayVec +{ + fn default() -> Self + { + Self { + items: [const { MaybeUninit::uninit() }; CAPACITY], + len: 0, + } + } +} + +impl Drop for ArrayVec +{ + fn drop(&mut self) + { + for item in &mut self.items[..self.len] { + // SAFETY: The items from index 0 to the length index will always be + // initialized and satisfy all the invariants of the Item type. + unsafe { + item.assume_init_drop(); + } + } + } +} diff --git a/engine-ecs/tests/phase.rs b/engine-ecs/tests/phase.rs new file mode 100644 index 0000000..8b755a9 --- /dev/null +++ b/engine-ecs/tests/phase.rs @@ -0,0 +1,36 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + +use engine_ecs::component::local::Local; +use engine_ecs::phase::UPDATE; +use engine_ecs::system::Into; +use engine_ecs::system::initializable::Initializable; +use engine_ecs::{Component, World}; + +#[derive(Component)] +struct Thing; + +#[test] +fn system_run_order_correct_when_one_has_local_comp() +{ + static COUNTER: AtomicUsize = AtomicUsize::new(0); + + fn first_system(_thing: Local) + { + assert_eq!(COUNTER.fetch_add(1, Ordering::Relaxed), 0); + } + + fn second_system() + { + assert_eq!(COUNTER.fetch_add(1, Ordering::Relaxed), 1); + } + + let mut world = World::new(); + + world.register_system(*UPDATE, first_system.into_system().initialize((Thing,))); + + world.register_system(*UPDATE, second_system); + + world.step(); + + assert_eq!(COUNTER.load(Ordering::Relaxed), 2); +} diff --git a/engine-ecs/tests/query.rs b/engine-ecs/tests/query.rs new file mode 100644 index 0000000..c7956e0 --- /dev/null +++ b/engine-ecs/tests/query.rs @@ -0,0 +1,413 @@ +use engine_ecs::component::Component; +use engine_ecs::pair::{Pair, Wildcard}; +use engine_ecs::query::term::Without; +use engine_ecs::query::{ + TermWithFieldTuple as QueryTermWithFieldTuple, + TermWithoutFieldTuple as QueryTermWithoutFieldTuple, +}; +use engine_ecs::uid::Uid; +use engine_ecs::{Component, Query, World}; +use parking_lot::{Mutex, Once}; + +pub static SETUP: Once = Once::new(); + +pub static TEST_LOCK: Mutex<()> = Mutex::new(()); + +#[derive(Component)] +struct A; + +#[derive(Component)] +struct B; + +#[derive(Component)] +struct C; + +#[derive(Component)] +struct D; + +#[derive(Component)] +struct E; + +#[derive(Component)] +struct F; + +#[derive(Component)] +struct G; + +fn setup() +{ + SETUP.call_once_force(|_| { + assert_eq!(A::id().id(), Uid::FIRST_UNIQUE_ID); + assert_eq!(B::id().id(), Uid::FIRST_UNIQUE_ID + 1); + assert_eq!(C::id().id(), Uid::FIRST_UNIQUE_ID + 2); + assert_eq!(D::id().id(), Uid::FIRST_UNIQUE_ID + 3); + assert_eq!(E::id().id(), Uid::FIRST_UNIQUE_ID + 4); + assert_eq!(F::id().id(), Uid::FIRST_UNIQUE_ID + 5); + assert_eq!(G::id().id(), Uid::FIRST_UNIQUE_ID + 6); + }); +} + +fn assert_query_finds_ents( + query: Query<'_, QueryFieldTerms, QueryFieldlessTerms>, + mut expected_ent_ids: Vec, +) where + QueryFieldTerms: QueryTermWithFieldTuple, + QueryFieldlessTerms: QueryTermWithoutFieldTuple, +{ + assert!( + query.iter_with_euids().all(|(ent_id, _)| { + let Some(index) = expected_ent_ids + .iter() + .position(|expected_id| *expected_id == ent_id) + else { + return false; + }; + + expected_ent_ids.remove(index); + + true + }), + "Unexpected entity was found. Expected entities left: {expected_ent_ids:?}" + ); + + assert_eq!( + expected_ent_ids.len(), + 0, + concat!( + "Not all entities expected to be found was found. ", + "Expected entities left: {:?}" + ), + expected_ent_ids + ); +} + +#[test] +fn query_archetype_exists_with_edges_to_next_archetypes() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, B, C)); + let ent_2_id = world.create_entity((A, B, C, D, E)); + let ent_3_id = world.create_entity((A, B, C, E)); + let ent_4_id = world.create_entity((A, B, C, G, F)); + + assert_query_finds_ents( + world.query::<(&A, &B, &C), ()>(), + vec![ent_1_id, ent_2_id, ent_3_id, ent_4_id], + ); +} + +#[test] +fn query_archetype_exists_with_2_comps_diff_to_next_archetype() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, B, C, D, F)); + + let ent_2_id = world.create_entity((A, B, F)); + + assert_query_finds_ents(world.query::<(&A, &B, &F), ()>(), vec![ent_1_id, ent_2_id]); +} + +#[test] +fn query_archetype_exists_with_2_comps_diff_to_next_archetype_rev() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, B, F)); + + let ent_2_id = world.create_entity((A, B, C, D, F)); + + assert_query_finds_ents(world.query::<(&A, &B, &F), ()>(), vec![ent_1_id, ent_2_id]); +} + +#[test] +fn query_archetype_exists_with_3_comps_diff_to_next_archetype() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, B, C, D, E, F)); + + let ent_2_id = world.create_entity((A, B, F)); + + assert_query_finds_ents(world.query::<(&A, &B, &F), ()>(), vec![ent_1_id, ent_2_id]); +} + +#[test] +fn query_archetype_exists_with_3_comps_diff_to_next_archetype_rev() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, B, F)); + + let ent_2_id = world.create_entity((A, B, C, D, E, F)); + + assert_query_finds_ents(world.query::<(&A, &B, &F), ()>(), vec![ent_1_id, ent_2_id]); +} + +#[test] +fn query_archetype_exists_with_4_comps_diff_to_next_archetype() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, B, C, D, E, F, G)); + + let ent_2_id = world.create_entity((A, B, G)); + + assert_query_finds_ents(world.query::<(&A, &B, &G), ()>(), vec![ent_1_id, ent_2_id]); +} + +#[test] +fn query_archetype_exists_with_4_comps_diff_to_next_archetype_rev() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, B, G)); + + let ent_2_id = world.create_entity((A, B, C, D, E, F, G)); + + assert_query_finds_ents(world.query::<(&A, &B, &G), ()>(), vec![ent_1_id, ent_2_id]); +} + +#[test] +fn query_archetype_exists_with_4_comps_diff_to_next_archetype_and_opt_comp() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, B, C, D, E, F, G)); + + let ent_2_id = world.create_entity((A, B, G)); + + assert_query_finds_ents( + world.query::<(&A, Option<&E>, &G), ()>(), + vec![ent_1_id, ent_2_id], + ); +} + +#[test] +fn query_archetype_nonexistant() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + world.create_entity((A, B, C)); + + let ent_2_id = world.create_entity((A, B, C, D, E)); + let ent_3_id = world.create_entity((A, B, C, E)); + + world.create_entity((A, B, C, G, F)); + + assert_query_finds_ents(world.query::<(&A, &E), ()>(), vec![ent_2_id, ent_3_id]); +} + +#[test] +fn query_archetype_nonexistant_and_opt_comp() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + world.create_entity((A, B, C)); + let ent_2_id = world.create_entity((A, B, C, D, E)); + let ent_3_id = world.create_entity((A, B, C, E)); + world.create_entity((A, B, C, G, F)); + + assert_query_finds_ents( + world.query::<(&A, &E, Option<&D>), ()>(), + vec![ent_2_id, ent_3_id], + ); +} + +#[test] +fn query_without_comp_and_archetype_exists() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + let ent_1_id = world.create_entity((A, B, C)); + + world.create_entity((A, B, C, E)); + world.create_entity((A, B, C, F, E)); + + let ent_2_id = world.create_entity((A, B, C, G)); + let ent_3_id = world.create_entity((A, B, C, G, F)); + + assert_query_finds_ents( + world.query::<(&A, &B, &C), (Without,)>(), + vec![ent_1_id, ent_2_id, ent_3_id], + ); +} + +#[test] +fn query_without_required_comp_and_archetype_exists() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + world.create_entity((A, B, C)); + + world.create_entity((A, B, C, E)); + world.create_entity((A, B, C, F, E)); + + world.create_entity((A, B, C, G)); + world.create_entity((A, B, C, G, F)); + + assert_query_finds_ents(world.query::<(&A, &B), (Without,)>(), vec![]); +} + +#[test] +fn query_without_comp_and_archetype_nonexistant() +{ + setup(); + + let _test_lock = TEST_LOCK.lock(); + + let mut world = World::new(); + + world.create_entity((A, B, C)); + + let ent_1_id = world.create_entity((A, B, C, E)); + + world.create_entity((A, B, C, F, E)); + + let ent_2_id = world.create_entity((A, B, C, G, E)); + world.create_entity((A, B, C, G, F, E)); + + assert_query_finds_ents( + world.query::<(&A, &E), (Without,)>(), + 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::builder().relation::().target_id(ent_1_id).build(), + )); + + world.create_entity(( + B, + Pair::builder().relation::().target_id(ent_1_id).build(), + )); + world.create_entity(( + B, + A, + C, + Pair::builder().relation::().target_id(ent_1_id).build(), + )); + + let ent_3_id = world.create_entity(( + B, + Pair::builder().relation::().target_id(ent_2_id).build(), + )); + + let ent_4_id = world.create_entity(( + B, + E, + Pair::builder().relation::().target_as_data(D).build(), + )); + + assert_query_finds_ents( + world.query::<(&B, Pair), ()>(), + 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::builder().relation::().target_id(ent_1_id).build(), + )); + + world.create_entity(( + B, + Pair::builder().relation::().target_id(ent_1_id).build(), + )); + world.create_entity(( + B, + A, + C, + Pair::builder().relation::().target_id(ent_1_id).build(), + )); + + let ent_2_id = world + .create_entity((B, Pair::builder().relation::().target_as_data(F).build())); + + let ent_3_id = world.create_entity(( + B, + E, + Pair::builder().relation::().target_as_data(F).build(), + )); + + assert_query_finds_ents( + world.query::<(&B, Pair), ()>(), + vec![ent_2_id, ent_3_id], + ); +} diff --git a/engine/Cargo.toml b/engine/Cargo.toml index cd4902f..9b91df8 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -15,7 +15,7 @@ parking_lot = "0.12.3" crossbeam-channel = "0.5.15" safer-ffi = "0.1.13" nu-ansi-term = "0.46.0" -ecs = { workspace = true } +engine-ecs = { workspace = true } util-macros = { workspace = true } opengl-bindings = { workspace = true } engine-macros = { workspace = true } diff --git a/engine/src/asset.rs b/engine/src/asset.rs index e9a3831..1ab3cb1 100644 --- a/engine/src/asset.rs +++ b/engine/src/asset.rs @@ -15,10 +15,10 @@ use std::sync::mpsc::{ channel as mpsc_channel, }; -use ecs::pair::{ChildOf, Pair}; -use ecs::phase::{PRE_UPDATE as PRE_UPDATE_PHASE, Phase}; -use ecs::sole::Single; -use ecs::{Sole, declare_entity}; +use crate::ecs::pair::{ChildOf, Pair}; +use crate::ecs::phase::{PRE_UPDATE as PRE_UPDATE_PHASE, Phase}; +use crate::ecs::sole::Single; +use crate::ecs::{Sole, declare_entity}; use crate::work_queue::{Work, WorkQueue}; @@ -843,9 +843,9 @@ pub(crate) struct Extension pub assets: Assets, } -impl ecs::extension::Extension for Extension +impl crate::ecs::extension::Extension for Extension { - fn collect(self, mut collector: ecs::extension::Collector<'_>) + fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) { let _ = collector.add_sole(self.assets); diff --git a/engine/src/camera.rs b/engine/src/camera.rs index fa3d612..22a0702 100644 --- a/engine/src/camera.rs +++ b/engine/src/camera.rs @@ -1,4 +1,4 @@ -use ecs::Component; +use crate::ecs::Component; use crate::projection::{Perspective, Projection}; use crate::vector::Vec3; diff --git a/engine/src/camera/fly.rs b/engine/src/camera/fly.rs index 2042c0c..eddc8ff 100644 --- a/engine/src/camera/fly.rs +++ b/engine/src/camera/fly.rs @@ -1,9 +1,9 @@ -use ecs::component::local::Local; -use ecs::phase::UPDATE as UPDATE_PHASE; -use ecs::sole::Single; -use ecs::system::Into; -use ecs::system::initializable::Initializable; -use ecs::{Component, Query}; +use crate::ecs::component::local::Local; +use crate::ecs::phase::UPDATE as UPDATE_PHASE; +use crate::ecs::sole::Single; +use crate::ecs::system::Into; +use crate::ecs::system::initializable::Initializable; +use crate::ecs::{Component, Query}; use crate::builder; use crate::camera::{Active as ActiveCamera, Camera}; @@ -57,9 +57,9 @@ impl Default for Builder /// Fly camera extension. pub struct Extension(pub Options); -impl ecs::extension::Extension for Extension +impl crate::ecs::extension::Extension for Extension { - fn collect(self, mut collector: ecs::extension::Collector<'_>) + fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) { collector.add_system(*UPDATE_PHASE, update.into_system().initialize((self.0,))); } diff --git a/engine/src/collision.rs b/engine/src/collision.rs index aefd9b6..24cb152 100644 --- a/engine/src/collision.rs +++ b/engine/src/collision.rs @@ -1,4 +1,4 @@ -use ecs::Component; +use crate::ecs::Component; use crate::mesh::Mesh; use crate::vector::Vec3; diff --git a/engine/src/delta_time.rs b/engine/src/delta_time.rs index 33a2fc8..6f75247 100644 --- a/engine/src/delta_time.rs +++ b/engine/src/delta_time.rs @@ -1,8 +1,8 @@ use std::time::{Duration, Instant}; -use ecs::component::local::Local; -use ecs::sole::Single; -use ecs::{Component, Sole}; +use crate::ecs::component::local::Local; +use crate::ecs::sole::Single; +use crate::ecs::{Component, Sole}; #[derive(Debug, Clone, Default, Sole)] pub struct DeltaTime diff --git a/engine/src/draw_flags.rs b/engine/src/draw_flags.rs index 8328669..bd3d714 100644 --- a/engine/src/draw_flags.rs +++ b/engine/src/draw_flags.rs @@ -1,4 +1,4 @@ -use ecs::Component; +use crate::ecs::Component; use crate::builder; diff --git a/engine/src/input.rs b/engine/src/input.rs index 60dd1e7..613f0ad 100644 --- a/engine/src/input.rs +++ b/engine/src/input.rs @@ -1,7 +1,7 @@ -use ecs::declare_entity; -use ecs::extension::Collector as ExtensionCollector; -use ecs::pair::{ChildOf, Pair}; -use ecs::phase::Phase; +use crate::ecs::declare_entity; +use crate::ecs::extension::Collector as ExtensionCollector; +use crate::ecs::pair::{ChildOf, Pair}; +use crate::ecs::phase::Phase; use crate::windowing::PHASE as WINDOWING_PHASE; @@ -23,7 +23,7 @@ declare_entity!( #[derive(Debug, Default)] pub struct Extension {} -impl ecs::extension::Extension for Extension +impl crate::ecs::extension::Extension for Extension { fn collect(self, mut collector: ExtensionCollector<'_>) { diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 0941de0..5249e14 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1,18 +1,17 @@ #![deny(clippy::all, clippy::pedantic)] #![allow(clippy::needless_pass_by_value)] -use ecs::component::Sequence as ComponentSequence; -use ecs::extension::Extension; -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::{DeltaTime, LastUpdate, update as update_delta_time}; +use crate::ecs::component::Sequence as ComponentSequence; +use crate::ecs::extension::Extension; +use crate::ecs::phase::PRE_UPDATE as PRE_UPDATE_PHASE; +use crate::ecs::sole::Sole; +use crate::ecs::system::initializable::Initializable; +use crate::ecs::system::observer::Observer; +use crate::ecs::system::{Into, System}; +use crate::ecs::uid::Uid; +use crate::ecs::{SoleAlreadyExistsError, World}; use crate::shader::Extension as ShaderExtension; mod util; @@ -40,7 +39,7 @@ pub mod texture; pub mod transform; pub mod windowing; -pub extern crate ecs; +pub extern crate engine_ecs as ecs; pub(crate) use crate::data_types::matrix; pub use crate::data_types::{color, vector}; diff --git a/engine/src/lighting.rs b/engine/src/lighting.rs index 9ab2ca8..f1b6570 100644 --- a/engine/src/lighting.rs +++ b/engine/src/lighting.rs @@ -1,4 +1,4 @@ -use ecs::{Component, Sole}; +use crate::ecs::{Component, Sole}; use crate::builder; use crate::color::Color; diff --git a/engine/src/material.rs b/engine/src/material.rs index ed1c139..accb9a1 100644 --- a/engine/src/material.rs +++ b/engine/src/material.rs @@ -1,4 +1,4 @@ -use ecs::Component; +use crate::ecs::Component; use crate::asset::Handle as AssetHandle; use crate::builder; diff --git a/engine/src/model.rs b/engine/src/model.rs index ebf623f..0aaa877 100644 --- a/engine/src/model.rs +++ b/engine/src/model.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use std::collections::HashMap; -use ecs::Component; +use crate::ecs::Component; use crate::asset::{Assets, Handle as AssetHandle}; use crate::material::Material; diff --git a/engine/src/renderer.rs b/engine/src/renderer.rs index b7bb2db..a302b8e 100644 --- a/engine/src/renderer.rs +++ b/engine/src/renderer.rs @@ -2,17 +2,17 @@ use std::collections::VecDeque; use std::sync::atomic::{AtomicU64, Ordering}; use bitflags::bitflags; -use ecs::actions::Actions; -use ecs::component::local::Local; -use ecs::event::component::{Changed, EventMatchExt, Removed}; -use ecs::pair::{ChildOf, Pair}; -use ecs::phase::{POST_UPDATE as POST_UPDATE_PHASE, Phase}; -use ecs::query::term::With; -use ecs::sole::Single; -use ecs::system::Into; -use ecs::system::initializable::Initializable; -use ecs::system::observer::Observe; -use ecs::{Component, Query, Sole, declare_entity}; +use crate::ecs::actions::Actions; +use crate::ecs::component::local::Local; +use crate::ecs::event::component::{Changed, EventMatchExt, Removed}; +use crate::ecs::pair::{ChildOf, Pair}; +use crate::ecs::phase::{POST_UPDATE as POST_UPDATE_PHASE, Phase}; +use crate::ecs::query::term::With; +use crate::ecs::sole::Single; +use crate::ecs::system::Into; +use crate::ecs::system::initializable::Initializable; +use crate::ecs::system::observer::Observe; +use crate::ecs::{Component, Query, Sole, declare_entity}; use engine_macros::Reflection; use crate::asset::Handle as AssetHandle; @@ -83,9 +83,9 @@ impl Extension } } -impl ecs::extension::Extension for Extension +impl crate::ecs::extension::Extension for Extension { - fn collect(self, mut collector: ecs::extension::Collector<'_>) + fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) { collector.add_declared_entity(&PRE_RENDER_PHASE); collector.add_declared_entity(&RENDER_PHASE); diff --git a/engine/src/renderer/main_render_pass.rs b/engine/src/renderer/main_render_pass.rs index 7492379..abd1022 100644 --- a/engine/src/renderer/main_render_pass.rs +++ b/engine/src/renderer/main_render_pass.rs @@ -1,6 +1,6 @@ -use ecs::Query; -use ecs::query::term::{With, Without}; -use ecs::sole::Single; +use crate::ecs::Query; +use crate::ecs::query::term::{With, Without}; +use crate::ecs::sole::Single; use crate::asset::Assets; use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; diff --git a/engine/src/renderer/object.rs b/engine/src/renderer/object.rs index bdff885..8441959 100644 --- a/engine/src/renderer/object.rs +++ b/engine/src/renderer/object.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::sync::atomic::{AtomicU64, Ordering}; -use ecs::Sole; +use crate::ecs::Sole; use crate::asset::Id as AssetId; diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs index 378a89d..dcdd6a5 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -3,10 +3,10 @@ use std::borrow::Cow; use std::collections::HashMap; -use ecs::actions::Actions; -use ecs::query::term::Without; -use ecs::sole::Single; -use ecs::{Component, Query, Sole}; +use crate::ecs::actions::Actions; +use crate::ecs::query::term::Without; +use crate::ecs::sole::Single; +use crate::ecs::{Component, Query, Sole}; use glutin::config::Config as GlutinConfig; use glutin::display::GetGlDisplay; use glutin::error::Error as GlutinError; @@ -164,9 +164,9 @@ enum GraphicsContextObject #[non_exhaustive] pub struct Extension {} -impl ecs::extension::Extension for Extension +impl crate::ecs::extension::Extension for Extension { - fn collect(self, mut collector: ecs::extension::Collector<'_>) + fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) { collector.add_system(*RENDER_PHASE, handle_commands); diff --git a/engine/src/shader.rs b/engine/src/shader.rs index 1b368fb..d5611ca 100644 --- a/engine/src/shader.rs +++ b/engine/src/shader.rs @@ -6,10 +6,10 @@ use std::path::Path; use std::str::Utf8Error; use bitflags::{bitflags, bitflags_match}; -use ecs::pair::{ChildOf, Pair}; -use ecs::phase::{POST_UPDATE as POST_UPDATE_PHASE, Phase, START as START_PHASE}; -use ecs::sole::Single; -use ecs::{Component, Sole, declare_entity}; +use crate::ecs::pair::{ChildOf, Pair}; +use crate::ecs::phase::{POST_UPDATE as POST_UPDATE_PHASE, Phase, START as START_PHASE}; +use crate::ecs::sole::Single; +use crate::ecs::{Component, Sole, declare_entity}; use shader_slang::{ Blob as SlangBlob, ComponentType as SlangComponentType, @@ -972,9 +972,9 @@ declare_entity!( pub(crate) struct Extension; -impl ecs::extension::Extension for Extension +impl crate::ecs::extension::Extension for Extension { - fn collect(self, mut collector: ecs::extension::Collector<'_>) + fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) { let Some(global_session) = SlangGlobalSession::new() else { tracing::error!("Unable to create global shader-slang session"); diff --git a/engine/src/shader/default.rs b/engine/src/shader/default.rs index 7f0d1bb..c217af6 100644 --- a/engine/src/shader/default.rs +++ b/engine/src/shader/default.rs @@ -1,10 +1,10 @@ use std::path::Path; use std::sync::LazyLock; -use ecs::Query; -use ecs::actions::Actions; -use ecs::query::term::Without; -use ecs::sole::Single; +use crate::ecs::Query; +use crate::ecs::actions::Actions; +use crate::ecs::query::term::Without; +use crate::ecs::sole::Single; use crate::asset::{Assets, Label as AssetLabel}; use crate::camera::{Active as ActiveCamera, Camera}; diff --git a/engine/src/transform.rs b/engine/src/transform.rs index 05819bc..e80bd00 100644 --- a/engine/src/transform.rs +++ b/engine/src/transform.rs @@ -1,4 +1,4 @@ -use ecs::Component; +use crate::ecs::Component; use crate::builder; use crate::vector::Vec3; diff --git a/engine/src/util.rs b/engine/src/util.rs index f18a9c7..439fe9a 100644 --- a/engine/src/util.rs +++ b/engine/src/util.rs @@ -1,4 +1,4 @@ -use ecs::util::VecExt; +use crate::ecs::util::VecExt; #[derive(Debug)] pub struct MapVec diff --git a/engine/src/windowing.rs b/engine/src/windowing.rs index e07ba3e..e34aaf0 100644 --- a/engine/src/windowing.rs +++ b/engine/src/windowing.rs @@ -8,16 +8,16 @@ use crossbeam_channel::{ TrySendError, bounded as bounded_channel, }; -use ecs::actions::Actions; -use ecs::component::Component; -use ecs::entity::obtainer::Obtainer as EntityObtainer; -use ecs::event::component::{Added, Changed, EventMatchExt, Removed}; -use ecs::pair::{ChildOf, Pair}; -use ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; -use ecs::sole::Single; -use ecs::system::observer::Observe; -use ecs::uid::Uid; -use ecs::{Query, Sole, declare_entity}; +use crate::ecs::actions::Actions; +use crate::ecs::component::Component; +use crate::ecs::entity::obtainer::Obtainer as EntityObtainer; +use crate::ecs::event::component::{Added, Changed, EventMatchExt, Removed}; +use crate::ecs::pair::{ChildOf, Pair}; +use crate::ecs::phase::{Phase, UPDATE as UPDATE_PHASE}; +use crate::ecs::sole::Single; +use crate::ecs::system::observer::Observe; +use crate::ecs::uid::Uid; +use crate::ecs::{Query, Sole, declare_entity}; use raw_window_handle::{DisplayHandle, HandleError, HasDisplayHandle, WindowHandle}; use winit::application::ApplicationHandler; use winit::dpi::PhysicalPosition; @@ -74,9 +74,9 @@ declare_entity!( #[non_exhaustive] pub struct Extension {} -impl ecs::extension::Extension for Extension +impl crate::ecs::extension::Extension for Extension { - fn collect(self, mut collector: ecs::extension::Collector<'_>) + fn collect(self, mut collector: crate::ecs::extension::Collector<'_>) { collector.add_sole(Context::default()).ok(); collector.add_sole(Keyboard::default()).ok(); diff --git a/engine/src/windowing/keyboard.rs b/engine/src/windowing/keyboard.rs index a1c3e22..0bb70a7 100644 --- a/engine/src/windowing/keyboard.rs +++ b/engine/src/windowing/keyboard.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use ecs::Sole; +use crate::ecs::Sole; #[derive(Debug, Default, Sole)] pub struct Keyboard diff --git a/engine/src/windowing/mouse.rs b/engine/src/windowing/mouse.rs index 0ea04e2..2a3324e 100644 --- a/engine/src/windowing/mouse.rs +++ b/engine/src/windowing/mouse.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use ecs::Sole; +use crate::ecs::Sole; use crate::vector::Vec2; diff --git a/engine/src/windowing/window.rs b/engine/src/windowing/window.rs index 627bdec..72c6342 100644 --- a/engine/src/windowing/window.rs +++ b/engine/src/windowing/window.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use ecs::Component; +use crate::ecs::Component; use crate::data_types::dimens::Dimens; -- cgit v1.2.3-18-g5258