Compare commits

...

4 Commits

32 changed files with 638 additions and 294 deletions

85
Cargo.lock generated
View File

@ -319,12 +319,6 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "atomicell"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "157342dd84c64f16899b4b16c1fb2cce54b887990362aac3c590b3d13810890f"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -690,42 +684,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "edict"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d85bf7cde5687ce04b093bfe183453fa12996b6bdfd2567a0262ebd621761d77"
dependencies = [
"atomicell",
"edict-proc",
"hashbrown 0.13.2",
"parking_lot",
"smallvec",
"tiny-fn",
]
[[package]]
name = "edict-proc"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c94d80dc0f05250a9082bb9455bbf3d6c6c51db388b060df914aebcfb4a9b9f1"
dependencies = [
"edict-proc-lib",
"syn 2.0.48",
]
[[package]]
name = "edict-proc-lib"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52d98f9931a4f71c7eb7d85cf4ef1271b27014625c85a65376a52c10ac4ffaea"
dependencies = [
"proc-easy",
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.9.0" version = "1.9.0"
@ -1149,15 +1107,6 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.3" version = "0.14.3"
@ -1504,8 +1453,10 @@ dependencies = [
"anyhow", "anyhow",
"lyra-ecs-derive", "lyra-ecs-derive",
"lyra-math", "lyra-math",
"paste",
"rand", "rand",
"thiserror", "thiserror",
"unique",
] ]
[[package]] [[package]]
@ -1569,6 +1520,7 @@ dependencies = [
"lyra-ecs", "lyra-ecs",
"lyra-math", "lyra-math",
"lyra-reflect-derive", "lyra-reflect-derive",
"lyra-resource",
] ]
[[package]] [[package]]
@ -1587,11 +1539,11 @@ dependencies = [
"anyhow", "anyhow",
"base64 0.21.5", "base64 0.21.5",
"crossbeam", "crossbeam",
"edict",
"glam", "glam",
"gltf", "gltf",
"image", "image",
"infer", "infer",
"lyra-ecs",
"mime", "mime",
"notify", "notify",
"notify-debouncer-full", "notify-debouncer-full",
@ -2035,6 +1987,12 @@ dependencies = [
"windows-targets 0.48.5", "windows-targets 0.48.5",
] ]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -2125,17 +2083,6 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-easy"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea59c637cd0e6b71ae18e589854e9de9b7cb17fefdbf2047e42bd38e24285b19"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.3.1" version = "1.3.1"
@ -2595,12 +2542,6 @@ dependencies = [
"time-core", "time-core",
] ]
[[package]]
name = "tiny-fn"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af7b2c33e09916c65a15c92c1e583946052527e06102689ed11c6125f64fa8ba"
[[package]] [[package]]
name = "tiny-skia" name = "tiny-skia"
version = "0.8.4" version = "0.8.4"
@ -2747,6 +2688,12 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unique"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d360722e1f3884f5b14d332185f02ff111f771f0c76a313268fe6af1409aba96"
[[package]] [[package]]
name = "urlencoding" name = "urlencoding"
version = "2.1.3" version = "2.1.3"

View File

@ -206,7 +206,7 @@ async fn main() {
Ok(()) Ok(())
}; };
let fps_plugin = move |game: &mut Game| { let fps_plugin = move |game: &mut Game| {
let world = game.world(); let world = game.world_mut();
world.add_resource(fps_counter::FPSCounter::new()); world.add_resource(fps_counter::FPSCounter::new());
}; };
@ -228,7 +228,7 @@ async fn main() {
}; };
let jiggle_plugin = move |game: &mut Game| { let jiggle_plugin = move |game: &mut Game| {
game.world().add_resource(TpsAccumulator(0.0)); game.world_mut().add_resource(TpsAccumulator(0.0));
let mut sys = BatchedSystem::new(); let mut sys = BatchedSystem::new();
sys.with_criteria(FixedTimestep::new(45)); sys.with_criteria(FixedTimestep::new(45));
@ -293,7 +293,7 @@ async fn main() {
Ok(()) Ok(())
}; */ }; */
let world = game.world(); let world = game.world_mut();
world.add_resource(action_handler); world.add_resource(action_handler);
world.spawn((Vec3::new(0.5, 0.1, 3.0),)); world.spawn((Vec3::new(0.5, 0.1, 3.0),));
game.with_plugin(InputActionPlugin); game.with_plugin(InputActionPlugin);
@ -303,7 +303,7 @@ async fn main() {
let script_test_plugin = |game: &mut Game| { let script_test_plugin = |game: &mut Game| {
game.with_plugin(LuaScriptingPlugin); game.with_plugin(LuaScriptingPlugin);
let world = game.world(); let world = game.world_mut();
let mut res_man = world.get_resource_mut::<ResourceManager>(); let mut res_man = world.get_resource_mut::<ResourceManager>();
let script = res_man.request::<LuaScript>("scripts/test.lua").unwrap(); let script = res_man.request::<LuaScript>("scripts/test.lua").unwrap();
res_man.watch("scripts/test.lua", false).unwrap(); res_man.watch("scripts/test.lua", false).unwrap();

View File

@ -13,6 +13,8 @@ lyra-ecs-derive = { path = "./lyra-ecs-derive" }
lyra-math = { path = "../lyra-math", optional = true } lyra-math = { path = "../lyra-math", optional = true }
anyhow = "1.0.75" anyhow = "1.0.75"
thiserror = "1.0.50" thiserror = "1.0.50"
unique = "0.9.1"
paste = "1.0.14"
[dev-dependencies] [dev-dependencies]
rand = "0.8.5" # used for tests rand = "0.8.5" # used for tests

View File

@ -1,6 +1,6 @@
use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::{DerefMut, Deref}}; use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::{DerefMut, Deref}};
use crate::{world::{Entity, ArchetypeEntityId}, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick}; use crate::{world::ArchetypeEntityId, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick, Entity};
#[derive(Clone)] #[derive(Clone)]
pub struct ComponentColumn { pub struct ComponentColumn {
@ -419,7 +419,7 @@ mod tests {
use rand::Rng; use rand::Rng;
use crate::{tests::{Vec2, Vec3}, world::{Entity, EntityId}, bundle::Bundle, ComponentInfo, MemoryLayout, DynTypeId, DynamicBundle, Tick}; use crate::{bundle::Bundle, tests::{Vec2, Vec3}, ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, MemoryLayout, Tick};
use super::Archetype; use super::Archetype;

114
lyra-ecs/src/command.rs Normal file
View File

@ -0,0 +1,114 @@
use std::{cell::RefMut, collections::VecDeque, mem, ptr::{self, NonNull}};
use unique::Unique;
use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World};
pub trait Command {
fn run(self, world: &mut World) -> anyhow::Result<()>;
}
impl<F> Command for F
where
F: FnOnce(&mut World) -> anyhow::Result<()>
{
fn run(self, world: &mut World) -> anyhow::Result<()> {
self(world)
}
}
type RunCommand = unsafe fn(cmd: Unique<()>, world: &mut World) -> anyhow::Result<()>;
#[derive(Default)]
pub struct CommandQueue(VecDeque<(RunCommand, Unique<()>)>);
pub struct Commands<'a, 'b> {
queue: &'b mut CommandQueue,
entities: &'a mut Entities,
}
impl<'a, 'b> Commands<'a, 'b> {
pub fn new(queue: &'b mut CommandQueue, world: &'a mut World) -> Self {
Self {
queue,
entities: &mut world.entities,
}
}
/// Add a command to the end of the command queue
pub fn add<C: Command>(&mut self, mut cmd: C) {
// get an owned pointer to the command, then forget it to ensure its destructor isn't ran
let ptr = Unique::from(&mut cmd).cast::<()>();
mem::forget(cmd);
let run_fn = |cmd_ptr: Unique<()>, world: &mut World| unsafe {
let cmd = cmd_ptr.cast::<C>();
let cmd = ptr::read(cmd.as_ptr());
cmd.run(world)?;
Ok(())
};
self.queue.0.push_back((run_fn, ptr));
}
pub fn spawn<B: Bundle>(&mut self, mut bundle: B) -> Entity {
let e = self.entities.reserve();
let bundle_ptr = Unique::from(&mut bundle);
mem::forget(bundle);
//let bundle_box = Box::new(bundle);
self.add(move |world: &mut World| {
let bundle = unsafe { ptr::read(bundle_ptr.as_ptr()) };
world.spawn_into(e, bundle);
Ok(())
});
e
}
/// Execute all commands in the queue, in order of insertion
pub fn execute(&mut self, world: &mut World) -> anyhow::Result<()> {
while let Some((cmd_fn, cmd_ptr)) = self.queue.0.pop_front() {
unsafe {
cmd_fn(cmd_ptr, world)?;
}
}
Ok(())
}
}
impl FnArgFetcher for Commands<'_, '_> {
type State = CommandQueue;
type Arg<'a, 'state> = Commands<'a, 'state>;
fn world_access(&self) -> Access {
Access::Write
}
unsafe fn get<'a, 'state>(state: &'state mut Self::State, mut world_ptr: ptr::NonNull<World>) -> Self::Arg<'a, 'state> {
let world = world_ptr.as_mut();
Commands::new(state, world)
}
fn create_state(_: NonNull<World>) -> Self::State {
CommandQueue::default()
}
fn apply_deferred<'a>(mut state: Self::State, mut world_ptr: NonNull<World>) {
let world = unsafe { world_ptr.as_mut() };
let mut cmds = Commands::new(&mut state, world);
// safety: Commands has a mut borrow to entities in the world
let world = unsafe { world_ptr.as_mut() };
cmds.execute(world).unwrap()
}
}
pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut<Commands>) -> anyhow::Result<()> {
commands.execute(world)?;
Ok(())
}

58
lyra-ecs/src/entity.rs Normal file
View File

@ -0,0 +1,58 @@
use std::collections::{HashMap, VecDeque};
use crate::Record;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct EntityId(pub u64);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Entity {
pub(crate) id: EntityId,
pub(crate) generation: u64,
}
pub struct Entities {
pub(crate) arch_index: HashMap<EntityId, Record>,
dead: VecDeque<Entity>,
next_id: EntityId,
}
impl Default for Entities {
fn default() -> Self {
Self {
arch_index: Default::default(),
dead: Default::default(),
next_id: EntityId(0),
}
}
}
impl Entities {
pub fn reserve(&mut self) -> Entity {
match self.dead.pop_front() {
Some(mut e) => {
e.generation += 1;
e
}
None => {
println!("id is {}", self.next_id.0);
let new_id = self.next_id;
self.next_id.0 += 1;
Entity {
id: new_id,
generation: 0,
}
}
}
}
/// Retrieves the Archetype Record for the entity
pub(crate) fn entity_record(&self, entity: Entity) -> Option<Record> {
self.arch_index.get(&entity.id).cloned()
}
pub(crate) fn insert_entity_record(&mut self, entity: Entity, record: Record) {
self.arch_index.insert(entity.id, record);
}
}

View File

@ -8,11 +8,19 @@ pub(crate) mod lyra_engine {
} }
pub mod archetype; pub mod archetype;
use std::ops::BitOr;
pub use archetype::*; pub use archetype::*;
pub mod entity;
pub use entity::*;
pub mod world; pub mod world;
pub use world::*; pub use world::*;
pub mod command;
pub use command::*;
pub mod bundle; pub mod bundle;
pub use bundle::*; pub use bundle::*;
@ -49,3 +57,17 @@ pub enum Access {
Read, Read,
Write, Write,
} }
impl BitOr for Access {
type Output = Access;
fn bitor(self, rhs: Self) -> Self::Output {
if self == Access::Write || rhs == Access::Write {
Access::Write
} else if self == Access::Read || rhs == Access::Read {
Access::Read
} else {
Access::None
}
}
}

View File

@ -221,7 +221,7 @@ impl<T: Component> AsQuery for &mut T {
mod tests { mod tests {
use std::{mem::size_of, marker::PhantomData, ptr::NonNull}; use std::{mem::size_of, marker::PhantomData, ptr::NonNull};
use crate::{world::{World, Entity, EntityId}, archetype::{Archetype, ArchetypeId}, query::{View, Fetch}, tests::Vec2, bundle::Bundle, DynTypeId, Tick}; use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{Fetch, View}, tests::Vec2, world::World, DynTypeId, Entity, EntityId, Tick};
use super::{QueryBorrow, QueryBorrowMut, FetchBorrowMut}; use super::{QueryBorrow, QueryBorrowMut, FetchBorrowMut};

View File

@ -1,4 +1,4 @@
use crate::{world::{Entity, World}, archetype::Archetype}; use crate::{archetype::Archetype, world::World, Entity};
use super::{Fetch, Query, AsQuery}; use super::{Fetch, Query, AsQuery};

View File

@ -131,7 +131,7 @@ impl<'a, Q: Query> ViewOne<'a, Q> {
} }
pub fn get(&self) -> Option<Q::Item<'a>> { pub fn get(&self) -> Option<Q::Item<'a>> {
if let Some(record) = self.world.entity_index.get(&self.entity) { if let Some(record) = self.world.entities.arch_index.get(&self.entity) {
let arch = self.world.archetypes.get(&record.id) let arch = self.world.archetypes.get(&record.id)
.expect("An invalid record was specified for an entity"); .expect("An invalid record was specified for an entity");

View File

@ -79,6 +79,10 @@ impl System for BatchedSystem {
Ok(()) Ok(())
} }
fn execute_deferred(&mut self, _: std::ptr::NonNull<World>) -> anyhow::Result<()> {
todo!()
}
} }
impl IntoSystem<()> for BatchedSystem { impl IntoSystem<()> for BatchedSystem {

View File

@ -1,13 +1,19 @@
use std::{ptr::NonNull, marker::PhantomData}; use std::{ptr::NonNull, marker::PhantomData};
use unique::Unique;
use paste::paste;
use crate::{world::World, Access, ResourceObject, query::{Query, View, AsQuery, ResMut, Res}}; use crate::{world::World, Access, ResourceObject, query::{Query, View, AsQuery, ResMut, Res}};
use super::{System, IntoSystem}; use super::{System, IntoSystem};
pub trait FnArgFetcher { pub trait FnArgFetcher {
type Arg<'a>: FnArg<Fetcher = Self>; /// stores data that persists after an execution of a system
type State: 'static;
fn new() -> Self; type Arg<'a, 'state>: FnArgFetcher<State = Self::State>;
//fn new() -> Self;
fn create_state(world: NonNull<World>) -> Self::State;
/// Return the appropriate world access if this fetcher gets the world directly. /// Return the appropriate world access if this fetcher gets the world directly.
/// Return [`Access::None`] if you're only fetching components, or resources. /// Return [`Access::None`] if you're only fetching components, or resources.
@ -20,7 +26,10 @@ pub trait FnArgFetcher {
/// # Safety /// # Safety
/// The system executor must ensure that on execution of the system, it will be safe to /// The system executor must ensure that on execution of the system, it will be safe to
/// borrow the world. /// borrow the world.
unsafe fn get<'a>(&mut self, world: NonNull<World>) -> Self::Arg<'a>; unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state>;
/// Apply some action after the system was ran.
fn apply_deferred(state: Self::State, world: NonNull<World>);
} }
pub trait FnArg { pub trait FnArg {
@ -29,8 +38,10 @@ pub trait FnArg {
pub struct FnSystem<F, Args> { pub struct FnSystem<F, Args> {
inner: F, inner: F,
#[allow(dead_code)] //#[allow(dead_code)]
args: Args, //args: Args,
arg_state: Option<Vec<Unique<()>>>,
_marker: PhantomData<Args>,
} }
macro_rules! impl_fn_system_tuple { macro_rules! impl_fn_system_tuple {
@ -38,47 +49,87 @@ macro_rules! impl_fn_system_tuple {
#[allow(non_snake_case)] #[allow(non_snake_case)]
impl<F, $($name: FnArgFetcher,)+> System for FnSystem<F, ($($name,)+)> impl<F, $($name: FnArgFetcher,)+> System for FnSystem<F, ($($name,)+)>
where where
F: for<'a> FnMut($($name::Arg<'a>,)+) -> anyhow::Result<()>, F: for<'a> FnMut($($name::Arg<'a, '_>,)+) -> anyhow::Result<()>,
{ {
fn world_access(&self) -> Access { fn world_access(&self) -> Access {
todo!() todo!()
} }
fn execute(&mut self, world: NonNull<World>) -> anyhow::Result<()> { fn execute(&mut self, world: NonNull<World>) -> anyhow::Result<()> {
$(let $name = unsafe { $name::new().get(world) };)+ unsafe {
paste! {
$(
// get the arg fetcher, create its state, and get the arg
(self.inner)($($name,)+)?; let mut [<state_ $name:lower>]: $name::State = $name::create_state(world);
let [<$name:lower>] = $name::get(&mut [<state_ $name:lower>], world);
)+
(self.inner)($( [<$name:lower>] ),+)?;
let mut state = Vec::new();
$(
// type erase the now modified state, and store it
let [<state_ $name:lower _ptr>] = Unique::from(&mut [<state_ $name:lower>]);
std::mem::forget([<state_ $name:lower>]);
state.push([<state_ $name:lower _ptr>].cast::<()>());
)+
self.arg_state = Some(state);
}
Ok(()) Ok(())
} }
} }
/* impl<F, $($name: FnArg,)+> IntoSystem for F fn execute_deferred(&mut self, world: NonNull<World>) -> anyhow::Result<()> {
$(
let s = self.arg_state.as_mut().expect("Somehow there was no state").pop().unwrap();
let s = unsafe { std::ptr::read(s.cast::<$name::State>().as_ptr()) };
$name::apply_deferred(s, world);
)+
Ok(())
}
}
/* impl<F, $($name: FnArg,)+> IntoSystem<($($name,)+)> for F
where where
/* for <'a> &'a mut F:
FnMut($($name,)+) -> anyhow::Result<()>,
FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a, '_>,)+) -> anyhow::Result<()>, */
F: FnMut($($name,)+) -> anyhow::Result<()>, F: FnMut($($name,)+) -> anyhow::Result<()>,
F: for<'a> FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a>,)+) -> anyhow::Result<()>, F: for<'a> FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a, '_>,)+) -> anyhow::Result<()>,
{ {
type System = FnSystem<F, ($($name::Fetcher,)+)>; type System = FnSystem<F, ($($name::Fetcher,)+)>;
fn into_system(self) -> Self::System { fn into_system(self) -> Self::System {
FnSystem { FnSystem {
args: ($($name::Fetcher::new(),)+), //args: ($($name::Fetcher::new(),)+),
inner: self inner: self,
arg_state: None,
_marker: PhantomData::<($($name::Fetcher,)+)>::default(),
} }
} }
} */ } */
impl<F, $($name: FnArg,)+> IntoSystem<($($name,)+)> for F impl<F, $($name: FnArgFetcher,)+> IntoSystem<($($name,)+)> for F
where where
/* for <'a> &'a mut F:
FnMut($($name,)+) -> anyhow::Result<()>,
FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a, '_>,)+) -> anyhow::Result<()>, */
F: FnMut($($name,)+) -> anyhow::Result<()>, F: FnMut($($name,)+) -> anyhow::Result<()>,
F: for<'a> FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a>,)+) -> anyhow::Result<()>, F: for<'a> FnMut($($name::Arg<'a, '_>,)+) -> anyhow::Result<()>,
{ {
type System = FnSystem<F, ($($name::Fetcher,)+)>; type System = FnSystem<F, ($($name,)+)>;
fn into_system(self) -> Self::System { fn into_system(self) -> Self::System {
FnSystem { FnSystem {
args: ($($name::Fetcher::new(),)+), //args: ($($name::Fetcher::new(),)+),
inner: self inner: self,
arg_state: None,
_marker: PhantomData::<($($name,)+)>::default(),
} }
} }
} }
@ -103,127 +154,116 @@ impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O }
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P } impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P }
/// An ArgFetcher implementation for query [`View`]s /// An ArgFetcher implementation for query [`View`]s
pub struct ViewArgFetcher<Q: AsQuery> { /* pub struct ViewArgFetcher<Q: AsQuery> {
query: Q::Query query: Q::Query
} }
impl<'a, Q: AsQuery> FnArg for View<'a, Q> { impl<'a, Q: AsQuery> FnArg for View<'a, Q> {
type Fetcher = ViewArgFetcher<Q>; type Fetcher = ViewArgFetcher<Q>;
} } */
impl<Q: AsQuery> FnArgFetcher for ViewArgFetcher<Q> { impl<'c, Q> FnArgFetcher for View<'c, Q>
type Arg<'a> = View<'a, Q>; where
Q: AsQuery,
fn new() -> Self { <Q as AsQuery>::Query: 'static
ViewArgFetcher { {
query: <Q::Query as Query>::new(), type State = Q::Query;
} type Arg<'a, 'state> = View<'a, Q>;
}
fn world_access(&self) -> Access { fn world_access(&self) -> Access {
todo!() todo!()
} }
unsafe fn get<'a>(&mut self, world: NonNull<World>) -> Self::Arg<'a> { unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
let world = &*world.as_ptr(); let world = &*world.as_ptr();
let arch = world.archetypes.values().collect(); let arch = world.archetypes.values().collect();
let v = View::new(world, self.query, arch); let v = View::new(world, state.clone(), arch);
v v
} }
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
fn create_state(_: NonNull<World>) -> Self::State {
<Q::Query as Query>::new()
}
} }
/// An ArgFetcher implementation for borrowing the [`World`]. /// An ArgFetcher implementation for borrowing the [`World`].
pub struct WorldArgFetcher; /* pub struct WorldArgFetcher;
impl<'a> FnArg for &'a World { impl<'a> FnArg for &'a World {
type Fetcher = WorldArgFetcher; type Fetcher = WorldArgFetcher;
} } */
impl FnArgFetcher for WorldArgFetcher { impl FnArgFetcher for &'_ World {
type Arg<'a> = &'a World; type State = ();
type Arg<'a, 'state> = &'a World;
fn new() -> Self {
WorldArgFetcher
}
fn world_access(&self) -> Access { fn world_access(&self) -> Access {
Access::Read Access::Read
} }
unsafe fn get<'a>(&mut self, world: NonNull<World>) -> Self::Arg<'a> { unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
&*world.as_ptr() &*world.as_ptr()
} }
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
fn create_state(_: NonNull<World>) -> Self::State { () }
} }
/// An ArgFetcher implementation for mutably borrowing the [`World`]. impl FnArgFetcher for &'_ mut World {
pub struct WorldMutArgFetcher; type State = ();
type Arg<'a, 'state> = &'a mut World;
impl<'a> FnArg for &'a mut World {
type Fetcher = WorldMutArgFetcher;
}
impl FnArgFetcher for WorldMutArgFetcher {
type Arg<'a> = &'a mut World;
fn new() -> Self {
WorldMutArgFetcher
}
fn world_access(&self) -> Access { fn world_access(&self) -> Access {
Access::Write Access::Write
} }
unsafe fn get<'a>(&mut self, world: NonNull<World>) -> Self::Arg<'a> { unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
&mut *world.as_ptr() &mut *world.as_ptr()
} }
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
fn create_state(_: NonNull<World>) -> Self::State { () }
} }
pub struct ResourceArgFetcher<R: ResourceObject> { /* pub struct ResourceArgFetcher<R: ResourceObject> {
phantom: PhantomData<fn() -> R> phantom: PhantomData<fn() -> R>
} }
impl<'a, R: ResourceObject> FnArg for Res<'a, R> { impl<'a, R: ResourceObject> FnArg for Res<'a, R> {
type Fetcher = ResourceArgFetcher<R>; type Fetcher = ResourceArgFetcher<R>;
} } */
impl<R: ResourceObject> FnArgFetcher for ResourceArgFetcher<R> { impl<R: ResourceObject> FnArgFetcher for Res<'_, R> {
type Arg<'a> = Res<'a, R>; type State = ();
type Arg<'a, 'state> = Res<'a, R>;
fn new() -> Self { unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
ResourceArgFetcher {
phantom: PhantomData
}
}
unsafe fn get<'a>(&mut self, world: NonNull<World>) -> Self::Arg<'a> {
let world = world.as_ref(); let world = world.as_ref();
Res(world.get_resource::<R>()) Res(world.get_resource::<R>())
} }
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
fn create_state(_: NonNull<World>) -> Self::State { () }
} }
pub struct ResourceMutArgFetcher<R: ResourceObject> { impl<R: ResourceObject> FnArgFetcher for ResMut<'_, R> {
phantom: PhantomData<fn() -> R> type State = ();
} type Arg<'a, 'state> = ResMut<'a, R>;
impl<'a, R: ResourceObject> FnArg for ResMut<'a, R> { unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
type Fetcher = ResourceMutArgFetcher<R>;
}
impl<R: ResourceObject> FnArgFetcher for ResourceMutArgFetcher<R> {
type Arg<'a> = ResMut<'a, R>;
fn new() -> Self {
ResourceMutArgFetcher {
phantom: PhantomData
}
}
unsafe fn get<'a>(&mut self, world: NonNull<World>) -> Self::Arg<'a> {
let world = world.as_ref(); let world = world.as_ref();
ResMut(world.get_resource_mut::<R>()) ResMut(world.get_resource_mut::<R>())
} }
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
fn create_state(_: NonNull<World>) -> Self::State { () }
} }
#[cfg(test)] #[cfg(test)]

View File

@ -9,7 +9,9 @@ pub enum GraphExecutorError {
#[error("could not find a system's dependency named `{0}`")] #[error("could not find a system's dependency named `{0}`")]
MissingSystem(String), MissingSystem(String),
#[error("system `{0}` returned with an error: `{1}`")] #[error("system `{0}` returned with an error: `{1}`")]
SystemError(String, anyhow::Error) SystemError(String, anyhow::Error),
#[error("a command returned with an error: `{0}`")]
Command(anyhow::Error)
} }
/// A single system in the graph. /// A single system in the graph.
@ -56,7 +58,7 @@ impl GraphExecutor {
} }
/// Executes the systems in the graph /// Executes the systems in the graph
pub fn execute(&mut self, world: NonNull<World>, stop_on_error: bool) -> Result<Vec<GraphExecutorError>, GraphExecutorError> { pub fn execute(&mut self, world_ptr: NonNull<World>, stop_on_error: bool) -> Result<Vec<GraphExecutorError>, GraphExecutorError> {
let mut stack = VecDeque::new(); let mut stack = VecDeque::new();
let mut visited = HashSet::new(); let mut visited = HashSet::new();
@ -69,7 +71,7 @@ impl GraphExecutor {
while let Some(node) = stack.pop_front() { while let Some(node) = stack.pop_front() {
let system = self.systems.get_mut(node.as_str()).unwrap(); let system = self.systems.get_mut(node.as_str()).unwrap();
if let Err(e) = system.system.execute(world) if let Err(e) = system.system.execute(world_ptr)
.map_err(|e| GraphExecutorError::SystemError(node, e)) { .map_err(|e| GraphExecutorError::SystemError(node, e)) {
if stop_on_error { if stop_on_error {
return Err(e); return Err(e);
@ -78,6 +80,17 @@ impl GraphExecutor {
possible_errors.push(e); possible_errors.push(e);
unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error
} }
if let Err(e) = system.system.execute_deferred(world_ptr)
.map_err(|e| GraphExecutorError::Command(e)) {
if stop_on_error {
return Err(e);
}
possible_errors.push(e);
unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error
}
} }
Ok(possible_errors) Ok(possible_errors)

View File

@ -27,6 +27,8 @@ pub trait System {
let _ = world; let _ = world;
Ok(()) Ok(())
} }
fn execute_deferred(&mut self, world: NonNull<World>) -> anyhow::Result<()>;
} }
pub trait IntoSystem<T> { pub trait IntoSystem<T> {

View File

@ -1,21 +1,12 @@
use std::{collections::{HashMap, VecDeque}, any::TypeId, cell::{Ref, RefMut}, ptr::NonNull}; use std::{any::TypeId, cell::{Ref, RefMut}, collections::HashMap, ptr::NonNull};
use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, query::{Query, ViewIter, View, AsQuery}, resource::ResourceData, query::{dynamic::DynamicView, ViewOne}, ComponentInfo, DynTypeId, TickTracker, Tick, ResourceObject}; use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, View, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct EntityId(pub u64);
/// The id of the entity for the Archetype. /// The id of the entity for the Archetype.
/// The Archetype struct uses this as the index in the component columns /// The Archetype struct uses this as the index in the component columns
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ArchetypeEntityId(pub u64); pub struct ArchetypeEntityId(pub u64);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Entity {
pub(crate) id: EntityId,
pub(crate) generation: u64,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Record { pub struct Record {
pub id: ArchetypeId, pub id: ArchetypeId,
@ -25,11 +16,9 @@ pub struct Record {
pub struct World { pub struct World {
pub(crate) archetypes: HashMap<ArchetypeId, Archetype>, pub(crate) archetypes: HashMap<ArchetypeId, Archetype>,
next_archetype_id: ArchetypeId, next_archetype_id: ArchetypeId,
pub(crate) entity_index: HashMap<EntityId, Record>,
dead_entities: VecDeque<Entity>,
next_entity_id: EntityId,
resources: HashMap<TypeId, ResourceData>, resources: HashMap<TypeId, ResourceData>,
tracker: TickTracker, tracker: TickTracker,
pub(crate) entities: Entities,
} }
impl Default for World { impl Default for World {
@ -37,11 +26,9 @@ impl Default for World {
Self { Self {
archetypes: HashMap::new(), archetypes: HashMap::new(),
next_archetype_id: ArchetypeId(0), next_archetype_id: ArchetypeId(0),
entity_index: HashMap::new(),
dead_entities: VecDeque::new(),
next_entity_id: EntityId(0),
resources: HashMap::new(), resources: HashMap::new(),
tracker: TickTracker::new(), tracker: TickTracker::new(),
entities: Entities::default(),
} }
} }
} }
@ -51,32 +38,30 @@ impl World {
Self::default() Self::default()
} }
/// Gets a new Entity, will recycle dead entities and increment their generation. /// Reserves an entity in the world
fn get_new_entity(&mut self) -> Entity { pub fn reserve_entity(&mut self) -> Entity {
match self.dead_entities.pop_front() { self.entities.reserve()
Some(mut e) => {
e.generation += 1;
e
},
None => {
let new_id = self.next_entity_id;
self.next_entity_id.0 += 1;
Entity {
id: new_id,
generation: 0,
}
}
}
} }
/// Spawns a new entity and inserts the component `bundle` into it.
pub fn spawn<B>(&mut self, bundle: B) -> Entity pub fn spawn<B>(&mut self, bundle: B) -> Entity
where
B: Bundle
{
let new_entity = self.reserve_entity();
self.spawn_into(new_entity, bundle);
new_entity
}
/// Spawn the components into a reserved entity. Only do this with entities that
/// were 'reserved' with [`World::reserve`]
///
/// # Safety
/// Do not use this method with an entity that is currently alive, it WILL cause undefined behavior.
pub fn spawn_into<B>(&mut self, entity: Entity, bundle: B)
where where
B: Bundle B: Bundle
{ {
let bundle_types = bundle.type_ids(); let bundle_types = bundle.type_ids();
let new_entity = self.get_new_entity();
let tick = self.tick(); let tick = self.tick();
@ -86,7 +71,11 @@ impl World {
.find(|a| a.is_archetype_for(&bundle_types)); .find(|a| a.is_archetype_for(&bundle_types));
if let Some(archetype) = archetype { if let Some(archetype) = archetype {
let arche_idx = archetype.add_entity(new_entity, bundle, &tick); // make at just one check to ensure you're not spawning twice
debug_assert!(!archetype.entities.contains_key(&entity),
"You attempted to spawn components into an entity that already exists!");
let arche_idx = archetype.add_entity(entity, bundle, &tick);
// Create entity record and store it // Create entity record and store it
let record = Record { let record = Record {
@ -94,16 +83,17 @@ impl World {
index: arche_idx, index: arche_idx,
}; };
self.entity_index.insert(new_entity.id, record); self.entities.insert_entity_record(entity, record);
} }
// create a new archetype if one isn't found // create a new archetype if one isn't found
else { else {
// create archetype // create archetype
let new_arch_id = self.next_archetype_id.increment(); let new_arch_id = self.next_archetype_id.increment();
let mut archetype = Archetype::from_bundle_info(new_arch_id, bundle.info()); let mut archetype = Archetype::from_bundle_info(new_arch_id, bundle.info());
let entity_arch_id = archetype.add_entity(new_entity, bundle, &tick); let entity_arch_id = archetype.add_entity(entity, bundle, &tick);
// store archetype // store archetype
println!("About to store arch, cap is {}, len is {}", self.archetypes.capacity(), self.archetypes.len());
self.archetypes.insert(new_arch_id, archetype); self.archetypes.insert(new_arch_id, archetype);
// Create entity record and store it // Create entity record and store it
@ -113,27 +103,25 @@ impl World {
index: entity_arch_id, index: entity_arch_id,
}; };
self.entity_index.insert(new_entity.id, record); self.entities.insert_entity_record(entity, record);
} }
new_entity
} }
/// Despawn an entity from the World /// Despawn an entity from the World
pub fn despawn(&mut self, entity: Entity) { pub fn despawn(&mut self, entity: Entity) {
// Tick the tracker if the entity is spawned. This is done here instead of the `if let` // Tick the tracker if the entity is spawned. This is done here instead of the `if let`
// below due to the borrow checker complaining about multiple mutable borrows to self. // below due to the borrow checker complaining about multiple mutable borrows to self.
let tick = if self.entity_index.contains_key(&entity.id) { let tick = if self.entities.arch_index.contains_key(&entity.id) {
Some(self.tick()) Some(self.tick())
} else { None }; } else { None };
if let Some(record) = self.entity_index.get_mut(&entity.id) { if let Some(record) = self.entities.arch_index.get_mut(&entity.id) {
let tick = tick.unwrap(); let tick = tick.unwrap();
let arch = self.archetypes.get_mut(&record.id).unwrap(); let arch = self.archetypes.get_mut(&record.id).unwrap();
if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) { if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) {
// replace the archetype index of the moved index with its new index. // replace the archetype index of the moved index with its new index.
self.entity_index.get_mut(&moved.id).unwrap().index = new_index; self.entities.arch_index.get_mut(&moved.id).unwrap().index = new_index;
} }
} }
} }
@ -152,7 +140,7 @@ impl World {
let tick = self.tick(); let tick = self.tick();
let record = *self.entity_index.get(&entity.id).unwrap(); let record = self.entities.entity_record(entity).unwrap();
let current_arch = self.archetypes.get(&record.id).unwrap(); let current_arch = self.archetypes.get(&record.id).unwrap();
let mut col_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id).collect(); let mut col_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id).collect();
@ -188,7 +176,7 @@ impl World {
id: arch.id, id: arch.id,
index: res_index, index: res_index,
}; };
self.entity_index.insert(entity.id, new_record); self.entities.insert_entity_record(entity, new_record);
} else { } else {
let new_arch_id = self.next_archetype_id.increment(); let new_arch_id = self.next_archetype_id.increment();
let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos); let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos);
@ -202,7 +190,7 @@ impl World {
index: entity_arch_id, index: entity_arch_id,
}; };
self.entity_index.insert(entity.id, record); self.entities.insert_entity_record(entity, record);
} }
let current_arch = self.archetypes.get_mut(&record.id).unwrap(); let current_arch = self.archetypes.get_mut(&record.id).unwrap();
@ -210,12 +198,12 @@ impl World {
} }
pub fn entity_archetype(&self, entity: Entity) -> Option<&Archetype> { pub fn entity_archetype(&self, entity: Entity) -> Option<&Archetype> {
self.entity_index.get(&entity.id) self.entities.entity_record(entity)
.and_then(|record| self.archetypes.get(&record.id)) .and_then(|record| self.archetypes.get(&record.id))
} }
pub fn entity_archetype_mut(&mut self, entity: Entity) -> Option<&mut Archetype> { pub fn entity_archetype_mut(&mut self, entity: Entity) -> Option<&mut Archetype> {
self.entity_index.get_mut(&entity.id) self.entities.entity_record(entity)
.and_then(|record| self.archetypes.get_mut(&record.id)) .and_then(|record| self.archetypes.get_mut(&record.id))
} }
@ -255,6 +243,14 @@ impl World {
.get_mut() .get_mut()
} }
/// Get a resource from the world, or insert it into the world as its default.
pub fn get_resource_or_default<T: Default + 'static>(&mut self) -> RefMut<T>
{
self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(T::default()))
.get_mut()
}
/// Gets a resource from the World. /// Gets a resource from the World.
/// ///
/// Will panic if the resource is not in the world. See [`try_get_resource`] for /// Will panic if the resource is not in the world. See [`try_get_resource`] for
@ -264,6 +260,11 @@ impl World {
.get() .get()
} }
/// Returns boolean indicating if the World contains a resource of type `T`.
pub fn has_resource<T: 'static>(&self) -> bool {
self.resources.contains_key(&TypeId::of::<T>())
}
/// Attempts to get a resource from the World. /// Attempts to get a resource from the World.
/// ///
/// Returns `None` if the resource was not found. /// Returns `None` if the resource was not found.
@ -364,7 +365,7 @@ mod tests {
world.despawn(middle_en); world.despawn(middle_en);
let record = world.entity_index.get(&last_en.id).unwrap(); let record = world.entities.entity_record(last_en).unwrap();
assert_eq!(record.index.0, 1); assert_eq!(record.index.0, 1);
} }

View File

@ -37,7 +37,7 @@ pub struct DeltaTimePlugin;
impl Plugin for DeltaTimePlugin { impl Plugin for DeltaTimePlugin {
fn setup(&self, game: &mut crate::game::Game) { fn setup(&self, game: &mut crate::game::Game) {
game.world().add_resource(DeltaTime(0.0, None)); game.world_mut().add_resource(DeltaTime(0.0, None));
game.add_system_to_stage(GameStages::First, "delta_time", delta_time_system, &[]); game.add_system_to_stage(GameStages::First, "delta_time", delta_time_system, &[]);
} }
} }

View File

@ -78,6 +78,6 @@ pub struct EventsPlugin;
impl Plugin for EventsPlugin { impl Plugin for EventsPlugin {
fn setup(&self, game: &mut crate::game::Game) { fn setup(&self, game: &mut crate::game::Game) {
game.world().add_resource(EventQueue::new()); game.world_mut().add_resource(EventQueue::new());
} }
} }

View File

@ -246,11 +246,17 @@ impl Game {
} }
/// Get the world of this game /// Get the world of this game
pub fn world(&mut self) -> &mut World { pub fn world_mut(&mut self) -> &mut World {
// world is always `Some`, so unwrapping is safe // world is always `Some`, so unwrapping is safe
self.world.as_mut().unwrap() self.world.as_mut().unwrap()
} }
/// Get the world of this game
pub fn world(&self) -> &World {
// world is always `Some`, so unwrapping is safe
self.world.as_ref().unwrap()
}
/// Add a system to the ecs world /// Add a system to the ecs world
pub fn with_system<S, A>(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self pub fn with_system<S, A>(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self
where where

View File

@ -120,6 +120,10 @@ impl crate::ecs::system::System for InputSystem {
fn world_access(&self) -> lyra_ecs::Access { fn world_access(&self) -> lyra_ecs::Access {
lyra_ecs::Access::Write lyra_ecs::Access::Write
} }
fn execute_deferred(&mut self, _: NonNull<World>) -> anyhow::Result<()> {
Ok(())
}
} }
impl IntoSystem<()> for InputSystem { impl IntoSystem<()> for InputSystem {

View File

@ -1,3 +1,4 @@
use lyra_ecs::Commands;
use lyra_resource::ResourceManager; use lyra_resource::ResourceManager;
use crate::EventsPlugin; use crate::EventsPlugin;
@ -98,7 +99,7 @@ pub struct ResourceManagerPlugin;
impl Plugin for ResourceManagerPlugin { impl Plugin for ResourceManagerPlugin {
fn setup(&self, game: &mut Game) { fn setup(&self, game: &mut Game) {
game.world().add_resource(ResourceManager::new()); game.world_mut().add_resource(ResourceManager::new());
} }
} }

View File

@ -373,7 +373,7 @@ impl Plugin for WindowPlugin {
fn setup(&self, game: &mut crate::game::Game) { fn setup(&self, game: &mut crate::game::Game) {
let window_options = WindowOptions::default(); let window_options = WindowOptions::default();
game.world().add_resource(Ct::new(window_options)); game.world_mut().add_resource(Ct::new(window_options));
game.with_system("window_updater", window_updater_system, &[]); game.with_system("window_updater", window_updater_system, &[]);
} }
} }

View File

@ -1,10 +1,11 @@
use lyra_ecs::Component; use lyra_ecs::Component;
use lyra_reflect::Reflect;
use lyra_resource::ResHandle; use lyra_resource::ResHandle;
use crate::assets::Model; use crate::assets::Model;
#[derive(Clone, Component)] #[derive(Clone, Component, Reflect)]
pub struct ModelComponent(pub ResHandle<Model>); pub struct ModelComponent(#[reflect(skip)] pub ResHandle<Model>);
impl From<ResHandle<Model>> for ModelComponent { impl From<ResHandle<Model>> for ModelComponent {
fn from(value: ResHandle<Model>) -> Self { fn from(value: ResHandle<Model>) -> Self {

View File

@ -9,7 +9,9 @@ pub enum StagedExecutorError {
#[error("[stage={0}] could not find a system's dependency named `{1}`")] #[error("[stage={0}] could not find a system's dependency named `{1}`")]
MissingSystem(String, String), MissingSystem(String, String),
#[error("[stage={0}] system `{1}` returned with an error: `{2}`")] #[error("[stage={0}] system `{1}` returned with an error: `{2}`")]
SystemError(String, String, anyhow::Error) SystemError(String, String, anyhow::Error),
#[error("[stage={0}] a command returned with an error: `{1}`")]
CommandError(String, anyhow::Error),
} }
impl StagedExecutorError { impl StagedExecutorError {
@ -17,6 +19,7 @@ impl StagedExecutorError {
match value { match value {
GraphExecutorError::MissingSystem(s) => Self::MissingSystem(stage, s), GraphExecutorError::MissingSystem(s) => Self::MissingSystem(stage, s),
GraphExecutorError::SystemError(s, e) => Self::SystemError(stage, s, e), GraphExecutorError::SystemError(s, e) => Self::SystemError(stage, s, e),
GraphExecutorError::Command(e) => Self::CommandError(stage, e)
} }
} }
} }

View File

@ -1,4 +1,4 @@
use std::{any::TypeId, cell::{Ref, RefMut}}; use std::{any::{Any, TypeId}, cell::{Ref, RefMut}};
use lyra_ecs::{Component, ComponentInfo, World, Entity, DynamicBundle}; use lyra_ecs::{Component, ComponentInfo, World, Entity, DynamicBundle};
@ -13,21 +13,21 @@ pub struct ReflectedComponent {
//from_world: for<'a> fn (world: &'a mut World) -> Box<dyn Reflect>, //from_world: for<'a> fn (world: &'a mut World) -> Box<dyn Reflect>,
/// Inserts component into entity in the world /// Inserts component into entity in the world
fn_insert: for<'a> fn (world: &'a mut World, entity: Entity, component: &dyn Reflect), fn_insert: for<'a> fn (world: &'a mut World, entity: Entity, component: Box<dyn Reflect>),
/// Inserts component into a bundle /// Inserts component into a bundle
fn_bundle_insert: for<'a> fn (dynamic_bundle: &'a mut DynamicBundle, component: &dyn Reflect), fn_bundle_insert: for<'a> fn (dynamic_bundle: &'a mut DynamicBundle, component: Box<dyn Reflect>),
fn_reflect: for<'a> fn (world: &'a World, entity: Entity) -> Option<Ref<'a, dyn Reflect>>, fn_reflect: for<'a> fn (world: &'a World, entity: Entity) -> Option<Ref<'a, dyn Reflect>>,
fn_reflect_mut: for<'a> fn (world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>>, fn_reflect_mut: for<'a> fn (world: &'a mut World, entity: Entity) -> Option<RefMut<'a, dyn Reflect>>,
} }
impl ReflectedComponent { impl ReflectedComponent {
/// Insert the reflected component into an entity. /// Insert the reflected component into an entity.
pub fn insert(&self, world: &mut World, entity: Entity, component: &dyn Reflect) { pub fn insert(&self, world: &mut World, entity: Entity, component: Box<dyn Reflect>) {
(self.fn_insert)(world, entity, component); (self.fn_insert)(world, entity, component);
} }
/// Insert this component into a DynamicBundle /// Insert this component into a DynamicBundle
pub fn bundle_insert(&self, dynamic_bundle: &mut DynamicBundle, component: &dyn Reflect) { pub fn bundle_insert(&self, dynamic_bundle: &mut DynamicBundle, component: Box<dyn Reflect>) {
(self.fn_bundle_insert)(dynamic_bundle, component) (self.fn_bundle_insert)(dynamic_bundle, component)
} }
@ -42,19 +42,23 @@ impl ReflectedComponent {
} }
} }
impl<C: Component + Reflect + Default> FromType<C> for ReflectedComponent { impl<C: Component + Reflect> FromType<C> for ReflectedComponent {
fn from_type() -> Self { fn from_type() -> Self {
ReflectedComponent { ReflectedComponent {
type_id: TypeId::of::<C>(), type_id: TypeId::of::<C>(),
info: ComponentInfo::new::<C>(), info: ComponentInfo::new::<C>(),
fn_insert: |world: &mut World, entity: Entity, component: &dyn Reflect| { fn_insert: |world: &mut World, entity: Entity, component: Box<dyn Reflect>| {
let mut c = C::default(); let c = component as Box<dyn Any>;
c.apply(component); let c = c.downcast::<C>()
.expect("Provided a non-matching type to ReflectedComponent insert method!");
let c = *c;
world.insert(entity, (c,)); world.insert(entity, (c,));
}, },
fn_bundle_insert: |bundle: &mut DynamicBundle, component: &dyn Reflect| { fn_bundle_insert: |bundle: &mut DynamicBundle, component: Box<dyn Reflect>| {
let mut c = C::default(); let c = component as Box<dyn Any>;
c.apply(component); let c = c.downcast::<C>()
.expect("Provided a non-matching type to ReflectedComponent insert method!");
let c = *c;
bundle.push(c); bundle.push(c);
}, },
fn_reflect: |world: &World, entity: Entity| { fn_reflect: |world: &World, entity: Entity| {

View File

@ -6,10 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
lyra-ecs = { path = "../lyra-ecs" }
anyhow = "1.0.75" anyhow = "1.0.75"
base64 = "0.21.4" base64 = "0.21.4"
crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] } crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] }
edict = "0.5.0"
glam = "0.24.1" glam = "0.24.1"
gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness", "KHR_materials_specular"] } gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness", "KHR_materials_specular"] }
image = "0.24.7" image = "0.24.7"

View File

@ -16,7 +16,17 @@ pub use model::*;
pub mod material; pub mod material;
pub use material::*; pub use material::*;
pub mod world_ext;
pub use world_ext::*;
pub(crate) mod util; pub(crate) mod util;
pub use crossbeam::channel as channel; pub use crossbeam::channel as channel;
pub use notify; pub use notify;
#[allow(unused_imports)]
pub(crate) mod lyra_engine {
pub(crate) mod ecs {
pub use lyra_ecs::*;
}
}

View File

@ -1,6 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::Material; use crate::Material;
use crate::lyra_engine;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -81,7 +82,7 @@ pub enum MeshVertexAttribute {
Other(String), Other(String),
} }
#[derive(Clone, edict::Component)] #[derive(Clone, lyra_ecs::Component)]
pub struct Mesh { pub struct Mesh {
pub uuid: uuid::Uuid, pub uuid: uuid::Uuid,
pub attributes: HashMap<MeshVertexAttribute, VertexAttributeData>, pub attributes: HashMap<MeshVertexAttribute, VertexAttributeData>,

View File

@ -1,7 +1,9 @@
use std::sync::{Arc, RwLock}; use std::{any::Any, sync::{Arc, RwLock}};
use uuid::Uuid; use uuid::Uuid;
use crate::ResourceStorage;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ResourceState { pub enum ResourceState {
Loading, Loading,
@ -21,7 +23,7 @@ impl<'a, T> std::ops::Deref for ResourceDataRef<'a, T> {
} }
} }
pub(crate) struct Resource<T> { pub struct Resource<T> {
path: String, path: String,
pub(crate) data: Option<T>, pub(crate) data: Option<T>,
pub(crate) version: usize, pub(crate) version: usize,
@ -122,34 +124,51 @@ impl<T> ResHandle<T> {
None None
} }
} }
}
/* /// Get a reference to the data in the resource
/// impl<T: Send + Sync + 'static> ResourceStorage for ResHandle<T> {
/// # Panics fn as_any(&self) -> &dyn Any {
/// Panics if the resource was not loaded yet. self
pub fn data_ref(&self) -> &T { }
self.data.as_ref()
.expect("Resource is not loaded yet (use try_data_ref, or wait until its loaded)!") fn as_any_mut(&mut self) -> &mut dyn Any {
} self
}
/// If the resource is loaded, returns `Some` reference to the data in the resource,
/// else it will return `None` fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
pub fn try_data_ref(&self) -> Option<&T> { self.clone()
self.data.as_ref() }
}
fn as_box_any(self: Box<Self>) -> Box<dyn Any + Send + Sync> {
/// Get a **mutable** reference to the data in the resource self
/// }
/// # Panics
/// Panics if the resource was not loaded yet. fn set_watched(&self, watched: bool) {
pub fn data_mut(&mut self) -> &mut T { let mut w = self.data.write().unwrap();
self.data.as_mut() w.is_watched = watched;
.expect("Resource is not loaded yet (use try_data_ref, or wait until its loaded)!") }
}
fn path(&self) -> String {
/// If the resource is loaded, returns `Some` **mutable** reference to the data in the resource, self.path()
/// else it will return `None` }
pub fn try_data_mut(&mut self) -> Option<&mut T> {
self.data.as_mut() fn version(&self) -> usize {
} */ self.version()
}
fn state(&self) -> ResourceState {
self.state()
}
fn uuid(&self) -> Uuid {
self.uuid()
}
fn is_watched(&self) -> bool {
self.is_watched()
}
fn is_loaded(&self) -> bool {
self.is_loaded()
}
} }

View File

@ -4,39 +4,27 @@ use crossbeam::channel::Receiver;
use notify::{Watcher, RecommendedWatcher}; use notify::{Watcher, RecommendedWatcher};
use notify_debouncer_full::{DebouncedEvent, FileIdMap}; use notify_debouncer_full::{DebouncedEvent, FileIdMap};
use thiserror::Error; use thiserror::Error;
use uuid::Uuid;
use crate::{resource::ResHandle, loader::{ResourceLoader, LoaderError, image::ImageLoader, model::ModelLoader}}; use crate::{loader::{image::ImageLoader, model::ModelLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState};
/// A trait for type erased storage of a resource.
/// Implemented for [`ResHandle<T>`]
pub trait ResourceStorage: Send + Sync + Any + 'static { pub trait ResourceStorage: Send + Sync + Any + 'static {
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any;
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>; fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
fn as_box_any(self: Box<Self>) -> Box<dyn Any + Send + Sync>; fn as_box_any(self: Box<Self>) -> Box<dyn Any + Send + Sync>;
/// Do not set a resource to watched if it is not actually watched.
/// This is used internally.
fn set_watched(&self, watched: bool); fn set_watched(&self, watched: bool);
}
/// Implements this trait for anything that fits the type bounds fn path(&self) -> String;
impl<T: Send + Sync + 'static> ResourceStorage for ResHandle<T> { fn version(&self) -> usize;
fn as_any(&self) -> &dyn Any { fn state(&self) -> ResourceState;
self fn uuid(&self) -> Uuid;
} fn is_watched(&self) -> bool;
fn is_loaded(&self) -> bool;
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
self.clone()
}
fn as_box_any(self: Box<Self>) -> Box<dyn Any + Send + Sync> {
self
}
fn set_watched(&self, watched: bool) {
let mut w = self.data.write().unwrap();
w.is_watched = watched;
}
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -131,6 +119,35 @@ impl ResourceManager {
} }
} }
/// Request a resource without downcasting to a ResHandle<T>.
/// Whenever you're ready to downcast, you can do so like this:
/// ```compile_fail
/// let arc_any = res_arc.as_arc_any();
/// let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
/// ```
pub fn request_raw(&mut self, path: &str) -> Result<Arc<dyn ResourceStorage>, RequestError> {
match self.resources.get(&path.to_string()) {
Some(res) => {
Ok(res.clone())
},
None => {
if let Some(loader) = self.loaders.iter()
.find(|l| l.does_support_file(path)) {
// Load the resource and store it
let loader = Arc::clone(loader); // stop borrowing from self
let res = loader.load(self, path)?;
let res: Arc<dyn ResourceStorage> = Arc::from(res);
self.resources.insert(path.to_string(), res.clone());
Ok(res)
} else {
Err(RequestError::UnsupportedFileExtension(path.to_string()))
}
}
}
}
/// Store bytes in the manager. If there is already an entry with the same identifier it will be updated. /// Store bytes in the manager. If there is already an entry with the same identifier it will be updated.
/// ///
/// Panics: If there is already an entry with the same `ident`, and the entry is not bytes, this function will panic. /// Panics: If there is already an entry with the same `ident`, and the entry is not bytes, this function will panic.

View File

@ -0,0 +1,75 @@
use std::any::Any;
use crossbeam::channel::Receiver;
use lyra_ecs::World;
use notify_debouncer_full::DebouncedEvent;
use crate::{RequestError, ResHandle, ResourceLoader, ResourceManager};
pub trait WorldAssetExt {
/// Register a resource loader with the resource manager.
fn register_res_loader<L>(&mut self)
where
L: ResourceLoader + Default + 'static;
/// Request a resource from the resource manager.
fn request_res<T>(&mut self, path: &str) -> Result<ResHandle<T>, RequestError>
where
T: Send + Sync + Any + 'static;
/// Start watching a resource for changes. Returns a crossbeam channel that can be used to listen for events.
fn watch_res(&mut self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>>;
/// Stop watching a resource for changes.
fn stop_watching_res(&mut self, path: &str) -> notify::Result<()>;
/// Try to retrieve a crossbeam channel for a path that is currently watched. Returns None if the path is not watched
fn res_watcher_recv(&self, path: &str) -> Option<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>>;
/// Reload a resource. The data will be updated in the handle. This is not
/// automatically triggered if the resource is being watched.
fn reload_res<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError>
where
T: Send + Sync + Any + 'static;
}
impl WorldAssetExt for World {
fn register_res_loader<L>(&mut self)
where
L: ResourceLoader + Default + 'static
{
let mut man = self.get_resource_or_default::<ResourceManager>();
man.register_loader::<L>();
}
fn request_res<T>(&mut self, path: &str) -> Result<ResHandle<T>, RequestError>
where
T: Send + Sync + Any + 'static
{
let mut man = self.get_resource_or_default::<ResourceManager>();
man.request(path)
}
fn watch_res(&mut self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
let mut man = self.get_resource_or_default::<ResourceManager>();
man.watch(path, recursive)
}
fn stop_watching_res(&mut self, path: &str) -> notify::Result<()> {
let mut man = self.get_resource_or_default::<ResourceManager>();
man.stop_watching(path)
}
fn res_watcher_recv(&self, path: &str) -> Option<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
let man = self.get_resource::<ResourceManager>();
man.watcher_event_recv(path)
}
fn reload_res<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError>
where
T: Send + Sync + Any + 'static
{
let mut man = self.get_resource_or_default::<ResourceManager>();
man.reload(resource)
}
}

View File

@ -134,7 +134,7 @@ impl GameScriptExt for Game {
T: ScriptHost, T: ScriptHost,
P: ScriptApiProvider<ScriptContext = T::ScriptContext> + 'static P: ScriptApiProvider<ScriptContext = T::ScriptContext> + 'static
{ {
let world = self.world(); let world = self.world_mut();
provider.prepare_world(world); provider.prepare_world(world);
let mut providers = world.get_resource_mut::<ScriptApiProviders<T>>(); let mut providers = world.get_resource_mut::<ScriptApiProviders<T>>();
providers.add_provider(provider); providers.add_provider(provider);

View File

@ -171,7 +171,7 @@ pub struct LuaScriptingPlugin;
impl Plugin for LuaScriptingPlugin { impl Plugin for LuaScriptingPlugin {
fn setup(&self, game: &mut lyra_game::game::Game) { fn setup(&self, game: &mut lyra_game::game::Game) {
let world = game.world(); let world = game.world_mut();
world.add_resource_default::<TypeRegistry>(); world.add_resource_default::<TypeRegistry>();