game: improve event system

This commit is contained in:
SeanOMik 2024-09-27 21:03:57 -04:00
parent f5aca87ede
commit d6d6b2df72
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
13 changed files with 345 additions and 202 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

@ -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

@ -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::*;