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
|
@ -330,6 +330,12 @@ version = "1.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "atomic_refcell"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
|
@ -708,12 +714,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.18"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
|
@ -984,12 +987,6 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fps_counter"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3aaba7ff514ee9d802b562927f80b1e94e93d8e74c31b134c9c3762dabf1a36b"
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
|
@ -1755,6 +1752,7 @@ name = "lyra-ecs"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atomic_refcell",
|
||||
"lyra-ecs-derive",
|
||||
"lyra-math",
|
||||
"paste",
|
||||
|
@ -1797,6 +1795,7 @@ dependencies = [
|
|||
"lyra-math",
|
||||
"lyra-reflect",
|
||||
"lyra-resource",
|
||||
"lyra-scene",
|
||||
"quote",
|
||||
"syn 2.0.51",
|
||||
"thiserror",
|
||||
|
@ -1850,6 +1849,7 @@ dependencies = [
|
|||
"lyra-ecs",
|
||||
"lyra-math",
|
||||
"lyra-reflect",
|
||||
"lyra-scene",
|
||||
"mime",
|
||||
"notify",
|
||||
"notify-debouncer-full",
|
||||
|
@ -3104,7 +3104,6 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"fps_counter",
|
||||
"lyra-engine",
|
||||
"tracing",
|
||||
]
|
||||
|
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 457 KiB After Width: | Height: | Size: 457 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
@ -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;
|
||||
|
||||
struct FixedTimestep {
|
||||
|
@ -73,7 +73,7 @@ async fn main() {
|
|||
//let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").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_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 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()
|
||||
.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);
|
||||
|
||||
sponza_model.wait_recurse_dependencies_load();
|
||||
|
|
|
@ -14,6 +14,7 @@ lyra-math = { path = "../lyra-math", optional = true }
|
|||
anyhow = "1.0.75"
|
||||
thiserror = "1.0.50"
|
||||
paste = "1.0.14"
|
||||
atomic_refcell = "0.1.13"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5" # used for tests
|
||||
|
|
|
@ -19,7 +19,9 @@ impl Drop for ComponentColumn {
|
|||
|
||||
unsafe {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,24 @@ pub trait Bundle {
|
|||
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 {
|
||||
fn type_ids(&self) -> Vec<DynTypeId> {
|
||||
vec![DynTypeId::of::<C>()]
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
@ -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.
|
||||
///
|
||||
/// 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.
|
||||
#[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.
|
||||
///
|
||||
/// 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
|
||||
/// fn particle_spawner_system(
|
||||
/// commands: Commands,
|
||||
/// view: ViewState<(&Campfire, &Transform)>
|
||||
/// view: View<(&Campfire, &Transform)>
|
||||
/// ) -> anyhow::Result<()> {
|
||||
/// for (campfire, pos) in view.iter() {
|
||||
/// // 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
|
||||
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| {
|
||||
let cmd = cmd.as_any_boxed()
|
||||
.downcast::<C>()
|
||||
.unwrap();
|
||||
|
||||
cmd.run(world);
|
||||
// the size of the command must be returned to increment the pointer when applying
|
||||
// the command queue.
|
||||
mem::size_of::<C>()
|
||||
};
|
||||
|
||||
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`]
|
||||
|
@ -91,14 +167,8 @@ impl<'a, 'b> Commands<'a, 'b> {
|
|||
}
|
||||
|
||||
/// Execute all commands in the queue, in order of insertion
|
||||
pub fn execute(&mut self, world: &mut World) -> anyhow::Result<()> {
|
||||
while let Some((cmd_fn, cmd_ptr)) = self.queue.0.pop_front() {
|
||||
unsafe {
|
||||
cmd_fn(cmd_ptr, world);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
pub fn execute(&mut self, world: &mut World) {
|
||||
self.queue.execute(Some(world));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,21 +195,26 @@ impl FnArgFetcher for Commands<'_, '_> {
|
|||
let mut cmds = Commands::new(&mut state, world);
|
||||
// safety: Commands has a mut borrow only to entities in the world
|
||||
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<()> {
|
||||
commands.execute(world)?;
|
||||
commands.execute(world);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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]
|
||||
fn deferred_commands() {
|
||||
|
@ -170,4 +245,28 @@ mod tests {
|
|||
let vec2: Ref<Vec2> = unsafe { col.get(3) };
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -51,6 +51,9 @@ pub mod math;
|
|||
|
||||
pub use lyra_ecs_derive::*;
|
||||
|
||||
pub use atomic_refcell::AtomicRef;
|
||||
pub use atomic_refcell::AtomicRefMut;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
@ -9,7 +11,7 @@ pub struct FetchResource<'a, 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>;
|
||||
|
||||
fn dangling() -> Self {
|
||||
|
@ -79,7 +81,7 @@ impl<R: ResourceObject> AsQuery for QueryResource<R> {
|
|||
}
|
||||
|
||||
/// 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> {
|
||||
type Target = T;
|
||||
|
@ -98,7 +100,7 @@ pub struct FetchResourceMut<'a, 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>;
|
||||
|
||||
fn dangling() -> Self {
|
||||
|
@ -167,7 +169,7 @@ impl<R: ResourceObject> AsQuery for QueryResourceMut<R> {
|
|||
}
|
||||
|
||||
/// 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> {
|
||||
type Target = T;
|
||||
|
|
|
@ -83,8 +83,10 @@ where
|
|||
/// 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)`.
|
||||
/// Similar to [`RelatesTo`](super::RelatesTo), you can use [`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`].
|
||||
/// Similar to [`RelatesTo`](super::RelatesTo), you can use
|
||||
/// [`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> {
|
||||
_marker: PhantomData<R>,
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
pub trait ResourceObject: 'static {}
|
||||
impl<T: 'static> ResourceObject for T {}
|
||||
pub trait ResourceObject: Send + Sync + Any {
|
||||
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.
|
||||
pub struct ResourceData {
|
||||
pub(crate) data: Box<RefCell<dyn Any>>,
|
||||
pub(crate) data: Box<AtomicRefCell<dyn ResourceObject>>,
|
||||
type_id: TypeId,
|
||||
}
|
||||
|
||||
impl ResourceData {
|
||||
pub fn new<T: Any>(data: T) -> Self {
|
||||
pub fn new<T: ResourceObject>(data: T) -> Self {
|
||||
|
||||
Self {
|
||||
data: Box::new(RefCell::new(data)),
|
||||
data: Box::new(AtomicRefCell::new(data)),
|
||||
type_id: TypeId::of::<T>(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>()
|
||||
}
|
||||
|
||||
|
@ -30,8 +44,8 @@ impl ResourceData {
|
|||
///
|
||||
/// * If the data is already borrowed mutably, this will panic.
|
||||
/// * If the type of `T` is not the same as the resource type.
|
||||
pub fn get<T: 'static>(&self) -> Ref<T> {
|
||||
Ref::map(self.data.borrow(), |a| a.downcast_ref().unwrap())
|
||||
pub fn get<T: ResourceObject>(&self) -> AtomicRef<T> {
|
||||
AtomicRef::map(self.data.borrow(), |a| a.as_any().downcast_ref().unwrap())
|
||||
}
|
||||
|
||||
/// 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 type of `T` is not the same as the resource type.
|
||||
pub fn get_mut<T: 'static>(&self) -> RefMut<T> {
|
||||
RefMut::map(self.data.borrow_mut(), |a| a.downcast_mut().unwrap())
|
||||
pub fn get_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
|
||||
AtomicRefMut::map(self.data.borrow_mut(), |a| a.as_any_mut().downcast_mut().unwrap())
|
||||
}
|
||||
|
||||
/// Borrow the data inside of the resource.
|
||||
|
@ -49,10 +63,9 @@ impl ResourceData {
|
|||
/// # Panics
|
||||
///
|
||||
/// * 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()
|
||||
.map(|r| Ref::map(r, |a| a.downcast_ref().unwrap()))
|
||||
.map(|r| AtomicRef::map(r, |a| a.as_any().downcast_ref().unwrap()))
|
||||
.ok()
|
||||
}
|
||||
|
||||
|
@ -61,9 +74,9 @@ impl ResourceData {
|
|||
/// # Panics
|
||||
///
|
||||
/// * 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()
|
||||
.map(|r| RefMut::map(r, |a| a.downcast_mut().unwrap()))
|
||||
.map(|r| AtomicRefMut::map(r, |a| a.as_any_mut().downcast_mut().unwrap()))
|
||||
.ok()
|
||||
}
|
||||
}
|
|
@ -58,7 +58,8 @@ impl GraphExecutor {
|
|||
}
|
||||
|
||||
/// 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 visited = HashSet::new();
|
||||
|
||||
|
@ -99,23 +100,15 @@ impl GraphExecutor {
|
|||
let mut commands = Commands::new(&mut queue, world);
|
||||
|
||||
let world = unsafe { world_ptr.as_mut() };
|
||||
if let Err(e) = 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
|
||||
}
|
||||
commands.execute(world);
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
visited.insert(&node.name);
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
@ -271,17 +273,17 @@ impl World {
|
|||
|
||||
//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));
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
/// Get a resource from the world, or insert it into the world with the provided
|
||||
/// `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
|
||||
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.
|
||||
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>())
|
||||
.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
|
||||
/// 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>())
|
||||
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
|
||||
.get()
|
||||
}
|
||||
|
||||
/// 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>())
|
||||
}
|
||||
|
||||
/// Attempts to get a resource from the World.
|
||||
///
|
||||
/// 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>())
|
||||
.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
|
||||
/// 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>())
|
||||
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
|
||||
.get_mut()
|
||||
|
@ -334,7 +336,7 @@ impl World {
|
|||
/// Attempts to get a mutable borrow of a resource from the World.
|
||||
///
|
||||
/// 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>())
|
||||
.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)]
|
||||
mod tests {
|
||||
use crate::{query::TickOf, tests::{Vec2, Vec3}, Entity};
|
||||
|
@ -440,16 +446,14 @@ mod tests {
|
|||
#[test]
|
||||
fn resource_multi_borrow() {
|
||||
let mut world = World::new();
|
||||
{
|
||||
let counter = SimpleCounter(4582);
|
||||
world.add_resource(counter);
|
||||
}
|
||||
let counter = SimpleCounter(4582);
|
||||
world.add_resource(counter);
|
||||
|
||||
// test multiple borrows at the same time
|
||||
let counter = world.get_resource::<SimpleCounter>();
|
||||
assert_eq!(counter.0, 4582);
|
||||
let counter2 = world.get_resource::<SimpleCounter>();
|
||||
assert_eq!(counter2.0, 4582);
|
||||
assert_eq!(counter.0, 4582);
|
||||
assert_eq!(counter2.0, 4582);
|
||||
}
|
||||
|
||||
|
@ -461,7 +465,7 @@ mod tests {
|
|||
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>();
|
||||
assert_eq!(counter.0, 4582);
|
||||
assert!(world.try_get_resource_mut::<SimpleCounter>().is_none());
|
||||
|
|
|
@ -8,6 +8,7 @@ lyra-resource = { path = "../lyra-resource" }
|
|||
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
||||
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
|
||||
lyra-math = { path = "../lyra-math" }
|
||||
lyra-scene = { path = "../lyra-scene" }
|
||||
|
||||
winit = "0.28.1"
|
||||
tracing = "0.1.37"
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
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_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
self
|
||||
}
|
|
@ -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 {}
|
||||
impl<T: Clone + Send + Sync + 'static> Event for 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 {
|
||||
events: HashMap<TypeId, RefCell<Box<dyn CastableAny>>>,
|
||||
event_write_queue: HashMap<TypeId, RefCell<Box<dyn CastableAny>>>
|
||||
events: HashMap<TypeId, TypeErasedBuffer>,
|
||||
event_write_queue: HashMap<TypeId, TypeErasedBuffer>,
|
||||
}
|
||||
|
||||
impl Default for EventQueue {
|
||||
|
@ -32,7 +99,7 @@ impl EventQueue {
|
|||
E: Event
|
||||
{
|
||||
// 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
|
||||
let type_id = event.type_id();
|
||||
|
@ -42,7 +109,13 @@ impl EventQueue {
|
|||
|
||||
// downcast resource as an events storage
|
||||
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
|
||||
|
@ -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
|
||||
E: Event
|
||||
{
|
||||
if let Some(event ) = self.events.get(&TypeId::of::<E>()) {
|
||||
let eref = event.borrow();
|
||||
Some(eref.as_ref().as_any().downcast_ref::<Events<E>>().unwrap().clone())
|
||||
self.events.get(&TypeId::of::<E>())
|
||||
.map(|event| EventReader::new(event.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 {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -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 mut sum = Vec2::ZERO;
|
||||
while let Some(mm) = mouse_events.pop_front() {
|
||||
for mm in mouse_events {
|
||||
sum += mm.delta;
|
||||
}
|
||||
Some(sum / count as f32)
|
||||
|
|
|
@ -110,8 +110,8 @@ impl crate::ecs::system::System for InputSystem {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let mut events = queue.unwrap();
|
||||
while let Some(event) = events.pop_front() {
|
||||
let events = queue.unwrap();
|
||||
for event in events {
|
||||
self.process_event(world, &event);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ pub mod game;
|
|||
pub mod render;
|
||||
pub mod resources;
|
||||
pub mod input;
|
||||
pub mod castable_any;
|
||||
pub mod as_any;
|
||||
pub mod plugin;
|
||||
pub mod change_tracker;
|
||||
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
use lyra_ecs::Entity;
|
||||
|
||||
use super::transform_buffer_storage::TransformIndex;
|
||||
|
||||
pub struct RenderJob {
|
||||
pub entity: Entity,
|
||||
pub shader_id: u64,
|
||||
pub mesh_uuid: uuid::Uuid,
|
||||
pub transform_id: TransformIndex,
|
||||
}
|
||||
|
||||
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 {
|
||||
entity,
|
||||
shader_id,
|
||||
mesh_uuid: mesh_buffer_id,
|
||||
transform_id
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ use lyra_ecs::query::filter::{Has, Or};
|
|||
use lyra_ecs::{Entity, Tick};
|
||||
use lyra_ecs::query::{Entities, TickOf};
|
||||
use lyra_ecs::World;
|
||||
use lyra_resource::gltf::GltfScene;
|
||||
use lyra_scene::SceneGraph;
|
||||
use tracing::{debug, warn};
|
||||
use uuid::Uuid;
|
||||
use wgpu::{BindGroupLayout, Limits};
|
||||
|
@ -29,14 +29,14 @@ use super::light_cull_compute::LightCullCompute;
|
|||
use super::material::Material;
|
||||
use super::render_buffer::BufferWrapper;
|
||||
use super::texture::RenderTexture;
|
||||
use super::transform_buffer_storage::TransformBuffers;
|
||||
use super::transform_buffer_storage::{TransformBuffers, TransformGroup};
|
||||
use super::vertex::Vertex;
|
||||
use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
|
||||
|
||||
use lyra_resource::{gltf::Mesh, ResHandle};
|
||||
|
||||
type MeshHandle = ResHandle<Mesh>;
|
||||
type SceneHandle = ResHandle<GltfScene>;
|
||||
type SceneHandle = ResHandle<SceneGraph>;
|
||||
|
||||
pub trait Renderer {
|
||||
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.
|
||||
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.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)]
|
||||
if !self.mesh_buffers.contains_key(&mesh_uuid) {
|
||||
|
@ -451,10 +452,14 @@ impl Renderer for BasicRenderer {
|
|||
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()
|
||||
.data_ref().unwrap();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -465,27 +470,33 @@ impl Renderer for BasicRenderer {
|
|||
|
||||
if let Some(scene) = scene_han.data_ref() {
|
||||
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() {
|
||||
if let Some(mesh) = mesh_han.data_ref() {
|
||||
let mesh_interpo = interpo_pos + mesh_pos;
|
||||
scene.traverse_down(|sw: &World, mesh_han, pos| {
|
||||
if let Some(mesh_han) = sw.view_one::<&MeshHandle>(mesh_han.entity()).get() {
|
||||
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
|
||||
// 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);
|
||||
let scene_mesh_group = TransformGroup::Res(scene_han.uuid(), mesh_han.uuid());
|
||||
let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into());
|
||||
let transform_id = self.transform_buffers.update_or_push(&self.queue, &self.render_limits,
|
||||
group, || ( mesh_interpo.calculate_mat4(), glam::Mat3::from_quat(mesh_interpo.rotation) ));
|
||||
|
||||
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.
|
||||
let transform_indices = *self.transform_buffers.transform_indices(job.mesh_uuid).unwrap();
|
||||
let bindgroup = self.transform_buffers.bind_group(transform_indices).unwrap();
|
||||
let offset = TransformBuffers::index_offset(&self.render_limits, transform_indices) as u32;
|
||||
let bindgroup = self.transform_buffers.bind_group(job.transform_id);
|
||||
let offset = self.transform_buffers.buffer_offset(job.transform_id);
|
||||
render_pass.set_bind_group(1, bindgroup, &[ offset, offset, ]);
|
||||
|
||||
render_pass.set_bind_group(2, &self.camera_buffer.bindgroup(), &[]);
|
||||
|
|
|
@ -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 wgpu::Limits;
|
||||
|
||||
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)]
|
||||
pub(crate) struct TransformBufferIndices {
|
||||
pub buffer_index: usize,
|
||||
pub transform_index: usize,
|
||||
pub struct TransformIndex {
|
||||
/// The index of the entry in the buffer chain.
|
||||
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
|
||||
pub(crate) struct TransformBufferEntry {
|
||||
struct BufferEntry {
|
||||
pub len: usize,
|
||||
pub transform_buf: wgpu::Buffer,
|
||||
pub normal_mat_buf: wgpu::Buffer,
|
||||
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.
|
||||
///
|
||||
/// This struct manages a "chain" of uniform buffers that store Transform for meshes. When
|
||||
/// first created it only has a single "chain-link" with a buffer that is the maximum length
|
||||
/// This struct manages a "chain" of uniform buffers that store Transform for [`TransformGroup`]s.
|
||||
/// 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
|
||||
/// be the maximum length the GPU supports. When the new buffer fills up, a new one will be
|
||||
/// created once again, and so on.
|
||||
///
|
||||
/// `Uuid`'s are used to represent entries (usually Meshes) in the buffer. The Uuid's can be used
|
||||
/// to insert, update, and retrieve the transforms.
|
||||
pub(crate) struct TransformBuffers {
|
||||
/// [`TransformGroup`]s are used to represent entries in the buffer. They are used to insert,
|
||||
/// update, and retrieve the transforms.
|
||||
pub struct TransformBuffers {
|
||||
pub bindgroup_layout: wgpu::BindGroupLayout,
|
||||
pub just_updated: HashMap<Uuid, TransformBufferIndices>,
|
||||
pub not_updated: HashMap<Uuid, TransformBufferIndices>,
|
||||
pub dead_indices: VecDeque<TransformBufferIndices>,
|
||||
pub next_indices: TransformBufferIndices,
|
||||
/// (transform count, buffer, bindgroup)
|
||||
pub buffer_bindgroups: Vec<TransformBufferEntry>,
|
||||
/// The max amount of transforms in a buffer
|
||||
pub max_transform_count: usize,
|
||||
groups: CachedValMap<TransformGroupId, TransformIndex>,
|
||||
entries: Vec<BufferEntry>,
|
||||
limits: wgpu::Limits,
|
||||
max_transform_count: usize,
|
||||
}
|
||||
|
||||
impl TransformBuffers {
|
||||
|
@ -72,13 +195,11 @@ impl TransformBuffers {
|
|||
});
|
||||
|
||||
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,
|
||||
just_updated: HashMap::new(),
|
||||
not_updated: HashMap::new(),
|
||||
dead_indices: VecDeque::new(),
|
||||
next_indices: TransformBufferIndices::default(),
|
||||
groups: Default::default(),
|
||||
entries: Default::default(),
|
||||
max_transform_count: (limits.max_uniform_buffer_binding_size / 2) as usize / (mem::size_of::<glam::Mat4>()),
|
||||
limits,
|
||||
};
|
||||
|
||||
// create the first uniform buffer
|
||||
|
@ -87,106 +208,84 @@ impl TransformBuffers {
|
|||
s
|
||||
}
|
||||
|
||||
/// Update an transform in the buffer.
|
||||
/// Update an existing transform in the buffers.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the entity isn't stored, you can check if it is before with [`TransformBuffers:contains`].
|
||||
pub fn update_transform(&mut self, queue: &wgpu::Queue, limits: &Limits, uuid: Uuid, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices {
|
||||
let indices = self.not_updated.remove(&uuid)
|
||||
.or_else(|| self.just_updated.remove(&uuid))
|
||||
.expect("Use 'insert_entity' for new entities");
|
||||
self.just_updated.insert(uuid, indices);
|
||||
/// Panics if the `entity_group` is not already inside of the buffers.
|
||||
pub fn update_transform(&mut self, queue: &wgpu::Queue, limits: &Limits, entity_group: TransformGroup, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformIndex {
|
||||
let index = *self.groups.get(entity_group.into())
|
||||
.expect("Use 'push_transform' for new entities");
|
||||
let entry = self.entries.get_mut(index.entry_index).unwrap();
|
||||
|
||||
let normal_matrix = glam::Mat4::from_mat3(normal_matrix);
|
||||
|
||||
let buffer = self.buffer_bindgroups.get(indices.buffer_index).unwrap();
|
||||
let offset = Self::index_offset(limits, indices);
|
||||
queue.write_buffer(&buffer.transform_buf, offset, bytemuck::bytes_of(&transform));
|
||||
queue.write_buffer(&buffer.normal_mat_buf, offset, bytemuck::bytes_of(&normal_matrix));
|
||||
indices
|
||||
// write the transform and normal to the end of the transform
|
||||
let offset = Self::get_buffer_offset(limits, index) as _;
|
||||
queue.write_buffer(&entry.transform_buffer, offset, bytemuck::bytes_of(&transform));
|
||||
queue.write_buffer(&entry.normal_buffer, offset, bytemuck::bytes_of(&normal_matrix));
|
||||
|
||||
index
|
||||
}
|
||||
|
||||
/// Insert a new transform into the buffer, returns where in the buffer it was stored.
|
||||
pub fn insert_transform(&mut self, queue: &wgpu::Queue, limits: &Limits, uuid: Uuid, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices {
|
||||
let indices = match self.dead_indices.pop_front() {
|
||||
Some(indices) => indices,
|
||||
None => {
|
||||
let indices = &mut self.next_indices;
|
||||
let this_idx = *indices;
|
||||
let entry = self.buffer_bindgroups.get_mut(indices.buffer_index).unwrap();
|
||||
/// Push a new transform into the buffers.
|
||||
pub fn push_transform(&mut self, queue: &wgpu::Queue, limits: &Limits, entity_group: TransformGroup, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformIndex {
|
||||
self.groups.insert(entity_group.into(), || {
|
||||
// this closure is only called when there are no values that can be reused,
|
||||
// so we get a brand new index at the end of the last entry in the chain.
|
||||
let last = self.entries.last_mut().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 {
|
||||
panic!("Transform buffer is filled and 'next_indices' was not incremented! Was a new buffer created?");
|
||||
}
|
||||
|
||||
entry.len += 1;
|
||||
indices.transform_index += 1;
|
||||
this_idx
|
||||
TransformIndex {
|
||||
entry_index: self.entries.len() - 1,
|
||||
transform_index: tidx
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
self.just_updated.insert(uuid, indices);
|
||||
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)
|
||||
self.update_transform(queue, limits, entity_group, transform, normal_matrix)
|
||||
}
|
||||
|
||||
/// Collect the dead transforms and prepare self to check next time.
|
||||
pub fn tick(&mut self) {
|
||||
// take the dead entities, these were ones that were not updated this tick
|
||||
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();
|
||||
self.groups.update();
|
||||
}
|
||||
|
||||
/// Returns the offset for the transform index in the buffer
|
||||
pub fn index_offset(limits: &Limits, indices: TransformBufferIndices) -> u64 {
|
||||
indices.transform_index as u64 * limits.min_uniform_buffer_offset_alignment as u64
|
||||
/// Returns a boolean indicating if the buffer contains this group.
|
||||
pub fn contains(&self, group: TransformGroup) -> bool {
|
||||
self.groups.contains(group.into())
|
||||
}
|
||||
|
||||
/// Returns whether or not the transform buffers should be expanded
|
||||
pub fn should_expand(&self) -> bool {
|
||||
if let Some(entry) = self.buffer_bindgroups.last() {
|
||||
entry.len >= self.max_transform_count
|
||||
/// Update an existing transform group or if its not existing yet, pushes it to the buffer.
|
||||
///
|
||||
/// Returns: the index that the transform is at in the buffers.
|
||||
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 {
|
||||
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.
|
||||
///
|
||||
/// This object has a chain of uniform buffers, when the buffers are expanded, a new
|
||||
/// "chain-link" is created.
|
||||
pub fn expand_buffers(&mut self, device: &wgpu::Device) {
|
||||
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(
|
||||
&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,
|
||||
size: max_buffer_sizes,
|
||||
mapped_at_creation: false,
|
||||
|
@ -195,7 +294,7 @@ impl TransformBuffers {
|
|||
|
||||
let normal_mat_buffer = device.create_buffer(
|
||||
&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,
|
||||
size: max_buffer_sizes,
|
||||
mapped_at_creation: false,
|
||||
|
@ -234,17 +333,43 @@ impl TransformBuffers {
|
|||
label: Some("BG_Transforms"),
|
||||
});
|
||||
|
||||
let entry = TransformBufferEntry {
|
||||
let entry = BufferEntry {
|
||||
bindgroup: transform_bind_group,
|
||||
transform_buf: transform_buffer,
|
||||
normal_mat_buf: normal_mat_buffer,
|
||||
transform_buffer,
|
||||
normal_buffer: normal_mat_buffer,
|
||||
len: 0,
|
||||
};
|
||||
self.buffer_bindgroups.push(entry);
|
||||
self.entries.push(entry);
|
||||
}
|
||||
|
||||
/// Returns the indices of the Transform
|
||||
pub fn transform_indices(&self, uuid: Uuid) -> Option<&TransformBufferIndices> {
|
||||
self.just_updated.get(&uuid).or_else(|| self.not_updated.get(&uuid))
|
||||
/// Returns the bind group for the transform index.
|
||||
pub fn bind_group(&self, transform_id: TransformIndex) -> &wgpu::BindGroup {
|
||||
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,
|
||||
}
|
|
@ -341,8 +341,8 @@ fn window_updater_system(world: &mut World) -> anyhow::Result<()> {
|
|||
let mut opts = world.get_resource_mut::<Ct<WindowOptions>>();
|
||||
|
||||
if let Some(event_queue) = world.try_get_resource_mut::<EventQueue>() {
|
||||
if let Some(mut events) = event_queue.read_events::<InputEvent>() {
|
||||
while let Some(ev) = events.pop_front() {
|
||||
if let Some(events) = event_queue.read_events::<InputEvent>() {
|
||||
for ev in events {
|
||||
match ev {
|
||||
InputEvent::CursorEntered { .. } => {
|
||||
opts.cursor_inside_window = true;
|
||||
|
|
|
@ -32,7 +32,7 @@ impl From<&Variant> for VariantType {
|
|||
|
||||
/// Generates the following different outputs:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// // for struct variants
|
||||
/// 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:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// /// generated one field here
|
||||
/// if name == "msg" {
|
||||
/// return Some(msg);
|
||||
|
@ -129,7 +129,7 @@ fn gen_if_field_names(variant: &Variant) -> proc_macro2::TokenStream {
|
|||
|
||||
/// Generates the following rust code:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// match name {
|
||||
/// "msg" | "code" => true,
|
||||
/// _ => false,
|
||||
|
@ -153,7 +153,7 @@ fn gen_match_names(variant: &Variant) -> proc_macro2::TokenStream {
|
|||
|
||||
/// Generates the following:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// /// generated one field here
|
||||
/// if idx == 0 {
|
||||
/// return Some(a);
|
||||
|
@ -190,7 +190,7 @@ fn gen_if_field_indices(variant: &Variant) -> proc_macro2::TokenStream {
|
|||
|
||||
/// Generates the following:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// /// generated one field here
|
||||
/// if idx == 0 {
|
||||
/// return Some("a");
|
||||
|
@ -226,7 +226,7 @@ fn gen_if_field_indices_names(variant: &Variant) -> proc_macro2::TokenStream {
|
|||
}
|
||||
|
||||
/// Generates the following:
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// /// when `by_index` is false:
|
||||
///
|
||||
/// 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:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// if let TestEnum::Error { msg, code } = self {
|
||||
/// return match name {
|
||||
/// // 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:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// match self {
|
||||
/// TestEnum::Start => 0,
|
||||
/// 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:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// if let TestEnum::Error { msg, code } = self {
|
||||
/// if idx == 0 {
|
||||
/// 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:
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// match self {
|
||||
/// TestEnum::Start => 0,
|
||||
/// 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.
|
||||
///
|
||||
/// Example:
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// match self {
|
||||
/// TestEnum::Start => EnumType::Unit,
|
||||
/// TestEnum::Middle(a, b) => EnumType::Tuple,
|
||||
|
|
|
@ -28,7 +28,7 @@ impl StructType {
|
|||
/// contains a borrow (mutable borrow if `is_mut` is true) to the matching struct field.
|
||||
///
|
||||
/// Example:
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// // when `is_mut` = false
|
||||
/// match name {
|
||||
/// "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`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// match name {
|
||||
/// "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()))
|
||||
|
@ -140,7 +140,7 @@ fn gen_struct_set_field_match(data: &DataStruct) -> proc_macro2::TokenStream {
|
|||
/// the type of the field.
|
||||
///
|
||||
/// Example:
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// match name {
|
||||
/// "x" => Some("f32"),
|
||||
/// "y" => Some("f32"),
|
||||
|
@ -177,7 +177,7 @@ fn gen_struct_field_name_match(data: &DataStruct) -> proc_macro2::TokenStream {
|
|||
/// with the provided `val`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// match name {
|
||||
/// 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()))
|
||||
|
@ -243,7 +243,7 @@ fn gen_struct_set_field_match_idx(data: &DataStruct) -> proc_macro2::TokenStream
|
|||
/// type of the field.
|
||||
///
|
||||
/// Example:
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// match name {
|
||||
/// 0 => 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.
|
||||
///
|
||||
/// Example:
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// // when `is_mut` = false
|
||||
/// match idx {
|
||||
/// 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.
|
||||
///
|
||||
/// Example:
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// match idx {
|
||||
/// 0 => Some("x"),
|
||||
/// 1 => Some("y"),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
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;
|
||||
|
||||
|
@ -250,46 +250,46 @@ pub trait FromType<T> {
|
|||
|
||||
pub trait ReflectWorldExt {
|
||||
/// 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.
|
||||
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
|
||||
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.
|
||||
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.
|
||||
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 {
|
||||
fn get_type_registry(&self) -> Ref<TypeRegistry> {
|
||||
fn get_type_registry(&self) -> AtomicRef<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>()
|
||||
}
|
||||
|
||||
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>();
|
||||
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 {
|
||||
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>();
|
||||
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 {
|
||||
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>();
|
||||
RefMut::map(r, |tr| tr.get_type_or_default(type_id))
|
||||
AtomicRefMut::map(r, |tr| tr.get_type_or_default(type_id))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
#[derive(Default, Clone)]
|
||||
|
@ -44,19 +44,19 @@ impl TypeRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait TypeData: std::any::Any {
|
||||
fn as_any(&self) -> &dyn std::any::Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
|
||||
pub trait TypeData: Send + Sync + Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
|
||||
fn boxed_clone(&self) -> Box<dyn TypeData>;
|
||||
}
|
||||
|
||||
impl<T: Clone + 'static> TypeData for T {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
impl<T: Clone + Send + Sync + Any> TypeData for T {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
@ -8,20 +8,20 @@ use crate::{Reflect, FromType};
|
|||
pub struct ReflectedResource {
|
||||
pub type_id: TypeId,
|
||||
|
||||
fn_reflect: for<'a> fn (world: &'a World) -> Option<Ref<'a, dyn Reflect>>,
|
||||
fn_reflect_mut: for<'a> fn (world: &'a mut World) -> Option<RefMut<'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<AtomicRefMut<'a, dyn Reflect>>,
|
||||
fn_reflect_ptr: fn (world: &mut World) -> Option<NonNull<u8>>,
|
||||
fn_refl_insert: fn (world: &mut World, this: Box<dyn Reflect>),
|
||||
}
|
||||
|
||||
impl ReflectedResource {
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
|
@ -41,11 +41,15 @@ impl<T: ResourceObject + Reflect> FromType<T> for ReflectedResource {
|
|||
type_id: TypeId::of::<T>(),
|
||||
fn_reflect: |world: &World| {
|
||||
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| {
|
||||
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 {
|
||||
world.try_get_resource_ptr::<T>()
|
||||
|
|
|
@ -9,6 +9,7 @@ edition = "2021"
|
|||
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
||||
lyra-reflect = { path = "../lyra-reflect" }
|
||||
lyra-math = { path = "../lyra-math" }
|
||||
lyra-scene = { path = "../lyra-scene" }
|
||||
anyhow = "1.0.75"
|
||||
base64 = "0.21.4"
|
||||
crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] }
|
||||
|
|
|
@ -3,9 +3,10 @@ use std::{ffi::OsStr, path::{Path, PathBuf}, sync::Arc};
|
|||
use glam::{Quat, Vec3};
|
||||
use instant::Instant;
|
||||
use lyra_math::Transform;
|
||||
use lyra_scene::{SceneGraph, SceneNode};
|
||||
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 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();
|
||||
|
||||
node.transform = {
|
||||
|
@ -75,6 +76,8 @@ impl ModelLoader {
|
|||
};
|
||||
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() {
|
||||
let mut new_mesh = Mesh::default();
|
||||
|
||||
|
@ -127,11 +130,12 @@ impl ModelLoader {
|
|||
|
||||
let handle = ResHandle::new_ready(None, new_mesh);
|
||||
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() {
|
||||
let cmesh = ModelLoader::process_node(ctx, materials, child);
|
||||
let cmesh = ModelLoader::process_node(ctx, materials, scene, &scene_node, child);
|
||||
node.children.push(cmesh);
|
||||
}
|
||||
|
||||
|
@ -210,7 +214,21 @@ impl ResourceLoader for ModelLoader {
|
|||
debug!("Loaded {} materials in {}s", materials.len(), mat_time.as_secs_f32());
|
||||
|
||||
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()
|
||||
.map(|node| ModelLoader::process_node(&mut context, &materials, node))
|
||||
.collect();
|
||||
|
@ -228,7 +246,7 @@ impl ResourceLoader for ModelLoader {
|
|||
nodes,
|
||||
};
|
||||
let scene = ResHandle::new_ready(Some(path.as_str()), scene);
|
||||
gltf_out.scenes.push(scene);
|
||||
gltf_out.scenes.push(scene); */
|
||||
}
|
||||
|
||||
gltf_out.materials = materials;
|
||||
|
@ -249,6 +267,8 @@ impl ResourceLoader for ModelLoader {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use lyra_ecs::{query::Entities, relation::ChildOf};
|
||||
|
||||
use crate::tests::busy_wait_resource;
|
||||
|
||||
use super::*;
|
||||
|
@ -272,14 +292,30 @@ mod tests {
|
|||
let scene = &gltf.scenes[0]
|
||||
.data_ref().unwrap();
|
||||
|
||||
assert_eq!(scene.nodes.len(), 1);
|
||||
let mnode = &scene.nodes[0];
|
||||
let mut node = None;
|
||||
scene.traverse_down(|_, no, _tran| {
|
||||
node = Some(no.clone());
|
||||
});
|
||||
|
||||
assert!(mnode.mesh.is_some());
|
||||
assert_eq!(mnode.transform, Transform::from_xyz(0.0, 0.0, 0.0));
|
||||
assert_eq!(mnode.children.len(), 0);
|
||||
let world = scene.world();
|
||||
let node = node.unwrap();
|
||||
|
||||
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();
|
||||
assert!(mesh.position().unwrap().len() > 0);
|
||||
assert!(mesh.normals().unwrap().len() > 0);
|
||||
|
|
|
@ -2,7 +2,7 @@ pub mod loader;
|
|||
pub use loader::*;
|
||||
|
||||
pub mod material;
|
||||
use lyra_math::Transform;
|
||||
use lyra_scene::SceneGraph;
|
||||
use crate::ResourceData;
|
||||
pub use material::*;
|
||||
|
||||
|
@ -17,7 +17,7 @@ use crate::ResHandle;
|
|||
/// A loaded Gltf file
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Gltf {
|
||||
pub scenes: Vec<ResHandle<GltfScene>>,
|
||||
pub scenes: Vec<ResHandle<SceneGraph>>,
|
||||
pub materials: Vec<ResHandle<Material>>,
|
||||
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
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use lyra_math::Transform;
|
||||
use lyra_scene::SceneGraph;
|
||||
use crate::{optionally_add_to_dep, ResourceData, UntypedResHandle};
|
||||
|
||||
use super::Mesh;
|
||||
|
@ -33,8 +34,8 @@ impl ResourceData for GltfNode {
|
|||
}
|
||||
}
|
||||
|
||||
/// A Scene in a Gltf file
|
||||
#[derive(Clone)]
|
||||
// A Scene in a Gltf file
|
||||
/* #[derive(Clone)]
|
||||
pub struct GltfScene {
|
||||
pub nodes: Vec<GltfNode>,
|
||||
}
|
||||
|
@ -92,4 +93,21 @@ impl GltfScene {
|
|||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -31,4 +31,4 @@ pub(crate) mod lyra_engine {
|
|||
pub(crate) mod reflect {
|
||||
pub use lyra_reflect::*;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -96,7 +96,7 @@ pub struct UntypedResource {
|
|||
pub(crate) condvar: Arc<(Mutex<bool>, Condvar)>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Component)]
|
||||
pub struct UntypedResHandle{
|
||||
pub(crate) res: Arc<RwLock<UntypedResource>>,
|
||||
#[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
|
||||
/// and has a write lock on the data. This means that most of the time, it is not blocking.
|
||||
#[derive(Component)]
|
||||
pub struct ResHandle<T: 'static> {
|
||||
pub struct ResHandle<T: ResourceData> {
|
||||
pub(crate) handle: UntypedResHandle,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Clone for ResHandle<T> {
|
||||
impl<T: ResourceData> Clone for ResHandle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
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;
|
||||
|
||||
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 {
|
||||
&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 {
|
||||
self
|
||||
}
|
||||
|
|
|
@ -173,7 +173,7 @@ impl ResourceManager {
|
|||
|
||||
/// Request a resource without downcasting to a `ResHandle<T>`.
|
||||
/// Whenever you're ready to downcast, you can do so like this:
|
||||
/// ```compile_fail
|
||||
/// ```nobuild
|
||||
/// let arc_any = res_arc.as_arc_any();
|
||||
/// 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
|
||||
/// 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();
|
||||
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
|
||||
/// 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();
|
||||
match state.resources.get(&uuid.to_string())
|
||||
.or_else(|| state.uuid_resources.get(&uuid))
|
||||
|
@ -291,7 +291,7 @@ impl ResourceManager {
|
|||
/// Requests bytes from the manager.
|
||||
pub fn request_loaded_bytes<T>(&self, ident: &str) -> Result<Arc<ResHandle<T>>, RequestError>
|
||||
where
|
||||
T: Send + Sync + Any + 'static
|
||||
T: ResourceData
|
||||
{
|
||||
let state = self.state();
|
||||
match state.resources.get(&ident.to_string()) {
|
||||
|
@ -366,7 +366,7 @@ impl ResourceManager {
|
|||
/// the handle.
|
||||
pub fn reload<T>(&self, resource: ResHandle<T>) -> Result<(), RequestError>
|
||||
where
|
||||
T: Send + Sync + Any + 'static
|
||||
T: ResourceData
|
||||
{
|
||||
let state = self.state();
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::any::Any;
|
||||
|
||||
use crossbeam::channel::Receiver;
|
||||
use lyra_ecs::World;
|
||||
use notify_debouncer_full::DebouncedEvent;
|
||||
|
@ -30,7 +28,7 @@ pub trait WorldAssetExt {
|
|||
/// automatically triggered if the resource is being watched.
|
||||
fn reload_res<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError>
|
||||
where
|
||||
T: Send + Sync + Any + 'static;
|
||||
T: ResourceData;
|
||||
}
|
||||
|
||||
impl WorldAssetExt for World {
|
||||
|
@ -67,7 +65,7 @@ impl WorldAssetExt for World {
|
|||
|
||||
fn reload_res<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError>
|
||||
where
|
||||
T: Send + Sync + Any + 'static
|
||||
T: ResourceData
|
||||
{
|
||||
let man = self.get_resource_or_default::<ResourceManager>();
|
||||
man.reload(resource)
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
@ -21,77 +21,31 @@ pub struct SceneNodeFlag;
|
|||
#[derive(Component)]
|
||||
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.
|
||||
///
|
||||
/// 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.
|
||||
//#[derive(Default)]
|
||||
pub struct SceneGraph<'a> {
|
||||
pub(crate) world: MutCow<'a, World>,
|
||||
pub struct SceneGraph {
|
||||
pub(crate) world: World,
|
||||
root_node: SceneNode,
|
||||
}
|
||||
|
||||
impl<'a> SceneGraph<'a> {
|
||||
impl SceneGraph {
|
||||
/// Create a new SceneGraph with its own ECS World.
|
||||
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));
|
||||
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),
|
||||
})
|
||||
}
|
||||
Self::from_world(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 = SceneNode::new(None, root_en);
|
||||
|
||||
Self {
|
||||
world: MutCow::Mut(world),
|
||||
world,
|
||||
root_node: root,
|
||||
}
|
||||
}
|
||||
|
@ -162,6 +116,13 @@ impl<'a> SceneGraph<'a> {
|
|||
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 {
|
||||
let e = self.world.spawn((SceneNodeFlag, local_transform));
|
||||
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.
|
||||
pub fn traverse_down<F>(&self, mut callback: F)
|
||||
where
|
||||
F: FnMut(&SceneNode, Transform),
|
||||
F: FnMut(&World, &SceneNode, Transform),
|
||||
{
|
||||
self.traverse_down_from(self.root_node.clone(), &mut callback);
|
||||
}
|
||||
|
@ -183,15 +144,16 @@ impl<'a> SceneGraph<'a> {
|
|||
/// SceneNode and its world transform.
|
||||
fn traverse_down_from<F>(&self, start: SceneNode, callback: &mut F)
|
||||
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());
|
||||
|
||||
for ((e, _), _rel) in v.iter() {
|
||||
let node = SceneNode::new(Some(start.entity()), e);
|
||||
let world_pos = node.world_transform(self);
|
||||
callback(&node, world_pos);
|
||||
callback(&self.world, &node, world_pos);
|
||||
|
||||
self.traverse_down_from(node, callback);
|
||||
}
|
||||
|
@ -200,6 +162,11 @@ impl<'a> SceneGraph<'a> {
|
|||
pub fn root_node(&self) -> SceneNode {
|
||||
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.
|
||||
|
@ -255,7 +222,7 @@ pub mod tests {
|
|||
assert!(b.parent(&scene).unwrap() == a);
|
||||
|
||||
let mut idx = 0;
|
||||
scene.traverse_down(|_e, pos| {
|
||||
scene.traverse_down(|_, _, pos| {
|
||||
if idx == 0 {
|
||||
assert_eq!(pos, Transform::from_translation(v2s[idx]));
|
||||
} else if idx == 1 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
date = "2023-11-21"
|
||||
channel = "nightly-2023-11-21"
|
||||
#date = "2023-11-21"
|
||||
targets = [ "x86_64-unknown-linux-gnu" ]
|
15
shell.nix
|
@ -1,6 +1,10 @@
|
|||
let
|
||||
moz_overlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz);
|
||||
nixpkgs = import <nixpkgs> { overlays = [ moz_overlay ]; };
|
||||
rust_overlay = import (builtins.fetchTarball https://github.com/oxalica/rust-overlay/archive/master.tar.gz);
|
||||
nixpkgs = import <nixpkgs> { overlays = [ rust_overlay ]; };
|
||||
|
||||
rust = (nixpkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override {
|
||||
extensions = [ "rust-analysis" "rust-src" "miri-preview" ];
|
||||
};
|
||||
in
|
||||
with nixpkgs;
|
||||
stdenv.mkDerivation rec {
|
||||
|
@ -15,12 +19,7 @@ in
|
|||
mold
|
||||
udev
|
||||
lua5_4_compat
|
||||
((nixpkgs.rustChannelOf { rustToolchain = ./rust-toolchain.toml; }).rust.override {
|
||||
extensions = [
|
||||
"rust-src"
|
||||
"rust-analysis"
|
||||
];
|
||||
})
|
||||
rust
|
||||
];
|
||||
buildInputs = [
|
||||
udev alsa-lib libGL gcc
|
||||
|
|