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",
"--lib",
"--package=lyra-ecs",
"world::tests::view_change_tracking",
"command::tests::deferred_commands",
"--",
"--exact --nocapture"
],

8
Cargo.lock generated
View File

@ -1456,7 +1456,6 @@ dependencies = [
"paste",
"rand",
"thiserror",
"unique",
]
[[package]]
@ -1520,7 +1519,6 @@ dependencies = [
"lyra-ecs",
"lyra-math",
"lyra-reflect-derive",
"lyra-resource",
]
[[package]]
@ -2688,12 +2686,6 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unique"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d360722e1f3884f5b14d332185f02ff111f771f0c76a313268fe6af1409aba96"
[[package]]
name = "urlencoding"
version = "2.1.3"

View File

@ -13,7 +13,6 @@ lyra-ecs-derive = { path = "./lyra-ecs-derive" }
lyra-math = { path = "../lyra-math", optional = true }
anyhow = "1.0.75"
thiserror = "1.0.50"
unique = "0.9.1"
paste = "1.0.14"
[dev-dependencies]

View File

@ -1,26 +1,30 @@
use std::{cell::RefMut, collections::VecDeque, mem, ptr::{self, NonNull}};
use unique::Unique;
use std::{any::Any, cell::RefMut, collections::VecDeque, ptr::{self, NonNull}};
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<()>;
}
impl<F> Command for F
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<()> {
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)]
pub struct CommandQueue(VecDeque<(RunCommand, Unique<()>)>);
pub struct CommandQueue(VecDeque<(RunCommand, Box<dyn Command>)>);
pub struct Commands<'a, 'b> {
queue: &'b mut CommandQueue,
@ -36,31 +40,26 @@ impl<'a, 'b> Commands<'a, 'b> {
}
/// 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);
pub fn add<C: Command>(&mut self, cmd: C) {
let cmd = Box::new(cmd);
let run_fn = |cmd_ptr: Unique<()>, world: &mut World| unsafe {
let cmd = cmd_ptr.cast::<C>();
let cmd = ptr::read(cmd.as_ptr());
let run_fn = |cmd: Box<dyn Command>, world: &mut World| {
let cmd = cmd.as_any_boxed()
.downcast::<C>()
.unwrap();
cmd.run(world)?;
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 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(())
});
@ -101,7 +100,7 @@ impl FnArgFetcher for Commands<'_, '_> {
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
// safety: Commands has a mut borrow only to entities in the world
let world = unsafe { world_ptr.as_mut() };
cmds.execute(world).unwrap()
}
@ -111,4 +110,41 @@ pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut<Command
commands.execute(world)?;
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
}
None => {
println!("id is {}", self.next_id.0);
let new_id = self.next_id;
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
pub(crate) fn entity_record(&self, entity: Entity) -> Option<Record> {
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 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>;
//fn new() -> Self;
fn create_state(world: NonNull<World>) -> Self::State;
/// Return the appropriate world access if this fetcher gets the world directly.
@ -40,7 +38,7 @@ pub struct FnSystem<F, Args> {
inner: F,
//#[allow(dead_code)]
//args: Args,
arg_state: Option<Vec<Unique<()>>>,
arg_state: Option<Vec<Box<dyn Any>>>,
_marker: PhantomData<Args>,
}
@ -60,10 +58,8 @@ macro_rules! impl_fn_system_tuple {
paste! {
$(
// get the arg fetcher, create its state, and get the arg
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>] ),+)?;
@ -71,9 +67,8 @@ macro_rules! impl_fn_system_tuple {
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::<()>());
let boxed = Box::new([<state_ $name:lower>]) as Box<dyn Any>;
state.push(boxed);
)+
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<()> {
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 s = unsafe { std::ptr::read(s.cast::<$name::State>().as_ptr()) };
$name::apply_deferred(s, world);
let arg_state_box = state.pop()
.expect("Missing expected arg state");
let arg_state = *arg_state_box.downcast::<$name::State>()
.unwrap();
$name::apply_deferred(arg_state, world);
)+
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
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::Arg<'a, '_>,)+) -> anyhow::Result<()>,
{
@ -126,7 +102,6 @@ macro_rules! impl_fn_system_tuple {
fn into_system(self) -> Self::System {
FnSystem {
//args: ($($name::Fetcher::new(),)+),
inner: self,
arg_state: None,
_marker: PhantomData::<($($name,)+)>::default(),

View File

@ -93,7 +93,6 @@ impl World {
let entity_arch_id = archetype.add_entity(entity, bundle, &tick);
// store archetype
println!("About to store arch, cap is {}, len is {}", self.archetypes.capacity(), self.archetypes.len());
self.archetypes.insert(new_arch_id, archetype);
// Create entity record and store it