Compare commits

..

3 Commits

Author SHA1 Message Date
SeanOMik 798719a7a2
game: remove unused enum InputEvent, remove some warnings 2024-09-27 21:09:33 -04:00
SeanOMik d6d6b2df72
game: improve event system 2024-09-27 21:03:57 -04:00
SeanOMik f5aca87ede
ecs: don't automatically tick the world, use Res and ResMut anywhere ecs resources are requested to track changes better
now the user must manually tick the world. The engine will do this before every update
2024-09-27 21:03:27 -04:00
27 changed files with 569 additions and 504 deletions

View File

@ -1,17 +1,12 @@
use lyra_engine::{
assets::{gltf::Gltf, ResourceManager},
game::App,
input::{
assets::{gltf::Gltf, ResourceManager}, ecs::query::View, game::App, input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
},
math::{self, Transform, Vec3},
render::light::directional::DirectionalLight,
scene::{
}, math::{self, Transform, Vec3}, render::{light::directional::DirectionalLight, window::WindowOptions}, 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]
@ -52,7 +47,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),
],
@ -60,7 +55,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),
],
@ -87,6 +82,7 @@ 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)
@ -96,7 +92,7 @@ async fn main() {
fn setup_scene_plugin(app: &mut App) {
let world = &mut app.world;
let resman = world.get_resource_mut::<ResourceManager>();
let resman = world.get_resource_mut::<ResourceManager>().unwrap();
/* let camera_gltf = resman
.request::<Gltf>("../assets/AntiqueCamera.glb")
@ -145,3 +141,11 @@ 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::{World, resource::ResourceObject};
use crate::{resource::ResourceObject, Tick, TrackedResource, World};
use super::{Query, Fetch, AsQuery};
@ -22,12 +22,18 @@ impl<'a, T: ResourceObject + 'a> Fetch<'a> for FetchResource<'a, T> {
}
fn can_visit_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> bool {
true
let w = self.world.unwrap();
w.has_resource::<T>()
}
unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item {
let w = self.world.unwrap();
Res(w.get_resource::<T>())
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>,
}
}
}
@ -81,13 +87,37 @@ impl<R: ResourceObject> AsQuery for QueryResource<R> {
}
/// A struct used for querying resources from the World.
pub struct Res<'a, T: ResourceObject>(pub(crate) AtomicRef<'a, T>);
pub struct Res<'a, T: ResourceObject> {
pub(crate) inner: AtomicRef<'a, TrackedResource<dyn ResourceObject>>,
pub(crate) world_tick: Tick,
pub(crate) _marker: PhantomData<T>,
}
impl<'a, T: ResourceObject> std::ops::Deref for Res<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0.deref()
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
}
}
@ -111,12 +141,18 @@ impl<'a, T: ResourceObject + 'a> Fetch<'a> for FetchResourceMut<'a, T> {
}
fn can_visit_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> bool {
true
let w = self.world.unwrap();
w.has_resource::<T>()
}
unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item {
let w = self.world.unwrap();
ResMut(w.get_resource_mut::<T>())
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>,
}
}
}
@ -169,20 +205,51 @@ 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) 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>,
}
impl<'a, T: ResourceObject> std::ops::Deref for ResMut<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0.deref()
self.inner.res.as_any().downcast_ref::<T>().unwrap()
}
}
impl<'a, T: ResourceObject> std::ops::DerefMut for ResMut<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0.deref_mut()
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
}
}

View File

@ -58,7 +58,6 @@ where
ViewIter {
world: self.world,
tick: self.world.current_tick(),
has_ticked: false,
query: self.query,
filter: self.filter,
fetcher: None,
@ -73,7 +72,6 @@ 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>>,
@ -112,13 +110,6 @@ 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);
}
@ -174,9 +165,6 @@ 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,22 +20,24 @@ 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<dyn ResourceObject>>,
pub(crate) data: Arc<AtomicRefCell<TrackedResource<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(data)),
data: Arc::new(AtomicRefCell::new(TrackedResource { tick, res: data })),
type_id: TypeId::of::<T>(),
tick: TickTracker::from(*tick),
}
}
@ -51,7 +53,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.as_any().downcast_ref().unwrap())
AtomicRef::map(self.data.borrow(), |a| a.res.as_any().downcast_ref().unwrap())
}
/// Mutably borrow the data inside of the resource.
@ -61,7 +63,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.as_any_mut().downcast_mut().unwrap())
AtomicRefMut::map(self.data.borrow_mut(), |a| a.res.as_any_mut().downcast_mut().unwrap())
}
/// Borrow the data inside of the resource.
@ -71,7 +73,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.as_any().downcast_ref().unwrap()))
.map(|r| AtomicRef::map(r, |a| a.res.as_any().downcast_ref().unwrap()))
.ok()
}
@ -82,11 +84,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.as_any_mut().downcast_mut().unwrap()))
.map(|r| AtomicRefMut::map(r, |a| a.res.as_any_mut().downcast_mut().unwrap()))
.ok()
}
pub fn changed(&self, tick: Tick) -> bool {
self.tick.current() >= tick
self.data.borrow().tick >= tick
}
}

View File

@ -20,6 +20,7 @@ pub trait FnArgFetcher {
Access::Read
}
// TODO: check if the fetcher can fetch before getting.
/// Get the arg from the world
///
/// # Safety
@ -214,7 +215,9 @@ 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();
Res(world.get_resource::<R>())
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>()))
}
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
@ -228,7 +231,9 @@ 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();
ResMut(world.get_resource_mut::<R>())
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>()))
}
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
@ -307,7 +312,8 @@ mod tests {
world.add_resource(SomeCounter(0));
let test_system = |world: &World| -> anyhow::Result<()> {
let mut counter = world.get_resource_mut::<SomeCounter>();
let mut counter = world.get_resource_mut::<SomeCounter>()
.expect("Counter resource is missing");
counter.0 += 10;
Ok(())
@ -316,7 +322,8 @@ 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>();
let mut counter = world.get_resource_mut::<SomeCounter>()
.expect("Counter resource is missing");
counter.0 += 10;
Ok(())
@ -324,7 +331,8 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap();
let counter = world.get_resource::<SomeCounter>();
let counter = world.get_resource::<SomeCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 20);
}
@ -336,7 +344,8 @@ mod tests {
world.add_resource(SomeCounter(0));
let test_system = |world: &World| -> anyhow::Result<()> {
let mut counter = world.get_resource_mut::<SomeCounter>();
let mut counter = world.get_resource_mut::<SomeCounter>()
.expect("Counter resource is missing");
counter.0 += 10;
Ok(())
@ -346,7 +355,8 @@ mod tests {
#[allow(dead_code)]
fn test_system(world: &mut World) -> anyhow::Result<()> {
let mut counter = world.get_resource_mut::<SomeCounter>();
let mut counter = world.get_resource_mut::<SomeCounter>()
.expect("Counter resource is missing");
counter.0 += 10;
Ok(())
@ -354,7 +364,8 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap();
let counter = world.get_resource::<SomeCounter>();
let counter = world.get_resource::<SomeCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 20);
}
@ -366,16 +377,15 @@ mod tests {
world.add_resource(SomeCounter(0));
let test_system = |mut counter: ResMut<SomeCounter>| -> anyhow::Result<()> {
// .0 is twice here since ResMut's tuple field is pub(crate).
// Users wont need to do this
counter.0.0 += 10;
counter.0 += 10;
Ok(())
};
test_system.into_system().execute(NonNull::from(&world)).unwrap();
let counter = world.get_resource::<SomeCounter>();
let counter = world.get_resource::<SomeCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 10);
}
@ -389,9 +399,7 @@ 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);
// .0 is twice here since ResMut's tuple field is pub(crate).
// Users wont need to do this
counter.0.0 += 1;
counter.0 += 1;
}
Ok(())
@ -399,7 +407,8 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap();
let counter = world.get_resource::<SomeCounter>();
let counter = world.get_resource::<SomeCounter>()
.expect("Counter resource is missing");
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.try_get_resource_mut::<CommandQueue>() {
if let Some(mut queue) = world.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,7 +189,8 @@ mod tests {
exec.execute(NonNull::from(&world), true).unwrap();
println!("Executed systems");
let order = world.get_resource::<Vec<String>>();
let order = world.get_resource::<Vec<String>>()
.expect("missing Vec<String> resource");
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 [`Tick::clone`] only clones the inner value of atomic, and not the atomic itself.
/// Note that [`TickTracker::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, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker};
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};
/// The id of the entity for the Archetype.
///
@ -65,10 +65,9 @@ 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()
@ -112,14 +111,8 @@ impl World {
/// Despawn an entity from the World
pub fn despawn(&mut self, entity: Entity) {
// 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 };
let tick = self.current_tick();
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) {
@ -138,8 +131,7 @@ impl World {
where
B: Bundle
{
let tick = self.tick();
let tick = self.current_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();
@ -375,32 +367,24 @@ impl World {
}
/// Add a resource to the world.
///
/// Ticks the world.
pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
let tick = self.tick();
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, tick));
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, self.current_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) {
let tick = self.tick();
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default(), tick));
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default(), self.current_tick()));
}
/// Add the default value of a resource if it does not already exist.
///
/// Returns a boolean indicating if the resource was added. Ticks the world if the resource
/// was added.
/// Returns a boolean indicating 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) {
let tick = self.tick();
self.resources.insert(id, ResourceData::new(T::default(), tick));
self.resources.insert(id, ResourceData::new(T::default(), self.current_tick()));
true
} else {
@ -410,39 +394,60 @@ impl World {
/// Get a resource from the world, or insert it into the world with the provided
/// `fn` and return it.
///
/// Ticks the world.
pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> AtomicRefMut<T>
pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> ResMut<T>
where
F: Fn() -> T + 'static
{
let tick = self.tick();
let tick = self.current_tick();
let res = self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(f(), tick));
res.tick.tick_to(&tick);
res.get_mut()
ResMut {
inner: res.data.borrow_mut(),
world_tick: tick,
_marker: std::marker::PhantomData::<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>
/// Get a resource from the world, or insert its default value.
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> ResMut<T>
{
let tick = self.tick();
let tick = self.current_tick();
let res = self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(T::default(), tick));
res.tick.tick_to(&tick);
res.get_mut()
ResMut {
inner: res.data.borrow_mut(),
world_tick: tick,
_marker: std::marker::PhantomData::<T>,
}
}
/// 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.
///
/// 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> {
/// 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>>> {
self.resources.get(&TypeId::of::<T>())
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
.get()
.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())
}
/// Returns a boolean indicating if the resource changed.
@ -458,7 +463,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.tick.current())
.map(|r| r.data.borrow().tick)
}
/// Returns boolean indicating if the World contains a resource of type `T`.
@ -466,56 +471,33 @@ 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.
///
/// 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()
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>,
})
}
/// Get the corresponding [`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> {
pub fn get_resource_data<T: ResourceObject>(&self) -> Option<ResourceData> {
self.resources.get(&TypeId::of::<T>())
.map(|r| r.clone())
}
/// Increments the TickTracker which is used for tracking changes to components.
/// Increments the world current tick for tracking changes to components and resources.
///
/// Most users wont need to call this manually, its done for you through queries and views.
/// # Note:
/// For change tracking to work correctly, this must be ran each loop before you run world
/// systems.
pub fn tick(&self) -> Tick {
self.tracker.tick()
}
/// Gets the current tick that the world is at.
///
/// See [`TickTracker`]
/// See [`World::tick`].
pub fn current_tick(&self) -> Tick {
self.tracker.current()
}
@ -525,7 +507,7 @@ impl World {
}
/// Attempts to find a resource in the world and returns a NonNull pointer to it
pub unsafe fn try_get_resource_ptr<T: ResourceObject>(&self) -> Option<NonNull<T>> {
pub unsafe fn 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) })
}
@ -601,15 +583,17 @@ mod tests {
world.add_resource(counter);
}
let counter = world.get_resource::<SimpleCounter>();
let counter = world.get_resource::<SimpleCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 0);
drop(counter);
let mut counter = world.get_resource_mut::<SimpleCounter>();
let mut counter = world.get_resource_mut::<SimpleCounter>()
.expect("Counter resource is missing");
counter.0 += 4582;
drop(counter);
assert!(world.try_get_resource::<u32>().is_none());
assert!(world.get_resource::<u32>().is_none());
}
#[test]
@ -619,9 +603,11 @@ mod tests {
world.add_resource(counter);
// test multiple borrows at the same time
let counter = world.get_resource::<SimpleCounter>();
let counter = world.get_resource::<SimpleCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 4582);
let counter2 = world.get_resource::<SimpleCounter>();
let counter2 = world.get_resource::<SimpleCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 4582);
assert_eq!(counter2.0, 4582);
}
@ -635,9 +621,10 @@ mod tests {
}
// test that its only possible to get a single mutable borrow
let counter = world.get_resource_mut::<SimpleCounter>();
let counter = world.get_resource_mut::<SimpleCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 4582);
assert!(world.try_get_resource_mut::<SimpleCounter>().is_none());
assert!(world.get_resource_mut::<SimpleCounter>().is_none());
assert_eq!(counter.0, 4582);
}
@ -763,7 +750,8 @@ mod tests {
assert!(!world.has_resource_changed::<SimpleCounter>());
let mut counter = world.get_resource_mut::<SimpleCounter>();
let mut counter = world.get_resource_mut::<SimpleCounter>()
.expect("Counter resource is missing");
counter.0 += 100;
assert!(world.has_resource_changed::<SimpleCounter>());

View File

@ -1,5 +1,5 @@
use instant::Instant;
use lyra_ecs::{Component, World};
use lyra_ecs::{query::ResMut, Component};
use lyra_reflect::Reflect;
use crate::{plugin::Plugin, game::GameStages};
@ -30,9 +30,8 @@ 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(world: &mut World) -> anyhow::Result<()> {
pub fn delta_time_system(mut delta: ResMut<DeltaTime>) -> 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::sync::Arc;
use std::{cell::RefCell, rc::Rc, sync::Arc};
use atomic_refcell::AtomicRefCell;
use lyra_ecs::{query::{ResMut, WorldTick}, system::FnArgFetcher, Tick};
@ -6,14 +6,83 @@ 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<Vec<T>>>,
last_updated_tick: Option<Tick>,
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,
}
impl<T: Event> Default for Events<T> {
fn default() -> Self {
Self { events: Default::default(), last_updated_tick: None }
Self { events: Default::default(), last_cleared_at: Default::default(), reset_cursor: false }
}
}
@ -30,7 +99,7 @@ impl<T: Event> Events<T> {
pub fn reader(&self) -> EventReader<T> {
EventReader {
events: self.events.clone(),
cursor: 0,
cursor: Rc::new(RefCell::new(0)),
}
}
@ -42,27 +111,28 @@ impl<T: Event> Events<T> {
}
pub struct EventReader<T: Event> {
events: Arc<AtomicRefCell<Vec<T>>>,
cursor: usize,
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
cursor: Rc<RefCell<usize>>,
}
impl<T: Event> EventReader<T> {
pub fn read(&mut self) -> Option<atomic_refcell::AtomicRef<T>> {
let events = self.events.borrow();
if self.cursor >= events.len() {
let mut cursor = self.cursor.borrow_mut();
if *cursor >= events.total_len() {
None
} else {
let e = atomic_refcell::AtomicRef::map(events,
|e| e.get(self.cursor).unwrap());
self.cursor += 1;
|e| e.get(*cursor).unwrap());
*cursor += 1;
Some(e)
}
}
}
pub struct EventWriter<T: Event> {
events: Arc<AtomicRefCell<Vec<T>>>,
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
}
impl<T: Event> EventWriter<T> {
@ -77,40 +147,48 @@ pub fn event_cleaner_system<T>(tick: WorldTick, mut events: ResMut<Events<T>>) -
where
T: Event
{
let last_tick = *events.last_updated_tick.unwrap_or(*tick);
let last_tick = *events.last_cleared_at;
let world_tick = **tick;
if last_tick + 2 < world_tick {
events.last_updated_tick = Some(*tick);
events.last_cleared_at = *tick;
events.reset_cursor = true;
let mut events = events.events.borrow_mut();
events.clear();
events.clear_oldest();
} else {
events.reset_cursor = false;
}
let mut events = events.events.borrow_mut();
events.waterfall();
Ok(())
}
impl<T: Event> FnArgFetcher for EventReader<T> {
type State = usize;
type State = Rc<RefCell<usize>>;
type Arg<'a, 'state> = EventReader<T>;
fn create_state(_: std::ptr::NonNull<lyra_ecs::World>) -> Self::State {
0
Rc::new(RefCell::new(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>>();
let events = world.get_resource::<Events<T>>()
.unwrap_or_else(|| panic!("world missing Events<{}> resource", std::any::type_name::<T>()));
// 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;
if events.reset_cursor {
let mut state_num = state.borrow_mut();
*state_num = 0;
}
let mut reader = events.reader();
reader.cursor = *state;
let reader = EventReader {
events: events.events.clone(),
cursor: state.clone(),
};
reader
}
@ -129,7 +207,8 @@ 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>>();
let events = world.get_resource::<Events<T>>()
.unwrap_or_else(|| panic!("world missing Events<{}> resource", std::any::type_name::<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::{info, error, Level};
use tracing::{error, info, Level};
use tracing_appender::non_blocking;
use tracing_subscriber::{
layer::SubscriberExt,
@ -102,6 +102,7 @@ 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) {
@ -227,7 +228,7 @@ impl App {
pub fn push_event<T: Event>(&mut self, event: T) {
let world = &mut self.world;
let mut events = world.try_get_resource_mut::<Events<T>>()
let mut events = world.get_resource_mut::<Events<T>>()
.expect("missing events for event type! Must use `App::register_event` first");
events.push_event(event);
}

View File

@ -1,140 +0,0 @@
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,16 +1,13 @@
pub mod system;
mod system;
pub use system::*;
pub mod input_event;
pub use input_event::*;
pub mod events;
mod events;
pub use events::*;
pub mod buttons;
mod buttons;
pub use buttons::*;
pub mod action;
mod action;
pub use action::*;
pub type KeyCode = winit::keyboard::KeyCode;
@ -122,7 +119,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_app1" => Some(KeyCode::LaunchApp2),
"launch_app2" => 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, InputEvent};
use super::{events::*, InputButtons};
fn write_scroll_delta(mouse_scroll_ev: &mut EventWriter<MouseScroll>, delta: &MouseScrollDelta) {
let event = match delta {
@ -119,7 +119,6 @@ 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 {
) -> wgpu::RenderPass<'a> {
self.encoder
.as_mut()
.expect(

View File

@ -203,7 +203,8 @@ 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>();
let screen_size = world.get_resource::<ScreenSize>()
.expect("world missing ScreenSize resource");
if screen_size.xy() != self.workgroup_size {
self.workgroup_size = screen_size.xy();
todo!("Resize buffers and other resources");

View File

@ -286,7 +286,8 @@ 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>();
let mut render_meshes = world.get_resource_mut::<RenderMeshes>()
.expect("world missing RenderMeshes resource");
render_meshes.clear();
}
@ -459,7 +460,8 @@ impl Node for MeshPrepNode {
world.insert(en, interp);
}
let mut transforms = world.get_resource_mut::<TransformBuffers>();
let mut transforms = world.get_resource_mut::<TransformBuffers>()
.expect("world missing TransformBuffers resource");
transforms.send_to_gpu(queue);
}

View File

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

View File

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

View File

@ -269,7 +269,8 @@ 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>();
let mut world_ss = world.get_resource_mut::<ScreenSize>()
.expect("world missing ScreenSize resource");
world_ss.0 = glam::UVec2::new(new_size.width, new_size.height);

View File

@ -1,101 +1,16 @@
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::window::{CustomCursor, Fullscreen, Window};
use winit::{dpi::{PhysicalPosition, PhysicalSize, Position, Size}, 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;
@ -168,7 +83,7 @@ pub struct WindowOptions {
/// * **Web:** Can only return None or Borderless(None).
pub fullscreen: Option<Fullscreen>,
/// Gets the position of the top-left hand corner of the windows client area relative to
/// 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
@ -185,7 +100,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 inner_position: Option<Position>,
pub position: Option<IVec2>,
/// Gets/sets the size of the view in the window.
///
@ -193,7 +108,7 @@ pub struct WindowOptions {
///
/// Platform-specific
/// * **Web:** The size of the canvas element. Doesnt account for CSS `transform`.
pub size: Size,
physical_size: UVec2,
/// Gets/sets if the window has decorations.
///
@ -237,24 +152,6 @@ 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
@ -308,12 +205,12 @@ pub struct WindowOptions {
/// * **X11:** Enabling IME will disable dead keys reporting during compose.
pub ime_allowed: bool,
/// Sets area of IME candidate box in window client area coordinates relative to the top left.
/// Sets area of IME box in physical coordinates relative to the top left.
///
/// Platform-specific
/// * **X11:** - area is not supported, only position.
/// * **iOS / Android / Web / Orbital:** Unsupported.
pub ime_cursor_area: Option<Area>,
pub physical_ime_cursor_area: Option<Area<Vec2, Vec2>>,
/// Gets/sets the minimum size of the window.
///
@ -380,12 +277,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.
/// at a specified position in physical coordinates.
///
/// 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 show_window_menu: Option<Position>,
pub physical_window_menu_pos: Option<Vec2>,
/// Gets the window's occluded state (completely hidden from view).
///
@ -400,6 +297,13 @@ 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 {
@ -408,15 +312,19 @@ impl From<winit::window::WindowAttributes> for WindowOptions {
enabled_buttons: value.enabled_buttons,
focused: false,
fullscreen: value.fullscreen,
inner_position: None,
size: value.inner_size.map(|s| s.into())
.unwrap_or(Size::new_physical(1280, 720)),
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)),
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,
@ -431,7 +339,7 @@ impl From<winit::window::WindowAttributes> for WindowOptions {
visible: true,
},
ime_allowed: false,
ime_cursor_area: None,
physical_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,
@ -439,8 +347,9 @@ impl From<winit::window::WindowAttributes> for WindowOptions {
transparent: value.transparent,
window_icon: None,
window_level: value.window_level,
show_window_menu: None,
physical_window_menu_pos: None,
occluded: false,
physical_cursor_position: None,
}
}
}
@ -458,12 +367,12 @@ impl WindowOptions {
att.enabled_buttons = self.enabled_buttons.clone();
att.fullscreen = self.fullscreen.clone();
att.inner_size = Some(self.size.into());
att.inner_size = Some(Size::Physical(PhysicalSize::new(self.physical_size.x, self.physical_size.y)));
att.decorations = self.decorated;
att.maximized = self.maximized;
att.resizable = self.resizable;
att.visible = self.visible.unwrap_or(true);
att.position = self.outer_position.map(|p| p.into());
att.position = self.position.map(|p| Position::Physical(PhysicalPosition::new(p.x, p.y)));
att.resize_increments = self.resize_increments.map(|i| i.into());
att.blur = self.blur;
att.content_protected = self.content_protected;
@ -483,6 +392,73 @@ 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.
@ -491,7 +467,7 @@ impl WindowOptions {
/// when syncing the winit window with the component.
#[derive(Clone, Component)]
pub struct LastWindow {
last: WindowOptions,
pub last: WindowOptions,
}
impl Deref for LastWindow {
@ -532,8 +508,9 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
window.set_fullscreen(opts.fullscreen.clone());
}
if opts.size != last.size {
if window.request_inner_size(opts.size).is_some() {
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() {
error!("request to increase window size failed");
}
}
@ -554,8 +531,10 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
window.set_visible(opts.visible.unwrap());
}
if opts.outer_position != last.outer_position && opts.outer_position.is_some() {
window.set_outer_position(opts.outer_position.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.resize_increments != last.resize_increments {
@ -563,7 +542,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 {
@ -597,9 +576,11 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
window.set_ime_allowed(opts.ime_allowed);
}
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.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.min_size != last.min_size {
@ -636,8 +617,18 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
window.set_window_level(opts.window_level);
}
if opts.show_window_menu != last.show_window_menu && opts.show_window_menu.is_some() {
window.show_window_menu(opts.show_window_menu.unwrap());
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);
}
}
last.last = opts.clone();

View File

@ -1,7 +1,7 @@
use std::{collections::VecDeque, sync::Arc};
use async_std::task::block_on;
use glam::IVec2;
use glam::{DVec2, IVec2, UVec2};
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::{PrimaryWindow, Size, WindowOptions},
window::{LastWindow, PrimaryWindow, WindowOptions},
},
};
@ -143,7 +143,8 @@ impl ApplicationHandler for WinitRunner {
Err(e) => eprintln!("{:?}", e),
}
let windows = self.app.world.get_resource::<WinitWindows>();
let windows = self.app.world.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource");
for window in windows.windows.values() {
window.request_redraw();
}
@ -152,13 +153,15 @@ 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>();
let mut windows = world.get_resource_mut::<WinitWindows>()
.expect("world missing WinitWindows resource");
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, PrimaryWindow));
let en = world.spawn((to_create_window.clone(), LastWindow { last: to_create_window }, PrimaryWindow));
let mut windows = world.get_resource_mut::<WinitWindows>();
let mut windows = world.get_resource_mut::<WinitWindows>()
.expect("world missing WinitWindows resource");
let wid = windows.create_window(event_loop, en, window_attr).unwrap();
let window = windows.windows.get(&wid).unwrap().clone();
drop(windows);
@ -195,28 +198,59 @@ 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_opts = self
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>(*e).get())
.and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e).get())
.unwrap();
window_opts.size = Size::new_physical(physical_size.width, physical_size.height);
}
// 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);
},
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!(),
@ -226,51 +260,55 @@ 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");
}
},
_ => {}
}
}

22
lyra-math/src/area.rs Normal file
View File

@ -0,0 +1,22 @@
#[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,6 +4,9 @@ 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::{AtomicRef, AtomicRefMut, World};
use lyra_ecs::{query::{Res, ResMut}, 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) -> AtomicRef<TypeRegistry>;
fn get_type_registry(&self) -> Option<Res<TypeRegistry>>;
/// Retrieves the type registry mutably from the world.
fn get_type_registry_mut(&self) -> AtomicRefMut<TypeRegistry>;
fn get_type_registry_mut(&self) -> Option<ResMut<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,17 +262,19 @@ pub trait ReflectWorldExt {
}
impl ReflectWorldExt for World {
fn get_type_registry(&self) -> AtomicRef<TypeRegistry> {
fn get_type_registry(&self) -> Option<Res<TypeRegistry>> {
self.get_resource::<TypeRegistry>()
}
fn get_type_registry_mut(&self) -> AtomicRefMut<TypeRegistry> {
fn get_type_registry_mut(&self) -> Option<ResMut<TypeRegistry>> {
self.get_resource_mut::<TypeRegistry>()
}
fn get_type<T>(&self, type_id: TypeId) -> Option<AtomicRef<RegisteredType>> {
let r = self.get_resource::<TypeRegistry>();
let r = self.get_resource::<TypeRegistry>()
.expect("world is missing TypeRegistry resource");
if r.has_type(type_id) {
let r = r.get_inner();
Some(AtomicRef::map(r, |tr| tr.get_type(type_id).unwrap()))
} else {
None
@ -280,8 +282,10 @@ impl ReflectWorldExt for World {
}
fn get_type_mut<T>(&self, type_id: TypeId) -> Option<AtomicRefMut<RegisteredType>> {
let r = self.get_resource_mut::<TypeRegistry>();
let r = self.get_resource_mut::<TypeRegistry>()
.expect("world is missing TypeRegistry resource");
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
@ -289,7 +293,9 @@ impl ReflectWorldExt for World {
}
fn get_type_or_default<T>(&self, type_id: TypeId) -> AtomicRefMut<RegisteredType> {
let r = self.get_resource_mut::<TypeRegistry>();
let r = self.get_resource_mut::<TypeRegistry>()
.expect("world is missing TypeRegistry resource");
let r = r.get_inner();
AtomicRefMut::map(r, |tr| tr.get_type_or_default(type_id))
}
}

View File

@ -40,19 +40,23 @@ impl<T: ResourceObject + Reflect> FromType<T> for ReflectedResource {
Self {
type_id: TypeId::of::<T>(),
fn_reflect: |world: &World| {
world.try_get_resource::<T>()
world.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.try_get_resource_mut::<T>()
world.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.try_get_resource_ptr::<T>()
world.get_resource_ptr::<T>()
.map(|ptr| ptr.cast::<u8>())
},
fn_refl_insert: |world: &mut World, this: Box<dyn Reflect>| {

View File

@ -59,7 +59,8 @@ 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>();
let man = self.get_resource::<ResourceManager>()
.expect("world is missing ResourceManager resource");
man.watcher_event_recv(path)
}