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::{ use lyra_engine::{
assets::{gltf::Gltf, ResourceManager}, assets::{gltf::Gltf, ResourceManager}, ecs::query::View, game::App, input::{
game::App,
input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput, 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, CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
}, }
}; };
#[async_std::main] #[async_std::main]
@ -52,7 +47,7 @@ async fn main() {
.bind( .bind(
ACTLBL_LOOK_LEFT_RIGHT, 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::ArrowLeft).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::ArrowRight).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::ArrowRight).into_binding_modifier(1.0),
], ],
@ -60,7 +55,7 @@ async fn main() {
.bind( .bind(
ACTLBL_LOOK_UP_DOWN, 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::ArrowUp).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::ArrowDown).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(); let mut a = App::new();
a.with_plugin(lyra_engine::DefaultPlugins) a.with_plugin(lyra_engine::DefaultPlugins)
.with_system("mouse_pos_print", mouse_pos_system, &[])
.with_plugin(setup_scene_plugin) .with_plugin(setup_scene_plugin)
.with_plugin(action_handler_plugin) .with_plugin(action_handler_plugin)
//.with_plugin(camera_debug_plugin) //.with_plugin(camera_debug_plugin)
@ -96,7 +92,7 @@ async fn main() {
fn setup_scene_plugin(app: &mut App) { fn setup_scene_plugin(app: &mut App) {
let world = &mut app.world; let world = &mut app.world;
let resman = world.get_resource_mut::<ResourceManager>(); let resman = world.get_resource_mut::<ResourceManager>().unwrap();
/* let camera_gltf = resman /* let camera_gltf = resman
.request::<Gltf>("../assets/AntiqueCamera.glb") .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); camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
world.spawn((camera, FreeFlyCamera::default())); 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 atomic_refcell::{AtomicRef, AtomicRefMut};
use crate::{World, resource::ResourceObject}; use crate::{resource::ResourceObject, Tick, TrackedResource, World};
use super::{Query, Fetch, AsQuery}; 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 { 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 { unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item {
let w = self.world.unwrap(); 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. /// 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> { impl<'a, T: ResourceObject> std::ops::Deref for Res<'a, T> {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { 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 { 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 { unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item {
let w = self.world.unwrap(); 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. /// 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> { impl<'a, T: ResourceObject> std::ops::Deref for ResMut<'a, T> {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { 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> { impl<'a, T: ResourceObject> std::ops::DerefMut for ResMut<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target { 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 { ViewIter {
world: self.world, world: self.world,
tick: self.world.current_tick(), tick: self.world.current_tick(),
has_ticked: false,
query: self.query, query: self.query,
filter: self.filter, filter: self.filter,
fetcher: None, fetcher: None,
@ -73,7 +72,6 @@ where
pub struct ViewIter<'a, Q: Query, F: Filter> { pub struct ViewIter<'a, Q: Query, F: Filter> {
world: &'a World, world: &'a World,
tick: Tick, tick: Tick,
has_ticked: bool,
query: Q, query: Q,
filter: F, filter: F,
fetcher: Option<Q::Fetch<'a>>, fetcher: Option<Q::Fetch<'a>>,
@ -112,13 +110,6 @@ where
let entity_index = ArchetypeEntityId(entity_index); let entity_index = ArchetypeEntityId(entity_index);
if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(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) }; let i = unsafe { fetcher.get_item(entity_index) };
return Some(i); return Some(i);
} }
@ -174,9 +165,6 @@ impl<'a, Q: Query> ViewOne<'a, Q> {
if self.query.can_visit_archetype(arch) { if self.query.can_visit_archetype(arch) {
let mut fetch = unsafe { self.query.fetch(self.world, arch, self.tick) }; let mut fetch = unsafe { self.query.fetch(self.world, arch, self.tick) };
if fetch.can_visit_item(record.index) { 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) }); 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. /// A type erased storage for a Resource.
#[derive(Clone)] #[derive(Clone)]
pub struct ResourceData { pub struct ResourceData {
pub(crate) data: Arc<AtomicRefCell<dyn ResourceObject>>, pub(crate) data: Arc<AtomicRefCell<TrackedResource<dyn ResourceObject>>>,
type_id: TypeId, type_id: TypeId,
// use a tick tracker which has interior mutability
pub(crate) tick: TickTracker,
} }
impl ResourceData { impl ResourceData {
pub fn new<T: ResourceObject>(data: T, tick: Tick) -> Self { pub fn new<T: ResourceObject>(data: T, tick: Tick) -> Self {
Self { Self {
data: Arc::new(AtomicRefCell::new(data)), data: Arc::new(AtomicRefCell::new(TrackedResource { tick, res: data })),
type_id: TypeId::of::<T>(), 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 data is already borrowed mutably, this will panic.
/// * If the type of `T` is not the same as the resource type. /// * If the type of `T` is not the same as the resource type.
pub fn get<T: ResourceObject>(&self) -> AtomicRef<T> { 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. /// 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 data is already borrowed mutably, this will panic.
/// * If the type of `T` is not the same as the resource type. /// * If the type of `T` is not the same as the resource type.
pub fn get_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> { 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. /// 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. /// * If the type of `T` is not the same as the resource type.
pub fn try_get<T: ResourceObject>(&self) -> Option<AtomicRef<T>> { pub fn try_get<T: ResourceObject>(&self) -> Option<AtomicRef<T>> {
self.data.try_borrow() 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() .ok()
} }
@ -82,11 +84,11 @@ impl ResourceData {
/// * If the type of `T` is not the same as the resource type. /// * If the type of `T` is not the same as the resource type.
pub fn try_get_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> { pub fn try_get_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
self.data.try_borrow_mut() 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() .ok()
} }
pub fn changed(&self, tick: Tick) -> bool { 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 Access::Read
} }
// TODO: check if the fetcher can fetch before getting.
/// Get the arg from the world /// Get the arg from the world
/// ///
/// # Safety /// # 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> { unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
let world = world.as_ref(); 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>) { } 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> { unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
let world = world.as_ref(); 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>) { } fn apply_deferred(_: Self::State, _: NonNull<World>) { }
@ -307,7 +312,8 @@ mod tests {
world.add_resource(SomeCounter(0)); world.add_resource(SomeCounter(0));
let test_system = |world: &World| -> anyhow::Result<()> { 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; counter.0 += 10;
Ok(()) Ok(())
@ -316,7 +322,8 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap(); test_system.into_system().execute(NonNull::from(&world)).unwrap();
let test_system = |world: &mut World| -> anyhow::Result<()> { 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; counter.0 += 10;
Ok(()) Ok(())
@ -324,7 +331,8 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap(); 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); assert_eq!(counter.0, 20);
} }
@ -336,7 +344,8 @@ mod tests {
world.add_resource(SomeCounter(0)); world.add_resource(SomeCounter(0));
let test_system = |world: &World| -> anyhow::Result<()> { 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; counter.0 += 10;
Ok(()) Ok(())
@ -346,7 +355,8 @@ mod tests {
#[allow(dead_code)] #[allow(dead_code)]
fn test_system(world: &mut World) -> anyhow::Result<()> { 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; counter.0 += 10;
Ok(()) Ok(())
@ -354,7 +364,8 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap(); 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); assert_eq!(counter.0, 20);
} }
@ -366,16 +377,15 @@ mod tests {
world.add_resource(SomeCounter(0)); world.add_resource(SomeCounter(0));
let test_system = |mut counter: ResMut<SomeCounter>| -> anyhow::Result<()> { let test_system = |mut counter: ResMut<SomeCounter>| -> anyhow::Result<()> {
// .0 is twice here since ResMut's tuple field is pub(crate). counter.0 += 10;
// Users wont need to do this
counter.0.0 += 10;
Ok(()) Ok(())
}; };
test_system.into_system().execute(NonNull::from(&world)).unwrap(); 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); assert_eq!(counter.0, 10);
} }
@ -389,9 +399,7 @@ mod tests {
let test_system = |mut counter: ResMut<SomeCounter>, view: ViewState<QueryBorrow<Vec2>, ()>| -> anyhow::Result<()> { let test_system = |mut counter: ResMut<SomeCounter>, view: ViewState<QueryBorrow<Vec2>, ()>| -> anyhow::Result<()> {
for v2 in view.into_iter() { for v2 in view.into_iter() {
println!("Got v2 at '{:?}'", v2); println!("Got v2 at '{:?}'", v2);
// .0 is twice here since ResMut's tuple field is pub(crate). counter.0 += 1;
// Users wont need to do this
counter.0.0 += 1;
} }
Ok(()) Ok(())
@ -399,7 +407,8 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap(); 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); assert_eq!(counter.0, 2);
} }
} }

View File

@ -103,7 +103,7 @@ impl GraphExecutor {
} }
let world = unsafe { world_ptr.as_mut() }; 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 // Safety: Commands only borrows world.entities when adding commands
let world = unsafe { world_ptr.as_mut() }; let world = unsafe { world_ptr.as_mut() };
let mut commands = Commands::new(&mut queue, world); let mut commands = Commands::new(&mut queue, world);
@ -189,7 +189,8 @@ mod tests {
exec.execute(NonNull::from(&world), true).unwrap(); exec.execute(NonNull::from(&world), true).unwrap();
println!("Executed systems"); 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(); let mut order_iter = order.iter();
// ... but still executed in order // ... 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 is used for tracking changes of [`Component`](crate::Component)s and entities.
/// ///
/// TickTracker stores an [`AtomicU64`], making all operations on `TickTracker`, atomic as well. /// 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)] #[derive(Debug, Default)]
pub struct TickTracker { pub struct TickTracker {
tick: AtomicU64, tick: AtomicU64,

View File

@ -2,7 +2,7 @@ use std::{any::TypeId, collections::HashMap, ptr::NonNull};
use atomic_refcell::{AtomicRef, AtomicRefMut}; 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. /// The id of the entity for the Archetype.
/// ///
@ -65,10 +65,9 @@ impl World {
where where
B: Bundle B: Bundle
{ {
let tick = self.current_tick();
let bundle_types = bundle.type_ids(); let bundle_types = bundle.type_ids();
let tick = self.tick();
// try to find an archetype // try to find an archetype
let archetype = self.archetypes let archetype = self.archetypes
.values_mut() .values_mut()
@ -112,14 +111,8 @@ impl World {
/// Despawn an entity from the World /// Despawn an entity from the World
pub fn despawn(&mut self, entity: Entity) { pub fn despawn(&mut self, entity: Entity) {
// Tick the tracker if the entity is spawned. This is done here instead of the `if let` let tick = self.current_tick();
// 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) { 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(); let arch = self.archetypes.get_mut(&record.id).unwrap();
if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) { if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) {
@ -138,8 +131,7 @@ impl World {
where where
B: Bundle B: Bundle
{ {
let tick = self.tick(); let tick = self.current_tick();
let record = self.entities.entity_record(entity).unwrap(); let record = self.entities.entity_record(entity).unwrap();
let current_arch = self.archetypes.get(&record.id).unwrap(); let current_arch = self.archetypes.get(&record.id).unwrap();
let current_arch_len = current_arch.len(); let current_arch_len = current_arch.len();
@ -375,32 +367,24 @@ impl World {
} }
/// Add a resource to the world. /// Add a resource to the world.
///
/// Ticks the world.
pub fn add_resource<T: ResourceObject>(&mut self, data: T) { pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
let tick = self.tick(); self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, self.current_tick()));
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, tick));
} }
/// Add the default value of a resource. /// Add the default value of a resource.
/// ///
/// Ticks the world.
///
/// > Note: This will replace existing values. /// > Note: This will replace existing values.
pub fn add_resource_default<T: ResourceObject + Default>(&mut self) { pub fn add_resource_default<T: ResourceObject + Default>(&mut self) {
let tick = self.tick(); self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default(), self.current_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. /// 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 /// Returns a boolean indicating if the resource was added.
/// was added.
pub fn add_resource_default_if_absent<T: ResourceObject + Default>(&mut self) -> bool { pub fn add_resource_default_if_absent<T: ResourceObject + Default>(&mut self) -> bool {
let id = TypeId::of::<T>(); let id = TypeId::of::<T>();
if !self.resources.contains_key(&id) { if !self.resources.contains_key(&id) {
let tick = self.tick(); self.resources.insert(id, ResourceData::new(T::default(), self.current_tick()));
self.resources.insert(id, ResourceData::new(T::default(), tick));
true true
} else { } else {
@ -410,39 +394,60 @@ impl World {
/// Get a resource from the world, or insert it into the world with the provided /// Get a resource from the world, or insert it into the world with the provided
/// `fn` and return it. /// `fn` and return it.
/// pub fn get_resource_or_else<T: 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 where
F: Fn() -> T + 'static F: Fn() -> T + 'static
{ {
let tick = self.tick(); let tick = self.current_tick();
let res = self.resources.entry(TypeId::of::<T>()) let res = self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(f(), tick)); .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. /// Get a resource from the world, or insert its default value.
/// pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> ResMut<T>
/// Ticks the world.
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> AtomicRefMut<T>
{ {
let tick = self.tick(); let tick = self.current_tick();
let res = self.resources.entry(TypeId::of::<T>()) let res = self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(T::default(), tick)); .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. /// 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 /// You will have to manually downcast the inner resource. Most people don't need this, see
/// a function that returns an option. /// [`World::get_resource`].
pub fn get_resource<T: ResourceObject>(&self) -> AtomicRef<T> { pub fn get_tracked_resource<T: ResourceObject>(&self) -> Option<AtomicRef<TrackedResource<dyn ResourceObject>>> {
self.resources.get(&TypeId::of::<T>()) self.resources.get(&TypeId::of::<T>())
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>())) .map(|r| r.data.borrow())
.get() }
/// 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. /// Returns a boolean indicating if the resource changed.
@ -458,7 +463,7 @@ impl World {
/// Returns the [`Tick`] that the resource was last modified at. /// Returns the [`Tick`] that the resource was last modified at.
pub fn resource_tick<T: ResourceObject>(&self) -> Option<Tick> { pub fn resource_tick<T: ResourceObject>(&self) -> Option<Tick> {
self.resources.get(&TypeId::of::<T>()) 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`. /// 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>()) 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. /// Gets a mutable borrow of a resource from the World.
/// pub fn get_resource_mut<T: ResourceObject>(&self) -> Option<ResMut<T>> {
/// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for self.get_tracked_resource_mut::<T>().map(|r| ResMut {
/// a function that returns an option. inner: r,
/// world_tick: self.current_tick(),
/// Ticks the world. _marker: std::marker::PhantomData::<T>,
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`]. /// 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>()) self.resources.get(&TypeId::of::<T>())
.map(|r| r.clone()) .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 { pub fn tick(&self) -> Tick {
self.tracker.tick() self.tracker.tick()
} }
/// Gets the current tick that the world is at. /// Gets the current tick that the world is at.
/// ///
/// See [`TickTracker`] /// See [`World::tick`].
pub fn current_tick(&self) -> Tick { pub fn current_tick(&self) -> Tick {
self.tracker.current() self.tracker.current()
} }
@ -525,7 +507,7 @@ impl World {
} }
/// Attempts to find a resource in the world and returns a NonNull pointer to it /// 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>()) self.resources.get(&TypeId::of::<T>())
.map(|d| unsafe { NonNull::new_unchecked(d.data.as_ptr() as *mut T) }) .map(|d| unsafe { NonNull::new_unchecked(d.data.as_ptr() as *mut T) })
} }
@ -601,15 +583,17 @@ mod tests {
world.add_resource(counter); 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); assert_eq!(counter.0, 0);
drop(counter); 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; counter.0 += 4582;
drop(counter); drop(counter);
assert!(world.try_get_resource::<u32>().is_none()); assert!(world.get_resource::<u32>().is_none());
} }
#[test] #[test]
@ -619,9 +603,11 @@ mod tests {
world.add_resource(counter); world.add_resource(counter);
// test multiple borrows at the same time // test multiple borrows at the same time
let counter = world.get_resource::<SimpleCounter>(); let counter = world.get_resource::<SimpleCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 4582); 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!(counter.0, 4582);
assert_eq!(counter2.0, 4582); assert_eq!(counter2.0, 4582);
} }
@ -635,9 +621,10 @@ mod tests {
} }
// test that its only possible to get a single mutable borrow // 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_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); assert_eq!(counter.0, 4582);
} }
@ -763,7 +750,8 @@ mod tests {
assert!(!world.has_resource_changed::<SimpleCounter>()); 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; counter.0 += 100;
assert!(world.has_resource_changed::<SimpleCounter>()); assert!(world.has_resource_changed::<SimpleCounter>());

View File

@ -1,5 +1,5 @@
use instant::Instant; use instant::Instant;
use lyra_ecs::{Component, World}; use lyra_ecs::{query::ResMut, Component};
use lyra_reflect::Reflect; use lyra_reflect::Reflect;
use crate::{plugin::Plugin, game::GameStages}; use crate::{plugin::Plugin, game::GameStages};
@ -30,9 +30,8 @@ impl std::ops::DerefMut for DeltaTime {
/// A system that updates the [`DeltaTime``] resource. /// A system that updates the [`DeltaTime``] resource.
/// ///
/// The resource is updated in the [`GameStages::First`] stage. /// 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 now = Instant::now();
let mut delta = world.get_resource_mut::<DeltaTime>();
delta.0 = delta.1.unwrap_or(now).elapsed().as_secs_f32(); delta.0 = delta.1.unwrap_or(now).elapsed().as_secs_f32();
delta.1 = Some(now); 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 atomic_refcell::AtomicRefCell;
use lyra_ecs::{query::{ResMut, WorldTick}, system::FnArgFetcher, Tick}; 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 {} pub trait Event: Clone + Send + Sync + 'static {}
impl<T: Clone + Send + Sync + 'static> Event for T {} impl<T: Clone + Send + Sync + 'static> Event for T {}
/// 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> { pub struct Events<T: Event> {
events: Arc<AtomicRefCell<Vec<T>>>, events: Arc<AtomicRefCell<WaterfallVec<T>>>,
last_updated_tick: Option<Tick>, /// 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> { impl<T: Event> Default for Events<T> {
fn default() -> Self { 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> { pub fn reader(&self) -> EventReader<T> {
EventReader { EventReader {
events: self.events.clone(), 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> { pub struct EventReader<T: Event> {
events: Arc<AtomicRefCell<Vec<T>>>, events: Arc<AtomicRefCell<WaterfallVec<T>>>,
cursor: usize, cursor: Rc<RefCell<usize>>,
} }
impl<T: Event> EventReader<T> { impl<T: Event> EventReader<T> {
pub fn read(&mut self) -> Option<atomic_refcell::AtomicRef<T>> { pub fn read(&mut self) -> Option<atomic_refcell::AtomicRef<T>> {
let events = self.events.borrow(); let events = self.events.borrow();
if self.cursor >= events.len() { let mut cursor = self.cursor.borrow_mut();
if *cursor >= events.total_len() {
None None
} else { } else {
let e = atomic_refcell::AtomicRef::map(events, let e = atomic_refcell::AtomicRef::map(events,
|e| e.get(self.cursor).unwrap()); |e| e.get(*cursor).unwrap());
self.cursor += 1; *cursor += 1;
Some(e) Some(e)
} }
} }
} }
pub struct EventWriter<T: Event> { pub struct EventWriter<T: Event> {
events: Arc<AtomicRefCell<Vec<T>>>, events: Arc<AtomicRefCell<WaterfallVec<T>>>,
} }
impl<T: Event> EventWriter<T> { impl<T: Event> EventWriter<T> {
@ -77,40 +147,48 @@ pub fn event_cleaner_system<T>(tick: WorldTick, mut events: ResMut<Events<T>>) -
where where
T: Event T: Event
{ {
let last_tick = *events.last_updated_tick.unwrap_or(*tick); let last_tick = *events.last_cleared_at;
let world_tick = **tick; let world_tick = **tick;
if last_tick + 2 < world_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(); 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(()) Ok(())
} }
impl<T: Event> FnArgFetcher for EventReader<T> { impl<T: Event> FnArgFetcher for EventReader<T> {
type State = usize; type State = Rc<RefCell<usize>>;
type Arg<'a, 'state> = EventReader<T>; type Arg<'a, 'state> = EventReader<T>;
fn create_state(_: std::ptr::NonNull<lyra_ecs::World>) -> Self::State { 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> { 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 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 if events.reset_cursor {
let world_tick = world.current_tick(); let mut state_num = state.borrow_mut();
if world_tick == events.last_updated_tick.unwrap_or(world_tick) { *state_num = 0;
*state = 0;
} }
let mut reader = events.reader(); let reader = EventReader {
reader.cursor = *state; events: events.events.clone(),
cursor: state.clone(),
};
reader 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> { 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 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() 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_ecs::{system::{IntoSystem, System}, ResourceObject, World};
use lyra_math::IVec2; use lyra_math::IVec2;
use tracing::{info, error, Level}; use tracing::{error, info, Level};
use tracing_appender::non_blocking; use tracing_appender::non_blocking;
use tracing_subscriber::{ use tracing_subscriber::{
layer::SubscriberExt, layer::SubscriberExt,
@ -102,6 +102,7 @@ impl App {
} }
pub fn update(&mut self) { pub fn update(&mut self) {
self.world.tick();
let wptr = NonNull::from(&self.world); let wptr = NonNull::from(&self.world);
if let Err(e) = self.staged_exec.execute(wptr, true) { 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) { pub fn push_event<T: Event>(&mut self, event: T) {
let world = &mut self.world; 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"); .expect("missing events for event type! Must use `App::register_event` first");
events.push_event(event); 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 use system::*;
pub mod input_event; mod events;
pub use input_event::*;
pub mod events;
pub use events::*; pub use events::*;
pub mod buttons; mod buttons;
pub use buttons::*; pub use buttons::*;
pub mod action; mod action;
pub use action::*; pub use action::*;
pub type KeyCode = winit::keyboard::KeyCode; pub type KeyCode = winit::keyboard::KeyCode;
@ -122,7 +119,7 @@ pub fn keycode_from_str(s: &str) -> Option<KeyCode> {
"numpad_star" => Some(KeyCode::NumpadStar), "numpad_star" => Some(KeyCode::NumpadStar),
"quote" => Some(KeyCode::Quote), "quote" => Some(KeyCode::Quote),
"launch_app1" => Some(KeyCode::LaunchApp1), "launch_app1" => Some(KeyCode::LaunchApp1),
"launch_app1" => Some(KeyCode::LaunchApp2), "launch_app2" => Some(KeyCode::LaunchApp2),
"backslash" => Some(KeyCode::Backslash), "backslash" => Some(KeyCode::Backslash),
"caps_lock" => Some(KeyCode::CapsLock), "caps_lock" => Some(KeyCode::CapsLock),
"comma" => Some(KeyCode::Comma), "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 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) { fn write_scroll_delta(mouse_scroll_ev: &mut EventWriter<MouseScroll>, delta: &MouseScrollDelta) {
let event = match delta { let event = match delta {
@ -119,7 +119,6 @@ impl Plugin for InputPlugin {
app.add_resource(InputButtons::<MouseButton>::default()); app.add_resource(InputButtons::<MouseButton>::default());
app.add_resource(Touches::default()); app.add_resource(Touches::default());
app.register_event::<InputEvent>();
app.register_event::<MouseScroll>(); app.register_event::<MouseScroll>();
app.register_event::<MouseButton>(); app.register_event::<MouseButton>();
app.register_event::<MouseMotion>(); app.register_event::<MouseMotion>();

View File

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

View File

@ -203,7 +203,8 @@ impl Node for LightCullComputePass {
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, context: &mut RenderGraphContext) { fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, context: &mut RenderGraphContext) {
context.queue_buffer_write_with(LightCullComputePassSlots::IndexCounterBuffer, 0, 0); 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 { if screen_size.xy() != self.workgroup_size {
self.workgroup_size = screen_size.xy(); self.workgroup_size = screen_size.xy();
todo!("Resize buffers and other resources"); 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::<RenderAssets<Arc<GpuMaterial>>>(world);
Self::try_init_resource::<FxHashMap<Entity, uuid::Uuid>>(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(); render_meshes.clear();
} }
@ -459,7 +460,8 @@ impl Node for MeshPrepNode {
world.insert(en, interp); 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); transforms.send_to_gpu(queue);
} }

View File

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

View File

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

View File

@ -269,7 +269,8 @@ impl Renderer for BasicRenderer {
rt.surface.configure(&self.device, &rt.surface_config); */ rt.surface.configure(&self.device, &rt.surface_config); */
// update screen size resource in ecs // 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); world_ss.0 = glam::UVec2::new(new_size.width, new_size.height);

View File

@ -1,101 +1,16 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use glam::{DVec2, IVec2, UVec2, Vec2};
use lyra_ecs::{query::{filter::Changed, Entities, Res, View}, Component}; use lyra_ecs::{query::{filter::Changed, Entities, Res, View}, Component};
use lyra_math::Area;
use lyra_resource::Image; use lyra_resource::Image;
use tracing::error; 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}; pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel};
use crate::{plugin::Plugin, winit::WinitWindows}; 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 /// Flag component that
#[derive(Clone, Component)] #[derive(Clone, Component)]
pub struct PrimaryWindow; pub struct PrimaryWindow;
@ -168,7 +83,7 @@ pub struct WindowOptions {
/// * **Web:** Can only return None or Borderless(None). /// * **Web:** Can only return None or Borderless(None).
pub fullscreen: Option<Fullscreen>, 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. /// the top-left hand corner of the desktop.
/// ///
/// Note that the top-left hand corner of the desktop is not necessarily the same /// 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 /// * **Web:** Value is the top-left coordinates relative to the viewport. Note: this will be
/// the same value as [`WindowOptions::outer_position`]. /// the same value as [`WindowOptions::outer_position`].
/// * **Android / Wayland:** Unsupported. /// * **Android / Wayland:** Unsupported.
pub inner_position: Option<Position>, pub position: Option<IVec2>,
/// Gets/sets the size of the view in the window. /// Gets/sets the size of the view in the window.
/// ///
@ -193,7 +108,7 @@ pub struct WindowOptions {
/// ///
/// Platform-specific /// Platform-specific
/// * **Web:** The size of the canvas element. Doesnt account for CSS `transform`. /// * **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. /// Gets/sets if the window has decorations.
/// ///
@ -237,24 +152,6 @@ pub struct WindowOptions {
/// * **iOS:** Setting is not implemented, getting is unsupported. /// * **iOS:** Setting is not implemented, getting is unsupported.
pub visible: Option<bool>, 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. /// Gets/sets the window resize increments.
/// ///
/// This is a niche constraint hint usually employed by terminal emulators and other apps /// 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. /// * **X11:** Enabling IME will disable dead keys reporting during compose.
pub ime_allowed: bool, 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 /// Platform-specific
/// * **X11:** - area is not supported, only position. /// * **X11:** - area is not supported, only position.
/// * **iOS / Android / Web / Orbital:** Unsupported. /// * **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. /// Gets/sets the minimum size of the window.
/// ///
@ -380,12 +277,12 @@ pub struct WindowOptions {
pub window_level: WindowLevel, pub window_level: WindowLevel,
/// Show [window menu](https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu) /// 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. /// This is the context menu that is normally shown when interacting with the title bar. This is useful when implementing custom decorations.
/// Platform-specific /// Platform-specific
/// * **Android / iOS / macOS / Orbital / Wayland / Web / X11:** Unsupported. /// * **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). /// 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. /// * **Web:** Doesn't take into account CSS border, padding, or transform.
/// * **Android / Wayland / Windows / Orbital:** Unsupported. /// * **Android / Wayland / Windows / Orbital:** Unsupported.
pub occluded: bool, 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 { impl From<winit::window::WindowAttributes> for WindowOptions {
@ -408,15 +312,19 @@ impl From<winit::window::WindowAttributes> for WindowOptions {
enabled_buttons: value.enabled_buttons, enabled_buttons: value.enabled_buttons,
focused: false, focused: false,
fullscreen: value.fullscreen, fullscreen: value.fullscreen,
inner_position: None, position: value.position.map(|p| {
size: value.inner_size.map(|s| s.into()) let s = p.to_physical::<i32>(1.0);
.unwrap_or(Size::new_physical(1280, 720)), 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, decorated: value.decorations,
maximized: value.maximized, maximized: value.maximized,
minimized: None, minimized: None,
resizable: value.resizable, resizable: value.resizable,
visible: Some(value.visible), visible: Some(value.visible),
outer_position: value.position.map(|p| p.into()),
resize_increments: value.resize_increments.map(|r| r.into()), resize_increments: value.resize_increments.map(|r| r.into()),
scale_factor: 1.0, scale_factor: 1.0,
blur: value.blur, blur: value.blur,
@ -431,7 +339,7 @@ impl From<winit::window::WindowAttributes> for WindowOptions {
visible: true, visible: true,
}, },
ime_allowed: false, ime_allowed: false,
ime_cursor_area: None, physical_ime_cursor_area: None,
min_size: value.min_inner_size.map(|m| m.into()), min_size: value.min_inner_size.map(|m| m.into()),
max_size: value.max_inner_size.map(|m| m.into()), max_size: value.max_inner_size.map(|m| m.into()),
theme: value.preferred_theme, theme: value.preferred_theme,
@ -439,8 +347,9 @@ impl From<winit::window::WindowAttributes> for WindowOptions {
transparent: value.transparent, transparent: value.transparent,
window_icon: None, window_icon: None,
window_level: value.window_level, window_level: value.window_level,
show_window_menu: None, physical_window_menu_pos: None,
occluded: false, occluded: false,
physical_cursor_position: None,
} }
} }
} }
@ -458,12 +367,12 @@ impl WindowOptions {
att.enabled_buttons = self.enabled_buttons.clone(); att.enabled_buttons = self.enabled_buttons.clone();
att.fullscreen = self.fullscreen.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.decorations = self.decorated;
att.maximized = self.maximized; att.maximized = self.maximized;
att.resizable = self.resizable; att.resizable = self.resizable;
att.visible = self.visible.unwrap_or(true); 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.resize_increments = self.resize_increments.map(|i| i.into());
att.blur = self.blur; att.blur = self.blur;
att.content_protected = self.content_protected; att.content_protected = self.content_protected;
@ -483,6 +392,73 @@ impl WindowOptions {
att 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. /// The state of the window last time it was changed.
@ -491,7 +467,7 @@ impl WindowOptions {
/// when syncing the winit window with the component. /// when syncing the winit window with the component.
#[derive(Clone, Component)] #[derive(Clone, Component)]
pub struct LastWindow { pub struct LastWindow {
last: WindowOptions, pub last: WindowOptions,
} }
impl Deref for LastWindow { 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()); window.set_fullscreen(opts.fullscreen.clone());
} }
if opts.size != last.size { if opts.physical_size != last.physical_size {
if window.request_inner_size(opts.size).is_some() { 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"); 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()); window.set_visible(opts.visible.unwrap());
} }
if opts.outer_position != last.outer_position && opts.outer_position.is_some() { if opts.position != last.position && opts.position.is_some() {
window.set_outer_position(opts.outer_position.unwrap()); 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 { 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 { if opts.blur != last.blur {
window.set_blur(opts.blur) window.set_blur(opts.blur);
} }
if opts.content_protected != last.content_protected { 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); window.set_ime_allowed(opts.ime_allowed);
} }
if opts.ime_cursor_area != last.ime_cursor_area && opts.ime_cursor_area.is_some() { if opts.physical_ime_cursor_area != last.physical_ime_cursor_area && opts.physical_ime_cursor_area.is_some() {
let area = opts.ime_cursor_area.as_ref().unwrap(); let area = opts.physical_ime_cursor_area.unwrap();
window.set_ime_cursor_area(area.position, area.size); 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 { 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); window.set_window_level(opts.window_level);
} }
if opts.show_window_menu != last.show_window_menu && opts.show_window_menu.is_some() { if opts.physical_window_menu_pos != last.physical_window_menu_pos && opts.physical_window_menu_pos.is_some() {
window.show_window_menu(opts.show_window_menu.unwrap()); 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(); last.last = opts.clone();

View File

@ -1,7 +1,7 @@
use std::{collections::VecDeque, sync::Arc}; use std::{collections::VecDeque, sync::Arc};
use async_std::task::block_on; use async_std::task::block_on;
use glam::IVec2; use glam::{DVec2, IVec2, UVec2};
use lyra_ecs::Entity; use lyra_ecs::Entity;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
@ -17,7 +17,7 @@ use crate::{
plugin::Plugin, plugin::Plugin,
render::{ render::{
renderer::BasicRenderer, renderer::BasicRenderer,
window::{PrimaryWindow, Size, WindowOptions}, window::{LastWindow, PrimaryWindow, WindowOptions},
}, },
}; };
@ -143,7 +143,8 @@ impl ApplicationHandler for WinitRunner {
Err(e) => eprintln!("{:?}", e), 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() { for window in windows.windows.values() {
window.request_redraw(); window.request_redraw();
} }
@ -152,13 +153,15 @@ impl ApplicationHandler for WinitRunner {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let world = &mut self.app.world; 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 to_create_window = windows.window_queue.pop_front().unwrap_or_default();
let window_attr = to_create_window.as_attributes(); let window_attr = to_create_window.as_attributes();
drop(windows); 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 wid = windows.create_window(event_loop, en, window_attr).unwrap();
let window = windows.windows.get(&wid).unwrap().clone(); let window = windows.windows.get(&wid).unwrap().clone();
drop(windows); drop(windows);
@ -195,28 +198,59 @@ impl ApplicationHandler for WinitRunner {
self.app.push_event(event.clone()); self.app.push_event(event.clone());
match event { 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::ActivationTokenDone { .. } => todo!(),
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
self.app.on_resize(physical_size); self.app.on_resize(physical_size);
let mut window_opts = self let (mut window, mut last_window) = self
.app .app
.world .world
.get_resource::<WinitWindows>() .get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity .window_to_entity
.get(&window_id) .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(); .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) => { WindowEvent::Moved(physical_position) => {
let mut state = self.app.world.get_resource_or_else(WindowState::new); let mut state = self.app.world.get_resource_or_else(WindowState::new);
state.position = IVec2::new(physical_position.x, physical_position.y); state.position = IVec2::new(physical_position.x, physical_position.y);
} },
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
self.app.on_exit(); self.app.on_exit();
event_loop.exit(); event_loop.exit();
} },
WindowEvent::Destroyed => todo!(), WindowEvent::Destroyed => todo!(),
WindowEvent::DroppedFile(_path_buf) => todo!(), WindowEvent::DroppedFile(_path_buf) => todo!(),
WindowEvent::HoveredFile(_path_buf) => todo!(), WindowEvent::HoveredFile(_path_buf) => todo!(),
@ -226,51 +260,55 @@ impl ApplicationHandler for WinitRunner {
.app .app
.world .world
.get_resource::<WinitWindows>() .get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity .window_to_entity
.get(&window_id) .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>(*e).get())
.unwrap(); .unwrap();
window_opts.focused = focused; window_opts.focused = focused;
} },
WindowEvent::ModifiersChanged(modifiers) => { WindowEvent::ModifiersChanged(modifiers) => {
debug!("modifiers changed: {:?}", modifiers) debug!("modifiers changed: {:?}", modifiers)
} },
WindowEvent::ScaleFactorChanged { scale_factor, .. } => { WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
let mut window_opts = self let mut window_opts = self
.app .app
.world .world
.get_resource::<WinitWindows>() .get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity .window_to_entity
.get(&window_id) .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>(*e).get())
.unwrap(); .unwrap();
window_opts.scale_factor = scale_factor; window_opts.scale_factor = scale_factor;
} },
WindowEvent::ThemeChanged(theme) => { WindowEvent::ThemeChanged(theme) => {
let mut window_opts = self let mut window_opts = self
.app .app
.world .world
.get_resource::<WinitWindows>() .get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity .window_to_entity
.get(&window_id) .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>(*e).get())
.unwrap(); .unwrap();
window_opts.theme = Some(theme); window_opts.theme = Some(theme);
} },
WindowEvent::Occluded(occ) => { WindowEvent::Occluded(occ) => {
let mut window_opts = self let mut window_opts = self
.app .app
.world .world
.get_resource::<WinitWindows>() .get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity .window_to_entity
.get(&window_id) .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>(*e).get())
.unwrap(); .unwrap();
window_opts.occluded = occ; window_opts.occluded = occ;
} },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
//debug!("should redraw"); //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 mod angle;
pub use angle::*; pub use angle::*;
mod area;
pub use area::*;
pub mod transform; pub mod transform;
pub use transform::*; pub use transform::*;

View File

@ -2,7 +2,7 @@
use std::{any::TypeId, any::Any}; 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; extern crate self as lyra_reflect;
@ -250,9 +250,9 @@ pub trait FromType<T> {
pub trait ReflectWorldExt { pub trait ReflectWorldExt {
/// Retrieves the type registry from the world. /// Retrieves the type registry from the world.
fn get_type_registry(&self) -> AtomicRef<TypeRegistry>; fn get_type_registry(&self) -> Option<Res<TypeRegistry>>;
/// Retrieves the type registry mutably from the world. /// 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 /// 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>>; fn get_type<T>(&self, type_id: TypeId) -> Option<AtomicRef<RegisteredType>>;
/// Get a mutable registered type from the type registry in the world. returns `None` if the type is not registered. /// Get a mutable registered type from the type registry in the world. returns `None` if the type is not registered.
@ -262,17 +262,19 @@ pub trait ReflectWorldExt {
} }
impl ReflectWorldExt for World { impl ReflectWorldExt for World {
fn get_type_registry(&self) -> AtomicRef<TypeRegistry> { fn get_type_registry(&self) -> Option<Res<TypeRegistry>> {
self.get_resource::<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>() self.get_resource_mut::<TypeRegistry>()
} }
fn get_type<T>(&self, type_id: TypeId) -> Option<AtomicRef<RegisteredType>> { 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) { if r.has_type(type_id) {
let r = r.get_inner();
Some(AtomicRef::map(r, |tr| tr.get_type(type_id).unwrap())) Some(AtomicRef::map(r, |tr| tr.get_type(type_id).unwrap()))
} else { } else {
None None
@ -280,8 +282,10 @@ impl ReflectWorldExt for World {
} }
fn get_type_mut<T>(&self, type_id: TypeId) -> Option<AtomicRefMut<RegisteredType>> { fn get_type_mut<T>(&self, type_id: TypeId) -> Option<AtomicRefMut<RegisteredType>> {
let r = self.get_resource_mut::<TypeRegistry>(); let r = self.get_resource_mut::<TypeRegistry>()
.expect("world is missing TypeRegistry resource");
if r.has_type(type_id) { if r.has_type(type_id) {
let r = r.get_inner();
Some(AtomicRefMut::map(r, |tr| tr.get_type_mut(type_id).unwrap())) Some(AtomicRefMut::map(r, |tr| tr.get_type_mut(type_id).unwrap()))
} else { } else {
None None
@ -289,7 +293,9 @@ impl ReflectWorldExt for World {
} }
fn get_type_or_default<T>(&self, type_id: TypeId) -> AtomicRefMut<RegisteredType> { fn get_type_or_default<T>(&self, type_id: TypeId) -> AtomicRefMut<RegisteredType> {
let r = self.get_resource_mut::<TypeRegistry>(); let r = self.get_resource_mut::<TypeRegistry>()
.expect("world is missing TypeRegistry resource");
let r = r.get_inner();
AtomicRefMut::map(r, |tr| tr.get_type_or_default(type_id)) 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 { Self {
type_id: TypeId::of::<T>(), type_id: TypeId::of::<T>(),
fn_reflect: |world: &World| { fn_reflect: |world: &World| {
world.try_get_resource::<T>() world.get_resource::<T>()
.map(|r| { .map(|r| {
// TODO: figure out change tracking for reflected resource
let r = r.get_inner();
AtomicRef::map(r, |r| r as &dyn Reflect) AtomicRef::map(r, |r| r as &dyn Reflect)
}) })
}, },
fn_reflect_mut: |world: &mut World| { fn_reflect_mut: |world: &mut World| {
world.try_get_resource_mut::<T>() world.get_resource_mut::<T>()
.map(|r| { .map(|r| {
// TODO: figure out change tracking for reflected resource
let r = r.get_inner();
AtomicRefMut::map(r, |r| r as &mut dyn Reflect) AtomicRefMut::map(r, |r| r as &mut dyn Reflect)
}) })
}, },
fn_reflect_ptr: |world: &mut World| unsafe { fn_reflect_ptr: |world: &mut World| unsafe {
world.try_get_resource_ptr::<T>() world.get_resource_ptr::<T>()
.map(|ptr| ptr.cast::<u8>()) .map(|ptr| ptr.cast::<u8>())
}, },
fn_refl_insert: |world: &mut World, this: Box<dyn Reflect>| { 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>>>> { 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) man.watcher_event_recv(path)
} }