Compare commits

..

No commits in common. "798719a7a2aa21f23948d50101b12b1d7fa86460" and "9b1cc8c364b6732df58b0437fa2a6a6f8f4c9564" have entirely different histories.

27 changed files with 504 additions and 569 deletions

View File

@ -1,12 +1,17 @@
use lyra_engine::{
assets::{gltf::Gltf, ResourceManager}, ecs::query::View, game::App, input::{
assets::{gltf::Gltf, ResourceManager},
game::App,
input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
}, math::{self, Transform, Vec3}, render::{light::directional::DirectionalLight, window::WindowOptions}, scene::{
},
math::{self, Transform, Vec3},
render::light::directional::DirectionalLight,
scene::{
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
}
},
};
#[async_std::main]
@ -47,7 +52,7 @@ async fn main() {
.bind(
ACTLBL_LOOK_LEFT_RIGHT,
&[
//ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
ActionSource::Keyboard(KeyCode::ArrowLeft).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::ArrowRight).into_binding_modifier(1.0),
],
@ -55,7 +60,7 @@ async fn main() {
.bind(
ACTLBL_LOOK_UP_DOWN,
&[
//ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
ActionSource::Keyboard(KeyCode::ArrowUp).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::ArrowDown).into_binding_modifier(1.0),
],
@ -82,7 +87,6 @@ async fn main() {
let mut a = App::new();
a.with_plugin(lyra_engine::DefaultPlugins)
.with_system("mouse_pos_print", mouse_pos_system, &[])
.with_plugin(setup_scene_plugin)
.with_plugin(action_handler_plugin)
//.with_plugin(camera_debug_plugin)
@ -92,7 +96,7 @@ async fn main() {
fn setup_scene_plugin(app: &mut App) {
let world = &mut app.world;
let resman = world.get_resource_mut::<ResourceManager>().unwrap();
let resman = world.get_resource_mut::<ResourceManager>();
/* let camera_gltf = resman
.request::<Gltf>("../assets/AntiqueCamera.glb")
@ -141,11 +145,3 @@ fn setup_scene_plugin(app: &mut App) {
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
world.spawn((camera, FreeFlyCamera::default()));
}
fn mouse_pos_system(view: View<&WindowOptions>) -> anyhow::Result<()> {
for win in view.iter() {
//println!("Mouse pos: {:?}", win.cursor_position());
}
Ok(())
}

View File

@ -2,7 +2,7 @@ use std::marker::PhantomData;
use atomic_refcell::{AtomicRef, AtomicRefMut};
use crate::{resource::ResourceObject, Tick, TrackedResource, World};
use crate::{World, resource::ResourceObject};
use super::{Query, Fetch, AsQuery};
@ -22,18 +22,12 @@ impl<'a, T: ResourceObject + 'a> Fetch<'a> for FetchResource<'a, T> {
}
fn can_visit_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> bool {
let w = self.world.unwrap();
w.has_resource::<T>()
true
}
unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item {
let w = self.world.unwrap();
Res {
// this unwrap is safe since `can_visit_item` ensures the resource exists
inner: w.get_tracked_resource::<T>().unwrap(),
world_tick: w.current_tick(),
_marker: PhantomData::<T>,
}
Res(w.get_resource::<T>())
}
}
@ -87,37 +81,13 @@ impl<R: ResourceObject> AsQuery for QueryResource<R> {
}
/// A struct used for querying resources from the World.
pub struct Res<'a, T: ResourceObject> {
pub(crate) inner: AtomicRef<'a, TrackedResource<dyn ResourceObject>>,
pub(crate) world_tick: Tick,
pub(crate) _marker: PhantomData<T>,
}
pub struct Res<'a, T: ResourceObject>(pub(crate) AtomicRef<'a, T>);
impl<'a, T: ResourceObject> std::ops::Deref for Res<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner.res.as_any().downcast_ref::<T>().unwrap()
}
}
impl<'a, T: ResourceObject> Res<'a, T> {
/// Get the inner [`AtomicRef`].
///
/// This inner type does not have change tracking. If you `DerefMut` it, the change will not be tracked!
#[inline]
pub fn get_inner(self) -> atomic_refcell::AtomicRef<'a, T> {
atomic_refcell::AtomicRef::map(self.inner, |r| r.res.as_any().downcast_ref().unwrap())
}
/// Returns a boolean indicating if the resource changed.
pub fn changed(&self) -> bool {
self.inner.tick >= self.world_tick
}
/// The tick that this resource was last modified at
pub fn tick(&self) -> Tick {
self.inner.tick
self.0.deref()
}
}
@ -141,18 +111,12 @@ impl<'a, T: ResourceObject + 'a> Fetch<'a> for FetchResourceMut<'a, T> {
}
fn can_visit_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> bool {
let w = self.world.unwrap();
w.has_resource::<T>()
true
}
unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item {
let w = self.world.unwrap();
ResMut {
// this is safe since `can_visit_item` ensures that the resource exists.
inner: w.get_tracked_resource_mut::<T>().unwrap(),
world_tick: w.current_tick(),
_marker: PhantomData::<T>,
}
ResMut(w.get_resource_mut::<T>())
}
}
@ -205,51 +169,20 @@ impl<R: ResourceObject> AsQuery for QueryResourceMut<R> {
}
/// A struct used for querying resources from the World.
//pub struct ResMut<'a, T: ResourceObject>(pub(crate) AtomicRefMut<'a, T>);
pub struct ResMut<'a, T: ResourceObject> {
pub(crate) inner: AtomicRefMut<'a, TrackedResource<dyn ResourceObject>>,
pub(crate) world_tick: Tick,
pub(crate) _marker: PhantomData<T>,
}
pub struct ResMut<'a, T: ResourceObject>(pub(crate) AtomicRefMut<'a, T>);
impl<'a, T: ResourceObject> std::ops::Deref for ResMut<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner.res.as_any().downcast_ref::<T>().unwrap()
self.0.deref()
}
}
impl<'a, T: ResourceObject> std::ops::DerefMut for ResMut<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.mark_changed();
self.inner.res.as_any_mut().downcast_mut::<T>().unwrap()
}
}
impl<'a, T: ResourceObject> ResMut<'a, T> {
/// Get the inner [`AtomicRefMut`].
///
/// This inner type does not have change tracking. If you `DerefMut` it, the change will not be tracked!
pub fn get_inner(self) -> atomic_refcell::AtomicRefMut<'a, T> {
atomic_refcell::AtomicRefMut::map(self.inner, |r| r.res.as_any_mut().downcast_mut().unwrap())
}
pub fn changed(&self) -> bool {
self.inner.tick > self.world_tick
}
/// The tick that this resource was last modified at
pub fn tick(&self) -> Tick {
self.inner.tick
}
/// Manually mark the resource as changed.
///
/// This is useful if you have a resource with interior mutability and you want to
/// mark this resource as changed.
pub fn mark_changed(&mut self) {
self.inner.tick = self.world_tick
self.0.deref_mut()
}
}

View File

@ -58,6 +58,7 @@ where
ViewIter {
world: self.world,
tick: self.world.current_tick(),
has_ticked: false,
query: self.query,
filter: self.filter,
fetcher: None,
@ -72,6 +73,7 @@ where
pub struct ViewIter<'a, Q: Query, F: Filter> {
world: &'a World,
tick: Tick,
has_ticked: bool,
query: Q,
filter: F,
fetcher: Option<Q::Fetch<'a>>,
@ -110,6 +112,13 @@ where
let entity_index = ArchetypeEntityId(entity_index);
if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(entity_index) {
// only tick the world if the filter has fetched, and when the world hasn't
// been ticked yet.
if Q::MUTATES && !self.has_ticked {
self.has_ticked = true;
self.tick = self.world.tick();
}
let i = unsafe { fetcher.get_item(entity_index) };
return Some(i);
}
@ -165,6 +174,9 @@ impl<'a, Q: Query> ViewOne<'a, Q> {
if self.query.can_visit_archetype(arch) {
let mut fetch = unsafe { self.query.fetch(self.world, arch, self.tick) };
if fetch.can_visit_item(record.index) {
// only tick the world when something is actually fetched.
self.world.tick();
return Some(unsafe { fetch.get_item(record.index) });
}
}

View File

@ -20,24 +20,22 @@ impl<T: Send + Sync + Any> ResourceObject for T {
}
}
pub struct TrackedResource<T: ?Sized> {
pub tick: Tick,
pub res: T,
}
/// A type erased storage for a Resource.
#[derive(Clone)]
pub struct ResourceData {
pub(crate) data: Arc<AtomicRefCell<TrackedResource<dyn ResourceObject>>>,
pub(crate) data: Arc<AtomicRefCell<dyn ResourceObject>>,
type_id: TypeId,
// use a tick tracker which has interior mutability
pub(crate) tick: TickTracker,
}
impl ResourceData {
pub fn new<T: ResourceObject>(data: T, tick: Tick) -> Self {
Self {
data: Arc::new(AtomicRefCell::new(TrackedResource { tick, res: data })),
data: Arc::new(AtomicRefCell::new(data)),
type_id: TypeId::of::<T>(),
tick: TickTracker::from(*tick),
}
}
@ -53,7 +51,7 @@ 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: ResourceObject>(&self) -> AtomicRef<T> {
AtomicRef::map(self.data.borrow(), |a| a.res.as_any().downcast_ref().unwrap())
AtomicRef::map(self.data.borrow(), |a| a.as_any().downcast_ref().unwrap())
}
/// Mutably borrow the data inside of the resource.
@ -63,7 +61,7 @@ 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: ResourceObject>(&self) -> AtomicRefMut<T> {
AtomicRefMut::map(self.data.borrow_mut(), |a| a.res.as_any_mut().downcast_mut().unwrap())
AtomicRefMut::map(self.data.borrow_mut(), |a| a.as_any_mut().downcast_mut().unwrap())
}
/// Borrow the data inside of the resource.
@ -73,7 +71,7 @@ impl ResourceData {
/// * If the type of `T` is not the same as the resource type.
pub fn try_get<T: ResourceObject>(&self) -> Option<AtomicRef<T>> {
self.data.try_borrow()
.map(|r| AtomicRef::map(r, |a| a.res.as_any().downcast_ref().unwrap()))
.map(|r| AtomicRef::map(r, |a| a.as_any().downcast_ref().unwrap()))
.ok()
}
@ -84,11 +82,11 @@ impl ResourceData {
/// * If the type of `T` is not the same as the resource type.
pub fn try_get_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
self.data.try_borrow_mut()
.map(|r| AtomicRefMut::map(r, |a| a.res.as_any_mut().downcast_mut().unwrap()))
.map(|r| AtomicRefMut::map(r, |a| a.as_any_mut().downcast_mut().unwrap()))
.ok()
}
pub fn changed(&self, tick: Tick) -> bool {
self.data.borrow().tick >= tick
self.tick.current() >= tick
}
}

View File

@ -20,7 +20,6 @@ pub trait FnArgFetcher {
Access::Read
}
// TODO: check if the fetcher can fetch before getting.
/// Get the arg from the world
///
/// # Safety
@ -215,9 +214,7 @@ impl<R: ResourceObject> FnArgFetcher for Res<'_, R> {
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
let world = world.as_ref();
world.get_resource::<R>()
// TODO: check if the resource exists before attempting to fetch
.unwrap_or_else(|| panic!("world is missing resource: {}", std::any::type_name::<R>()))
Res(world.get_resource::<R>())
}
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
@ -231,9 +228,7 @@ impl<R: ResourceObject> FnArgFetcher for ResMut<'_, R> {
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
let world = world.as_ref();
world.get_resource_mut::<R>()
// TODO: check if the resource exists before attempting to fetch
.unwrap_or_else(|| panic!("world is missing resource: {}", std::any::type_name::<R>()))
ResMut(world.get_resource_mut::<R>())
}
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
@ -312,8 +307,7 @@ mod tests {
world.add_resource(SomeCounter(0));
let test_system = |world: &World| -> anyhow::Result<()> {
let mut counter = world.get_resource_mut::<SomeCounter>()
.expect("Counter resource is missing");
let mut counter = world.get_resource_mut::<SomeCounter>();
counter.0 += 10;
Ok(())
@ -322,8 +316,7 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap();
let test_system = |world: &mut World| -> anyhow::Result<()> {
let mut counter = world.get_resource_mut::<SomeCounter>()
.expect("Counter resource is missing");
let mut counter = world.get_resource_mut::<SomeCounter>();
counter.0 += 10;
Ok(())
@ -331,8 +324,7 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap();
let counter = world.get_resource::<SomeCounter>()
.expect("Counter resource is missing");
let counter = world.get_resource::<SomeCounter>();
assert_eq!(counter.0, 20);
}
@ -344,8 +336,7 @@ mod tests {
world.add_resource(SomeCounter(0));
let test_system = |world: &World| -> anyhow::Result<()> {
let mut counter = world.get_resource_mut::<SomeCounter>()
.expect("Counter resource is missing");
let mut counter = world.get_resource_mut::<SomeCounter>();
counter.0 += 10;
Ok(())
@ -355,8 +346,7 @@ mod tests {
#[allow(dead_code)]
fn test_system(world: &mut World) -> anyhow::Result<()> {
let mut counter = world.get_resource_mut::<SomeCounter>()
.expect("Counter resource is missing");
let mut counter = world.get_resource_mut::<SomeCounter>();
counter.0 += 10;
Ok(())
@ -364,8 +354,7 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap();
let counter = world.get_resource::<SomeCounter>()
.expect("Counter resource is missing");
let counter = world.get_resource::<SomeCounter>();
assert_eq!(counter.0, 20);
}
@ -377,15 +366,16 @@ mod tests {
world.add_resource(SomeCounter(0));
let test_system = |mut counter: ResMut<SomeCounter>| -> anyhow::Result<()> {
counter.0 += 10;
// .0 is twice here since ResMut's tuple field is pub(crate).
// Users wont need to do this
counter.0.0 += 10;
Ok(())
};
test_system.into_system().execute(NonNull::from(&world)).unwrap();
let counter = world.get_resource::<SomeCounter>()
.expect("Counter resource is missing");
let counter = world.get_resource::<SomeCounter>();
assert_eq!(counter.0, 10);
}
@ -399,7 +389,9 @@ mod tests {
let test_system = |mut counter: ResMut<SomeCounter>, view: ViewState<QueryBorrow<Vec2>, ()>| -> anyhow::Result<()> {
for v2 in view.into_iter() {
println!("Got v2 at '{:?}'", v2);
counter.0 += 1;
// .0 is twice here since ResMut's tuple field is pub(crate).
// Users wont need to do this
counter.0.0 += 1;
}
Ok(())
@ -407,8 +399,7 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap();
let counter = world.get_resource::<SomeCounter>()
.expect("Counter resource is missing");
let counter = world.get_resource::<SomeCounter>();
assert_eq!(counter.0, 2);
}
}

View File

@ -103,7 +103,7 @@ impl GraphExecutor {
}
let world = unsafe { world_ptr.as_mut() };
if let Some(mut queue) = world.get_resource_mut::<CommandQueue>() {
if let Some(mut queue) = world.try_get_resource_mut::<CommandQueue>() {
// Safety: Commands only borrows world.entities when adding commands
let world = unsafe { world_ptr.as_mut() };
let mut commands = Commands::new(&mut queue, world);
@ -189,8 +189,7 @@ mod tests {
exec.execute(NonNull::from(&world), true).unwrap();
println!("Executed systems");
let order = world.get_resource::<Vec<String>>()
.expect("missing Vec<String> resource");
let order = world.get_resource::<Vec<String>>();
let mut order_iter = order.iter();
// ... but still executed in order

View File

@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
/// TickTracker is used for tracking changes of [`Component`](crate::Component)s and entities.
///
/// TickTracker stores an [`AtomicU64`], making all operations on `TickTracker`, atomic as well.
/// Note that [`TickTracker::clone`] only clones the inner value of atomic, and not the atomic itself.
/// Note that [`Tick::clone`] only clones the inner value of atomic, and not the atomic itself.
#[derive(Debug, Default)]
pub struct TickTracker {
tick: AtomicU64,

View File

@ -2,7 +2,7 @@ use std::{any::TypeId, collections::HashMap, ptr::NonNull};
use atomic_refcell::{AtomicRef, AtomicRefMut};
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsFilter, AsQuery, Query, Res, ResMut, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker, TrackedResource};
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsFilter, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker};
/// The id of the entity for the Archetype.
///
@ -65,9 +65,10 @@ impl World {
where
B: Bundle
{
let tick = self.current_tick();
let bundle_types = bundle.type_ids();
let tick = self.tick();
// try to find an archetype
let archetype = self.archetypes
.values_mut()
@ -111,8 +112,14 @@ impl World {
/// Despawn an entity from the World
pub fn despawn(&mut self, entity: Entity) {
let tick = self.current_tick();
// Tick the tracker if the entity is spawned. This is done here instead of the `if let`
// below due to the borrow checker complaining about multiple mutable borrows to self.
let tick = if self.entities.arch_index.contains_key(&entity.id) {
Some(self.tick())
} else { None };
if let Some(record) = self.entities.arch_index.get_mut(&entity.id) {
let tick = tick.unwrap();
let arch = self.archetypes.get_mut(&record.id).unwrap();
if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) {
@ -131,7 +138,8 @@ impl World {
where
B: Bundle
{
let tick = self.current_tick();
let tick = self.tick();
let record = self.entities.entity_record(entity).unwrap();
let current_arch = self.archetypes.get(&record.id).unwrap();
let current_arch_len = current_arch.len();
@ -367,24 +375,32 @@ impl World {
}
/// Add a resource to the world.
///
/// Ticks the world.
pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, self.current_tick()));
let tick = self.tick();
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, tick));
}
/// Add the default value of a resource.
///
/// Ticks the world.
///
/// > Note: This will replace existing values.
pub fn add_resource_default<T: ResourceObject + Default>(&mut self) {
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default(), self.current_tick()));
let tick = self.tick();
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default(), tick));
}
/// Add the default value of a resource if it does not already exist.
///
/// Returns a boolean indicating if the resource was added.
/// Returns a boolean indicating if the resource was added. Ticks the world if the resource
/// was added.
pub fn add_resource_default_if_absent<T: ResourceObject + Default>(&mut self) -> bool {
let id = TypeId::of::<T>();
if !self.resources.contains_key(&id) {
self.resources.insert(id, ResourceData::new(T::default(), self.current_tick()));
let tick = self.tick();
self.resources.insert(id, ResourceData::new(T::default(), tick));
true
} else {
@ -394,60 +410,39 @@ impl World {
/// 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: ResourceObject, F>(&mut self, f: F) -> ResMut<T>
///
/// Ticks the world.
pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> AtomicRefMut<T>
where
F: Fn() -> T + 'static
{
let tick = self.current_tick();
let tick = self.tick();
let res = self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(f(), tick));
ResMut {
inner: res.data.borrow_mut(),
world_tick: tick,
_marker: std::marker::PhantomData::<T>,
}
res.tick.tick_to(&tick);
res.get_mut()
}
/// Get a resource from the world, or insert its default value.
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> ResMut<T>
/// Get a resource from the world, or insert it into the world as its default.
///
/// Ticks the world.
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> AtomicRefMut<T>
{
let tick = self.current_tick();
let tick = self.tick();
let res = self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(T::default(), tick));
ResMut {
inner: res.data.borrow_mut(),
world_tick: tick,
_marker: std::marker::PhantomData::<T>,
}
res.tick.tick_to(&tick);
res.get_mut()
}
/// Gets a resource from the World.
pub fn get_resource<T: ResourceObject>(&self) -> Option<Res<T>> {
self.get_tracked_resource::<T>().map(|r| Res {
inner: r,
world_tick: self.current_tick(),
_marker: std::marker::PhantomData::<T>,
})
}
/// Gets a reference to a change tracked resource.
///
/// You will have to manually downcast the inner resource. Most people don't need this, see
/// [`World::get_resource`].
pub fn get_tracked_resource<T: ResourceObject>(&self) -> Option<AtomicRef<TrackedResource<dyn ResourceObject>>> {
/// 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: ResourceObject>(&self) -> AtomicRef<T> {
self.resources.get(&TypeId::of::<T>())
.map(|r| r.data.borrow())
}
/// Gets a mutable borrow to a change tracked resource.
///
/// You will have to manually downcast the inner resource. Most people don't need this, see
/// [`World::get_resource_mut`].
pub fn get_tracked_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<TrackedResource<dyn ResourceObject>>> {
self.resources.get(&TypeId::of::<T>())
.map(|r| r.data.borrow_mut())
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
.get()
}
/// Returns a boolean indicating if the resource changed.
@ -463,7 +458,7 @@ impl World {
/// Returns the [`Tick`] that the resource was last modified at.
pub fn resource_tick<T: ResourceObject>(&self) -> Option<Tick> {
self.resources.get(&TypeId::of::<T>())
.map(|r| r.data.borrow().tick)
.map(|r| r.tick.current())
}
/// Returns boolean indicating if the World contains a resource of type `T`.
@ -471,33 +466,56 @@ impl World {
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: ResourceObject>(&self) -> Option<AtomicRef<T>> {
self.resources.get(&TypeId::of::<T>())
.and_then(|r| r.try_get())
}
/// Gets a mutable borrow of a resource from the World.
pub fn get_resource_mut<T: ResourceObject>(&self) -> Option<ResMut<T>> {
self.get_tracked_resource_mut::<T>().map(|r| ResMut {
inner: r,
world_tick: self.current_tick(),
_marker: std::marker::PhantomData::<T>,
})
///
/// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for
/// a function that returns an option.
///
/// Ticks the world.
pub fn get_resource_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
self.try_get_resource_mut::<T>()
.unwrap_or_else(|| panic!("World is missing resource of type '{}'", std::any::type_name::<T>()))
}
/// Attempts to get a mutable borrow of a resource from the World.
///
/// Returns `None` if the resource was not found. Ticks the world if the resource was found.
pub fn try_get_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
self.resources.get(&TypeId::of::<T>())
.and_then(|r| {
// now that the resource was retrieved, tick the world and the resource
let new_tick = self.tick();
r.tick.tick_to(&new_tick);
r.try_get_mut()
})
}
/// Get the corresponding [`ResourceData`].
pub fn get_resource_data<T: ResourceObject>(&self) -> Option<ResourceData> {
///
/// > Note: If you borrow the resource mutably, the world and the resource will not be ticked.
pub fn try_get_resource_data<T: ResourceObject>(&self) -> Option<ResourceData> {
self.resources.get(&TypeId::of::<T>())
.map(|r| r.clone())
}
/// Increments the world current tick for tracking changes to components and resources.
/// Increments the TickTracker which is used for tracking changes to components.
///
/// # Note:
/// For change tracking to work correctly, this must be ran each loop before you run world
/// systems.
/// Most users wont need to call this manually, its done for you through queries and views.
pub fn tick(&self) -> Tick {
self.tracker.tick()
}
/// Gets the current tick that the world is at.
///
/// See [`World::tick`].
/// See [`TickTracker`]
pub fn current_tick(&self) -> Tick {
self.tracker.current()
}
@ -507,7 +525,7 @@ impl World {
}
/// Attempts to find a resource in the world and returns a NonNull pointer to it
pub unsafe fn get_resource_ptr<T: ResourceObject>(&self) -> Option<NonNull<T>> {
pub unsafe fn try_get_resource_ptr<T: ResourceObject>(&self) -> Option<NonNull<T>> {
self.resources.get(&TypeId::of::<T>())
.map(|d| unsafe { NonNull::new_unchecked(d.data.as_ptr() as *mut T) })
}
@ -583,17 +601,15 @@ mod tests {
world.add_resource(counter);
}
let counter = world.get_resource::<SimpleCounter>()
.expect("Counter resource is missing");
let counter = world.get_resource::<SimpleCounter>();
assert_eq!(counter.0, 0);
drop(counter);
let mut counter = world.get_resource_mut::<SimpleCounter>()
.expect("Counter resource is missing");
let mut counter = world.get_resource_mut::<SimpleCounter>();
counter.0 += 4582;
drop(counter);
assert!(world.get_resource::<u32>().is_none());
assert!(world.try_get_resource::<u32>().is_none());
}
#[test]
@ -603,11 +619,9 @@ mod tests {
world.add_resource(counter);
// test multiple borrows at the same time
let counter = world.get_resource::<SimpleCounter>()
.expect("Counter resource is missing");
let counter = world.get_resource::<SimpleCounter>();
assert_eq!(counter.0, 4582);
let counter2 = world.get_resource::<SimpleCounter>()
.expect("Counter resource is missing");
let counter2 = world.get_resource::<SimpleCounter>();
assert_eq!(counter.0, 4582);
assert_eq!(counter2.0, 4582);
}
@ -621,10 +635,9 @@ mod tests {
}
// test that its only possible to get a single mutable borrow
let counter = world.get_resource_mut::<SimpleCounter>()
.expect("Counter resource is missing");
let counter = world.get_resource_mut::<SimpleCounter>();
assert_eq!(counter.0, 4582);
assert!(world.get_resource_mut::<SimpleCounter>().is_none());
assert!(world.try_get_resource_mut::<SimpleCounter>().is_none());
assert_eq!(counter.0, 4582);
}
@ -750,8 +763,7 @@ mod tests {
assert!(!world.has_resource_changed::<SimpleCounter>());
let mut counter = world.get_resource_mut::<SimpleCounter>()
.expect("Counter resource is missing");
let mut counter = world.get_resource_mut::<SimpleCounter>();
counter.0 += 100;
assert!(world.has_resource_changed::<SimpleCounter>());

View File

@ -1,5 +1,5 @@
use instant::Instant;
use lyra_ecs::{query::ResMut, Component};
use lyra_ecs::{Component, World};
use lyra_reflect::Reflect;
use crate::{plugin::Plugin, game::GameStages};
@ -30,8 +30,9 @@ impl std::ops::DerefMut for DeltaTime {
/// A system that updates the [`DeltaTime``] resource.
///
/// The resource is updated in the [`GameStages::First`] stage.
pub fn delta_time_system(mut delta: ResMut<DeltaTime>) -> anyhow::Result<()> {
pub fn delta_time_system(world: &mut World) -> anyhow::Result<()> {
let now = Instant::now();
let mut delta = world.get_resource_mut::<DeltaTime>();
delta.0 = delta.1.unwrap_or(now).elapsed().as_secs_f32();
delta.1 = Some(now);

View File

@ -1,4 +1,4 @@
use std::{cell::RefCell, rc::Rc, sync::Arc};
use std::sync::Arc;
use atomic_refcell::AtomicRefCell;
use lyra_ecs::{query::{ResMut, WorldTick}, system::FnArgFetcher, Tick};
@ -6,83 +6,14 @@ use lyra_ecs::{query::{ResMut, WorldTick}, system::FnArgFetcher, Tick};
pub trait Event: Clone + Send + Sync + 'static {}
impl<T: Clone + Send + Sync + 'static> Event for T {}
/// A Vec with other Vecs in it to track relative age of items.
///
/// The vec has 3 levels, a `newest`, `medium` and `old`. Items are pushed to the `newest`
/// internal vec. When [`WaterfallVec::waterfall`] is called the items in `newest` are
/// put into `medium`, and items in `medium` goes to `old`.
///
/// By checking the items in each internal vec, you can see a relative age between the items.
/// The event system uses this to clear the `old` vec to ensure keep events for only two
/// frames at a time.
struct WaterfallVec<T> {
newest: Vec<T>,
medium: Vec<T>,
old: Vec<T>,
}
impl<T> Default for WaterfallVec<T> {
fn default() -> Self {
Self {
newest: Default::default(),
medium: Default::default(),
old: Default::default(),
}
}
}
impl<T> WaterfallVec<T> {
fn total_len(&self) -> usize {
self.newest.len() + self.medium.len() + self.old.len()
}
fn get(&self, mut i: usize) -> Option<&T> {
if i >= self.old.len() {
i -= self.old.len();
if i >= self.medium.len() {
i -= self.medium.len();
self.newest.get(i)
} else {
self.medium.get(i)
}
} else {
self.old.get(i)
}
}
/// Age elements.
///
/// This moves elements in `newest` to `medium` and elements in `medium` to `old`.
/// This is what drives the relative age of the [`WaterfallVec`].
fn waterfall(&mut self) {
self.old.append(&mut self.medium);
self.medium.append(&mut self.newest);
}
/// Push a new element to the newest queue.
fn push(&mut self, event: T) {
self.newest.push(event);
}
/// Clear oldest items.
fn clear_oldest(&mut self) {
self.old.clear();
}
}
pub struct Events<T: Event> {
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
/// Used to track when the old events were last cleared.
last_cleared_at: Tick,
/// Used to indicate when the cursor in readers should be reset to zero.
/// This becomes true after the old events are cleared.
reset_cursor: bool,
events: Arc<AtomicRefCell<Vec<T>>>,
last_updated_tick: Option<Tick>,
}
impl<T: Event> Default for Events<T> {
fn default() -> Self {
Self { events: Default::default(), last_cleared_at: Default::default(), reset_cursor: false }
Self { events: Default::default(), last_updated_tick: None }
}
}
@ -99,7 +30,7 @@ impl<T: Event> Events<T> {
pub fn reader(&self) -> EventReader<T> {
EventReader {
events: self.events.clone(),
cursor: Rc::new(RefCell::new(0)),
cursor: 0,
}
}
@ -111,28 +42,27 @@ impl<T: Event> Events<T> {
}
pub struct EventReader<T: Event> {
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
cursor: Rc<RefCell<usize>>,
events: Arc<AtomicRefCell<Vec<T>>>,
cursor: usize,
}
impl<T: Event> EventReader<T> {
pub fn read(&mut self) -> Option<atomic_refcell::AtomicRef<T>> {
let events = self.events.borrow();
let mut cursor = self.cursor.borrow_mut();
if *cursor >= events.total_len() {
if self.cursor >= events.len() {
None
} else {
let e = atomic_refcell::AtomicRef::map(events,
|e| e.get(*cursor).unwrap());
*cursor += 1;
|e| e.get(self.cursor).unwrap());
self.cursor += 1;
Some(e)
}
}
}
pub struct EventWriter<T: Event> {
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
events: Arc<AtomicRefCell<Vec<T>>>,
}
impl<T: Event> EventWriter<T> {
@ -147,48 +77,40 @@ pub fn event_cleaner_system<T>(tick: WorldTick, mut events: ResMut<Events<T>>) -
where
T: Event
{
let last_tick = *events.last_cleared_at;
let last_tick = *events.last_updated_tick.unwrap_or(*tick);
let world_tick = **tick;
if last_tick + 2 < world_tick {
events.last_cleared_at = *tick;
events.reset_cursor = true;
events.last_updated_tick = Some(*tick);
let mut events = events.events.borrow_mut();
events.clear_oldest();
} else {
events.reset_cursor = false;
events.clear();
}
let mut events = events.events.borrow_mut();
events.waterfall();
Ok(())
}
impl<T: Event> FnArgFetcher for EventReader<T> {
type State = Rc<RefCell<usize>>;
type State = usize;
type Arg<'a, 'state> = EventReader<T>;
fn create_state(_: std::ptr::NonNull<lyra_ecs::World>) -> Self::State {
Rc::new(RefCell::new(0))
0
}
unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: std::ptr::NonNull<lyra_ecs::World>) -> Self::Arg<'a, 'state> {
let world = world.as_ref();
let events = world.get_resource::<Events<T>>()
.unwrap_or_else(|| panic!("world missing Events<{}> resource", std::any::type_name::<T>()));
let events = world.get_resource::<Events<T>>();
if events.reset_cursor {
let mut state_num = state.borrow_mut();
*state_num = 0;
// reset the reader cursor when the events are cleared
let world_tick = world.current_tick();
if world_tick == events.last_updated_tick.unwrap_or(world_tick) {
*state = 0;
}
let reader = EventReader {
events: events.events.clone(),
cursor: state.clone(),
};
let mut reader = events.reader();
reader.cursor = *state;
reader
}
@ -207,8 +129,7 @@ impl<T: Event> FnArgFetcher for EventWriter<T> {
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: std::ptr::NonNull<lyra_ecs::World>) -> Self::Arg<'a, 'state> {
let world = world.as_ref();
let events = world.get_resource::<Events<T>>()
.unwrap_or_else(|| panic!("world missing Events<{}> resource", std::any::type_name::<T>()));
let events = world.get_resource::<Events<T>>();
events.writer()
}

View File

@ -2,7 +2,7 @@ use std::{cell::OnceCell, collections::VecDeque, ptr::NonNull};
use lyra_ecs::{system::{IntoSystem, System}, ResourceObject, World};
use lyra_math::IVec2;
use tracing::{error, info, Level};
use tracing::{info, error, Level};
use tracing_appender::non_blocking;
use tracing_subscriber::{
layer::SubscriberExt,
@ -102,7 +102,6 @@ impl App {
}
pub fn update(&mut self) {
self.world.tick();
let wptr = NonNull::from(&self.world);
if let Err(e) = self.staged_exec.execute(wptr, true) {
@ -228,7 +227,7 @@ impl App {
pub fn push_event<T: Event>(&mut self, event: T) {
let world = &mut self.world;
let mut events = world.get_resource_mut::<Events<T>>()
let mut events = world.try_get_resource_mut::<Events<T>>()
.expect("missing events for event type! Must use `App::register_event` first");
events.push_event(event);
}

View File

@ -0,0 +1,140 @@
use winit::{dpi::PhysicalPosition, event::{AxisId, DeviceId, ElementState, KeyEvent, MouseButton, MouseScrollDelta, Touch, TouchPhase, WindowEvent}};
/// Wrapper around events from `winit::WindowEvent` that are specific to input related events.
///
/// The `winit::WindowEvent` enum has many values that are related to inputs.
/// Ex:
/// * winit::WindowEvent::KeyboardInput
/// * winit::WindowEvent::CursorMoved
/// * winit::WindowEvent::CursorEntered
/// * winit::WindowEvent::MouseWheel
/// * winit::WindowEvent::CursorLeft
/// * winit::WindowEvent::MouseInput
/// etc.
///
/// Its easier for these to all be in a single `InputEvent` type enum to check if any input was received.
/// The comments for all the methods were taken from `winit::WindowEvent`
#[derive(Debug, PartialEq, Clone)]
pub enum InputEvent {
/// An event from the keyboard has been received.
KeyboardInput {
device_id: DeviceId,
event: KeyEvent,
/// If true, the event was generated synthetically by winit in one of the following circumstances:
/// Synthetic key press events are generated for all keys pressed when a window gains focus.
/// Likewise, synthetic key release events are generated for all keys pressed when a window goes out of focus.
/// Currently, this is only functional on X11 and Windows
is_synthetic: bool,
},
/// The cursor has moved on the window.
CursorMoved {
device_id: DeviceId,
position: PhysicalPosition<f64>,
},
/// The cursor has entered the window.
CursorEntered {
device_id: DeviceId,
},
/// The cursor has left the window.
CursorLeft {
device_id: DeviceId,
},
/// A mouse wheel movement or touchpad scroll occurred.
MouseWheel {
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
},
/// An mouse button press has been received.
MouseInput {
device_id: DeviceId,
state: ElementState,
button: MouseButton,
},
/// Touchpad pressure event.
///
/// At the moment, only supported on Apple forcetouch-capable macbooks.
/// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad
/// is being pressed) and stage (integer representing the click level).
TouchpadPressure {
device_id: DeviceId,
pressure: f32,
stage: i64,
},
/// Motion on some analog axis. May report data redundant to other, more specific events.
AxisMotion {
device_id: DeviceId,
axis: AxisId,
value: f64,
},
/// Touch event has been received
///
/// ## Platform-specific
/// - **Web**: Doesnt take into account CSS border, padding, or transform.
/// - **macOS:** Unsupported.
Touch(Touch),
}
impl InputEvent {
pub fn from_window_event(value: &WindowEvent) -> Option<Self> {
match value {
WindowEvent::KeyboardInput { device_id, event, is_synthetic } =>
Some(InputEvent::KeyboardInput {
device_id: *device_id,
event: event.clone(),
is_synthetic: *is_synthetic
}),
#[allow(deprecated, reason="Compatibility")]
WindowEvent::CursorMoved { device_id, position, } =>
Some(InputEvent::CursorMoved {
device_id: *device_id,
position: *position,
}),
WindowEvent::CursorEntered { device_id } =>
Some(InputEvent::CursorEntered {
device_id: *device_id
}),
WindowEvent::CursorLeft { device_id } =>
Some(InputEvent::CursorLeft {
device_id: *device_id
}),
#[allow(deprecated, reason="Compatibility")]
WindowEvent::MouseWheel { device_id, delta, phase } =>
Some(InputEvent::MouseWheel {
device_id: *device_id,
delta: *delta,
phase: *phase,
}),
#[allow(deprecated, reason="Compatibility")]
WindowEvent::MouseInput { device_id, state, button } =>
Some(InputEvent::MouseInput {
device_id: *device_id,
state: *state,
button: *button,
}),
WindowEvent::TouchpadPressure { device_id, pressure, stage } =>
Some(InputEvent::TouchpadPressure {
device_id: *device_id,
pressure: *pressure,
stage: *stage
}),
WindowEvent::AxisMotion { device_id, axis, value } =>
Some(InputEvent::AxisMotion {
device_id: *device_id,
axis: *axis,
value: *value
}),
WindowEvent::Touch(t) => Some(InputEvent::Touch(*t)),
_ => None,
}
}
}

View File

@ -1,13 +1,16 @@
mod system;
pub mod system;
pub use system::*;
mod events;
pub mod input_event;
pub use input_event::*;
pub mod events;
pub use events::*;
mod buttons;
pub mod buttons;
pub use buttons::*;
mod action;
pub mod action;
pub use action::*;
pub type KeyCode = winit::keyboard::KeyCode;
@ -119,7 +122,7 @@ pub fn keycode_from_str(s: &str) -> Option<KeyCode> {
"numpad_star" => Some(KeyCode::NumpadStar),
"quote" => Some(KeyCode::Quote),
"launch_app1" => Some(KeyCode::LaunchApp1),
"launch_app2" => Some(KeyCode::LaunchApp2),
"launch_app1" => Some(KeyCode::LaunchApp2),
"backslash" => Some(KeyCode::Backslash),
"caps_lock" => Some(KeyCode::CapsLock),
"comma" => Some(KeyCode::Comma),

View File

@ -6,7 +6,7 @@ use winit::{event::{MouseScrollDelta, WindowEvent}, keyboard::PhysicalKey};
use crate::{game::GameStages, plugin::Plugin, winit::DeviceEventPair, EventReader, EventWriter};
use super::{events::*, InputButtons};
use super::{events::*, InputButtons, InputEvent};
fn write_scroll_delta(mouse_scroll_ev: &mut EventWriter<MouseScroll>, delta: &MouseScrollDelta) {
let event = match delta {
@ -119,6 +119,7 @@ impl Plugin for InputPlugin {
app.add_resource(InputButtons::<MouseButton>::default());
app.add_resource(Touches::default());
app.register_event::<InputEvent>();
app.register_event::<MouseScroll>();
app.register_event::<MouseButton>();
app.register_event::<MouseMotion>();

View File

@ -42,7 +42,7 @@ impl<'a> RenderGraphContext<'a> {
pub fn begin_render_pass(
&'a mut self,
desc: wgpu::RenderPassDescriptor<'a>,
) -> wgpu::RenderPass<'a> {
) -> wgpu::RenderPass {
self.encoder
.as_mut()
.expect(

View File

@ -203,8 +203,7 @@ impl Node for LightCullComputePass {
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, context: &mut RenderGraphContext) {
context.queue_buffer_write_with(LightCullComputePassSlots::IndexCounterBuffer, 0, 0);
let screen_size = world.get_resource::<ScreenSize>()
.expect("world missing ScreenSize resource");
let screen_size = world.get_resource::<ScreenSize>();
if screen_size.xy() != self.workgroup_size {
self.workgroup_size = screen_size.xy();
todo!("Resize buffers and other resources");

View File

@ -286,8 +286,7 @@ impl Node for MeshPrepNode {
Self::try_init_resource::<RenderAssets<Arc<GpuMaterial>>>(world);
Self::try_init_resource::<FxHashMap<Entity, uuid::Uuid>>(world);
let mut render_meshes = world.get_resource_mut::<RenderMeshes>()
.expect("world missing RenderMeshes resource");
let mut render_meshes = world.get_resource_mut::<RenderMeshes>();
render_meshes.clear();
}
@ -460,8 +459,7 @@ impl Node for MeshPrepNode {
world.insert(en, interp);
}
let mut transforms = world.get_resource_mut::<TransformBuffers>()
.expect("world missing TransformBuffers resource");
let mut transforms = world.get_resource_mut::<TransformBuffers>();
transforms.send_to_gpu(queue);
}

View File

@ -305,17 +305,17 @@ impl Node for MeshPass {
});
let transforms = world
.get_resource_data::<TransformBuffers>()
.try_get_resource_data::<TransformBuffers>()
.expect("Missing transform buffers");
self.transform_buffers = Some(transforms.clone());
let render_meshes = world
.get_resource_data::<RenderMeshes>()
.try_get_resource_data::<RenderMeshes>()
.expect("Missing transform buffers");
self.render_meshes = Some(render_meshes.clone());
let mesh_buffers = world
.get_resource_data::<RenderAssets<MeshBufferStorage>>()
.try_get_resource_data::<RenderAssets<MeshBufferStorage>>()
.expect("Missing render meshes");
self.mesh_buffers = Some(mesh_buffers.clone());

View File

@ -639,8 +639,7 @@ impl Node for ShadowMapsPass {
if world.has_resource_changed::<ShadowCasterSettings>() {
debug!("Detected change in ShadowSettings, recreating poisson disks");
let settings = world.get_resource::<ShadowCasterSettings>()
.expect("world missing ShadowCasterSettings resource");
let settings = world.get_resource::<ShadowCasterSettings>();
// convert to uniform now since the from impl limits to max values
let uniform = ShadowSettingsUniform::from(*settings);
@ -672,17 +671,16 @@ impl Node for ShadowMapsPass {
context.queue_buffer_write_with(ShadowMapsPassSlots::ShadowSettingsUniform, 0, uniform);
}
let settings = *world.get_resource::<ShadowCasterSettings>()
.expect("world missing ShadowCasterSettings resource");
let settings = *world.get_resource::<ShadowCasterSettings>();
if settings.use_back_faces {
// TODO: shadow maps rendering with back faces
todo!("render with back faces");
}
self.render_meshes = world.get_resource_data::<RenderMeshes>();
self.transform_buffers = world.get_resource_data::<TransformBuffers>();
self.mesh_buffers = world.get_resource_data::<RenderAssets<MeshBufferStorage>>();
self.render_meshes = world.try_get_resource_data::<RenderMeshes>();
self.transform_buffers = world.try_get_resource_data::<TransformBuffers>();
self.mesh_buffers = world.try_get_resource_data::<RenderAssets<MeshBufferStorage>>();
world.add_resource(self.atlas.clone());

View File

@ -269,8 +269,7 @@ impl Renderer for BasicRenderer {
rt.surface.configure(&self.device, &rt.surface_config); */
// update screen size resource in ecs
let mut world_ss = world.get_resource_mut::<ScreenSize>()
.expect("world missing ScreenSize resource");
let mut world_ss = world.get_resource_mut::<ScreenSize>();
world_ss.0 = glam::UVec2::new(new_size.width, new_size.height);

View File

@ -1,16 +1,101 @@
use std::ops::{Deref, DerefMut};
use glam::{DVec2, IVec2, UVec2, Vec2};
use lyra_ecs::{query::{filter::Changed, Entities, Res, View}, Component};
use lyra_math::Area;
use lyra_resource::Image;
use tracing::error;
use winit::{dpi::{PhysicalPosition, PhysicalSize, Position, Size}, window::{CustomCursor, Fullscreen, Window}};
use winit::window::{CustomCursor, Fullscreen, Window};
pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel};
use crate::{plugin::Plugin, winit::WinitWindows};
#[derive(Default, Clone, Copy, PartialEq)]
pub struct Area {
position: Position,
size: Size,
}
#[derive(Clone, Copy, PartialEq)]
pub enum Size {
Physical { x: u32, y: u32 },
Logical { x: f64, y: f64 },
}
impl Default for Size {
fn default() -> Self {
Self::Physical { x: 0, y: 0 }
}
}
impl Into<winit::dpi::Size> for Size {
fn into(self) -> winit::dpi::Size {
match self {
Size::Physical { x, y } => winit::dpi::PhysicalSize::new(x, y).into(),
Size::Logical { x, y } => winit::dpi::LogicalSize::new(x, y).into(),
}
}
}
impl From<winit::dpi::Size> for Size {
fn from(value: winit::dpi::Size) -> Self {
match value {
winit::dpi::Size::Physical(physical_position) => Self::new_physical(physical_position.width, physical_position.height),
winit::dpi::Size::Logical(logical_position) => Self::new_logical(logical_position.width, logical_position.height),
}
}
}
impl Size {
pub fn new_physical(x: u32, y: u32) -> Self {
Self::Physical { x, y }
}
pub fn new_logical(x: f64, y: f64) -> Self {
Self::Logical { x, y }
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum Position {
Physical { x: i32, y: i32 },
Logical { x: f64, y: f64 },
}
impl Default for Position {
fn default() -> Self {
Self::Physical { x: 0, y: 0 }
}
}
impl Into<winit::dpi::Position> for Position {
fn into(self) -> winit::dpi::Position {
match self {
Position::Physical { x, y } => winit::dpi::PhysicalPosition::new(x, y).into(),
Position::Logical { x, y } => winit::dpi::LogicalPosition::new(x, y).into(),
}
}
}
impl From<winit::dpi::Position> for Position {
fn from(value: winit::dpi::Position) -> Self {
match value {
winit::dpi::Position::Physical(physical_position) => Self::new_physical(physical_position.x, physical_position.y),
winit::dpi::Position::Logical(logical_position) => Self::new_logical(logical_position.x, logical_position.y),
}
}
}
impl Position {
pub fn new_physical(x: i32, y: i32) -> Self {
Self::Physical { x, y }
}
pub fn new_logical(x: f64, y: f64) -> Self {
Self::Logical { x, y }
}
}
/// Flag component that
#[derive(Clone, Component)]
pub struct PrimaryWindow;
@ -83,7 +168,7 @@ pub struct WindowOptions {
/// * **Web:** Can only return None or Borderless(None).
pub fullscreen: Option<Fullscreen>,
/// Gets/sets the position of the top-left hand corner of the window relative to
/// Gets the position of the top-left hand corner of the windows client area relative to
/// the top-left hand corner of the desktop.
///
/// Note that the top-left hand corner of the desktop is not necessarily the same
@ -100,7 +185,7 @@ pub struct WindowOptions {
/// * **Web:** Value is the top-left coordinates relative to the viewport. Note: this will be
/// the same value as [`WindowOptions::outer_position`].
/// * **Android / Wayland:** Unsupported.
pub position: Option<IVec2>,
pub inner_position: Option<Position>,
/// Gets/sets the size of the view in the window.
///
@ -108,7 +193,7 @@ pub struct WindowOptions {
///
/// Platform-specific
/// * **Web:** The size of the canvas element. Doesnt account for CSS `transform`.
physical_size: UVec2,
pub size: Size,
/// Gets/sets if the window has decorations.
///
@ -152,6 +237,24 @@ pub struct WindowOptions {
/// * **iOS:** Setting is not implemented, getting is unsupported.
pub visible: Option<bool>,
/// Gets/sets the position of the top-left hand corner of the window relative to
/// the top-left hand corner of the desktop.
///
/// Note that the top-left hand corner of the desktop is not necessarily the same
/// as the screen. If the user uses a desktop with multiple monitors, the top-left
/// hand corner of the desktop is the top-left hand corner of the monitor at the
/// top-left of the desktop.
///
/// If this is none, the position will be chosen by the windowing manager at creation, then set
/// when the window is created.
///
/// Platform-specific
/// * **iOS:** Value is the top left coordinates of the windows safe area in the screen
/// space coordinate system.
/// * **Web:** Value is the top-left coordinates relative to the viewport.
/// * **Android / Wayland:** Unsupported.
pub outer_position: Option<Position>,
/// Gets/sets the window resize increments.
///
/// This is a niche constraint hint usually employed by terminal emulators and other apps
@ -205,12 +308,12 @@ pub struct WindowOptions {
/// * **X11:** Enabling IME will disable dead keys reporting during compose.
pub ime_allowed: bool,
/// Sets area of IME box in physical coordinates relative to the top left.
/// Sets area of IME candidate box in window client area coordinates relative to the top left.
///
/// Platform-specific
/// * **X11:** - area is not supported, only position.
/// * **iOS / Android / Web / Orbital:** Unsupported.
pub physical_ime_cursor_area: Option<Area<Vec2, Vec2>>,
pub ime_cursor_area: Option<Area>,
/// Gets/sets the minimum size of the window.
///
@ -277,12 +380,12 @@ pub struct WindowOptions {
pub window_level: WindowLevel,
/// Show [window menu](https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu)
/// at a specified position in physical coordinates.
/// at a specified position.
///
/// This is the context menu that is normally shown when interacting with the title bar. This is useful when implementing custom decorations.
/// Platform-specific
/// * **Android / iOS / macOS / Orbital / Wayland / Web / X11:** Unsupported.
pub physical_window_menu_pos: Option<Vec2>,
pub show_window_menu: Option<Position>,
/// Gets the window's occluded state (completely hidden from view).
///
@ -297,13 +400,6 @@ pub struct WindowOptions {
/// * **Web:** Doesn't take into account CSS border, padding, or transform.
/// * **Android / Wayland / Windows / Orbital:** Unsupported.
pub occluded: bool,
/// Gets/sets the position of the cursor in physical coordinates.
///
/// Platform-specific
/// * **Wayland:** Cursor must be in [`CursorGrabMode::Locked`].
/// * **iOS / Android / Web / Orbital:** Unsupported.
physical_cursor_position: Option<DVec2>,
}
impl From<winit::window::WindowAttributes> for WindowOptions {
@ -312,19 +408,15 @@ impl From<winit::window::WindowAttributes> for WindowOptions {
enabled_buttons: value.enabled_buttons,
focused: false,
fullscreen: value.fullscreen,
position: value.position.map(|p| {
let s = p.to_physical::<i32>(1.0);
IVec2::new(s.x, s.y)
}),
physical_size: value.inner_size.map(|s| {
let s = s.to_physical::<u32>(1.0);
UVec2::new(s.width, s.height)
}).unwrap_or(UVec2::new(1280, 720)),
inner_position: None,
size: value.inner_size.map(|s| s.into())
.unwrap_or(Size::new_physical(1280, 720)),
decorated: value.decorations,
maximized: value.maximized,
minimized: None,
resizable: value.resizable,
visible: Some(value.visible),
outer_position: value.position.map(|p| p.into()),
resize_increments: value.resize_increments.map(|r| r.into()),
scale_factor: 1.0,
blur: value.blur,
@ -339,7 +431,7 @@ impl From<winit::window::WindowAttributes> for WindowOptions {
visible: true,
},
ime_allowed: false,
physical_ime_cursor_area: None,
ime_cursor_area: None,
min_size: value.min_inner_size.map(|m| m.into()),
max_size: value.max_inner_size.map(|m| m.into()),
theme: value.preferred_theme,
@ -347,9 +439,8 @@ impl From<winit::window::WindowAttributes> for WindowOptions {
transparent: value.transparent,
window_icon: None,
window_level: value.window_level,
physical_window_menu_pos: None,
show_window_menu: None,
occluded: false,
physical_cursor_position: None,
}
}
}
@ -367,12 +458,12 @@ impl WindowOptions {
att.enabled_buttons = self.enabled_buttons.clone();
att.fullscreen = self.fullscreen.clone();
att.inner_size = Some(Size::Physical(PhysicalSize::new(self.physical_size.x, self.physical_size.y)));
att.inner_size = Some(self.size.into());
att.decorations = self.decorated;
att.maximized = self.maximized;
att.resizable = self.resizable;
att.visible = self.visible.unwrap_or(true);
att.position = self.position.map(|p| Position::Physical(PhysicalPosition::new(p.x, p.y)));
att.position = self.outer_position.map(|p| p.into());
att.resize_increments = self.resize_increments.map(|i| i.into());
att.blur = self.blur;
att.content_protected = self.content_protected;
@ -392,73 +483,6 @@ impl WindowOptions {
att
}
/// The size of the window in physical coordinates.
pub fn physical_size(&self) -> UVec2 {
self.physical_size
}
/// Set the size of the window in physical coordinates.
pub fn set_physical_size(&mut self, size: UVec2) {
self.physical_size = size;
}
/// The size of the window in logical coordinates.
pub fn size(&self) -> Vec2 {
self.physical_size.as_vec2() / self.scale_factor as f32
}
/// Set the size of the window in logical coordinates.
pub fn set_size(&mut self, size: Vec2) {
self.physical_size = (size * self.scale_factor as f32).as_uvec2();
}
/// Returns a boolean indicating if the mouse is inside the window.
pub fn is_mouse_inside(&self) -> bool {
if let Some(pos) = self.physical_cursor_position {
let s = self.physical_size;
return pos.x >= 0.0 && pos.x <= s.x as f64
&& pos.y >= 0.0 && pos.y <= s.y as f64;
}
false
}
/// The cursor position in the window in logical coordinates.
///
/// Returns `None` if the cursor is not in the window.
pub fn cursor_position(&self) -> Option<Vec2> {
if !self.is_mouse_inside() {
return None;
}
self.physical_cursor_position.map(|p| (p / self.scale_factor).as_vec2())
}
/// The cursor position in the window in physical coordinates.
///
/// Returns `None` if the cursor is not in the window.
pub fn physical_cursor_position(&self) -> Option<Vec2> {
if !self.is_mouse_inside() {
return None;
}
self.physical_cursor_position.map(|p| p.as_vec2())
}
/// Set the cursor position in logical coordinates.
///
/// Can be used to mark the cursor outside of the window as well.
pub fn set_cursor_position(&mut self, pos: Option<Vec2>) {
self.physical_cursor_position = pos.map(|p| p.as_dvec2() * self.scale_factor);
}
/// Set the cursor position in physical coordinates.
///
/// Can be used to mark the cursor outside of the window as well.
pub fn set_physical_cursor_position(&mut self, pos: Option<DVec2>) {
self.physical_cursor_position = pos;
}
}
/// The state of the window last time it was changed.
@ -467,7 +491,7 @@ impl WindowOptions {
/// when syncing the winit window with the component.
#[derive(Clone, Component)]
pub struct LastWindow {
pub last: WindowOptions,
last: WindowOptions,
}
impl Deref for LastWindow {
@ -508,9 +532,8 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
window.set_fullscreen(opts.fullscreen.clone());
}
if opts.physical_size != last.physical_size {
let size = PhysicalSize::new(opts.physical_size.x, opts.physical_size.y);
if window.request_inner_size(size).is_some() {
if opts.size != last.size {
if window.request_inner_size(opts.size).is_some() {
error!("request to increase window size failed");
}
}
@ -531,10 +554,8 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
window.set_visible(opts.visible.unwrap());
}
if opts.position != last.position && opts.position.is_some() {
let pos = opts.position.unwrap();
let pos = PhysicalPosition::new(pos.x, pos.y);
window.set_outer_position(pos);
if opts.outer_position != last.outer_position && opts.outer_position.is_some() {
window.set_outer_position(opts.outer_position.unwrap());
}
if opts.resize_increments != last.resize_increments {
@ -542,7 +563,7 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
}
if opts.blur != last.blur {
window.set_blur(opts.blur);
window.set_blur(opts.blur)
}
if opts.content_protected != last.content_protected {
@ -576,11 +597,9 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
window.set_ime_allowed(opts.ime_allowed);
}
if opts.physical_ime_cursor_area != last.physical_ime_cursor_area && opts.physical_ime_cursor_area.is_some() {
let area = opts.physical_ime_cursor_area.unwrap();
let pos = PhysicalPosition::new(area.position.x, area.position.y);
let size = PhysicalSize::new(area.size.x, area.size.y);
window.set_ime_cursor_area(pos, size);
if opts.ime_cursor_area != last.ime_cursor_area && opts.ime_cursor_area.is_some() {
let area = opts.ime_cursor_area.as_ref().unwrap();
window.set_ime_cursor_area(area.position, area.size);
}
if opts.min_size != last.min_size {
@ -617,18 +636,8 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
window.set_window_level(opts.window_level);
}
if opts.physical_window_menu_pos != last.physical_window_menu_pos && opts.physical_window_menu_pos.is_some() {
let pos = opts.physical_window_menu_pos.unwrap();
let pos = PhysicalPosition::new(pos.x, pos.y);
window.show_window_menu(pos);
}
if opts.physical_cursor_position != last.physical_cursor_position && opts.physical_cursor_position.is_some() {
let pos = opts.physical_cursor_position.unwrap();
let pos = PhysicalPosition::new(pos.x, pos.y);
if let Err(e) = window.set_cursor_position(pos) {
error!("failed to set cursor position: {}", e);
}
if opts.show_window_menu != last.show_window_menu && opts.show_window_menu.is_some() {
window.show_window_menu(opts.show_window_menu.unwrap());
}
last.last = opts.clone();

View File

@ -1,7 +1,7 @@
use std::{collections::VecDeque, sync::Arc};
use async_std::task::block_on;
use glam::{DVec2, IVec2, UVec2};
use glam::IVec2;
use lyra_ecs::Entity;
use rustc_hash::FxHashMap;
use tracing::{debug, error, warn};
@ -17,7 +17,7 @@ use crate::{
plugin::Plugin,
render::{
renderer::BasicRenderer,
window::{LastWindow, PrimaryWindow, WindowOptions},
window::{PrimaryWindow, Size, WindowOptions},
},
};
@ -143,8 +143,7 @@ impl ApplicationHandler for WinitRunner {
Err(e) => eprintln!("{:?}", e),
}
let windows = self.app.world.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource");
let windows = self.app.world.get_resource::<WinitWindows>();
for window in windows.windows.values() {
window.request_redraw();
}
@ -153,15 +152,13 @@ impl ApplicationHandler for WinitRunner {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let world = &mut self.app.world;
let mut windows = world.get_resource_mut::<WinitWindows>()
.expect("world missing WinitWindows resource");
let mut windows = world.get_resource_mut::<WinitWindows>();
let to_create_window = windows.window_queue.pop_front().unwrap_or_default();
let window_attr = to_create_window.as_attributes();
drop(windows);
let en = world.spawn((to_create_window.clone(), LastWindow { last: to_create_window }, PrimaryWindow));
let en = world.spawn((to_create_window, PrimaryWindow));
let mut windows = world.get_resource_mut::<WinitWindows>()
.expect("world missing WinitWindows resource");
let mut windows = world.get_resource_mut::<WinitWindows>();
let wid = windows.create_window(event_loop, en, window_attr).unwrap();
let window = windows.windows.get(&wid).unwrap().clone();
drop(windows);
@ -198,59 +195,28 @@ impl ApplicationHandler for WinitRunner {
self.app.push_event(event.clone());
match event {
WindowEvent::CursorMoved { position, .. } => {
let windows = self.app.world.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource");
let en = windows.window_to_entity.get(&window_id)
.expect("missing window entity");
// update the window and its cache so the sync system doesn't try to update the window
let (mut en_window, mut en_last_win) = self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*en).get().unwrap();
let pos = Some(DVec2::new(position.x, position.y));
en_window.set_physical_cursor_position(pos);
en_last_win.set_physical_cursor_position(pos);
},
WindowEvent::ActivationTokenDone { .. } => todo!(),
WindowEvent::Resized(physical_size) => {
self.app.on_resize(physical_size);
let (mut window, mut last_window) = self
let mut window_opts = self
.app
.world
.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity
.get(&window_id)
.and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e).get())
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
// update the window and its cache so the sync system doesn't try to update the window
let size = UVec2::new(physical_size.width, physical_size.height);
window.set_physical_size(size);
last_window.set_physical_size(size);
},
// Mark the cursor as outside the window when it leaves
WindowEvent::CursorLeft { .. } => {
let (mut window, mut last_window) = self
.app
.world
.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity
.get(&window_id)
.and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e).get())
.unwrap();
window.set_physical_cursor_position(None);
last_window.set_physical_cursor_position(None);
},
window_opts.size = Size::new_physical(physical_size.width, physical_size.height);
}
WindowEvent::Moved(physical_position) => {
let mut state = self.app.world.get_resource_or_else(WindowState::new);
state.position = IVec2::new(physical_position.x, physical_position.y);
},
}
WindowEvent::CloseRequested => {
self.app.on_exit();
event_loop.exit();
},
}
WindowEvent::Destroyed => todo!(),
WindowEvent::DroppedFile(_path_buf) => todo!(),
WindowEvent::HoveredFile(_path_buf) => todo!(),
@ -260,55 +226,51 @@ impl ApplicationHandler for WinitRunner {
.app
.world
.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity
.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.focused = focused;
},
}
WindowEvent::ModifiersChanged(modifiers) => {
debug!("modifiers changed: {:?}", modifiers)
},
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
let mut window_opts = self
.app
.world
.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity
.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.scale_factor = scale_factor;
},
}
WindowEvent::ThemeChanged(theme) => {
let mut window_opts = self
.app
.world
.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity
.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.theme = Some(theme);
},
}
WindowEvent::Occluded(occ) => {
let mut window_opts = self
.app
.world
.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity
.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.occluded = occ;
},
}
WindowEvent::RedrawRequested => {
//debug!("should redraw");
},
}
_ => {}
}
}

View File

@ -1,22 +0,0 @@
#[derive(Clone, Copy, PartialEq)]
pub struct Area<P, S>
where
P: Clone + Copy + PartialEq,
S: Clone + Copy + PartialEq,
{
pub position: P,
pub size: S
}
impl<P, S> Area<P, S>
where
P: Clone + Copy + PartialEq,
S: Clone + Copy + PartialEq,
{
pub fn new(pos: P, size: S) -> Self {
Self {
position: pos,
size,
}
}
}

View File

@ -4,9 +4,6 @@ pub use glam::*;
pub mod angle;
pub use angle::*;
mod area;
pub use area::*;
pub mod transform;
pub use transform::*;

View File

@ -2,7 +2,7 @@
use std::{any::TypeId, any::Any};
use lyra_ecs::{query::{Res, ResMut}, AtomicRef, AtomicRefMut, World};
use lyra_ecs::{AtomicRef, AtomicRefMut, World};
extern crate self as lyra_reflect;
@ -250,9 +250,9 @@ pub trait FromType<T> {
pub trait ReflectWorldExt {
/// Retrieves the type registry from the world.
fn get_type_registry(&self) -> Option<Res<TypeRegistry>>;
fn get_type_registry(&self) -> AtomicRef<TypeRegistry>;
/// Retrieves the type registry mutably from the world.
fn get_type_registry_mut(&self) -> Option<ResMut<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<AtomicRef<RegisteredType>>;
/// Get a mutable registered type from the type registry in the world. returns `None` if the type is not registered.
@ -262,19 +262,17 @@ pub trait ReflectWorldExt {
}
impl ReflectWorldExt for World {
fn get_type_registry(&self) -> Option<Res<TypeRegistry>> {
fn get_type_registry(&self) -> AtomicRef<TypeRegistry> {
self.get_resource::<TypeRegistry>()
}
fn get_type_registry_mut(&self) -> Option<ResMut<TypeRegistry>> {
fn get_type_registry_mut(&self) -> AtomicRefMut<TypeRegistry> {
self.get_resource_mut::<TypeRegistry>()
}
fn get_type<T>(&self, type_id: TypeId) -> Option<AtomicRef<RegisteredType>> {
let r = self.get_resource::<TypeRegistry>()
.expect("world is missing TypeRegistry resource");
let r = self.get_resource::<TypeRegistry>();
if r.has_type(type_id) {
let r = r.get_inner();
Some(AtomicRef::map(r, |tr| tr.get_type(type_id).unwrap()))
} else {
None
@ -282,10 +280,8 @@ impl ReflectWorldExt for World {
}
fn get_type_mut<T>(&self, type_id: TypeId) -> Option<AtomicRefMut<RegisteredType>> {
let r = self.get_resource_mut::<TypeRegistry>()
.expect("world is missing TypeRegistry resource");
let r = self.get_resource_mut::<TypeRegistry>();
if r.has_type(type_id) {
let r = r.get_inner();
Some(AtomicRefMut::map(r, |tr| tr.get_type_mut(type_id).unwrap()))
} else {
None
@ -293,9 +289,7 @@ impl ReflectWorldExt for World {
}
fn get_type_or_default<T>(&self, type_id: TypeId) -> AtomicRefMut<RegisteredType> {
let r = self.get_resource_mut::<TypeRegistry>()
.expect("world is missing TypeRegistry resource");
let r = r.get_inner();
let r = self.get_resource_mut::<TypeRegistry>();
AtomicRefMut::map(r, |tr| tr.get_type_or_default(type_id))
}
}

View File

@ -40,23 +40,19 @@ impl<T: ResourceObject + Reflect> FromType<T> for ReflectedResource {
Self {
type_id: TypeId::of::<T>(),
fn_reflect: |world: &World| {
world.get_resource::<T>()
world.try_get_resource::<T>()
.map(|r| {
// TODO: figure out change tracking for reflected resource
let r = r.get_inner();
AtomicRef::map(r, |r| r as &dyn Reflect)
})
},
fn_reflect_mut: |world: &mut World| {
world.get_resource_mut::<T>()
world.try_get_resource_mut::<T>()
.map(|r| {
// TODO: figure out change tracking for reflected resource
let r = r.get_inner();
AtomicRefMut::map(r, |r| r as &mut dyn Reflect)
})
},
fn_reflect_ptr: |world: &mut World| unsafe {
world.get_resource_ptr::<T>()
world.try_get_resource_ptr::<T>()
.map(|ptr| ptr.cast::<u8>())
},
fn_refl_insert: |world: &mut World, this: Box<dyn Reflect>| {

View File

@ -59,8 +59,7 @@ impl WorldAssetExt for World {
}
fn res_watcher_recv(&self, path: &str) -> Option<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
let man = self.get_resource::<ResourceManager>()
.expect("world is missing ResourceManager resource");
let man = self.get_resource::<ResourceManager>();
man.watcher_event_recv(path)
}