Compare commits

..

12 Commits

Author SHA1 Message Date
SeanOMik 7ae38476fa Merge pull request 'Fix #6: Rendering Shared 3D Models' (#10) from bug/6-rendering-shared-models into main
ci/woodpecker/push/debug Pipeline failed Details
Reviewed-on: #10
2024-04-01 11:03:00 -04:00
SeanOMik 0a9e5ebcdb
render: improve fix for rendering shared 3d modules 2024-04-01 10:50:17 -04:00
SeanOMik dd61e8e66c
render: hack to get rendering shared 3d modules working 2024-03-31 23:02:18 -04:00
SeanOMik a3118f32e2
resource: implement retrieving loaded SceneGraph dependencies 2024-03-31 13:37:25 -04:00
SeanOMik aa8d94851c
game: rewrite EventQueue due to new ecs requirement of Send + Sync for resources, use new SceneGraph in renderer 2024-03-31 13:24:32 -04:00
SeanOMik a39d259bb4
Switch nix-shell to use oxalica overlay to get miri working, fix memory leak in archetypes 2024-03-31 10:56:04 -04:00
SeanOMik a17c035c05
resource: use a SceneGraph for loading gltf nodes, make resources Send + Sync 2024-03-31 00:32:31 -04:00
SeanOMik a2aac25249
ecs, reflect: implement Bundle for (), use `nobuild` instead of `compile_fail` for reflect rustdocs 2024-03-31 00:29:12 -04:00
SeanOMik e5018c8258
reflect: fix type registry from changes with ecs resources 2024-03-30 22:42:41 -04:00
SeanOMik e00d0d71d1
examples: move assets outside of testbed for other examples 2024-03-30 22:20:53 -04:00
SeanOMik 46cdcfdd3b
ecs: make resources Send + Sync, rewrite Commands, CommandsQueue so that they are Send + Sync 2024-03-30 22:20:52 -04:00
SeanOMik 61efc358ce
scene: make scenes own its own world, no references 2024-03-24 22:40:38 -04:00
57 changed files with 806 additions and 405 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
use_nix

23
Cargo.lock generated
View File

@ -330,6 +330,12 @@ 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 = "atomic_refcell"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -708,12 +714,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.18" version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crunchy" name = "crunchy"
@ -984,12 +987,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fps_counter"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3aaba7ff514ee9d802b562927f80b1e94e93d8e74c31b134c9c3762dabf1a36b"
[[package]] [[package]]
name = "fsevent-sys" name = "fsevent-sys"
version = "4.1.0" version = "4.1.0"
@ -1755,6 +1752,7 @@ name = "lyra-ecs"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"atomic_refcell",
"lyra-ecs-derive", "lyra-ecs-derive",
"lyra-math", "lyra-math",
"paste", "paste",
@ -1797,6 +1795,7 @@ dependencies = [
"lyra-math", "lyra-math",
"lyra-reflect", "lyra-reflect",
"lyra-resource", "lyra-resource",
"lyra-scene",
"quote", "quote",
"syn 2.0.51", "syn 2.0.51",
"thiserror", "thiserror",
@ -1850,6 +1849,7 @@ dependencies = [
"lyra-ecs", "lyra-ecs",
"lyra-math", "lyra-math",
"lyra-reflect", "lyra-reflect",
"lyra-scene",
"mime", "mime",
"notify", "notify",
"notify-debouncer-full", "notify-debouncer-full",
@ -3104,7 +3104,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-std", "async-std",
"fps_counter",
"lyra-engine", "lyra-engine",
"tracing", "tracing",
] ]

View File

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

Before

Width:  |  Height:  |  Size: 457 KiB

After

Width:  |  Height:  |  Size: 457 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,6 +1,6 @@
use std::ptr::NonNull; use std::{ptr::NonNull, thread, time::Duration};
use lyra_engine::{assets::gltf::Gltf, change_tracker::Ct, ecs::{query::{QueryBorrow, Res, View, ViewState}, system::{BatchedSystem, Criteria, CriteriaSchedule, IntoSystem}, Component, World}, game::Game, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Quat, Transform, Vec3}, render::{light::{directional::DirectionalLight, PointLight, SpotLight}, window::{CursorGrabMode, WindowOptions}}, scene::{CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN}, DeltaTime}; use lyra_engine::{assets::gltf::Gltf, change_tracker::Ct, ecs::{query::{Res, View}, system::{Criteria, CriteriaSchedule, IntoSystem}, Component, World}, game::Game, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Quat, Transform, Vec3}, render::{light::{directional::DirectionalLight, PointLight, SpotLight}, window::{CursorGrabMode, WindowOptions}}, scene::{CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN}, DeltaTime};
use lyra_engine::assets::ResourceManager; use lyra_engine::assets::ResourceManager;
struct FixedTimestep { struct FixedTimestep {
@ -73,7 +73,7 @@ async fn main() {
//let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap(); //let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap();
//let antique_camera_model = resman.request::<Model>("assets/AntiqueCamera.glb").unwrap(); //let antique_camera_model = resman.request::<Model>("assets/AntiqueCamera.glb").unwrap();
//let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap(); //let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap();
let cube_gltf = resman.request::<Gltf>("assets/texture-sep/texture-sep.gltf").unwrap(); let cube_gltf = resman.request::<Gltf>("../assets/texture-sep/texture-sep.gltf").unwrap();
/*let crate_gltf = resman.request::<Gltf>("assets/crate/crate.gltf").unwrap(); /*let crate_gltf = resman.request::<Gltf>("assets/crate/crate.gltf").unwrap();
let separate_gltf = resman.request::<Gltf>("assets/pos-testing/child-node-cubes.glb").unwrap(); */ let separate_gltf = resman.request::<Gltf>("assets/pos-testing/child-node-cubes.glb").unwrap(); */
@ -88,7 +88,7 @@ async fn main() {
let separate_scene = &separate_gltf.data_ref() let separate_scene = &separate_gltf.data_ref()
.unwrap().scenes[0]; */ .unwrap().scenes[0]; */
let sponza_model = resman.request::<Gltf>("assets/sponza/Sponza.gltf").unwrap(); let sponza_model = resman.request::<Gltf>("../assets/sponza/Sponza.gltf").unwrap();
drop(resman); drop(resman);
sponza_model.wait_recurse_dependencies_load(); sponza_model.wait_recurse_dependencies_load();

View File

@ -14,6 +14,7 @@ lyra-math = { path = "../lyra-math", optional = true }
anyhow = "1.0.75" anyhow = "1.0.75"
thiserror = "1.0.50" thiserror = "1.0.50"
paste = "1.0.14" paste = "1.0.14"
atomic_refcell = "0.1.13"
[dev-dependencies] [dev-dependencies]
rand = "0.8.5" # used for tests rand = "0.8.5" # used for tests

View File

@ -19,7 +19,9 @@ impl Drop for ComponentColumn {
unsafe { unsafe {
// TODO: trigger drop on the components // TODO: trigger drop on the components
let layout = self.info.layout(); // SAFETY: The size of the data buffer is the capcity times the size of a component
let size = self.info.layout().size() * self.capacity;
let layout = Layout::from_size_align_unchecked(size, self.info.layout().align());
dealloc(data, layout); dealloc(data, layout);
} }
} }

View File

@ -17,6 +17,24 @@ pub trait Bundle {
fn is_dynamic(&self) -> bool; fn is_dynamic(&self) -> bool;
} }
impl Bundle for () {
fn type_ids(&self) -> Vec<DynTypeId> {
vec![DynTypeId::of::<()>()]
}
fn info(&self) -> Vec<ComponentInfo> {
vec![ComponentInfo::new::<()>()]
}
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, usize)) {
f(NonNull::from(&self).cast(), DynTypeId::of::<()>(), size_of::<()>());
}
fn is_dynamic(&self) -> bool {
false
}
}
impl<C: Component> Bundle for C { impl<C: Component> Bundle for C {
fn type_ids(&self) -> Vec<DynTypeId> { fn type_ids(&self) -> Vec<DynTypeId> {
vec![DynTypeId::of::<C>()] vec![DynTypeId::of::<C>()]

View File

@ -1,4 +1,4 @@
use std::{any::Any, cell::RefMut, collections::VecDeque, ptr::{self, NonNull}}; use std::{any::Any, cell::RefMut, mem::{self, MaybeUninit}, ptr::{self, NonNull}};
use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World}; use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World};
@ -23,24 +23,73 @@ where
} }
} }
type RunCommand = unsafe fn(cmd: Box<dyn Command>, world: &mut World); type RunCommand = unsafe fn(cmd: *mut (), world: Option<&mut World>) -> usize;
#[repr(C, packed)]
struct PackedCommand<T: Command> {
run: RunCommand,
cmd: T,
}
/// Stores a queue of commands that will get executed after the system is ran. /// Stores a queue of commands that will get executed after the system is ran.
/// ///
/// This struct can be inserted as a resource into the world, and the commands will be /// This struct can be inserted as a resource into the world, and the commands will be
/// executed by the [`GraphExecutor`](crate::system::GraphExecutor) after the system is executed. /// executed by the [`GraphExecutor`](crate::system::GraphExecutor) after the system is executed.
#[derive(Default)] #[derive(Default)]
pub struct CommandQueue(VecDeque<(RunCommand, Box<dyn Command>)>); pub struct CommandQueue {
data: Vec<MaybeUninit<u8>>,
}
impl CommandQueue {
/// Execute the commands in the queue.
///
/// If `world` is `None`, the commands will just be dropped and the memory freed.
fn execute(&mut self, mut world: Option<&mut World>) {
let range = self.data.as_mut_ptr_range();
let mut current = range.start;
let end = range.end;
while current < end {
// Retrieve the runner for the command.
// Safety: current pointer will either be the start of the buffer, or at the start of a new PackedCommand
let run_fn = unsafe { current.cast::<RunCommand>().read_unaligned() };
// Retrieves the pointer to the command which is just after RunCommand due to PackedCommand.
// Safety: PackedCommand is repr C and packed, so it will be right after the RunCommand.
current = unsafe { current.add(mem::size_of::<RunCommand>()) };
// Now run the command, providing the type erased pointer to the command.
let read_size = unsafe { run_fn(current.cast(), world.as_deref_mut()) };
// The pointer is added to so that it is just after the command that was ran.
// Safety: the RunCommand returns the size of the command
current = unsafe { current.add(read_size) };
}
// Safety: all of the commands were just read from the pointers.
unsafe { self.data.set_len(0) };
}
}
impl Drop for CommandQueue {
fn drop(&mut self) {
if !self.data.is_empty() {
println!("CommandQueue has commands but is being dropped");
}
self.execute(None);
}
}
/// Used in a system to queue up commands that will run right after this system. /// Used in a system to queue up commands that will run right after this system.
/// ///
/// This can be used to delay the mutation of the world until after the system is ran. These /// This can be used to delay the mutation of the world until after the system is ran. These
/// must be used if you're mutating the world inside a [`ViewState`](crate::query::ViewState). /// must be used if you're mutating the world inside a [`View`](crate::query::View).
/// ///
/// ```nobuild /// ```nobuild
/// fn particle_spawner_system( /// fn particle_spawner_system(
/// commands: Commands, /// commands: Commands,
/// view: ViewState<(&Campfire, &Transform)> /// view: View<(&Campfire, &Transform)>
/// ) -> anyhow::Result<()> { /// ) -> anyhow::Result<()> {
/// for (campfire, pos) in view.iter() { /// for (campfire, pos) in view.iter() {
/// // If you do not use commands to spawn this, the next iteration /// // If you do not use commands to spawn this, the next iteration
@ -66,17 +115,44 @@ 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, cmd: C) { pub fn add<C: Command>(&mut self, cmd: C) {
let cmd = Box::new(cmd); let run_fn = |cmd_ptr: *mut (), world: Option<&mut World>| {
// Safety: the pointer is a type-erased pointer to the command. The pointer is read
// then dropped out of scope, this closure will not be ran again so no use-after-free
// will occur.
let cmd: C = unsafe { ptr::read_unaligned(cmd_ptr.cast::<C>()) };
match world {
Some(world) => cmd.run(world),
None => {} // cmd just gets dropped
}
let run_fn = |cmd: Box<dyn Command>, world: &mut World| { // the size of the command must be returned to increment the pointer when applying
let cmd = cmd.as_any_boxed() // the command queue.
.downcast::<C>() mem::size_of::<C>()
.unwrap();
cmd.run(world);
}; };
self.queue.0.push_back((run_fn, cmd)); let data = &mut self.queue.data;
// Reserve enough bytes from the vec to store the packed command and its run fn.
let old_len = data.len();
data.reserve(mem::size_of::<PackedCommand<C>>());
// Get a pointer to the end of the packed data. Safe since we just reserved enough memory
// to store this command.
let end_ptr = unsafe { data.as_mut_ptr().add(old_len) };
unsafe {
// write the command and its runner into the buffer
end_ptr.cast::<PackedCommand<C>>()
// written unaligned to keep everything packed
.write_unaligned(PackedCommand {
run: run_fn,
cmd,
});
// we wrote to the vec's buffer without using its api, so we need manually
// set the length of the vec.
data.set_len(old_len + mem::size_of::<PackedCommand<C>>());
}
} }
/// Spawn an entity into the World. See [`World::spawn`] /// Spawn an entity into the World. See [`World::spawn`]
@ -91,14 +167,8 @@ impl<'a, 'b> Commands<'a, 'b> {
} }
/// Execute all commands in the queue, in order of insertion /// Execute all commands in the queue, in order of insertion
pub fn execute(&mut self, world: &mut World) -> anyhow::Result<()> { pub fn execute(&mut self, world: &mut World) {
while let Some((cmd_fn, cmd_ptr)) = self.queue.0.pop_front() { self.queue.execute(Some(world));
unsafe {
cmd_fn(cmd_ptr, world);
}
}
Ok(())
} }
} }
@ -125,21 +195,26 @@ impl FnArgFetcher for Commands<'_, '_> {
let mut cmds = Commands::new(&mut state, world); let mut cmds = Commands::new(&mut state, world);
// safety: Commands has a mut borrow only 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);
} }
} }
/// A system for executing deferred commands that are stored in a [`World`] as a Resource.
///
/// Commands are usually added inside a system from a [`Commands`] object created just for it
/// as an fn argument. However, there may be cases that commands cannot be added that way, so
/// they can also be added as a resource and executed later in this system.
pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut<Commands>) -> anyhow::Result<()> { pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut<Commands>) -> anyhow::Result<()> {
commands.execute(world)?; commands.execute(world);
Ok(()) Ok(())
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{cell::Ref, ptr::NonNull}; use std::{cell::Ref, ptr::NonNull, sync::{atomic::{AtomicU32, Ordering}, Arc}};
use crate::{system::{GraphExecutor, IntoSystem}, tests::Vec2, Commands, DynTypeId, World}; use crate::{system::{GraphExecutor, IntoSystem}, tests::Vec2, CommandQueue, Commands, DynTypeId, World};
#[test] #[test]
fn deferred_commands() { fn deferred_commands() {
@ -170,4 +245,28 @@ mod tests {
let vec2: Ref<Vec2> = unsafe { col.get(3) }; let vec2: Ref<Vec2> = unsafe { col.get(3) };
assert_eq!(vec2.clone(), spawned_vec); assert_eq!(vec2.clone(), spawned_vec);
} }
/// A test that ensures a command in a command queue will only ever run once.
#[test]
fn commands_only_one_exec() {
let mut world = World::new();
let counter = Arc::new(AtomicU32::new(0));
let mut queue = CommandQueue::default();
let mut commands = Commands::new(&mut queue, &mut world);
let counter_cl = counter.clone();
commands.add(move |_world: &mut World| {
counter_cl.fetch_add(1, Ordering::AcqRel);
});
queue.execute(Some(&mut world));
assert_eq!(1, counter.load(Ordering::Acquire));
queue.execute(Some(&mut world));
// If its not one, the command somehow was executed.
// I would be surprised it wouldn't cause some segfault but still increment the counter
assert_eq!(1, counter.load(Ordering::Acquire));
}
} }

View File

@ -51,6 +51,9 @@ pub mod math;
pub use lyra_ecs_derive::*; pub use lyra_ecs_derive::*;
pub use atomic_refcell::AtomicRef;
pub use atomic_refcell::AtomicRefMut;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

View File

@ -1,4 +1,6 @@
use std::{marker::PhantomData, cell::{Ref, RefMut}}; use std::marker::PhantomData;
use atomic_refcell::{AtomicRef, AtomicRefMut};
use crate::{World, resource::ResourceObject}; use crate::{World, resource::ResourceObject};
@ -9,7 +11,7 @@ pub struct FetchResource<'a, T> {
_phantom: PhantomData<T>, _phantom: PhantomData<T>,
} }
impl<'a, T: 'a + 'static> Fetch<'a> for FetchResource<'a, T> { impl<'a, T: ResourceObject + 'a> Fetch<'a> for FetchResource<'a, T> {
type Item = Res<'a, T>; type Item = Res<'a, T>;
fn dangling() -> Self { fn dangling() -> Self {
@ -79,7 +81,7 @@ impl<R: ResourceObject> AsQuery for QueryResource<R> {
} }
/// A struct used for querying resources from the World. /// A struct used for querying resources from the World.
pub struct Res<'a, T>(pub(crate) Ref<'a, T>); pub struct Res<'a, T>(pub(crate) AtomicRef<'a, T>);
impl<'a, T: ResourceObject> std::ops::Deref for Res<'a, T> { impl<'a, T: ResourceObject> std::ops::Deref for Res<'a, T> {
type Target = T; type Target = T;
@ -98,7 +100,7 @@ pub struct FetchResourceMut<'a, T> {
_phantom: PhantomData<T>, _phantom: PhantomData<T>,
} }
impl<'a, T: 'a + 'static> Fetch<'a> for FetchResourceMut<'a, T> { impl<'a, T: ResourceObject + 'a> Fetch<'a> for FetchResourceMut<'a, T> {
type Item = ResMut<'a, T>; type Item = ResMut<'a, T>;
fn dangling() -> Self { fn dangling() -> Self {
@ -167,7 +169,7 @@ impl<R: ResourceObject> AsQuery for QueryResourceMut<R> {
} }
/// A struct used for querying resources from the World. /// A struct used for querying resources from the World.
pub struct ResMut<'a, T>(pub(crate) RefMut<'a, T>); pub struct ResMut<'a, T>(pub(crate) AtomicRefMut<'a, T>);
impl<'a, T: ResourceObject> std::ops::Deref for ResMut<'a, T> { impl<'a, T: ResourceObject> std::ops::Deref for ResMut<'a, T> {
type Target = T; type Target = T;

View File

@ -83,8 +83,10 @@ where
/// A query that fetches the origin, and target of a relation of type `R`. /// A query that fetches the origin, and target of a relation of type `R`.
/// ///
/// It provides it as a tuple in the following format: `(origin, relation, target)`. /// It provides it as a tuple in the following format: `(origin, relation, target)`.
/// Similar to [`RelatesTo`](super::RelatesTo), you can use [`ViewState::relate_pair`] to get a view that fetches the /// Similar to [`RelatesTo`](super::RelatesTo), you can use
/// pair, or unlike [`RelatesTo`](super::RelatesTo), you can do the common procedure of using [`World::view`]. /// [`ViewState::relate_pair`](crate::relation::ViewState::relate_pair) to get a view that
/// fetches the pair, or unlike [`RelatesTo`](super::RelatesTo), you can do the common
/// procedure of using [`World::view`].
pub struct RelatePair<R: Relation> { pub struct RelatePair<R: Relation> {
_marker: PhantomData<R>, _marker: PhantomData<R>,
} }

View File

@ -1,26 +1,40 @@
use std::{any::{TypeId, Any}, cell::{RefCell, Ref, RefMut}}; use std::any::{TypeId, Any};
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
/// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually. /// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually.
pub trait ResourceObject: 'static {} pub trait ResourceObject: Send + Sync + Any {
impl<T: 'static> ResourceObject for T {} fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl<T: Send + Sync + Any> ResourceObject for T {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
/// A type erased storage for a Resource. /// A type erased storage for a Resource.
pub struct ResourceData { pub struct ResourceData {
pub(crate) data: Box<RefCell<dyn Any>>, pub(crate) data: Box<AtomicRefCell<dyn ResourceObject>>,
type_id: TypeId, type_id: TypeId,
} }
impl ResourceData { impl ResourceData {
pub fn new<T: Any>(data: T) -> Self { pub fn new<T: ResourceObject>(data: T) -> Self {
Self { Self {
data: Box::new(RefCell::new(data)), data: Box::new(AtomicRefCell::new(data)),
type_id: TypeId::of::<T>(), type_id: TypeId::of::<T>(),
} }
} }
/// Returns a boolean indicating whether or not `T`` is of the same type of the Resource /// Returns a boolean indicating whether or not `T`` is of the same type of the Resource
pub fn is<T: 'static>(&self) -> bool { pub fn is<T: ResourceObject>(&self) -> bool {
self.type_id == TypeId::of::<T>() self.type_id == TypeId::of::<T>()
} }
@ -30,8 +44,8 @@ impl ResourceData {
/// ///
/// * If the data is already borrowed mutably, this will panic. /// * If the data is already borrowed mutably, this will panic.
/// * If the type of `T` is not the same as the resource type. /// * If the type of `T` is not the same as the resource type.
pub fn get<T: 'static>(&self) -> Ref<T> { pub fn get<T: ResourceObject>(&self) -> AtomicRef<T> {
Ref::map(self.data.borrow(), |a| a.downcast_ref().unwrap()) AtomicRef::map(self.data.borrow(), |a| a.as_any().downcast_ref().unwrap())
} }
/// Mutably borrow the data inside of the resource. /// Mutably borrow the data inside of the resource.
@ -40,8 +54,8 @@ impl ResourceData {
/// ///
/// * If the data is already borrowed mutably, this will panic. /// * If the data is already borrowed mutably, this will panic.
/// * If the type of `T` is not the same as the resource type. /// * If the type of `T` is not the same as the resource type.
pub fn get_mut<T: 'static>(&self) -> RefMut<T> { pub fn get_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
RefMut::map(self.data.borrow_mut(), |a| a.downcast_mut().unwrap()) AtomicRefMut::map(self.data.borrow_mut(), |a| a.as_any_mut().downcast_mut().unwrap())
} }
/// Borrow the data inside of the resource. /// Borrow the data inside of the resource.
@ -49,10 +63,9 @@ impl ResourceData {
/// # Panics /// # Panics
/// ///
/// * If the type of `T` is not the same as the resource type. /// * If the type of `T` is not the same as the resource type.
pub fn try_get<T: 'static>(&self) -> Option<Ref<T>> { pub fn try_get<T: ResourceObject>(&self) -> Option<AtomicRef<T>> {
self.data.try_borrow() self.data.try_borrow()
.map(|r| Ref::map(r, |a| a.downcast_ref().unwrap())) .map(|r| AtomicRef::map(r, |a| a.as_any().downcast_ref().unwrap()))
.ok() .ok()
} }
@ -61,9 +74,9 @@ impl ResourceData {
/// # Panics /// # Panics
/// ///
/// * If the type of `T` is not the same as the resource type. /// * If the type of `T` is not the same as the resource type.
pub fn try_get_mut<T: 'static>(&self) -> Option<RefMut<T>> { pub fn try_get_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
self.data.try_borrow_mut() self.data.try_borrow_mut()
.map(|r| RefMut::map(r, |a| a.downcast_mut().unwrap())) .map(|r| AtomicRefMut::map(r, |a| a.as_any_mut().downcast_mut().unwrap()))
.ok() .ok()
} }
} }

View File

@ -58,7 +58,8 @@ impl GraphExecutor {
} }
/// Executes the systems in the graph /// Executes the systems in the graph
pub fn execute(&mut self, mut world_ptr: NonNull<World>, stop_on_error: bool) -> Result<Vec<GraphExecutorError>, GraphExecutorError> { pub fn execute(&mut self, mut 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();
@ -99,23 +100,15 @@ impl GraphExecutor {
let mut commands = Commands::new(&mut queue, world); let mut commands = Commands::new(&mut queue, world);
let world = unsafe { world_ptr.as_mut() }; let world = unsafe { world_ptr.as_mut() };
if let Err(e) = commands.execute(world) commands.execute(world);
.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)
} }
fn topological_sort<'a>(&'a self, stack: &mut VecDeque<String>, visited: &mut HashSet<&'a str>, node: &'a GraphSystem) -> Result<(), GraphExecutorError> { fn topological_sort<'a>(&'a self, stack: &mut VecDeque<String>,
visited: &mut HashSet<&'a str>, node: &'a GraphSystem) -> Result<(), GraphExecutorError> {
if !visited.contains(node.name.as_str()) { if !visited.contains(node.name.as_str()) {
visited.insert(&node.name); visited.insert(&node.name);

View File

@ -1,4 +1,6 @@
use std::{any::TypeId, cell::{Ref, RefMut}, collections::HashMap, ptr::NonNull}; use std::{any::TypeId, collections::HashMap, ptr::NonNull};
use atomic_refcell::{AtomicRef, AtomicRefMut};
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewState, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker}; use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewState, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker};
@ -271,17 +273,17 @@ impl World {
//pub fn view_one(&self, entity: EntityId) -> //pub fn view_one(&self, entity: EntityId) ->
pub fn add_resource<T: 'static>(&mut self, data: T) { pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data)); self.resources.insert(TypeId::of::<T>(), ResourceData::new(data));
} }
pub fn add_resource_default<T: Default + 'static>(&mut self) { pub fn add_resource_default<T: ResourceObject + Default>(&mut self) {
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default())); self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default()));
} }
/// Get a resource from the world, or insert it into the world with the provided /// Get a resource from the world, or insert it into the world with the provided
/// `fn` and return it. /// `fn` and return it.
pub fn get_resource_or_else<T: 'static, F>(&mut self, f: F) -> RefMut<T> pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> AtomicRefMut<T>
where where
F: Fn() -> T + 'static F: Fn() -> T + 'static
{ {
@ -291,7 +293,7 @@ impl World {
} }
/// Get a resource from the world, or insert it into the world as its default. /// 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> pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> AtomicRefMut<T>
{ {
self.resources.entry(TypeId::of::<T>()) self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(T::default())) .or_insert_with(|| ResourceData::new(T::default()))
@ -302,21 +304,21 @@ impl World {
/// ///
/// Will panic if the resource is not in the world. See [`World::try_get_resource`] for /// Will panic if the resource is not in the world. See [`World::try_get_resource`] for
/// a function that returns an option. /// a function that returns an option.
pub fn get_resource<T: 'static>(&self) -> Ref<T> { pub fn get_resource<T: ResourceObject>(&self) -> AtomicRef<T> {
self.resources.get(&TypeId::of::<T>()) self.resources.get(&TypeId::of::<T>())
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>())) .expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
.get() .get()
} }
/// Returns boolean indicating if the World contains a resource of type `T`. /// Returns boolean indicating if the World contains a resource of type `T`.
pub fn has_resource<T: 'static>(&self) -> bool { pub fn has_resource<T: ResourceObject>(&self) -> bool {
self.resources.contains_key(&TypeId::of::<T>()) 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.
pub fn try_get_resource<T: 'static>(&self) -> Option<Ref<T>> { pub fn try_get_resource<T: ResourceObject>(&self) -> Option<AtomicRef<T>> {
self.resources.get(&TypeId::of::<T>()) self.resources.get(&TypeId::of::<T>())
.and_then(|r| r.try_get()) .and_then(|r| r.try_get())
} }
@ -325,7 +327,7 @@ impl World {
/// ///
/// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for /// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for
/// a function that returns an option. /// a function that returns an option.
pub fn get_resource_mut<T: 'static>(&self) -> RefMut<T> { pub fn get_resource_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
self.resources.get(&TypeId::of::<T>()) self.resources.get(&TypeId::of::<T>())
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>())) .expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
.get_mut() .get_mut()
@ -334,7 +336,7 @@ impl World {
/// Attempts to get a mutable borrow of a resource from the World. /// Attempts to get a mutable borrow of a resource from the World.
/// ///
/// Returns `None` if the resource was not found. /// Returns `None` if the resource was not found.
pub fn try_get_resource_mut<T: 'static>(&self) -> Option<RefMut<T>> { pub fn try_get_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
self.resources.get(&TypeId::of::<T>()) self.resources.get(&TypeId::of::<T>())
.and_then(|r| r.try_get_mut()) .and_then(|r| r.try_get_mut())
} }
@ -364,6 +366,10 @@ impl World {
} }
} }
// TODO: Ensure that all non-send resources are only accessible on the main thread.
unsafe impl Send for World {}
unsafe impl Sync for World {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{query::TickOf, tests::{Vec2, Vec3}, Entity}; use crate::{query::TickOf, tests::{Vec2, Vec3}, Entity};
@ -440,16 +446,14 @@ mod tests {
#[test] #[test]
fn resource_multi_borrow() { fn resource_multi_borrow() {
let mut world = World::new(); let mut world = World::new();
{ let counter = SimpleCounter(4582);
let counter = SimpleCounter(4582); world.add_resource(counter);
world.add_resource(counter);
}
// test multiple borrows at the same time // test multiple borrows at the same time
let counter = world.get_resource::<SimpleCounter>(); let counter = world.get_resource::<SimpleCounter>();
assert_eq!(counter.0, 4582); assert_eq!(counter.0, 4582);
let counter2 = world.get_resource::<SimpleCounter>(); let counter2 = world.get_resource::<SimpleCounter>();
assert_eq!(counter2.0, 4582); assert_eq!(counter.0, 4582);
assert_eq!(counter2.0, 4582); assert_eq!(counter2.0, 4582);
} }
@ -461,7 +465,7 @@ mod tests {
world.add_resource(counter); world.add_resource(counter);
} }
// test multiple borrows at the same time // test that its only possible to get a single mutable borrow
let counter = world.get_resource_mut::<SimpleCounter>(); let counter = world.get_resource_mut::<SimpleCounter>();
assert_eq!(counter.0, 4582); assert_eq!(counter.0, 4582);
assert!(world.try_get_resource_mut::<SimpleCounter>().is_none()); assert!(world.try_get_resource_mut::<SimpleCounter>().is_none());

View File

@ -8,6 +8,7 @@ lyra-resource = { path = "../lyra-resource" }
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] } lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] } lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
lyra-math = { path = "../lyra-math" } lyra-math = { path = "../lyra-math" }
lyra-scene = { path = "../lyra-scene" }
winit = "0.28.1" winit = "0.28.1"
tracing = "0.1.37" tracing = "0.1.37"

View File

@ -1,12 +1,13 @@
use std::any::Any; use std::any::Any;
pub trait CastableAny: Send + Sync + 'static { /// A trait that is implemented for types that are `Send + Sync + 'static`.
pub trait AsSyncAny: Send + Sync + '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;
} }
/// Implements this trait for anything that fits the type bounds /// Implements this trait for anything that fits the type bounds
impl<T: Send + Sync + 'static> CastableAny for T { impl<T: Send + Sync + 'static> AsSyncAny for T {
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
self self
} }

View File

@ -1,15 +1,82 @@
use std::{collections::{HashMap, VecDeque}, any::{TypeId, Any}, cell::RefCell}; use std::{any::{Any, TypeId}, collections::{HashMap, VecDeque}, marker::PhantomData, mem::{self, MaybeUninit}, ops::Range};
use crate::{castable_any::CastableAny, plugin::Plugin}; use crate::plugin::Plugin;
pub trait Event: Clone + Send + Sync + 'static {} pub trait Event: Clone + Send + Sync + 'static {}
impl<T: Clone + Send + Sync + 'static> Event for T {} impl<T: Clone + Send + Sync + 'static> Event for T {}
pub type Events<T> = VecDeque<T>; pub type Events<T> = VecDeque<T>;
#[derive(Clone)]
struct TypeErasedBuffer {
type_id: TypeId,
item_size: usize,
data: Vec<MaybeUninit<u8>>,
fn_drop_item: fn(item: *mut ()),
}
impl Drop for TypeErasedBuffer {
fn drop(&mut self) {
let range = self.data.as_mut_ptr_range();
let mut current = range.start;
let end = range.end;
while current < end {
// SAFETY: current pointer will either be the start of the buffer, or at the start of
// the next item
(self.fn_drop_item)(current.cast());
// SAFETY: the items are placed right after eachother
current = unsafe { current.add(self.item_size) };
}
// Safety: all of the events were just dropped
unsafe { self.data.set_len(0) };
}
}
impl TypeErasedBuffer {
pub fn new<T: 'static>() -> Self {
Self {
type_id: TypeId::of::<T>(),
item_size: mem::size_of::<T>(),
data: Default::default(),
// dropped immediately after the read
fn_drop_item: |item| unsafe { item.cast::<T>().drop_in_place() },
}
}
pub fn push<T: 'static>(&mut self, value: T) {
debug_assert!(TypeId::of::<T>() == self.type_id, "The type of the buffer does not match the type that was added to it");
// reserve enough bytes to store T
let old_len = self.data.len();
self.data.reserve(mem::size_of::<T>());
// Get a pointer to the end of the data.
// SAFETY: we just reserved enough memory to store the data.
let end_ptr = unsafe { self.data.as_mut_ptr().add(old_len) };
unsafe {
// write the command and its runner into the buffer
end_ptr.cast::<T>()
// written unaligned to keep everything packed
.write_unaligned(value);
// we wrote to the vec's buffer without using its api, so we need manually
// set the length of the vec.
self.data.set_len(old_len + mem::size_of::<T>());
}
}
pub fn len(&self) -> usize {
self.data.len() / self.item_size
}
}
pub struct EventQueue { pub struct EventQueue {
events: HashMap<TypeId, RefCell<Box<dyn CastableAny>>>, events: HashMap<TypeId, TypeErasedBuffer>,
event_write_queue: HashMap<TypeId, RefCell<Box<dyn CastableAny>>> event_write_queue: HashMap<TypeId, TypeErasedBuffer>,
} }
impl Default for EventQueue { impl Default for EventQueue {
@ -32,7 +99,7 @@ impl EventQueue {
E: Event E: Event
{ {
// the compiler wants me to explicit right here for some reason // the compiler wants me to explicit right here for some reason
let default = || RefCell::new(Box::<Events::<E>>::default() as Box<dyn CastableAny>); /* let default = || RefCell::new(Box::<Events::<E>>::default() as Box<dyn AsSyncAny>);
// Get, or create, a list of events of this type // Get, or create, a list of events of this type
let type_id = event.type_id(); let type_id = event.type_id();
@ -42,7 +109,13 @@ impl EventQueue {
// downcast resource as an events storage // downcast resource as an events storage
let e: &mut Events<E> = events.as_mut().as_any_mut().downcast_mut().unwrap(); let e: &mut Events<E> = events.as_mut().as_any_mut().downcast_mut().unwrap();
e.push_back(event); e.push_back(event); */
let type_id = event.type_id();
let events = self.event_write_queue.entry(type_id)
.or_insert_with(|| TypeErasedBuffer::new::<E>());
events.push(event);
} }
// Clear events, this should happen at the start of every tick since events are cloned // Clear events, this should happen at the start of every tick since events are cloned
@ -60,13 +133,55 @@ impl EventQueue {
} }
} }
pub fn read_events<E>(&self) -> Option<Events<E>> pub fn read_events<E>(&self) -> Option<EventReader<E>>
where where
E: Event E: Event
{ {
if let Some(event ) = self.events.get(&TypeId::of::<E>()) { self.events.get(&TypeId::of::<E>())
let eref = event.borrow(); .map(|event| EventReader::new(event.clone()))
Some(eref.as_ref().as_any().downcast_ref::<Events<E>>().unwrap().clone()) }
}
pub struct EventReader<E: Event> {
buf: TypeErasedBuffer,
range: Option<Range<*mut MaybeUninit<u8>>>,
_marker: PhantomData<E>,
}
impl<E: Event> EventReader<E> {
fn new(buffer: TypeErasedBuffer) -> Self {
Self {
buf: buffer,
range: None,
_marker: PhantomData,
}
}
pub fn len(&self) -> usize {
self.buf.len()
}
}
impl<E: Event> Iterator for EventReader<E> {
type Item = E;
fn next(&mut self) -> Option<Self::Item> {
if self.range.is_none() {
self.range = Some(self.buf.data.as_mut_ptr_range());
}
let range = self.range.as_mut().unwrap();
if range.start < range.end {
// Retrieve the event in the buffer.
// SAFETY: current pointer will either be the start of the buffer, or at the start
// of the next event.
let event = unsafe { range.start.cast::<E>().read_unaligned() };
// Advance the pointer to be after this event.
range.start = unsafe { range.start.add(mem::size_of::<E>()) };
Some(event)
} else { } else {
None None
} }

View File

@ -521,10 +521,10 @@ fn actions_system(world: &mut World) -> anyhow::Result<()> {
} }
} }
let motion_avg = if let Some(mut mouse_events) = mouse_events { let motion_avg = if let Some(mouse_events) = mouse_events {
let count = mouse_events.len(); let count = mouse_events.len();
let mut sum = Vec2::ZERO; let mut sum = Vec2::ZERO;
while let Some(mm) = mouse_events.pop_front() { for mm in mouse_events {
sum += mm.delta; sum += mm.delta;
} }
Some(sum / count as f32) Some(sum / count as f32)

View File

@ -110,8 +110,8 @@ impl crate::ecs::system::System for InputSystem {
return Ok(()); return Ok(());
} }
let mut events = queue.unwrap(); let events = queue.unwrap();
while let Some(event) = events.pop_front() { for event in events {
self.process_event(world, &event); self.process_event(world, &event);
} }

View File

@ -8,7 +8,7 @@ pub mod game;
pub mod render; pub mod render;
pub mod resources; pub mod resources;
pub mod input; pub mod input;
pub mod castable_any; pub mod as_any;
pub mod plugin; pub mod plugin;
pub mod change_tracker; pub mod change_tracker;

View File

@ -1,17 +1,21 @@
use lyra_ecs::Entity; use lyra_ecs::Entity;
use super::transform_buffer_storage::TransformIndex;
pub struct RenderJob { pub struct RenderJob {
pub entity: Entity, pub entity: Entity,
pub shader_id: u64, pub shader_id: u64,
pub mesh_uuid: uuid::Uuid, pub mesh_uuid: uuid::Uuid,
pub transform_id: TransformIndex,
} }
impl RenderJob { impl RenderJob {
pub fn new(entity: Entity, shader_id: u64, mesh_buffer_id: uuid::Uuid) -> Self { pub fn new(entity: Entity, shader_id: u64, mesh_buffer_id: uuid::Uuid, transform_id: TransformIndex) -> Self {
Self { Self {
entity, entity,
shader_id, shader_id,
mesh_uuid: mesh_buffer_id, mesh_uuid: mesh_buffer_id,
transform_id
} }
} }
} }

View File

@ -10,7 +10,7 @@ use lyra_ecs::query::filter::{Has, Or};
use lyra_ecs::{Entity, Tick}; use lyra_ecs::{Entity, Tick};
use lyra_ecs::query::{Entities, TickOf}; use lyra_ecs::query::{Entities, TickOf};
use lyra_ecs::World; use lyra_ecs::World;
use lyra_resource::gltf::GltfScene; use lyra_scene::SceneGraph;
use tracing::{debug, warn}; use tracing::{debug, warn};
use uuid::Uuid; use uuid::Uuid;
use wgpu::{BindGroupLayout, Limits}; use wgpu::{BindGroupLayout, Limits};
@ -29,14 +29,14 @@ use super::light_cull_compute::LightCullCompute;
use super::material::Material; use super::material::Material;
use super::render_buffer::BufferWrapper; use super::render_buffer::BufferWrapper;
use super::texture::RenderTexture; use super::texture::RenderTexture;
use super::transform_buffer_storage::TransformBuffers; use super::transform_buffer_storage::{TransformBuffers, TransformGroup};
use super::vertex::Vertex; use super::vertex::Vertex;
use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob}; use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
use lyra_resource::{gltf::Mesh, ResHandle}; use lyra_resource::{gltf::Mesh, ResHandle};
type MeshHandle = ResHandle<Mesh>; type MeshHandle = ResHandle<Mesh>;
type SceneHandle = ResHandle<GltfScene>; type SceneHandle = ResHandle<SceneGraph>;
pub trait Renderer { pub trait Renderer {
fn prepare(&mut self, main_world: &mut World); fn prepare(&mut self, main_world: &mut World);
@ -374,12 +374,13 @@ impl BasicRenderer {
/// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed. /// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed.
fn process_mesh(&mut self, entity: Entity, transform: Transform, mesh: &Mesh, mesh_uuid: Uuid) -> bool { fn process_mesh(&mut self, entity: Entity, transform: Transform, mesh: &Mesh, mesh_uuid: Uuid) -> bool {
if self.transform_buffers.should_expand() { let _ = transform;
/* if self.transform_buffers.should_expand() {
self.transform_buffers.expand_buffers(&self.device); self.transform_buffers.expand_buffers(&self.device);
} }
self.transform_buffers.update_or_insert(&self.queue, &self.render_limits, self.transform_buffers.update_or_insert(&self.queue, &self.render_limits,
mesh_uuid, || ( transform.calculate_mat4(), glam::Mat3::from_quat(transform.rotation) )); entity, || ( transform.calculate_mat4(), glam::Mat3::from_quat(transform.rotation) )); */
#[allow(clippy::map_entry)] #[allow(clippy::map_entry)]
if !self.mesh_buffers.contains_key(&mesh_uuid) { if !self.mesh_buffers.contains_key(&mesh_uuid) {
@ -451,10 +452,14 @@ impl Renderer for BasicRenderer {
self.check_mesh_buffers(entity, &mesh_han); self.check_mesh_buffers(entity, &mesh_han);
} }
let group = TransformGroup::EntityRes(entity, mesh_han.uuid());
let transform_id = self.transform_buffers.update_or_push(&self.queue, &self.render_limits,
group, || ( interop_pos.calculate_mat4(), glam::Mat3::from_quat(interop_pos.rotation) ));
let material = mesh.material.as_ref().unwrap() let material = mesh.material.as_ref().unwrap()
.data_ref().unwrap(); .data_ref().unwrap();
let shader = material.shader_uuid.unwrap_or(0); let shader = material.shader_uuid.unwrap_or(0);
let job = RenderJob::new(entity, shader, mesh_han.uuid()); let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id);
self.render_jobs.push_back(job); self.render_jobs.push_back(job);
} }
} }
@ -465,27 +470,33 @@ impl Renderer for BasicRenderer {
if let Some(scene) = scene_han.data_ref() { if let Some(scene) = scene_han.data_ref() {
let interpo_pos = self.interpolate_transforms(now_inst, last_epoch, entity, &transform, transform_epoch); let interpo_pos = self.interpolate_transforms(now_inst, last_epoch, entity, &transform, transform_epoch);
let meshes = scene.collect_world_meshes();
for (mesh_han, mesh_pos) in meshes.into_iter() { scene.traverse_down(|sw: &World, mesh_han, pos| {
if let Some(mesh) = mesh_han.data_ref() { if let Some(mesh_han) = sw.view_one::<&MeshHandle>(mesh_han.entity()).get() {
let mesh_interpo = interpo_pos + mesh_pos; if let Some(mesh) = mesh_han.data_ref() {
let mesh_interpo = interpo_pos + pos;
// if process mesh did not just create a new mesh, and the epoch
// shows that the scene has changed, verify that the mesh buffers
// dont need to be resent to the gpu.
if !self.process_mesh(entity, mesh_interpo, &*mesh, mesh_han.uuid())
&& scene_epoch == last_epoch {
self.check_mesh_buffers(entity, &mesh_han);
}
// if process mesh did not just create a new mesh, and the epoch let scene_mesh_group = TransformGroup::Res(scene_han.uuid(), mesh_han.uuid());
// shows that the scene has changed, verify that the mesh buffers let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into());
// dont need to be resent to the gpu. let transform_id = self.transform_buffers.update_or_push(&self.queue, &self.render_limits,
if !self.process_mesh(entity, mesh_interpo, &*mesh, mesh_han.uuid()) group, || ( mesh_interpo.calculate_mat4(), glam::Mat3::from_quat(mesh_interpo.rotation) ));
&& scene_epoch == last_epoch {
self.check_mesh_buffers(entity, &mesh_han); let material = mesh.material.as_ref().unwrap()
.data_ref().unwrap();
let shader = material.shader_uuid.unwrap_or(0);
let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id);
self.render_jobs.push_back(job);
} }
let material = mesh.material.as_ref().unwrap()
.data_ref().unwrap();
let shader = material.shader_uuid.unwrap_or(0);
let job = RenderJob::new(entity, shader, mesh_han.uuid());
self.render_jobs.push_back(job);
} }
} });
} }
} }
} }
@ -575,9 +586,8 @@ impl Renderer for BasicRenderer {
} }
// Get the bindgroup for job's transform and bind to it using an offset. // Get the bindgroup for job's transform and bind to it using an offset.
let transform_indices = *self.transform_buffers.transform_indices(job.mesh_uuid).unwrap(); let bindgroup = self.transform_buffers.bind_group(job.transform_id);
let bindgroup = self.transform_buffers.bind_group(transform_indices).unwrap(); let offset = self.transform_buffers.buffer_offset(job.transform_id);
let offset = TransformBuffers::index_offset(&self.render_limits, transform_indices) as u32;
render_pass.set_bind_group(1, bindgroup, &[ offset, offset, ]); render_pass.set_bind_group(1, bindgroup, &[ offset, offset, ]);
render_pass.set_bind_group(2, &self.camera_buffer.bindgroup(), &[]); render_pass.set_bind_group(2, &self.camera_buffer.bindgroup(), &[]);

View File

@ -1,44 +1,167 @@
use std::{collections::{VecDeque, HashMap}, num::NonZeroU64}; use std::{collections::{HashMap, VecDeque}, hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}, num::NonZeroU64};
use lyra_ecs::Entity;
use uuid::Uuid; use uuid::Uuid;
use wgpu::Limits; use wgpu::Limits;
use std::mem; use std::mem;
/// A group id created from a [`TransformGroup`].
///
/// This is mainly created so that [`TransformGroup::OwnedGroup`] can use another group inside of it.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TransformGroupId(u64);
impl From<TransformGroup> for TransformGroupId {
fn from(value: TransformGroup) -> Self {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
let hash = hasher.finish();
TransformGroupId(hash)
}
}
/// Used as a key into the [`TransformBuffers`].
///
/// This enum is used as a key to identify a transform for a RenderJob. The renderer uses this
/// to differentiate a transform between two entities that share a resource handle to the same
/// scene:
/// ```nobuild
/// // The group of the mesh in the scene.
/// let scene_mesh_group = TransformGroup::Res(scene_handle.uuid(), mesh_handle.uuid());
/// // The group of the owned entity that has mesh in a scene.
/// let finished_group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into());
/// ```
///
/// A simpler example of the use of a transform group is when processing lone mesh handles
/// owned by entities:
/// ```nobuild
/// let group = TransformGroup::EntityRes(entity, mesh_handle.uuid());
/// ```
///
/// These were made to fix [#6](https://git.seanomik.net/SeanOMik/lyra-engine/issues/6).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TransformGroup {
/// Just an entity.
Entity(Entity),
/// An entity that owns another group.
OwnedGroup(Entity, TransformGroupId),
/// A resource uuid grouped with an owning Entity.
EntityRes(Entity, Uuid),
/// A resource uuid grouped with another resource uuid.
Res(Uuid, Uuid),
}
/// The index of a specific Transform inside of the buffers.
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] #[derive(Default, Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) struct TransformBufferIndices { pub struct TransformIndex {
pub buffer_index: usize, /// The index of the entry in the buffer chain.
pub transform_index: usize, entry_index: usize,
/// The index of the transform in the entry.
transform_index: usize,
} }
/// A struct representing a single transform buffer. There can be multiple of these /// A struct representing a single transform buffer. There can be multiple of these
pub(crate) struct TransformBufferEntry { struct BufferEntry {
pub len: usize, pub len: usize,
pub transform_buf: wgpu::Buffer,
pub normal_mat_buf: wgpu::Buffer,
pub bindgroup: wgpu::BindGroup, pub bindgroup: wgpu::BindGroup,
pub transform_buffer: wgpu::Buffer,
pub normal_buffer: wgpu::Buffer,
}
/// A HashMap that caches values for reuse.
///
/// The map detects dead values by tracking which entries were not updated since the last time
/// [`CachedValMap::update`] was ran. When dead values are collected, they can be reused on an
/// [`insert`](CachedValMap::insert) into the map.
struct CachedValMap<K, V, S = RandomState> {
latest: HashMap<K, V, S>,
old: HashMap<K, V, S>,
dead: VecDeque<V>,
}
impl<K, V, S: Default> Default for CachedValMap<K, V, S> {
fn default() -> Self {
Self {
latest: Default::default(),
old: Default::default(),
dead: Default::default()
}
}
}
#[allow(dead_code)]
impl<K: Hash + Eq + PartialEq + Clone, V: Clone, S: BuildHasher> CachedValMap<K, V, S> {
/// Insert a key, possibly reusing a value in the map.
///
/// Returns the reused value, if one was reused. If its `None`, then the value was retrieved
/// from running `val_fn`.
pub fn insert<F>(&mut self, key: K, mut val_fn: F) -> Option<V>
where
F: FnMut() -> V
{
if self.latest.contains_key(&key) {
self.latest.insert(key, val_fn());
None
} else {
let val = self.dead.pop_front()
.unwrap_or_else(val_fn);
self.latest.insert(key, val.clone());
Some(val)
}
}
/// Returns a reference to the value corresponding to the key.
pub fn get(&mut self, key: K) -> Option<&V> {
if let Some(v) = self.old.remove(&key) {
self.latest.insert(key.clone(), v);
}
self.latest.get(&key)
}
/// Keep a key alive without updating its value.
pub fn keep_alive(&mut self, key: K) {
if let Some(v) = self.old.remove(&key) {
self.latest.insert(key, v);
}
}
/// Returns `true` if the map contains a value for the specified key.
pub fn contains(&self, key: K) -> bool {
self.old.contains_key(&key) || self.latest.contains_key(&key)
}
/// Collects the now dead values for reuse.
///
/// This detects dead values by tracking which entries were not updated since the last time
/// update was ran.
pub fn update(&mut self) {
// drain the dead values into the dead queue
self.dead.extend(self.old.drain().map(|(_, v)| v));
// now drain the latest entries into the old entries
self.old.extend(self.latest.drain());
}
} }
/// A helper struct for managing the Transform buffers for meshes. /// A helper struct for managing the Transform buffers for meshes.
/// ///
/// This struct manages a "chain" of uniform buffers that store Transform for meshes. When /// This struct manages a "chain" of uniform buffers that store Transform for [`TransformGroup`]s.
/// first created it only has a single "chain-link" with a buffer that is the maximum length /// When first created it only has a single "chain-link" with a buffer that is the maximum length
/// the GPU supports. When the first buffer fills up, a new one should be created which will also /// the GPU supports. When the first buffer fills up, a new one should be created which will also
/// be the maximum length the GPU supports. When the new buffer fills up, a new one will be /// be the maximum length the GPU supports. When the new buffer fills up, a new one will be
/// created once again, and so on. /// created once again, and so on.
/// ///
/// `Uuid`'s are used to represent entries (usually Meshes) in the buffer. The Uuid's can be used /// [`TransformGroup`]s are used to represent entries in the buffer. They are used to insert,
/// to insert, update, and retrieve the transforms. /// update, and retrieve the transforms.
pub(crate) struct TransformBuffers { pub struct TransformBuffers {
pub bindgroup_layout: wgpu::BindGroupLayout, pub bindgroup_layout: wgpu::BindGroupLayout,
pub just_updated: HashMap<Uuid, TransformBufferIndices>, groups: CachedValMap<TransformGroupId, TransformIndex>,
pub not_updated: HashMap<Uuid, TransformBufferIndices>, entries: Vec<BufferEntry>,
pub dead_indices: VecDeque<TransformBufferIndices>, limits: wgpu::Limits,
pub next_indices: TransformBufferIndices, max_transform_count: usize,
/// (transform count, buffer, bindgroup)
pub buffer_bindgroups: Vec<TransformBufferEntry>,
/// The max amount of transforms in a buffer
pub max_transform_count: usize,
} }
impl TransformBuffers { impl TransformBuffers {
@ -72,13 +195,11 @@ impl TransformBuffers {
}); });
let mut s = Self { let mut s = Self {
max_transform_count: limits.max_uniform_buffer_binding_size as usize / (mem::size_of::<glam::Mat4>() * 2),
buffer_bindgroups: Vec::new(),
bindgroup_layout, bindgroup_layout,
just_updated: HashMap::new(), groups: Default::default(),
not_updated: HashMap::new(), entries: Default::default(),
dead_indices: VecDeque::new(), max_transform_count: (limits.max_uniform_buffer_binding_size / 2) as usize / (mem::size_of::<glam::Mat4>()),
next_indices: TransformBufferIndices::default(), limits,
}; };
// create the first uniform buffer // create the first uniform buffer
@ -87,106 +208,84 @@ impl TransformBuffers {
s s
} }
/// Update an transform in the buffer. /// Update an existing transform in the buffers.
/// ///
/// # Panics /// # Panics
/// Panics if the entity isn't stored, you can check if it is before with [`TransformBuffers:contains`]. /// Panics if the `entity_group` is not already inside of the buffers.
pub fn update_transform(&mut self, queue: &wgpu::Queue, limits: &Limits, uuid: Uuid, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices { pub fn update_transform(&mut self, queue: &wgpu::Queue, limits: &Limits, entity_group: TransformGroup, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformIndex {
let indices = self.not_updated.remove(&uuid) let index = *self.groups.get(entity_group.into())
.or_else(|| self.just_updated.remove(&uuid)) .expect("Use 'push_transform' for new entities");
.expect("Use 'insert_entity' for new entities"); let entry = self.entries.get_mut(index.entry_index).unwrap();
self.just_updated.insert(uuid, indices);
let normal_matrix = glam::Mat4::from_mat3(normal_matrix); let normal_matrix = glam::Mat4::from_mat3(normal_matrix);
let buffer = self.buffer_bindgroups.get(indices.buffer_index).unwrap(); // write the transform and normal to the end of the transform
let offset = Self::index_offset(limits, indices); let offset = Self::get_buffer_offset(limits, index) as _;
queue.write_buffer(&buffer.transform_buf, offset, bytemuck::bytes_of(&transform)); queue.write_buffer(&entry.transform_buffer, offset, bytemuck::bytes_of(&transform));
queue.write_buffer(&buffer.normal_mat_buf, offset, bytemuck::bytes_of(&normal_matrix)); queue.write_buffer(&entry.normal_buffer, offset, bytemuck::bytes_of(&normal_matrix));
indices
index
} }
/// Insert a new transform into the buffer, returns where in the buffer it was stored. /// Push a new transform into the buffers.
pub fn insert_transform(&mut self, queue: &wgpu::Queue, limits: &Limits, uuid: Uuid, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices { pub fn push_transform(&mut self, queue: &wgpu::Queue, limits: &Limits, entity_group: TransformGroup, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformIndex {
let indices = match self.dead_indices.pop_front() { self.groups.insert(entity_group.into(), || {
Some(indices) => indices, // this closure is only called when there are no values that can be reused,
None => { // so we get a brand new index at the end of the last entry in the chain.
let indices = &mut self.next_indices; let last = self.entries.last_mut().unwrap();
let this_idx = *indices;
let entry = self.buffer_bindgroups.get_mut(indices.buffer_index).unwrap(); // ensure the gpu buffer is not overflown
debug_assert!(last.len < self.max_transform_count,
"Transform buffer is filled and 'next_indices' was not incremented! \
Was a new buffer created?");
let tidx = last.len;
last.len += 1;
if entry.len >= self.max_transform_count { TransformIndex {
panic!("Transform buffer is filled and 'next_indices' was not incremented! Was a new buffer created?"); entry_index: self.entries.len() - 1,
} transform_index: tidx
entry.len += 1;
indices.transform_index += 1;
this_idx
} }
}; });
self.just_updated.insert(uuid, indices); self.update_transform(queue, limits, entity_group, transform, normal_matrix)
self.update_transform(queue, limits, uuid, transform, normal_matrix)
}
/// Update or insert a transform
pub fn update_or_insert<TFn>(&mut self, queue: &wgpu::Queue, limits: &Limits, uuid: Uuid, transform_fn: TFn) -> TransformBufferIndices
where TFn: Fn() -> (glam::Mat4, glam::Mat3)
{
let (tran, norm) = transform_fn();
if self.contains(uuid) {
self.update_transform(queue, limits, uuid, tran, norm)
} else {
self.insert_transform(queue, limits, uuid, tran, norm)
}
}
/// Returns true if the transform related to the `uuid` is stored (does not mean its up-to-date).
pub fn contains(&self, uuid: Uuid) -> bool {
self.not_updated.contains_key(&uuid) || self.just_updated.contains_key(&uuid)
} }
/// Collect the dead transforms and prepare self to check next time. /// Collect the dead transforms and prepare self to check next time.
pub fn tick(&mut self) { pub fn tick(&mut self) {
// take the dead entities, these were ones that were not updated this tick self.groups.update();
let dead: VecDeque<TransformBufferIndices> = self.not_updated.values().copied().collect();
self.dead_indices = dead;
self.not_updated = self.just_updated.clone();
self.just_updated.clear();
} }
/// Returns the offset for the transform index in the buffer /// Returns a boolean indicating if the buffer contains this group.
pub fn index_offset(limits: &Limits, indices: TransformBufferIndices) -> u64 { pub fn contains(&self, group: TransformGroup) -> bool {
indices.transform_index as u64 * limits.min_uniform_buffer_offset_alignment as u64 self.groups.contains(group.into())
} }
/// Returns whether or not the transform buffers should be expanded /// Update an existing transform group or if its not existing yet, pushes it to the buffer.
pub fn should_expand(&self) -> bool { ///
if let Some(entry) = self.buffer_bindgroups.last() { /// Returns: the index that the transform is at in the buffers.
entry.len >= self.max_transform_count pub fn update_or_push<F>(&mut self, queue: &wgpu::Queue, limits: &Limits, group: TransformGroup, transform_fn: F) -> TransformIndex
where F: Fn() -> (glam::Mat4, glam::Mat3)
{
let (transform, normal_matrix) = transform_fn();
if self.contains(group) {
self.update_transform(queue, limits, group, transform, normal_matrix)
} else { } else {
true self.push_transform(queue, limits, group, transform, normal_matrix)
} }
} }
/// Returns the bind group for the index
pub fn bind_group(&self, index: TransformBufferIndices) -> Option<&wgpu::BindGroup> {
self.buffer_bindgroups.get(index.buffer_index)
.map(|entry| &entry.bindgroup)
}
/// Expand the Transform buffers by adding another uniform buffer binding. /// Expand the Transform buffers by adding another uniform buffer binding.
/// ///
/// This object has a chain of uniform buffers, when the buffers are expanded, a new /// This object has a chain of uniform buffers, when the buffers are expanded, a new
/// "chain-link" is created. /// "chain-link" is created.
pub fn expand_buffers(&mut self, device: &wgpu::Device) { pub fn expand_buffers(&mut self, device: &wgpu::Device) {
let limits = device.limits(); let limits = device.limits();
let max_buffer_sizes = (limits.max_uniform_buffer_binding_size as u64) / 2; let max_buffer_sizes = self.max_transform_count as u64 * limits.min_uniform_buffer_offset_alignment as u64;
let transform_buffer = device.create_buffer( let transform_buffer = device.create_buffer(
&wgpu::BufferDescriptor { &wgpu::BufferDescriptor {
label: Some(&format!("B_Transform_{}", self.buffer_bindgroups.len())), label: Some(&format!("B_Transform_{}", self.entries.len())),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
size: max_buffer_sizes, size: max_buffer_sizes,
mapped_at_creation: false, mapped_at_creation: false,
@ -195,7 +294,7 @@ impl TransformBuffers {
let normal_mat_buffer = device.create_buffer( let normal_mat_buffer = device.create_buffer(
&wgpu::BufferDescriptor { &wgpu::BufferDescriptor {
label: Some(&format!("B_NormalMatrix_{}", self.buffer_bindgroups.len())), label: Some(&format!("B_NormalMatrix_{}", self.entries.len())),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
size: max_buffer_sizes, size: max_buffer_sizes,
mapped_at_creation: false, mapped_at_creation: false,
@ -234,17 +333,43 @@ impl TransformBuffers {
label: Some("BG_Transforms"), label: Some("BG_Transforms"),
}); });
let entry = TransformBufferEntry { let entry = BufferEntry {
bindgroup: transform_bind_group, bindgroup: transform_bind_group,
transform_buf: transform_buffer, transform_buffer,
normal_mat_buf: normal_mat_buffer, normal_buffer: normal_mat_buffer,
len: 0, len: 0,
}; };
self.buffer_bindgroups.push(entry); self.entries.push(entry);
} }
/// Returns the indices of the Transform /// Returns the bind group for the transform index.
pub fn transform_indices(&self, uuid: Uuid) -> Option<&TransformBufferIndices> { pub fn bind_group(&self, transform_id: TransformIndex) -> &wgpu::BindGroup {
self.just_updated.get(&uuid).or_else(|| self.not_updated.get(&uuid)) let entry = self.entries.get(transform_id.entry_index).unwrap();
&entry.bindgroup
} }
/// Get the buffer offset for a transform using wgpu limits.
///
/// If its possible to borrow immutably, use [`TransformBuffers::buffer_offset`].
fn get_buffer_offset(limits: &wgpu::Limits, transform_index: TransformIndex) -> u32 {
transform_index.transform_index as u32 * limits.min_uniform_buffer_offset_alignment as u32
}
/// Returns the offset of the transform inside the bind group buffer.
///
/// ```nobuild
/// let bindgroup = transform_buffers.bind_group(job.transform_id);
/// let offset = transform_buffers.buffer_offset(job.transform_id);
/// render_pass.set_bind_group(1, bindgroup, &[ offset, offset, ]);
/// ```
pub fn buffer_offset(&self, transform_index: TransformIndex) -> u32 {
Self::get_buffer_offset(&self.limits, transform_index)
}
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct TransformNormalMatPair {
transform: glam::Mat4,
normal_mat: glam::Mat4,
} }

View File

@ -341,8 +341,8 @@ fn window_updater_system(world: &mut World) -> anyhow::Result<()> {
let mut opts = world.get_resource_mut::<Ct<WindowOptions>>(); let mut opts = world.get_resource_mut::<Ct<WindowOptions>>();
if let Some(event_queue) = world.try_get_resource_mut::<EventQueue>() { if let Some(event_queue) = world.try_get_resource_mut::<EventQueue>() {
if let Some(mut events) = event_queue.read_events::<InputEvent>() { if let Some(events) = event_queue.read_events::<InputEvent>() {
while let Some(ev) = events.pop_front() { for ev in events {
match ev { match ev {
InputEvent::CursorEntered { .. } => { InputEvent::CursorEntered { .. } => {
opts.cursor_inside_window = true; opts.cursor_inside_window = true;

View File

@ -32,7 +32,7 @@ impl From<&Variant> for VariantType {
/// Generates the following different outputs: /// Generates the following different outputs:
/// ///
/// ```compile_fail /// ```nobuild
/// // for struct variants /// // for struct variants
/// TestEnum::Error { msg, code } /// TestEnum::Error { msg, code }
/// ///
@ -98,7 +98,7 @@ fn gen_variant_if(enum_id: &proc_macro2::Ident, variant: &Variant, if_body: proc
/// Generates the following: /// Generates the following:
/// ///
/// ```compile_fail /// ```nobuild
/// /// generated one field here /// /// generated one field here
/// if name == "msg" { /// if name == "msg" {
/// return Some(msg); /// return Some(msg);
@ -129,7 +129,7 @@ fn gen_if_field_names(variant: &Variant) -> proc_macro2::TokenStream {
/// Generates the following rust code: /// Generates the following rust code:
/// ///
/// ```compile_fail /// ```nobuild
/// match name { /// match name {
/// "msg" | "code" => true, /// "msg" | "code" => true,
/// _ => false, /// _ => false,
@ -153,7 +153,7 @@ fn gen_match_names(variant: &Variant) -> proc_macro2::TokenStream {
/// Generates the following: /// Generates the following:
/// ///
/// ```compile_fail /// ```nobuild
/// /// generated one field here /// /// generated one field here
/// if idx == 0 { /// if idx == 0 {
/// return Some(a); /// return Some(a);
@ -190,7 +190,7 @@ fn gen_if_field_indices(variant: &Variant) -> proc_macro2::TokenStream {
/// Generates the following: /// Generates the following:
/// ///
/// ```compile_fail /// ```nobuild
/// /// generated one field here /// /// generated one field here
/// if idx == 0 { /// if idx == 0 {
/// return Some("a"); /// return Some("a");
@ -226,7 +226,7 @@ fn gen_if_field_indices_names(variant: &Variant) -> proc_macro2::TokenStream {
} }
/// Generates the following: /// Generates the following:
/// ```compile_fail /// ```nobuild
/// /// when `by_index` is false: /// /// when `by_index` is false:
/// ///
/// if let TestEnum::Error{ msg, code} = self { /// if let TestEnum::Error{ msg, code} = self {
@ -300,7 +300,7 @@ fn gen_enum_if_stmts(enum_id: &proc_macro2::Ident, data: &DataEnum, by_index: bo
/// Generates the following rust code: /// Generates the following rust code:
/// ///
/// ```compile_fail /// ```nobuild
/// if let TestEnum::Error { msg, code } = self { /// if let TestEnum::Error { msg, code } = self {
/// return match name { /// return match name {
/// // expands for continuing struct fields /// // expands for continuing struct fields
@ -331,7 +331,7 @@ fn gen_enum_has_field(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc_mac
/// Generates the following code: /// Generates the following code:
/// ///
/// ```compile_fail /// ```nobuild
/// match self { /// match self {
/// TestEnum::Start => 0, /// TestEnum::Start => 0,
/// TestEnum::Middle(a, b) => 2, /// TestEnum::Middle(a, b) => 2,
@ -358,7 +358,7 @@ fn gen_enum_fields_len(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc_ma
/// Generates the following code: /// Generates the following code:
/// ///
/// ```compile_fail /// ```nobuild
/// if let TestEnum::Error { msg, code } = self { /// if let TestEnum::Error { msg, code } = self {
/// if idx == 0 { /// if idx == 0 {
/// return Some("msg"); /// return Some("msg");
@ -389,7 +389,7 @@ fn gen_enum_field_name_at(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc
} }
/// Generates the following code: /// Generates the following code:
/// ```compile_fail /// ```nobuild
/// match self { /// match self {
/// TestEnum::Start => 0, /// TestEnum::Start => 0,
/// TestEnum::Middle(a, b) => 1, /// TestEnum::Middle(a, b) => 1,
@ -427,7 +427,7 @@ fn gen_enum_variant_name(enum_id: &proc_macro2::Ident, data: &DataEnum, gen_inde
/// Generates a match statement that returns the types of the variants of the enum. /// Generates a match statement that returns the types of the variants of the enum.
/// ///
/// Example: /// Example:
/// ```compile_fail /// ```nobuild
/// match self { /// match self {
/// TestEnum::Start => EnumType::Unit, /// TestEnum::Start => EnumType::Unit,
/// TestEnum::Middle(a, b) => EnumType::Tuple, /// TestEnum::Middle(a, b) => EnumType::Tuple,

View File

@ -28,7 +28,7 @@ impl StructType {
/// contains a borrow (mutable borrow if `is_mut` is true) to the matching struct field. /// contains a borrow (mutable borrow if `is_mut` is true) to the matching struct field.
/// ///
/// Example: /// Example:
/// ```compile_fail /// ```nobuild
/// // when `is_mut` = false /// // when `is_mut` = false
/// match name { /// match name {
/// "x" => Some(&self.x), /// "x" => Some(&self.x),
@ -85,7 +85,7 @@ fn gen_struct_field_match(data: &DataStruct, is_mut: bool) -> proc_macro2::Token
/// with the provided `val`. /// with the provided `val`.
/// ///
/// Example: /// Example:
/// ```compile_fail /// ```nobuild
/// match name { /// match name {
/// "x" => self.x = any_val.downcast_ref::<f32>() /// "x" => self.x = any_val.downcast_ref::<f32>()
/// .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", "f32", val.name())) /// .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", "f32", val.name()))
@ -140,7 +140,7 @@ fn gen_struct_set_field_match(data: &DataStruct) -> proc_macro2::TokenStream {
/// the type of the field. /// the type of the field.
/// ///
/// Example: /// Example:
/// ```compile_fail /// ```nobuild
/// match name { /// match name {
/// "x" => Some("f32"), /// "x" => Some("f32"),
/// "y" => Some("f32"), /// "y" => Some("f32"),
@ -177,7 +177,7 @@ fn gen_struct_field_name_match(data: &DataStruct) -> proc_macro2::TokenStream {
/// with the provided `val`. /// with the provided `val`.
/// ///
/// Example: /// Example:
/// ```compile_fail /// ```nobuild
/// match name { /// match name {
/// 0 => self.x = any_val.downcast_ref::<f32>() /// 0 => self.x = any_val.downcast_ref::<f32>()
/// .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", "f32", val.name())) /// .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", "f32", val.name()))
@ -243,7 +243,7 @@ fn gen_struct_set_field_match_idx(data: &DataStruct) -> proc_macro2::TokenStream
/// type of the field. /// type of the field.
/// ///
/// Example: /// Example:
/// ```compile_fail /// ```nobuild
/// match name { /// match name {
/// 0 => Some("f32"), /// 0 => Some("f32"),
/// 1 => Some("f32"), /// 1 => Some("f32"),
@ -274,7 +274,7 @@ fn gen_struct_field_name_match_idx(data: &DataStruct) -> proc_macro2::TokenStrea
/// to the matching struct field. /// to the matching struct field.
/// ///
/// Example: /// Example:
/// ```compile_fail /// ```nobuild
/// // when `is_mut` = false /// // when `is_mut` = false
/// match idx { /// match idx {
/// 0 => Some(&self.x), /// 0 => Some(&self.x),
@ -335,7 +335,7 @@ fn gen_struct_field_match_idx(data: &DataStruct, is_mut: bool) -> proc_macro2::T
/// and returns an Option that contains the name of the field. /// and returns an Option that contains the name of the field.
/// ///
/// Example: /// Example:
/// ```compile_fail /// ```nobuild
/// match idx { /// match idx {
/// 0 => Some("x"), /// 0 => Some("x"),
/// 1 => Some("y"), /// 1 => Some("y"),

View File

@ -2,7 +2,7 @@
use std::{any::TypeId, any::Any, cell::{Ref, RefMut}}; use std::{any::TypeId, any::Any, cell::{Ref, RefMut}};
use lyra_ecs::World; use lyra_ecs::{AtomicRef, AtomicRefMut, World};
extern crate self as lyra_reflect; extern crate self as lyra_reflect;
@ -250,46 +250,46 @@ pub trait FromType<T> {
pub trait ReflectWorldExt { pub trait ReflectWorldExt {
/// Retrieves the type registry from the world. /// Retrieves the type registry from the world.
fn get_type_registry(&self) -> Ref<TypeRegistry>; fn get_type_registry(&self) -> AtomicRef<TypeRegistry>;
/// Retrieves the type registry mutably from the world. /// Retrieves the type registry mutably from the world.
fn get_type_registry_mut(&self) -> RefMut<TypeRegistry>; fn get_type_registry_mut(&self) -> AtomicRefMut<TypeRegistry>;
/// Get a registered type from the type registry. Returns `None` if the type is not registered /// Get a registered type from the type registry. Returns `None` if the type is not registered
fn get_type<T>(&self, type_id: TypeId) -> Option<Ref<RegisteredType>>; fn get_type<T>(&self, type_id: TypeId) -> Option<AtomicRef<RegisteredType>>;
/// Get a mutable registered type from the type registry in the world. returns `None` if the type is not registered. /// Get a mutable registered type from the type registry in the world. returns `None` if the type is not registered.
fn get_type_mut<T>(&self, type_id: TypeId) -> Option<RefMut<RegisteredType>>; fn get_type_mut<T>(&self, type_id: TypeId) -> Option<AtomicRefMut<RegisteredType>>;
/// Get a registered type, or register a new type and return it. /// Get a registered type, or register a new type and return it.
fn get_type_or_default<T>(&self, type_id: TypeId) -> RefMut<RegisteredType>; fn get_type_or_default<T>(&self, type_id: TypeId) -> AtomicRefMut<RegisteredType>;
} }
impl ReflectWorldExt for World { impl ReflectWorldExt for World {
fn get_type_registry(&self) -> Ref<TypeRegistry> { fn get_type_registry(&self) -> AtomicRef<TypeRegistry> {
self.get_resource::<TypeRegistry>() self.get_resource::<TypeRegistry>()
} }
fn get_type_registry_mut(&self) -> RefMut<TypeRegistry> { fn get_type_registry_mut(&self) -> AtomicRefMut<TypeRegistry> {
self.get_resource_mut::<TypeRegistry>() self.get_resource_mut::<TypeRegistry>()
} }
fn get_type<T>(&self, type_id: TypeId) -> Option<Ref<RegisteredType>> { fn get_type<T>(&self, type_id: TypeId) -> Option<AtomicRef<RegisteredType>> {
let r = self.get_resource::<TypeRegistry>(); let r = self.get_resource::<TypeRegistry>();
if r.has_type(type_id) { if r.has_type(type_id) {
Some(Ref::map(r, |tr| tr.get_type(type_id).unwrap())) Some(AtomicRef::map(r, |tr| tr.get_type(type_id).unwrap()))
} else { } else {
None None
} }
} }
fn get_type_mut<T>(&self, type_id: TypeId) -> Option<RefMut<RegisteredType>> { fn get_type_mut<T>(&self, type_id: TypeId) -> Option<AtomicRefMut<RegisteredType>> {
let r = self.get_resource_mut::<TypeRegistry>(); let r = self.get_resource_mut::<TypeRegistry>();
if r.has_type(type_id) { if r.has_type(type_id) {
Some(RefMut::map(r, |tr| tr.get_type_mut(type_id).unwrap())) Some(AtomicRefMut::map(r, |tr| tr.get_type_mut(type_id).unwrap()))
} else { } else {
None None
} }
} }
fn get_type_or_default<T>(&self, type_id: TypeId) -> RefMut<RegisteredType> { fn get_type_or_default<T>(&self, type_id: TypeId) -> AtomicRefMut<RegisteredType> {
let r = self.get_resource_mut::<TypeRegistry>(); let r = self.get_resource_mut::<TypeRegistry>();
RefMut::map(r, |tr| tr.get_type_or_default(type_id)) AtomicRefMut::map(r, |tr| tr.get_type_or_default(type_id))
} }
} }

View File

@ -1,4 +1,4 @@
use std::{any::{TypeId, type_name}, collections::HashMap, ops::Deref}; use std::{any::{type_name, Any, TypeId}, collections::HashMap, ops::Deref};
/// Storage for /// Storage for
#[derive(Default, Clone)] #[derive(Default, Clone)]
@ -44,19 +44,19 @@ impl TypeRegistry {
} }
} }
pub trait TypeData: std::any::Any { pub trait TypeData: Send + Sync + Any {
fn as_any(&self) -> &dyn std::any::Any; fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any; fn as_any_mut(&mut self) -> &mut dyn Any;
fn boxed_clone(&self) -> Box<dyn TypeData>; fn boxed_clone(&self) -> Box<dyn TypeData>;
} }
impl<T: Clone + 'static> TypeData for T { impl<T: Clone + Send + Sync + Any> TypeData for T {
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn Any {
self self
} }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { fn as_any_mut(&mut self) -> &mut dyn Any {
self self
} }

View File

@ -1,6 +1,6 @@
use std::{any::{Any, TypeId}, cell::{Ref, RefMut}, ptr::NonNull}; use std::{any::{Any, TypeId}, mem, ptr::NonNull};
use lyra_ecs::{World, ResourceObject}; use lyra_ecs::{AtomicRef, AtomicRefMut, ResourceObject, World};
use crate::{Reflect, FromType}; use crate::{Reflect, FromType};
@ -8,20 +8,20 @@ use crate::{Reflect, FromType};
pub struct ReflectedResource { pub struct ReflectedResource {
pub type_id: TypeId, pub type_id: TypeId,
fn_reflect: for<'a> fn (world: &'a World) -> Option<Ref<'a, dyn Reflect>>, fn_reflect: for<'a> fn (world: &'a World) -> Option<AtomicRef<'a, dyn Reflect>>,
fn_reflect_mut: for<'a> fn (world: &'a mut World) -> Option<RefMut<'a, dyn Reflect>>, fn_reflect_mut: for<'a> fn (world: &'a mut World) -> Option<AtomicRefMut<'a, dyn Reflect>>,
fn_reflect_ptr: fn (world: &mut World) -> Option<NonNull<u8>>, fn_reflect_ptr: fn (world: &mut World) -> Option<NonNull<u8>>,
fn_refl_insert: fn (world: &mut World, this: Box<dyn Reflect>), fn_refl_insert: fn (world: &mut World, this: Box<dyn Reflect>),
} }
impl ReflectedResource { impl ReflectedResource {
/// Retrieves the reflected resource from the world. /// Retrieves the reflected resource from the world.
pub fn reflect<'a>(&self, world: &'a World) -> Option<Ref<'a, dyn Reflect>> { pub fn reflect<'a>(&self, world: &'a World) -> Option<AtomicRef<'a, dyn Reflect>> {
(self.fn_reflect)(world) (self.fn_reflect)(world)
} }
/// Retrieves a mutable reflected resource from the world. /// Retrieves a mutable reflected resource from the world.
pub fn reflect_mut<'a>(&self, world: &'a mut World) -> Option<RefMut<'a, dyn Reflect>> { pub fn reflect_mut<'a>(&self, world: &'a mut World) -> Option<AtomicRefMut<'a, dyn Reflect>> {
(self.fn_reflect_mut)(world) (self.fn_reflect_mut)(world)
} }
@ -41,11 +41,15 @@ impl<T: ResourceObject + Reflect> FromType<T> for ReflectedResource {
type_id: TypeId::of::<T>(), type_id: TypeId::of::<T>(),
fn_reflect: |world: &World| { fn_reflect: |world: &World| {
world.try_get_resource::<T>() world.try_get_resource::<T>()
.map(|r| r as Ref<dyn Reflect>) .map(|r| {
AtomicRef::map(r, |r| r as &dyn Reflect)
})
}, },
fn_reflect_mut: |world: &mut World| { fn_reflect_mut: |world: &mut World| {
world.try_get_resource_mut::<T>() world.try_get_resource_mut::<T>()
.map(|r| r as RefMut<dyn Reflect>) .map(|r| {
AtomicRefMut::map(r, |r| r as &mut dyn Reflect)
})
}, },
fn_reflect_ptr: |world: &mut World| unsafe { fn_reflect_ptr: |world: &mut World| unsafe {
world.try_get_resource_ptr::<T>() world.try_get_resource_ptr::<T>()

View File

@ -9,6 +9,7 @@ edition = "2021"
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] } lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-reflect = { path = "../lyra-reflect" } lyra-reflect = { path = "../lyra-reflect" }
lyra-math = { path = "../lyra-math" } lyra-math = { path = "../lyra-math" }
lyra-scene = { path = "../lyra-scene" }
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" ] }

View File

@ -3,9 +3,10 @@ use std::{ffi::OsStr, path::{Path, PathBuf}, sync::Arc};
use glam::{Quat, Vec3}; use glam::{Quat, Vec3};
use instant::Instant; use instant::Instant;
use lyra_math::Transform; use lyra_math::Transform;
use lyra_scene::{SceneGraph, SceneNode};
use thiserror::Error; use thiserror::Error;
use crate::{gltf::GltfScene, loader::{LoaderError, PinedBoxLoaderFuture, ResourceLoader}, util, ResHandle, ResourceData, ResourceManager, ResourceStorage}; use crate::{loader::{LoaderError, PinedBoxLoaderFuture, ResourceLoader}, util, ResHandle, ResourceData, ResourceManager, ResourceStorage};
use super::{Gltf, GltfNode, Material, Mesh, MeshIndices, MeshVertexAttribute, VertexAttributeData}; use super::{Gltf, GltfNode, Material, Mesh, MeshIndices, MeshVertexAttribute, VertexAttributeData};
use tracing::debug; use tracing::debug;
@ -64,7 +65,7 @@ impl ModelLoader {
} }
} */ } */
fn process_node(ctx: &mut GltfLoadContext, materials: &Vec<ResHandle<Material>>, gnode: gltf::Node<'_>) -> GltfNode { fn process_node(ctx: &mut GltfLoadContext, materials: &Vec<ResHandle<Material>>, scene: &mut SceneGraph, scene_parent: &SceneNode, gnode: gltf::Node<'_>) -> GltfNode {
let mut node = GltfNode::default(); let mut node = GltfNode::default();
node.transform = { node.transform = {
@ -75,6 +76,8 @@ impl ModelLoader {
}; };
node.name = gnode.name().map(str::to_string); node.name = gnode.name().map(str::to_string);
let scene_node = scene.add_node_under(scene_parent, node.transform, ());
if let Some(mesh) = gnode.mesh() { if let Some(mesh) = gnode.mesh() {
let mut new_mesh = Mesh::default(); let mut new_mesh = Mesh::default();
@ -127,11 +130,12 @@ impl ModelLoader {
let handle = ResHandle::new_ready(None, new_mesh); let handle = ResHandle::new_ready(None, new_mesh);
ctx.resource_manager.store_uuid(handle.clone()); ctx.resource_manager.store_uuid(handle.clone());
node.mesh = Some(handle); node.mesh = Some(handle.clone());
scene.insert(&scene_node, (handle.clone(), handle.untyped_clone()));
} }
for child in gnode.children() { for child in gnode.children() {
let cmesh = ModelLoader::process_node(ctx, materials, child); let cmesh = ModelLoader::process_node(ctx, materials, scene, &scene_node, child);
node.children.push(cmesh); node.children.push(cmesh);
} }
@ -210,7 +214,21 @@ impl ResourceLoader for ModelLoader {
debug!("Loaded {} materials in {}s", materials.len(), mat_time.as_secs_f32()); debug!("Loaded {} materials in {}s", materials.len(), mat_time.as_secs_f32());
for (_idx, scene) in gltf.scenes().enumerate() { for (_idx, scene) in gltf.scenes().enumerate() {
let start_inst = Instant::now(); let mut graph = SceneGraph::new();
let root_node = graph.root_node();
for node in scene.nodes() {
let n = ModelLoader::process_node(&mut context, &materials, &mut graph, &root_node, node);
if let Some(mesh) = n.mesh {
gltf_out.meshes.push(mesh.clone());
}
}
let graph = ResHandle::new_ready(Some(path.as_str()), graph);
gltf_out.scenes.push(graph);
/* let start_inst = Instant::now();
let nodes: Vec<GltfNode> = scene.nodes() let nodes: Vec<GltfNode> = scene.nodes()
.map(|node| ModelLoader::process_node(&mut context, &materials, node)) .map(|node| ModelLoader::process_node(&mut context, &materials, node))
.collect(); .collect();
@ -228,7 +246,7 @@ impl ResourceLoader for ModelLoader {
nodes, nodes,
}; };
let scene = ResHandle::new_ready(Some(path.as_str()), scene); let scene = ResHandle::new_ready(Some(path.as_str()), scene);
gltf_out.scenes.push(scene); gltf_out.scenes.push(scene); */
} }
gltf_out.materials = materials; gltf_out.materials = materials;
@ -249,6 +267,8 @@ impl ResourceLoader for ModelLoader {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use lyra_ecs::{query::Entities, relation::ChildOf};
use crate::tests::busy_wait_resource; use crate::tests::busy_wait_resource;
use super::*; use super::*;
@ -272,14 +292,30 @@ mod tests {
let scene = &gltf.scenes[0] let scene = &gltf.scenes[0]
.data_ref().unwrap(); .data_ref().unwrap();
assert_eq!(scene.nodes.len(), 1); let mut node = None;
let mnode = &scene.nodes[0]; scene.traverse_down(|_, no, _tran| {
node = Some(no.clone());
});
assert!(mnode.mesh.is_some()); let world = scene.world();
assert_eq!(mnode.transform, Transform::from_xyz(0.0, 0.0, 0.0)); let node = node.unwrap();
assert_eq!(mnode.children.len(), 0);
let data = world.view_one::<(&ResHandle<Mesh>, &Transform)>(node.entity()).get();
debug_assert!(data.is_some(), "The mesh was not loaded"); // transform will always be there
let data = data.unwrap();
let mesh = mnode.mesh.as_ref().unwrap(); // ensure there are no children of the node
assert_eq!(
world.view::<Entities>()
.relates_to::<ChildOf>(node.entity())
.into_iter()
.count(),
0
);
assert_eq!(*data.1, Transform::from_xyz(0.0, 0.0, 0.0));
let mesh = data.0;
let mesh = mesh.data_ref().unwrap(); let mesh = mesh.data_ref().unwrap();
assert!(mesh.position().unwrap().len() > 0); assert!(mesh.position().unwrap().len() > 0);
assert!(mesh.normals().unwrap().len() > 0); assert!(mesh.normals().unwrap().len() > 0);

View File

@ -2,7 +2,7 @@ pub mod loader;
pub use loader::*; pub use loader::*;
pub mod material; pub mod material;
use lyra_math::Transform; use lyra_scene::SceneGraph;
use crate::ResourceData; use crate::ResourceData;
pub use material::*; pub use material::*;
@ -17,7 +17,7 @@ use crate::ResHandle;
/// A loaded Gltf file /// A loaded Gltf file
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct Gltf { pub struct Gltf {
pub scenes: Vec<ResHandle<GltfScene>>, pub scenes: Vec<ResHandle<SceneGraph>>,
pub materials: Vec<ResHandle<Material>>, pub materials: Vec<ResHandle<Material>>,
pub meshes: Vec<ResHandle<Mesh>>, pub meshes: Vec<ResHandle<Mesh>>,
} }
@ -50,19 +50,4 @@ impl ResourceData for Gltf {
} }
}
impl Gltf {
/// Collects all Gltf meshes and gets their world Transform.
pub fn collect_world_meshes(&self) -> Vec<(ResHandle<Mesh>, Transform)> {
let mut v = vec![];
for scene in self.scenes.iter() {
let mut tmp = scene.data_ref()
.unwrap().collect_world_meshes();
v.append(&mut tmp);
}
v
}
} }

View File

@ -1,4 +1,5 @@
use lyra_math::Transform; use lyra_math::Transform;
use lyra_scene::SceneGraph;
use crate::{optionally_add_to_dep, ResourceData, UntypedResHandle}; use crate::{optionally_add_to_dep, ResourceData, UntypedResHandle};
use super::Mesh; use super::Mesh;
@ -33,8 +34,8 @@ impl ResourceData for GltfNode {
} }
} }
/// A Scene in a Gltf file // A Scene in a Gltf file
#[derive(Clone)] /* #[derive(Clone)]
pub struct GltfScene { pub struct GltfScene {
pub nodes: Vec<GltfNode>, pub nodes: Vec<GltfNode>,
} }
@ -92,4 +93,21 @@ impl GltfScene {
v v
} }
} */
impl ResourceData for SceneGraph {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
self.world().view::<&crate::UntypedResHandle>()
.iter()
.map(|han| han.clone())
.collect()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
} }

View File

@ -31,4 +31,4 @@ pub(crate) mod lyra_engine {
pub(crate) mod reflect { pub(crate) mod reflect {
pub use lyra_reflect::*; pub use lyra_reflect::*;
} }
} }

View File

@ -96,7 +96,7 @@ pub struct UntypedResource {
pub(crate) condvar: Arc<(Mutex<bool>, Condvar)>, pub(crate) condvar: Arc<(Mutex<bool>, Condvar)>,
} }
#[derive(Clone)] #[derive(Clone, Component)]
pub struct UntypedResHandle{ pub struct UntypedResHandle{
pub(crate) res: Arc<RwLock<UntypedResource>>, pub(crate) res: Arc<RwLock<UntypedResource>>,
#[allow(dead_code)] #[allow(dead_code)]
@ -214,12 +214,12 @@ impl UntypedResHandle {
/// However, the only times it will be blocking is if another thread is reloading the resource /// However, the only times it will be blocking is if another thread is reloading the resource
/// and has a write lock on the data. This means that most of the time, it is not blocking. /// and has a write lock on the data. This means that most of the time, it is not blocking.
#[derive(Component)] #[derive(Component)]
pub struct ResHandle<T: 'static> { pub struct ResHandle<T: ResourceData> {
pub(crate) handle: UntypedResHandle, pub(crate) handle: UntypedResHandle,
_marker: PhantomData<T>, _marker: PhantomData<T>,
} }
impl<T> Clone for ResHandle<T> { impl<T: ResourceData> Clone for ResHandle<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
handle: self.handle.clone(), handle: self.handle.clone(),
@ -228,7 +228,7 @@ impl<T> Clone for ResHandle<T> {
} }
} }
impl<T> Deref for ResHandle<T> { impl<T: ResourceData> Deref for ResHandle<T> {
type Target = UntypedResHandle; type Target = UntypedResHandle;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -236,7 +236,7 @@ impl<T> Deref for ResHandle<T> {
} }
} }
impl<T> DerefMut for ResHandle<T> { impl<T: ResourceData> DerefMut for ResHandle<T> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.handle &mut self.handle
} }
@ -285,7 +285,7 @@ impl<T: ResourceData> ResHandle<T> {
} }
} }
impl<T: Send + Sync + 'static> ResourceStorage for ResHandle<T> { impl<T: ResourceData> ResourceStorage for ResHandle<T> {
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
self self
} }

View File

@ -173,7 +173,7 @@ impl ResourceManager {
/// Request a resource without downcasting to a `ResHandle<T>`. /// Request a resource without downcasting to a `ResHandle<T>`.
/// Whenever you're ready to downcast, you can do so like this: /// Whenever you're ready to downcast, you can do so like this:
/// ```compile_fail /// ```nobuild
/// let arc_any = res_arc.as_arc_any(); /// let arc_any = res_arc.as_arc_any();
/// let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource"); /// let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
/// ``` /// ```
@ -221,7 +221,7 @@ impl ResourceManager {
/// ///
/// The resource cannot be requested with [`ResourceManager::request`], it can only be /// The resource cannot be requested with [`ResourceManager::request`], it can only be
/// retrieved with [`ResourceManager::request_uuid`]. /// retrieved with [`ResourceManager::request_uuid`].
pub fn store_uuid<T: Send + Sync + 'static>(&self, res: ResHandle<T>) { pub fn store_uuid<T: ResourceData>(&self, res: ResHandle<T>) {
let mut state = self.state_mut(); let mut state = self.state_mut();
state.resources.insert(res.uuid().to_string(), Arc::new(res)); state.resources.insert(res.uuid().to_string(), Arc::new(res));
} }
@ -230,7 +230,7 @@ impl ResourceManager {
/// ///
/// Returns `None` if the resource was not found. The resource must of had been /// Returns `None` if the resource was not found. The resource must of had been
/// stored with [`ResourceManager::request`] to return `Some`. /// stored with [`ResourceManager::request`] to return `Some`.
pub fn request_uuid<T: Send + Sync + 'static>(&self, uuid: &Uuid) -> Option<ResHandle<T>> { pub fn request_uuid<T: ResourceData>(&self, uuid: &Uuid) -> Option<ResHandle<T>> {
let state = self.state(); let state = self.state();
match state.resources.get(&uuid.to_string()) match state.resources.get(&uuid.to_string())
.or_else(|| state.uuid_resources.get(&uuid)) .or_else(|| state.uuid_resources.get(&uuid))
@ -291,7 +291,7 @@ impl ResourceManager {
/// Requests bytes from the manager. /// Requests bytes from the manager.
pub fn request_loaded_bytes<T>(&self, ident: &str) -> Result<Arc<ResHandle<T>>, RequestError> pub fn request_loaded_bytes<T>(&self, ident: &str) -> Result<Arc<ResHandle<T>>, RequestError>
where where
T: Send + Sync + Any + 'static T: ResourceData
{ {
let state = self.state(); let state = self.state();
match state.resources.get(&ident.to_string()) { match state.resources.get(&ident.to_string()) {
@ -366,7 +366,7 @@ impl ResourceManager {
/// the handle. /// the handle.
pub fn reload<T>(&self, resource: ResHandle<T>) -> Result<(), RequestError> pub fn reload<T>(&self, resource: ResHandle<T>) -> Result<(), RequestError>
where where
T: Send + Sync + Any + 'static T: ResourceData
{ {
let state = self.state(); let state = self.state();

View File

@ -1,5 +1,3 @@
use std::any::Any;
use crossbeam::channel::Receiver; use crossbeam::channel::Receiver;
use lyra_ecs::World; use lyra_ecs::World;
use notify_debouncer_full::DebouncedEvent; use notify_debouncer_full::DebouncedEvent;
@ -30,7 +28,7 @@ pub trait WorldAssetExt {
/// automatically triggered if the resource is being watched. /// automatically triggered if the resource is being watched.
fn reload_res<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError> fn reload_res<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError>
where where
T: Send + Sync + Any + 'static; T: ResourceData;
} }
impl WorldAssetExt for World { impl WorldAssetExt for World {
@ -67,7 +65,7 @@ impl WorldAssetExt for World {
fn reload_res<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError> fn reload_res<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError>
where where
T: Send + Sync + Any + 'static T: ResourceData
{ {
let man = self.get_resource_or_default::<ResourceManager>(); let man = self.get_resource_or_default::<ResourceManager>();
man.reload(resource) man.reload(resource)

View File

@ -1,4 +1,4 @@
use std::{collections::VecDeque, ops::{Deref, DerefMut}}; use std::collections::VecDeque;
use lyra_ecs::{query::Entities, relation::ChildOf, Bundle, Component, Entity, World}; use lyra_ecs::{query::Entities, relation::ChildOf, Bundle, Component, Entity, World};
@ -21,77 +21,31 @@ pub struct SceneNodeFlag;
#[derive(Component)] #[derive(Component)]
pub struct SceneNodeRoot; pub struct SceneNodeRoot;
enum MutCow<'a, T> {
Mut(&'a mut T),
Owned(T),
}
impl<'a, T> Deref for MutCow<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
MutCow::Mut(t) => t,
MutCow::Owned(t) => t,
}
}
}
impl<'a, T> DerefMut for MutCow<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
MutCow::Mut(t) => t,
MutCow::Owned(t) => t,
}
}
}
/// A SceneGraph is a Graph of nodes that represents the hierarchy of a scene. /// A SceneGraph is a Graph of nodes that represents the hierarchy of a scene.
/// ///
/// This SceneGraph is special in the sense that it is literally just an ECS world with methods /// This SceneGraph is special in the sense that it is literally just an ECS world with methods
/// implemented for it that make it easier to use for a SceneGraph. /// implemented for it that make it easier to use for a SceneGraph.
//#[derive(Default)] //#[derive(Default)]
pub struct SceneGraph<'a> { pub struct SceneGraph {
pub(crate) world: MutCow<'a, World>, pub(crate) world: World,
root_node: SceneNode, root_node: SceneNode,
} }
impl<'a> SceneGraph<'a> { impl SceneGraph {
/// Create a new SceneGraph with its own ECS World. /// Create a new SceneGraph with its own ECS World.
pub fn new() -> Self { pub fn new() -> Self {
let mut world = World::new(); let world = World::new();
let e = world.spawn((Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), SceneNodeRoot)); Self::from_world(world)
let root = SceneNode::new(None, e);
Self {
world: MutCow::Owned(world),
root_node: root
}
}
/// Retrieve a SceneGraph from an ECS World.
///
/// Returns `None` if the `root_entity` was not created from a `SceneGraph` that was later
/// inserted into another world with [`SceneGraph::into_world`].
pub fn from_world(world: &'a mut World, root_entity: Entity) -> Option<Self> {
if world.view_one::<(&SceneNodeRoot, &Transform)>(root_entity).get().is_none() {
None
} else {
Some(Self {
world: MutCow::Mut(world),
root_node: SceneNode::new(None, root_entity),
})
}
} }
/// Create a new SceneGraph inside an existing ECS World. /// Create a new SceneGraph inside an existing ECS World.
pub fn new_from_world(world: &'a mut World) -> Self { pub fn from_world(mut world: World) -> Self {
let root_en = world.spawn((Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), SceneNodeRoot)); let root_en = world.spawn((Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), SceneNodeRoot));
let root = SceneNode::new(None, root_en); let root = SceneNode::new(None, root_en);
Self { Self {
world: MutCow::Mut(world), world,
root_node: root, root_node: root,
} }
} }
@ -162,6 +116,13 @@ impl<'a> SceneGraph<'a> {
world_add_child_node(&mut self.world, parent, local_transform, bundle) world_add_child_node(&mut self.world, parent, local_transform, bundle)
} }
/// Insert a component bundle to a SceneNode.
///
/// See [`lyra_ecs::World::insert`].
pub fn insert<B: Bundle>(&mut self, node: &SceneNode, bundle: B) {
self.world.insert(node.entity(), bundle);
}
pub fn add_empty_node_under(&mut self, parent: &SceneNode, local_transform: Transform) -> SceneNode { pub fn add_empty_node_under(&mut self, parent: &SceneNode, local_transform: Transform) -> SceneNode {
let e = self.world.spawn((SceneNodeFlag, local_transform)); let e = self.world.spawn((SceneNodeFlag, local_transform));
self.world.add_relation(e, ChildOf, parent.entity()); self.world.add_relation(e, ChildOf, parent.entity());
@ -174,7 +135,7 @@ impl<'a> SceneGraph<'a> {
/// The traversal does not include the root scene node. /// The traversal does not include the root scene node.
pub fn traverse_down<F>(&self, mut callback: F) pub fn traverse_down<F>(&self, mut callback: F)
where where
F: FnMut(&SceneNode, Transform), F: FnMut(&World, &SceneNode, Transform),
{ {
self.traverse_down_from(self.root_node.clone(), &mut callback); self.traverse_down_from(self.root_node.clone(), &mut callback);
} }
@ -183,15 +144,16 @@ impl<'a> SceneGraph<'a> {
/// SceneNode and its world transform. /// SceneNode and its world transform.
fn traverse_down_from<F>(&self, start: SceneNode, callback: &mut F) fn traverse_down_from<F>(&self, start: SceneNode, callback: &mut F)
where where
F: FnMut(&SceneNode, Transform), F: FnMut(&World, &SceneNode, Transform),
{ {
let v = self.world.view::<(Entities, &Transform)>() let v = self.world
.view::<(Entities, &Transform)>()
.relates_to::<ChildOf>(start.entity()); .relates_to::<ChildOf>(start.entity());
for ((e, _), _rel) in v.iter() { for ((e, _), _rel) in v.iter() {
let node = SceneNode::new(Some(start.entity()), e); let node = SceneNode::new(Some(start.entity()), e);
let world_pos = node.world_transform(self); let world_pos = node.world_transform(self);
callback(&node, world_pos); callback(&self.world, &node, world_pos);
self.traverse_down_from(node, callback); self.traverse_down_from(node, callback);
} }
@ -200,6 +162,11 @@ impl<'a> SceneGraph<'a> {
pub fn root_node(&self) -> SceneNode { pub fn root_node(&self) -> SceneNode {
self.root_node.clone() self.root_node.clone()
} }
/// Retrieve a borrow of the world that backs the Scene
pub fn world(&self) -> &World {
&self.world
}
} }
/// Add a node under a parent node. /// Add a node under a parent node.
@ -255,7 +222,7 @@ pub mod tests {
assert!(b.parent(&scene).unwrap() == a); assert!(b.parent(&scene).unwrap() == a);
let mut idx = 0; let mut idx = 0;
scene.traverse_down(|_e, pos| { scene.traverse_down(|_, _, pos| {
if idx == 0 { if idx == 0 {
assert_eq!(pos, Transform::from_translation(v2s[idx])); assert_eq!(pos, Transform::from_translation(v2s[idx]));
} else if idx == 1 { } else if idx == 1 {

View File

@ -1,4 +1,4 @@
[toolchain] [toolchain]
channel = "nightly" channel = "nightly-2023-11-21"
date = "2023-11-21" #date = "2023-11-21"
targets = [ "x86_64-unknown-linux-gnu" ] targets = [ "x86_64-unknown-linux-gnu" ]

View File

@ -1,6 +1,10 @@
let let
moz_overlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz); rust_overlay = import (builtins.fetchTarball https://github.com/oxalica/rust-overlay/archive/master.tar.gz);
nixpkgs = import <nixpkgs> { overlays = [ moz_overlay ]; }; nixpkgs = import <nixpkgs> { overlays = [ rust_overlay ]; };
rust = (nixpkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override {
extensions = [ "rust-analysis" "rust-src" "miri-preview" ];
};
in in
with nixpkgs; with nixpkgs;
stdenv.mkDerivation rec { stdenv.mkDerivation rec {
@ -15,12 +19,7 @@ in
mold mold
udev udev
lua5_4_compat lua5_4_compat
((nixpkgs.rustChannelOf { rustToolchain = ./rust-toolchain.toml; }).rust.override { rust
extensions = [
"rust-src"
"rust-analysis"
];
})
]; ];
buildInputs = [ buildInputs = [
udev alsa-lib libGL gcc udev alsa-lib libGL gcc