ecs: add commands test and fix Commands

This commit is contained in:
SeanOMik 2024-02-24 11:09:26 -05:00
parent 1f43a9d4da
commit 5dfc073db5
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
7 changed files with 75 additions and 70 deletions

2
.vscode/launch.json vendored
View File

@ -72,7 +72,7 @@
"--no-run", "--no-run",
"--lib", "--lib",
"--package=lyra-ecs", "--package=lyra-ecs",
"world::tests::view_change_tracking", "command::tests::deferred_commands",
"--", "--",
"--exact --nocapture" "--exact --nocapture"
], ],

8
Cargo.lock generated
View File

@ -1456,7 +1456,6 @@ dependencies = [
"paste", "paste",
"rand", "rand",
"thiserror", "thiserror",
"unique",
] ]
[[package]] [[package]]
@ -1520,7 +1519,6 @@ dependencies = [
"lyra-ecs", "lyra-ecs",
"lyra-math", "lyra-math",
"lyra-reflect-derive", "lyra-reflect-derive",
"lyra-resource",
] ]
[[package]] [[package]]
@ -2688,12 +2686,6 @@ 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

@ -13,7 +13,6 @@ 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" paste = "1.0.14"
[dev-dependencies] [dev-dependencies]

View File

@ -1,26 +1,30 @@
use std::{cell::RefMut, collections::VecDeque, mem, ptr::{self, NonNull}}; use std::{any::Any, cell::RefMut, collections::VecDeque, ptr::{self, NonNull}};
use unique::Unique;
use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World}; use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World};
pub trait Command { pub trait Command: Any {
fn as_any_boxed(self: Box<Self>) -> Box<dyn Any>;
fn run(self, world: &mut World) -> anyhow::Result<()>; fn run(self, world: &mut World) -> anyhow::Result<()>;
} }
impl<F> Command for F impl<F> Command for F
where where
F: FnOnce(&mut World) -> anyhow::Result<()> F: FnOnce(&mut World) -> anyhow::Result<()> + 'static
{ {
fn as_any_boxed(self: Box<Self>) -> Box<dyn Any> {
self
}
fn run(self, world: &mut World) -> anyhow::Result<()> { fn run(self, world: &mut World) -> anyhow::Result<()> {
self(world) self(world)
} }
} }
type RunCommand = unsafe fn(cmd: Unique<()>, world: &mut World) -> anyhow::Result<()>; type RunCommand = unsafe fn(cmd: Box<dyn Command>, world: &mut World) -> anyhow::Result<()>;
#[derive(Default)] #[derive(Default)]
pub struct CommandQueue(VecDeque<(RunCommand, Unique<()>)>); pub struct CommandQueue(VecDeque<(RunCommand, Box<dyn Command>)>);
pub struct Commands<'a, 'b> { pub struct Commands<'a, 'b> {
queue: &'b mut CommandQueue, queue: &'b mut CommandQueue,
@ -36,31 +40,26 @@ impl<'a, 'b> Commands<'a, 'b> {
} }
/// Add a command to the end of the command queue /// Add a command to the end of the command queue
pub fn add<C: Command>(&mut self, mut cmd: C) { pub fn add<C: Command>(&mut self, cmd: C) {
// get an owned pointer to the command, then forget it to ensure its destructor isn't ran let cmd = Box::new(cmd);
let ptr = Unique::from(&mut cmd).cast::<()>();
mem::forget(cmd); let run_fn = |cmd: Box<dyn Command>, world: &mut World| {
let cmd = cmd.as_any_boxed()
.downcast::<C>()
.unwrap();
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)?; cmd.run(world)?;
Ok(()) Ok(())
}; };
self.queue.0.push_back((run_fn, ptr)); self.queue.0.push_back((run_fn, cmd));
} }
pub fn spawn<B: Bundle>(&mut self, mut bundle: B) -> Entity { pub fn spawn<B: Bundle + 'static>(&mut self, bundle: B) -> Entity {
let e = self.entities.reserve(); 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| { self.add(move |world: &mut World| {
let bundle = unsafe { ptr::read(bundle_ptr.as_ptr()) };
world.spawn_into(e, bundle); world.spawn_into(e, bundle);
Ok(()) Ok(())
}); });
@ -101,7 +100,7 @@ impl FnArgFetcher for Commands<'_, '_> {
let world = unsafe { world_ptr.as_mut() }; let world = unsafe { world_ptr.as_mut() };
let mut cmds = Commands::new(&mut state, world); let mut cmds = Commands::new(&mut state, world);
// safety: Commands has a mut borrow to entities in the world // safety: Commands has a mut borrow only to entities in the world
let world = unsafe { world_ptr.as_mut() }; let world = unsafe { world_ptr.as_mut() };
cmds.execute(world).unwrap() cmds.execute(world).unwrap()
} }
@ -112,3 +111,40 @@ pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut<Command
Ok(()) Ok(())
} }
#[cfg(test)]
mod tests {
use std::ptr::NonNull;
use crate::{system::{GraphExecutor, IntoSystem}, tests::Vec2, Commands, DynTypeId, World};
#[test]
fn deferred_commands() {
let mut world = World::new();
let vecs = vec![Vec2::rand(), Vec2::rand(), Vec2::rand()];
world.spawn((vecs[0],));
world.spawn((vecs[1],));
world.spawn((vecs[2],));
let spawned_vec = Vec2::rand();
let spawned_vec_cl = spawned_vec.clone();
let test_sys = move |mut commands: Commands| -> anyhow::Result<()> {
commands.spawn((spawned_vec_cl.clone(),));
Ok(())
};
let mut graph_exec = GraphExecutor::new();
graph_exec.insert_system("test", test_sys.into_system(), &[]);
graph_exec.execute(NonNull::from(&world), true).unwrap();
assert_eq!(world.entities.len(), 4);
// there's only one archetype
let arch = world.archetypes.values().next().unwrap();
let col = arch.get_column(DynTypeId::of::<Vec2>()).unwrap();
let vec2: &Vec2 = unsafe { col.get(3) };
assert_eq!(vec2.clone(), spawned_vec);
}
}

View File

@ -35,7 +35,6 @@ impl Entities {
e e
} }
None => { None => {
println!("id is {}", self.next_id.0);
let new_id = self.next_id; let new_id = self.next_id;
self.next_id.0 += 1; self.next_id.0 += 1;
@ -47,6 +46,11 @@ impl Entities {
} }
} }
/// Returns the number of spawned entities
pub fn len(&self) -> usize {
self.next_id.0 as usize - self.dead.len()
}
/// Retrieves the Archetype Record for the entity /// Retrieves the Archetype Record for the entity
pub(crate) fn entity_record(&self, entity: Entity) -> Option<Record> { pub(crate) fn entity_record(&self, entity: Entity) -> Option<Record> {
self.arch_index.get(&entity.id).cloned() self.arch_index.get(&entity.id).cloned()

View File

@ -1,6 +1,5 @@
use std::{ptr::NonNull, marker::PhantomData}; use std::{any::Any, marker::PhantomData, ptr::NonNull};
use unique::Unique;
use paste::paste; 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}};
@ -12,7 +11,6 @@ pub trait FnArgFetcher {
type Arg<'a, 'state>: FnArgFetcher<State = Self::State>; type Arg<'a, 'state>: FnArgFetcher<State = Self::State>;
//fn new() -> Self;
fn create_state(world: NonNull<World>) -> Self::State; 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.
@ -40,7 +38,7 @@ pub struct FnSystem<F, Args> {
inner: F, inner: F,
//#[allow(dead_code)] //#[allow(dead_code)]
//args: Args, //args: Args,
arg_state: Option<Vec<Unique<()>>>, arg_state: Option<Vec<Box<dyn Any>>>,
_marker: PhantomData<Args>, _marker: PhantomData<Args>,
} }
@ -60,10 +58,8 @@ macro_rules! impl_fn_system_tuple {
paste! { paste! {
$( $(
// get the arg fetcher, create its state, and get the arg // get the arg fetcher, create its state, and get the arg
let mut [<state_ $name:lower>]: $name::State = $name::create_state(world); let mut [<state_ $name:lower>]: $name::State = $name::create_state(world);
let [<$name:lower>] = $name::get(&mut [<state_ $name:lower>], world); let [<$name:lower>] = $name::get(&mut [<state_ $name:lower>], world);
)+ )+
(self.inner)($( [<$name:lower>] ),+)?; (self.inner)($( [<$name:lower>] ),+)?;
@ -71,9 +67,8 @@ macro_rules! impl_fn_system_tuple {
let mut state = Vec::new(); let mut state = Vec::new();
$( $(
// type erase the now modified state, and store it // type erase the now modified state, and store it
let [<state_ $name:lower _ptr>] = Unique::from(&mut [<state_ $name:lower>]); let boxed = Box::new([<state_ $name:lower>]) as Box<dyn Any>;
std::mem::forget([<state_ $name:lower>]); state.push(boxed);
state.push([<state_ $name:lower _ptr>].cast::<()>());
)+ )+
self.arg_state = Some(state); self.arg_state = Some(state);
@ -84,41 +79,22 @@ macro_rules! impl_fn_system_tuple {
} }
fn execute_deferred(&mut self, world: NonNull<World>) -> anyhow::Result<()> { fn execute_deferred(&mut self, world: NonNull<World>) -> anyhow::Result<()> {
let state = self.arg_state.as_mut().expect("Somehow there was no state");
$( $(
let s = self.arg_state.as_mut().expect("Somehow there was no state").pop().unwrap(); let arg_state_box = state.pop()
let s = unsafe { std::ptr::read(s.cast::<$name::State>().as_ptr()) }; .expect("Missing expected arg state");
$name::apply_deferred(s, world); let arg_state = *arg_state_box.downcast::<$name::State>()
.unwrap();
$name::apply_deferred(arg_state, world);
)+ )+
Ok(()) Ok(())
} }
} }
/* impl<F, $($name: FnArg,)+> IntoSystem<($($name,)+)> for F
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: for<'a> FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a, '_>,)+) -> anyhow::Result<()>,
{
type System = FnSystem<F, ($($name::Fetcher,)+)>;
fn into_system(self) -> Self::System {
FnSystem {
//args: ($($name::Fetcher::new(),)+),
inner: self,
arg_state: None,
_marker: PhantomData::<($($name::Fetcher,)+)>::default(),
}
}
} */
impl<F, $($name: FnArgFetcher,)+> 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::Arg<'a, '_>,)+) -> anyhow::Result<()>, F: for<'a> FnMut($($name::Arg<'a, '_>,)+) -> anyhow::Result<()>,
{ {
@ -126,7 +102,6 @@ macro_rules! impl_fn_system_tuple {
fn into_system(self) -> Self::System { fn into_system(self) -> Self::System {
FnSystem { FnSystem {
//args: ($($name::Fetcher::new(),)+),
inner: self, inner: self,
arg_state: None, arg_state: None,
_marker: PhantomData::<($($name,)+)>::default(), _marker: PhantomData::<($($name,)+)>::default(),

View File

@ -93,7 +93,6 @@ impl World {
let entity_arch_id = archetype.add_entity(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