Compare commits

..

No commits in common. "eb43fad6c7d013e0f20699b94bd380e0251b66f3" and "2b44d7f354780b81073ce12033d1aa407a29589c" have entirely different histories.

51 changed files with 1937 additions and 3040 deletions

2636
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ use tracing::info;
#[async_std::main] #[async_std::main]
async fn main() { async fn main() {
let action_handler_plugin = |app: &mut App| { let action_handler_plugin = |game: &mut Game| {
let action_handler = ActionHandler::builder() let action_handler = ActionHandler::builder()
.add_layout(LayoutId::from(0)) .add_layout(LayoutId::from(0))
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis)) .add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
@ -105,7 +105,7 @@ async fn main() {
.await; .await;
} }
fn setup_scene_plugin(app: &mut App) { fn setup_scene_plugin(game: &mut Game) {
let world = game.world_mut(); let world = game.world_mut();
let resman = world.get_resource_mut::<ResourceManager>(); let resman = world.get_resource_mut::<ResourceManager>();
let camera_gltf = resman let camera_gltf = resman

View File

@ -18,7 +18,7 @@ use lyra_engine::{
#[async_std::main] #[async_std::main]
async fn main() { async fn main() {
let action_handler_plugin = |app: &mut App| { let action_handler_plugin = |game: &mut Game| {
let action_handler = ActionHandler::builder() let action_handler = ActionHandler::builder()
.add_layout(LayoutId::from(0)) .add_layout(LayoutId::from(0))
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis)) .add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
@ -99,7 +99,7 @@ async fn main() {
.await; .await;
} }
fn setup_scene_plugin(app: &mut App) { fn setup_scene_plugin(game: &mut Game) {
let world = game.world_mut(); let world = game.world_mut();
let resman = world.get_resource_mut::<ResourceManager>(); let resman = world.get_resource_mut::<ResourceManager>();
let camera_gltf = resman let camera_gltf = resman
@ -136,7 +136,7 @@ fn setup_scene_plugin(app: &mut App) {
world.spawn((camera, FreeFlyCamera::default())); world.spawn((camera, FreeFlyCamera::default()));
} }
fn setup_script_plugin(app: &mut App) { fn setup_script_plugin(game: &mut Game) {
game.with_plugin(LuaScriptingPlugin); game.with_plugin(LuaScriptingPlugin);
let world = game.world_mut(); let world = game.world_mut();

View File

@ -21,7 +21,7 @@ const POINT_LIGHT_MIN_Z: f32 = -5.0;
#[async_std::main] #[async_std::main]
async fn main() { async fn main() {
let action_handler_plugin = |app: &mut App| { let action_handler_plugin = |game: &mut Game| {
let action_handler = ActionHandler::builder() let action_handler = ActionHandler::builder()
.add_layout(LayoutId::from(0)) .add_layout(LayoutId::from(0))
@ -80,7 +80,7 @@ async fn main() {
.run().await; .run().await;
} }
fn setup_scene_plugin(app: &mut App) { fn setup_scene_plugin(game: &mut Game) {
let fps_counter = |mut counter: ResMut<fps_counter::FPSCounter>, delta: Res<DeltaTime>| -> anyhow::Result<()> { let fps_counter = |mut counter: ResMut<fps_counter::FPSCounter>, delta: Res<DeltaTime>| -> anyhow::Result<()> {
let tick = counter.tick(); let tick = counter.tick();
@ -174,7 +174,7 @@ fn setup_scene_plugin(app: &mut App) {
world.spawn(( camera, FreeFlyCamera::default() )); world.spawn(( camera, FreeFlyCamera::default() ));
} }
fn camera_debug_plugin(app: &mut App) { fn camera_debug_plugin(game: &mut Game) {
let sys = |handler: Res<ActionHandler>, view: View<&mut CameraComponent>| -> anyhow::Result<()> { let sys = |handler: Res<ActionHandler>, view: View<&mut CameraComponent>| -> anyhow::Result<()> {
if handler.was_action_just_pressed("Debug") { if handler.was_action_just_pressed("Debug") {
for mut cam in view.into_iter() { for mut cam in view.into_iter() {

View File

@ -19,7 +19,7 @@ use lyra_engine::{
#[async_std::main] #[async_std::main]
async fn main() { async fn main() {
let action_handler_plugin = |app: &mut App| { let action_handler_plugin = |game: &mut Game| {
let action_handler = ActionHandler::builder() let action_handler = ActionHandler::builder()
.add_layout(LayoutId::from(0)) .add_layout(LayoutId::from(0))
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis)) .add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
@ -99,7 +99,7 @@ async fn main() {
.await; .await;
} }
fn setup_scene_plugin(app: &mut App) { fn setup_scene_plugin(game: &mut Game) {
let world = game.world_mut(); let world = game.world_mut();
let resman = world.get_resource_mut::<ResourceManager>(); let resman = world.get_resource_mut::<ResourceManager>();

View File

@ -1,6 +1,6 @@
use lyra_engine::{ use lyra_engine::{
assets::{gltf::Gltf, ResourceManager}, assets::{gltf::Gltf, ResourceManager},
game::App, game::Game,
input::{ input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
@ -16,7 +16,7 @@ use lyra_engine::{
#[async_std::main] #[async_std::main]
async fn main() { async fn main() {
let action_handler_plugin = |app: &mut App| { let action_handler_plugin = |game: &mut Game| {
let action_handler = ActionHandler::builder() let action_handler = ActionHandler::builder()
.add_layout(LayoutId::from(0)) .add_layout(LayoutId::from(0))
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis)) .add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
@ -31,73 +31,75 @@ async fn main() {
.bind( .bind(
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_FORWARD_BACKWARD,
&[ &[
ActionSource::Keyboard(KeyCode::KeyW).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::KeyS).into_binding_modifier(-1.0), ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0),
], ],
) )
.bind( .bind(
ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_LEFT_RIGHT,
&[ &[
ActionSource::Keyboard(KeyCode::KeyA).into_binding_modifier(-1.0), ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::KeyD).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0),
], ],
) )
.bind( .bind(
ACTLBL_MOVE_UP_DOWN, ACTLBL_MOVE_UP_DOWN,
&[ &[
ActionSource::Keyboard(KeyCode::KeyC).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::KeyZ).into_binding_modifier(-1.0), ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0),
], ],
) )
.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::Left).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::ArrowRight).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
], ],
) )
.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::Up).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::ArrowDown).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
], ],
) )
.bind( .bind(
ACTLBL_LOOK_ROLL, ACTLBL_LOOK_ROLL,
&[ &[
ActionSource::Keyboard(KeyCode::KeyE).into_binding_modifier(-1.0), ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::KeyQ).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
], ],
) )
.bind( .bind(
"Debug", "Debug",
&[ActionSource::Keyboard(KeyCode::KeyB).into_binding()], &[ActionSource::Keyboard(KeyCode::B).into_binding()],
) )
.finish(), .finish(),
) )
.finish(); .finish();
//let world = app.world; let world = game.world_mut();
app.add_resource(action_handler); world.add_resource(action_handler);
app.with_plugin(InputActionPlugin); game.with_plugin(InputActionPlugin);
}; };
let mut a = App::new(); Game::initialize()
a.with_plugin(lyra_engine::DefaultPlugins) .await
.with_plugin(lyra_engine::DefaultPlugins)
.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)
.with_plugin(FreeFlyCameraPlugin); .with_plugin(FreeFlyCameraPlugin)
a.run(); .run()
.await;
} }
fn setup_scene_plugin(app: &mut App) { fn setup_scene_plugin(game: &mut Game) {
let world = &mut app.world; let world = game.world_mut();
let resman = world.get_resource_mut::<ResourceManager>(); let resman = world.get_resource_mut::<ResourceManager>();
/* let camera_gltf = resman /* let camera_gltf = resman
.request::<Gltf>("../assets/AntiqueCamera.glb") .request::<Gltf>("../assets/AntiqueCamera.glb")
.unwrap(); .unwrap();
@ -144,4 +146,4 @@ fn setup_scene_plugin(app: &mut App) {
let mut camera = CameraComponent::new_3d(); let mut camera = CameraComponent::new_3d();
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()));
} }

View File

@ -260,7 +260,7 @@ async fn main() {
Ok(()) Ok(())
}; };
let camera_debug_plugin = move |app: &mut App| { let camera_debug_plugin = move |game: &mut Game| {
let sys = |handler: Res<ActionHandler>, view: View<&mut CameraComponent>| -> anyhow::Result<()> { let sys = |handler: Res<ActionHandler>, view: View<&mut CameraComponent>| -> anyhow::Result<()> {
if handler.was_action_just_pressed("Debug") { if handler.was_action_just_pressed("Debug") {
for mut cam in view.into_iter() { for mut cam in view.into_iter() {
@ -275,7 +275,7 @@ async fn main() {
game.with_system("update_world_transforms", scene::system_update_world_transforms, &[]); game.with_system("update_world_transforms", scene::system_update_world_transforms, &[]);
}; };
let action_handler_plugin = |app: &mut App| { let action_handler_plugin = |game: &mut Game| {
let action_handler = ActionHandler::builder() let action_handler = ActionHandler::builder()
.add_layout(LayoutId::from(0)) .add_layout(LayoutId::from(0))
@ -327,7 +327,7 @@ async fn main() {
game.with_plugin(InputActionPlugin); game.with_plugin(InputActionPlugin);
}; };
/* let script_test_plugin = |app: &mut App| { /* let script_test_plugin = |game: &mut Game| {
game.with_plugin(LuaScriptingPlugin); game.with_plugin(LuaScriptingPlugin);
let world = game.world_mut(); let world = game.world_mut();

View File

@ -1,5 +1,3 @@
#![feature(associated_type_defaults)]
extern crate self as lyra_ecs; extern crate self as lyra_ecs;
#[allow(unused_imports)] #[allow(unused_imports)]

View File

@ -197,9 +197,11 @@ mod tests {
let mut view_iter = view.into_iter(); let mut view_iter = view.into_iter();
while let Some((_e, view_row)) = view_iter.next(&world) { while let Some((_e, view_row)) = view_iter.next(&world) {
assert_eq!(view_row.len(), 1); assert_eq!(view_row.len(), 1);
let mut row_iter = view_row.iter();
let mut row_iter = view_row.row.iter();
let dynamic_type = row_iter.next().unwrap(); let dynamic_type = row_iter.next().unwrap();
let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() }; let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() };
assert_eq!(*component_data, 50); assert_eq!(*component_data, 50);
} }
@ -224,9 +226,11 @@ mod tests {
for (_e, view_row) in view.into_iter() { for (_e, view_row) in view.into_iter() {
assert_eq!(view_row.len(), 1); assert_eq!(view_row.len(), 1);
let mut row_iter = view_row.iter();
let dynamic_type = row_iter.next().unwrap(); let mut row_iter = view_row.row.iter();
let dynamic_type = row_iter.next().unwrap();
let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() }; let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() };
assert_eq!(*component_data, 50); assert_eq!(*component_data, 50);
} }

View File

@ -1,97 +0,0 @@
use std::marker::PhantomData;
use crate::{query::{AsFilter, AsQuery, Fetch, Filter, Query}, Component, ComponentColumn, DynTypeId, Tick, World};
pub struct ChangedFetcher<'a, T> {
col: &'a ComponentColumn,
tick: Tick,
_phantom: PhantomData<&'a T>,
}
impl<'a, T> Fetch<'a> for ChangedFetcher<'a, T>
where
T: 'a,
{
type Item = bool;
fn dangling() -> Self {
unreachable!()
}
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
let tick = self.col.entity_ticks[entity.0 as usize];
tick >= self.tick
}
}
/// A filter that fetches components that have changed.
///
/// Since [`AsQuery`] is implemented for `&T`, you can use this query like this:
/// ```nobuild
/// for ts in world.view::<&T>() {
/// println!("Got a &T!");
/// }
/// ```
pub struct Changed<T> {
type_id: DynTypeId,
_phantom: PhantomData<T>
}
impl<T: Component> Default for Changed<T> {
fn default() -> Self {
Self {
type_id: DynTypeId::of::<T>(),
_phantom: PhantomData,
}
}
}
// manually implemented to avoid a Copy bound on T
impl<T> Copy for Changed<T> {}
// manually implemented to avoid a Clone bound on T
impl<T> Clone for Changed<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Component> Changed<T> {
pub fn new() -> Self {
Self::default()
}
}
impl<T: Component> Query for Changed<T>
where
T: 'static
{
type Item<'a> = bool;
type Fetch<'a> = ChangedFetcher<'a, T>;
fn new() -> Self {
Changed::<T>::new()
}
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
archetype.has_column(self.type_id)
}
unsafe fn fetch<'a>(&self, w: &'a World, a: &'a crate::archetype::Archetype, _: crate::Tick) -> Self::Fetch<'a> {
ChangedFetcher {
col: a.get_column(self.type_id).unwrap(),
tick: w.current_tick(),
_phantom: PhantomData::<&T>,
}
}
}
impl<T: Component> AsQuery for Changed<T> {
type Query = Self;
}
impl<'a, T: Component> Filter for Changed<T> { }
impl<T: Component> AsFilter for Changed<T> {
type Filter = Self;
}

View File

@ -5,7 +5,4 @@ mod or;
pub use or::*; pub use or::*;
mod not; mod not;
pub use not::*; pub use not::*;
mod changed;
pub use changed::*;

View File

@ -89,12 +89,6 @@ pub trait AsQuery {
type Query: Query; type Query: Query;
} }
/// A trait for getting the filter of a type.
pub trait AsFilter {
/// The query for this type
type Filter: Filter;
}
pub trait IntoQuery { pub trait IntoQuery {
fn into_query(self) -> Self; fn into_query(self) -> Self;
} }
@ -131,22 +125,10 @@ impl Query for () {
} }
} }
impl Filter for () {
type Item<'a> = bool;
}
impl AsQuery for () { impl AsQuery for () {
type Query = (); type Query = ();
} }
pub trait Filter: Query {
type Item<'a> = bool;
}
impl AsFilter for () {
type Filter = ();
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{World, archetype::Archetype, tests::Vec2}; use crate::{World, archetype::Archetype, tests::Vec2};

View File

@ -1,8 +1,8 @@
use crate::World; use crate::World;
use super::{Query, Fetch, AsQuery, Filter, AsFilter}; use super::{Query, Fetch, AsQuery};
/* impl<'a, F1> Fetch<'a> for (F1,) impl<'a, F1> Fetch<'a> for (F1,)
where where
F1: Fetch<'a>, F1: Fetch<'a>,
{ {
@ -102,7 +102,7 @@ where
Q2: AsQuery, Q2: AsQuery,
{ {
type Query = (Q1::Query, Q2::Query); type Query = (Q1::Query, Q2::Query);
} */ }
macro_rules! impl_bundle_tuple { macro_rules! impl_bundle_tuple {
( $($name: ident),+ ) => ( ( $($name: ident),+ ) => (
@ -154,39 +154,10 @@ macro_rules! impl_bundle_tuple {
impl<$($name: AsQuery),+> AsQuery for ($($name,)+) { impl<$($name: AsQuery),+> AsQuery for ($($name,)+) {
type Query = ($($name::Query,)+); type Query = ($($name::Query,)+);
} }
#[allow(non_snake_case)]
impl<$($name: Filter),+> Filter for ($($name,)+) {
//type Item<'a> = ($(<$name as Query>::Item<'a>,)+);
//type Fetch<'a> = ($(<$name as Query>::Fetch<'a>,)+);
/* fn new() -> Self {
( $($name::new(),)+ )
}
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
let ( $($name,)+ ) = self;
// this is the only way I could figure out how to do an 'and'
let bools = vec![$($name.can_visit_archetype(archetype),)+];
bools.iter().all(|b| *b)
}
unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
let ( $($name,)+ ) = self;
( $($name.fetch(world, archetype, tick),)+ )
} */
}
impl<$($name: AsFilter),+> AsFilter for ($($name,)+) {
type Filter = ($($name::Filter,)+);
}
); );
} }
// Hopefully up to 16 queries in a SINGLE view is enough // Hopefully up to 16 queries in a SINGLE view is enough
impl_bundle_tuple! { Q1 }
impl_bundle_tuple! { Q1, Q2 }
impl_bundle_tuple! { Q1, Q2, Q3 } impl_bundle_tuple! { Q1, Q2, Q3 }
impl_bundle_tuple! { Q1, Q2, Q3, Q4 } impl_bundle_tuple! { Q1, Q2, Q3, Q4 }
impl_bundle_tuple! { Q1, Q2, Q3, Q4, Q5 } impl_bundle_tuple! { Q1, Q2, Q3, Q4, Q5 }

View File

@ -2,11 +2,11 @@ use std::ops::Range;
use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, EntityId, Tick}; use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, EntityId, Tick};
use super::{AsFilter, AsQuery, Fetch, Filter, Query}; use super::{Query, Fetch, AsQuery};
pub type View<'a, Q, F = ()> = ViewState<'a, <Q as AsQuery>::Query, <F as AsQuery>::Query>; pub type View<'a, Q, F = ()> = ViewState<'a, <Q as AsQuery>::Query, <F as AsQuery>::Query>;
pub struct ViewState<'a, Q: Query, F: Filter> { pub struct ViewState<'a, Q: Query, F: Query> {
world: &'a World, world: &'a World,
query: Q, query: Q,
filter: F, filter: F,
@ -16,7 +16,7 @@ pub struct ViewState<'a, Q: Query, F: Filter> {
impl<'a, Q, F> ViewState<'a, Q, F> impl<'a, Q, F> ViewState<'a, Q, F>
where where
Q: Query, Q: Query,
F: Filter, F: Query,
{ {
pub fn new(world: &'a World, query: Q, filter: F, archetypes: Vec<&'a Archetype>) -> Self { pub fn new(world: &'a World, query: Q, filter: F, archetypes: Vec<&'a Archetype>) -> Self {
Self { Self {
@ -38,7 +38,7 @@ where
} }
/// Consumes `self`, adding a filter to the view. /// Consumes `self`, adding a filter to the view.
pub fn with<U: AsFilter>(self, filter: U::Filter) -> ViewState<'a, Q, (F, U::Filter)> { pub fn with<U: AsQuery>(self, filter: U::Query) -> ViewState<'a, Q, (F, U::Query)> {
ViewState::new(self.world, self.query, (self.filter, filter), self.archetypes) ViewState::new(self.world, self.query, (self.filter, filter), self.archetypes)
} }
} }
@ -46,19 +46,18 @@ where
impl<'a, Q, F> IntoIterator for ViewState<'a, Q, F> impl<'a, Q, F> IntoIterator for ViewState<'a, Q, F>
where where
Q: Query, Q: Query,
F: Filter, F: Query,
{ {
type Item = Q::Item<'a>; type Item = Q::Item<'a>;
type IntoIter = ViewIter<'a, Q, F>; type IntoIter = ViewIter<'a, Q, F>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
//let tick = self.world.tick_tracker().tick_when(Q::MUTATES); let tick = self.world.tick_tracker().tick_when(Q::MUTATES);
ViewIter { ViewIter {
world: self.world, world: self.world,
tick: self.world.current_tick(), tick,
has_ticked: false,
query: self.query, query: self.query,
filter: self.filter, filter: self.filter,
fetcher: None, fetcher: None,
@ -70,10 +69,9 @@ where
} }
} }
pub struct ViewIter<'a, Q: Query, F: Filter> { pub struct ViewIter<'a, Q: Query, F: Query> {
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>>,
@ -86,7 +84,7 @@ pub struct ViewIter<'a, Q: Query, F: Filter> {
impl<'a, Q, F> Iterator for ViewIter<'a, Q, F> impl<'a, Q, F> Iterator for ViewIter<'a, Q, F>
where where
Q: Query, Q: Query,
F: Filter, F: Query,
{ {
type Item = Q::Item<'a>; type Item = Q::Item<'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);
} }
@ -156,17 +147,17 @@ pub struct ViewOne<'a, Q: Query> {
impl<'a, Q: Query> ViewOne<'a, Q> { impl<'a, Q: Query> ViewOne<'a, Q> {
pub fn new(world: &'a World, entity: EntityId, query: Q) -> Self { pub fn new(world: &'a World, entity: EntityId, query: Q) -> Self {
//let tick = world.tick_tracker().tick_when(Q::MUTATES); let tick = world.tick_tracker().tick_when(Q::MUTATES);
Self { Self {
world, world,
tick: world.current_tick(), tick,
entity, entity,
query query
} }
} }
pub fn get(self) -> Option<Q::Item<'a>> { pub fn get(&self) -> Option<Q::Item<'a>> {
if let Some(record) = self.world.entities.arch_index.get(&self.entity) { if let Some(record) = self.world.entities.arch_index.get(&self.entity) {
let arch = self.world.archetypes.get(&record.id) let arch = self.world.archetypes.get(&record.id)
.expect("An invalid record was specified for an entity"); .expect("An invalid record was specified for an entity");
@ -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

@ -2,7 +2,6 @@ use std::marker::PhantomData;
use lyra_ecs_derive::Component; use lyra_ecs_derive::Component;
use crate::query::Filter;
use crate::query::Query; use crate::query::Query;
use crate::query::ViewState; use crate::query::ViewState;
use crate::Entity; use crate::Entity;
@ -99,7 +98,7 @@ impl World {
impl<'a, Q, F> ViewState<'a, Q, F> impl<'a, Q, F> ViewState<'a, Q, F>
where where
Q: Query, Q: Query,
F: Filter, F: Query,
{ {
/// Consumes `self` to return a view that fetches the relation to a specific target entity. /// Consumes `self` to return a view that fetches the relation to a specific target entity.
pub fn relates_to<R>(self, target: Entity) -> ViewState<'a, (Q, QueryRelatesTo<R>), F> pub fn relates_to<R>(self, target: Entity) -> ViewState<'a, (Q, QueryRelatesTo<R>), F>

View File

@ -1,7 +1,7 @@
use std::{any::Any, marker::PhantomData, ptr::NonNull}; use std::{any::Any, marker::PhantomData, ptr::NonNull};
use paste::paste; use paste::paste;
use crate::{World, Access, ResourceObject, query::{Query, Filter, ViewState, ResMut, Res}}; use crate::{World, Access, ResourceObject, query::{Query, ViewState, ResMut, Res}};
use super::{System, IntoSystem}; use super::{System, IntoSystem};
@ -140,7 +140,7 @@ impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P }
impl<'c, Q, F> FnArgFetcher for ViewState<'c, Q, F> impl<'c, Q, F> FnArgFetcher for ViewState<'c, Q, F>
where where
Q: Query + 'static, Q: Query + 'static,
F: Filter + 'static, F: Query + 'static,
{ {
type State = (Q, F); type State = (Q, F);
type Arg<'a, 'state> = ViewState<'a, Q, F>; type Arg<'a, 'state> = ViewState<'a, Q, F>;

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, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker};
/// The id of the entity for the Archetype. /// The id of the entity for the Archetype.
/// ///
@ -347,9 +347,9 @@ impl World {
} }
/// View into the world for a set of entities that satisfy the query and the filter. /// View into the world for a set of entities that satisfy the query and the filter.
pub fn filtered_view<Q: AsQuery, F: AsFilter>(&self) -> ViewState<Q::Query, F::Filter> { pub fn filtered_view<Q: AsQuery, F: AsQuery>(&self) -> ViewState<Q::Query, F::Query> {
let archetypes = self.archetypes.values().collect(); let archetypes = self.archetypes.values().collect();
ViewState::<Q::Query, F::Filter>::new(self, Q::Query::new(), F::Filter::new(), archetypes) ViewState::<Q::Query, F::Query>::new(self, Q::Query::new(), F::Query::new(), archetypes)
} }
/// View into the world for a set of entities that satisfy the queries. /// View into the world for a set of entities that satisfy the queries.
@ -360,9 +360,9 @@ impl World {
} }
/// View into the world for a set of entities that satisfy the queries. /// View into the world for a set of entities that satisfy the queries.
pub fn filtered_view_iter<Q: AsQuery, F: AsFilter>(&self) -> ViewIter<Q::Query, F::Filter> { pub fn filtered_view_iter<Q: AsQuery, F: AsQuery>(&self) -> ViewIter<Q::Query, F::Query> {
let archetypes = self.archetypes.values().collect(); let archetypes = self.archetypes.values().collect();
let v = ViewState::new(self, Q::Query::new(), F::Filter::new(), archetypes); let v = ViewState::new(self, Q::Query::new(), F::Query::new(), archetypes);
v.into_iter() v.into_iter()
} }

View File

@ -12,30 +12,31 @@ lyra-math = { path = "../lyra-math" }
lyra-scene = { path = "../lyra-scene" } lyra-scene = { path = "../lyra-scene" }
wgsl_preprocessor = { path = "../wgsl-preprocessor" } wgsl_preprocessor = { path = "../wgsl-preprocessor" }
winit = "0.30.5" winit = "0.28.1"
wgpu = { version = "22.1.0" } wgpu = { version = "0.15.1", features = [ "expose-ids"] }
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] } tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] }
tracing-log = "0.2.0" tracing-log = "0.1.3"
tracing-appender = "0.2.2" tracing-appender = "0.2.2"
tracing-tracy = { version = "0.11.0", optional = true } tracing-tracy = { version = "0.11.0", optional = true }
async-std = { version = "1.12.0", features = [ "unstable", "attributes" ] } async-std = { version = "1.12.0", features = [ "unstable", "attributes" ] }
cfg-if = "1" cfg-if = "1"
bytemuck = { version = "1.12", features = [ "derive", "min_const_generics" ] } bytemuck = { version = "1.12", features = [ "derive", "min_const_generics" ] }
image = "0.25.2" image = { version = "0.24", default-features = false, features = ["png", "jpeg"] }
anyhow = "1.0" anyhow = "1.0"
instant = "0.1" instant = "0.1"
async-trait = "0.1.65" async-trait = "0.1.65"
glam = { version = "0.29.0", features = ["bytemuck", "debug-glam-assert"] } glam = { version = "0.24.0", features = ["bytemuck", "debug-glam-assert"] }
gilrs-core = "0.5.6"
syn = "2.0.26" syn = "2.0.26"
quote = "1.0.29" quote = "1.0.29"
uuid = { version = "1.5.0", features = ["v4", "fast-rng"] } uuid = { version = "1.5.0", features = ["v4", "fast-rng"] }
itertools = "0.13.0" itertools = "0.11.0"
thiserror = "1.0.56" thiserror = "1.0.56"
unique = "0.9.1" unique = "0.9.1"
rustc-hash = "2.0.0" rustc-hash = "1.1.0"
petgraph = { version = "0.6.5", features = ["matrix_graph"] } petgraph = { version = "0.6.5", features = ["matrix_graph"] }
bind_match = "0.1.2" bind_match = "0.1.2"
round_mult = "0.1.3" round_mult = "0.1.3"

View File

@ -42,8 +42,8 @@ pub fn delta_time_system(world: &mut World) -> anyhow::Result<()> {
pub struct DeltaTimePlugin; pub struct DeltaTimePlugin;
impl Plugin for DeltaTimePlugin { impl Plugin for DeltaTimePlugin {
fn setup(&mut self, app: &mut crate::game::App) { fn setup(&self, game: &mut crate::game::Game) {
app.world.add_resource(DeltaTime(0.0, None)); game.world_mut().add_resource(DeltaTime(0.0, None));
app.add_system_to_stage(GameStages::First, "delta_time", delta_time_system, &[]); game.add_system_to_stage(GameStages::First, "delta_time", delta_time_system, &[]);
} }
} }

View File

@ -196,7 +196,7 @@ impl<E: Event> Iterator for EventReader<E> {
pub struct EventsPlugin; pub struct EventsPlugin;
impl Plugin for EventsPlugin { impl Plugin for EventsPlugin {
fn setup(&mut self, app: &mut crate::game::App) { fn setup(&self, game: &mut crate::game::Game) {
app.world.add_resource(EventQueue::new()); game.world_mut().add_resource(EventQueue::new());
} }
} }

View File

@ -1,8 +1,9 @@
use std::{cell::OnceCell, collections::VecDeque, ptr::NonNull}; use std::{sync::Arc, collections::VecDeque, ptr::NonNull};
use lyra_ecs::{system::{IntoSystem, System}, ResourceObject, World}; use async_std::task::block_on;
use lyra_math::IVec2;
use tracing::{info, error, Level}; use lyra_ecs::{World, system::{System, IntoSystem}};
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,
@ -10,7 +11,9 @@ use tracing_subscriber::{
util::SubscriberInitExt, fmt, util::SubscriberInitExt, fmt,
}; };
use crate::{plugin::Plugin, render::renderer::Renderer, Stage, StagedExecutor}; use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode, DeviceEvent}, event_loop::{EventLoop, ControlFlow}};
use crate::{render::{renderer::{Renderer, BasicRenderer}, window::WindowOptions}, input::InputEvent, plugin::Plugin, change_tracker::Ct, EventQueue, StagedExecutor, Stage};
#[derive(Clone, Copy, Hash, Debug)] #[derive(Clone, Copy, Hash, Debug)]
pub enum GameStages { pub enum GameStages {
@ -34,13 +37,8 @@ pub struct Controls<'a> {
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct WindowState { pub struct WindowState {
/// Indicates if the window is currently focused. pub is_focused: bool,
pub focused: bool, pub is_cursor_inside_window: bool,
/// Indicates if the window is currently occluded.
pub occluded: bool,
/// Indicates if the cursor is inside of the window.
pub cursor_inside_window: bool,
pub position: IVec2,
} }
impl WindowState { impl WindowState {
@ -49,41 +47,185 @@ impl WindowState {
} }
} }
pub struct App { struct GameLoop {
pub(crate) renderer: OnceCell<Box<dyn Renderer>>, window: Arc<Window>,
pub world: World, renderer: Box<dyn Renderer>,
plugins: VecDeque<Box<dyn Plugin>>,
startup_systems: VecDeque<Box<dyn System>>, world: World,
staged_exec: StagedExecutor, staged_exec: StagedExecutor,
run_fn: OnceCell<Box<dyn FnOnce(App)>>,
} }
impl App { impl GameLoop {
pub fn new() -> Self { pub async fn new(window: Arc<Window>, mut world: World, staged_exec: StagedExecutor) -> Self {
// init logging let renderer = BasicRenderer::create_with_window(&mut world, window.clone()).await;
let (stdout_layer, stdout_nb) = non_blocking(std::io::stdout());
{
let t = tracing_subscriber::registry()
.with(fmt::layer().with_writer(stdout_layer));
#[cfg(feature = "tracy")] Self {
let t = t.with(tracing_tracy::TracyLayer::default()); window: Arc::clone(&window),
renderer: Box::new(renderer),
t.with(filter::Targets::new() world,
// done by prefix, so it includes all lyra subpackages staged_exec,
.with_target("lyra", Level::DEBUG)
.with_target("wgsl_preprocessor", Level::DEBUG)
.with_target("wgpu", Level::WARN)
.with_target("winit", Level::DEBUG)
.with_default(Level::INFO))
.init();
} }
}
// store the logger worker guard to ensure logging still happens pub async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
let mut world = World::new(); self.renderer.on_resize(&mut self.world, new_size);
world.add_resource(stdout_nb); }
// initialize ecs system stages pub async fn on_init(&mut self) {
// Create the EventQueue resource in the world
self.world.add_resource(self.window.clone());
}
pub fn run_sync(&mut self, event: Event<()>, control_flow: &mut ControlFlow) {
block_on(self.run_event_loop(event, control_flow))
}
async fn update(&mut self) {
let world_ptr = NonNull::from(&self.world);
if let Err(e) = self.staged_exec.execute(world_ptr, true) {
error!("Error when executing staged systems: '{}'", e);
}
}
async fn input_update(&mut self, event: &InputEvent) -> Option<ControlFlow> {
match event {
InputEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
..
} => {
self.on_exit();
Some(ControlFlow::Exit)
},
_ => {
//debug!("Got unhandled input event: \"{:?}\"", event);
None
}
}
}
fn on_exit(&mut self) {
info!("On exit!");
}
pub async fn run_event_loop(&mut self, event: Event<'_, ()>, control_flow: &mut ControlFlow) {
*control_flow = ControlFlow::Poll;
match event {
Event::DeviceEvent { device_id, event: DeviceEvent::MouseMotion { delta } } => {
//debug!("motion: {delta:?}");
// convert a MouseMotion event to an InputEvent
// make sure that the mouse is inside the window and the mouse has focus before reporting mouse motion
/* let trigger = matches!(self.world.get_resource::<WindowState>(), Some(window_state)
if window_state.is_focused && window_state.is_cursor_inside_window); */
let trigger = matches!(self.world.try_get_resource::<Ct<WindowOptions>>(), Some(window)
if window.focused && window.cursor_inside_window);
if trigger {
let mut event_queue = self.world.try_get_resource_mut::<EventQueue>().unwrap();
let input_event = InputEvent::MouseMotion { device_id, delta, };
event_queue.trigger_event(input_event);
}
},
Event::WindowEvent {
ref event,
window_id,
} if window_id == self.window.id() => {
// If try_from failed, that means that the WindowEvent is not an
// input related event.
if let Ok(input_event) = InputEvent::try_from(event) {
// Its possible to receive multiple input events before the update event for
// the InputSystem is called, so we must use a queue for the events.
{
if let Some(mut event_queue) = self.world.try_get_resource_mut::<EventQueue>() {
event_queue.trigger_event(input_event.clone());
};
}
if let Some(new_control) = self.input_update(&input_event).await {
*control_flow = new_control;
}
} else {
match event {
WindowEvent::CloseRequested => {
self.on_exit();
*control_flow = ControlFlow::Exit
},
WindowEvent::Resized(physical_size) => {
self.on_resize(*physical_size).await;
},
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
self.on_resize(**new_inner_size).await;
},
WindowEvent::Focused(is_focused) => {
let mut state = self.world.get_resource_or_else(WindowState::new);
state.is_focused = *is_focused;
},
_ => {}
}
}
},
Event::RedrawRequested(window_id) if window_id == self.window.id() => {
// Update the world
self.update().await;
/* self.fps_counter.tick();
if let Some(fps) = self.fps_counter.get_change() {
debug!("FPS: {}fps, {:.2}ms/frame", fps, self.fps_counter.get_tick_time());
} */
self.renderer.as_mut().prepare(&mut self.world);
if let Some(mut event_queue) = self.world.try_get_resource_mut::<EventQueue>() {
event_queue.update_events();
}
match self.renderer.as_mut().render() {
Ok(_) => {}
// Reconfigure the surface if lost
Err(wgpu::SurfaceError::Lost) => self.on_resize(self.renderer.as_ref().surface_size()).await,
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// All other errors (Outdated, Timeout) should be resolved by the next frame
Err(e) => eprintln!("{:?}", e),
}
},
Event::MainEventsCleared => {
self.window.request_redraw();
},
_ => {}
}
}
}
pub struct Game {
world: Option<World>,
plugins: VecDeque<Box<dyn Plugin>>,
system_exec: Option<StagedExecutor>,
startup_systems: VecDeque<Box<dyn System>>,
}
impl Default for Game {
fn default() -> Self {
let mut staged = StagedExecutor::new(); let mut staged = StagedExecutor::new();
staged.add_stage(GameStages::First); staged.add_stage(GameStages::First);
staged.add_stage_after(GameStages::First, GameStages::PreUpdate); staged.add_stage_after(GameStages::First, GameStages::PreUpdate);
@ -92,35 +234,29 @@ impl App {
staged.add_stage_after(GameStages::PostUpdate, GameStages::Last); staged.add_stage_after(GameStages::PostUpdate, GameStages::Last);
Self { Self {
renderer: OnceCell::new(), world: Some(World::new()),
world, plugins: VecDeque::new(),
plugins: Default::default(), system_exec: Some(staged),
startup_systems: Default::default(), startup_systems: VecDeque::new(),
staged_exec: staged,
run_fn: OnceCell::new(),
} }
} }
}
pub fn update(&mut self) { impl Game {
let wptr = NonNull::from(&self.world); pub async fn initialize() -> Game {
Self::default()
if let Err(e) = self.staged_exec.execute(wptr, true) {
error!("Error when executing staged systems: '{}'", e);
}
} }
pub(crate) fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) { /// Get the world of this game
self.renderer.get_mut() pub fn world_mut(&mut self) -> &mut World {
.expect("renderer was not initialized") // world is always `Some`, so unwrapping is safe
.on_resize(&mut self.world, new_size); self.world.as_mut().unwrap()
} }
pub(crate) fn on_exit(&mut self) { /// Get the world of this game
info!("On exit!"); pub fn world(&self) -> &World {
} // world is always `Some`, so unwrapping is safe
self.world.as_ref().unwrap()
pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
self.world.add_resource(data);
} }
/// Add a system to the ecs world /// Add a system to the ecs world
@ -129,7 +265,8 @@ impl App {
S: IntoSystem<A>, S: IntoSystem<A>,
<S as IntoSystem<A>>::System: 'static <S as IntoSystem<A>>::System: 'static
{ {
self.staged_exec.add_system_to_stage(GameStages::Update, name, system.into_system(), depends); let system_dispatcher = self.system_exec.as_mut().unwrap();
system_dispatcher.add_system_to_stage(GameStages::Update, name, system.into_system(), depends);
self self
} }
@ -138,7 +275,8 @@ impl App {
/// ///
/// This stage could run at any moment if nothing is dependent on it. /// This stage could run at any moment if nothing is dependent on it.
pub fn add_stage<T: Stage>(&mut self, stage: T) -> &mut Self { pub fn add_stage<T: Stage>(&mut self, stage: T) -> &mut Self {
self.staged_exec.add_stage(stage); let system_dispatcher = self.system_exec.as_mut().unwrap();
system_dispatcher.add_stage(stage);
self self
} }
@ -149,7 +287,8 @@ impl App {
/// * `before` - The stage that will run before `after`. /// * `before` - The stage that will run before `after`.
/// * `after` - The stage that will run after `before`. /// * `after` - The stage that will run after `before`.
pub fn add_stage_after<T: Stage, U: Stage>(&mut self, before: T, after: U) -> &mut Self { pub fn add_stage_after<T: Stage, U: Stage>(&mut self, before: T, after: U) -> &mut Self {
self.staged_exec.add_stage_after(before, after); let system_dispatcher = self.system_exec.as_mut().unwrap();
system_dispatcher.add_stage_after(before, after);
self self
} }
@ -165,7 +304,8 @@ impl App {
S: IntoSystem<A>, S: IntoSystem<A>,
<S as IntoSystem<A>>::System: 'static <S as IntoSystem<A>>::System: 'static
{ {
self.staged_exec.add_system_to_stage(stage, name, system.into_system(), depends); let system_dispatcher = self.system_exec.as_mut().unwrap();
system_dispatcher.add_system_to_stage(stage, name, system.into_system(), depends);
self self
} }
@ -182,12 +322,12 @@ impl App {
} }
/// Add a plugin to the game. These are executed as they are added. /// Add a plugin to the game. These are executed as they are added.
pub fn with_plugin<P>(&mut self, mut plugin: P) -> &mut Self pub fn with_plugin<P>(&mut self, plugin: P) -> &mut Self
where where
P: Plugin + 'static P: Plugin + 'static
{ {
plugin.setup(self);
let plugin = Box::new(plugin); let plugin = Box::new(plugin);
plugin.as_ref().setup(self);
self.plugins.push_back(plugin); self.plugins.push_back(plugin);
self self
@ -197,21 +337,53 @@ impl App {
/// ///
/// This isn't recommended, you should create a startup system and add it to `with_startup_system` /// This isn't recommended, you should create a startup system and add it to `with_startup_system`
pub fn with_world(&mut self, world: World) -> &mut Self { pub fn with_world(&mut self, world: World) -> &mut Self {
self.world = world; self.world = Some(world);
self self
} }
pub fn set_run_fn<F>(&self, f: F) /// Start the game
where pub async fn run(&mut self) {
F: FnOnce(App) + 'static // init logging
{ let (stdout_layer, _stdout_nb) = non_blocking(std::io::stdout());
// ignore if a runner function was already set {
let _ = self.run_fn.set(Box::new(f)); let t = tracing_subscriber::registry()
} .with(fmt::layer().with_writer(stdout_layer));
pub fn run(mut self) { #[cfg(feature = "tracy")]
let f = self.run_fn.take() let t = t.with(tracing_tracy::TracyLayer::default());
.expect("No run function set");
f(self); t.with(filter::Targets::new()
// done by prefix, so it includes all lyra subpackages
.with_target("lyra", Level::DEBUG)
.with_target("wgsl_preprocessor", Level::DEBUG)
.with_target("wgpu", Level::WARN)
.with_target("winit", Level::DEBUG)
.with_default(Level::INFO))
.init();
}
let world = self.world.take().unwrap_or_default();
// run startup systems
while let Some(mut startup) = self.startup_systems.pop_front() {
let startup = startup.as_mut();
let world_ptr = NonNull::from(&world);
startup.setup(world_ptr).expect("World returned an error!");
startup.execute(world_ptr).expect("World returned an error!");
}
// start winit event loops
let event_loop = EventLoop::new();
let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap());
let system_dispatcher = self.system_exec.take().unwrap();
let mut g_loop = GameLoop::new(Arc::clone(&window), world, system_dispatcher).await;
g_loop.on_init().await;
event_loop.run(move |event, _, control_flow| {
g_loop.run_sync(event, control_flow);
});
} }
} }

View File

@ -597,7 +597,7 @@ fn actions_system(world: &mut World) -> anyhow::Result<()> {
pub struct InputActionPlugin; pub struct InputActionPlugin;
impl Plugin for InputActionPlugin { impl Plugin for InputActionPlugin {
fn setup(&mut self, app: &mut crate::game::App) { fn setup(&self, game: &mut crate::game::Game) {
app.add_system_to_stage(GameStages::PreUpdate, "input_actions", actions_system, &[]); game.add_system_to_stage(GameStages::PreUpdate, "input_actions", actions_system, &[]);
} }
} }

View File

@ -88,8 +88,6 @@ pub enum MouseButton {
Left, Left,
Right, Right,
Middle, Middle,
Back,
Forward,
Other(u16), Other(u16),
} }

View File

@ -1,4 +1,4 @@
use winit::{dpi::PhysicalPosition, event::{AxisId, DeviceId, ElementState, KeyEvent, MouseButton, MouseScrollDelta, Touch, TouchPhase, WindowEvent}}; use winit::{event::{DeviceId, KeyboardInput, ModifiersState, MouseScrollDelta, TouchPhase, MouseButton, AxisId, Touch, WindowEvent, ElementState}, dpi::PhysicalPosition};
/// Wrapper around events from `winit::WindowEvent` that are specific to input related events. /// Wrapper around events from `winit::WindowEvent` that are specific to input related events.
/// ///
@ -19,7 +19,7 @@ pub enum InputEvent {
/// An event from the keyboard has been received. /// An event from the keyboard has been received.
KeyboardInput { KeyboardInput {
device_id: DeviceId, device_id: DeviceId,
event: KeyEvent, input: KeyboardInput,
/// If true, the event was generated synthetically by winit in one of the following circumstances: /// 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. /// Synthetic key press events are generated for all keys pressed when a window gains focus.
@ -28,10 +28,20 @@ pub enum InputEvent {
is_synthetic: bool, is_synthetic: bool,
}, },
/// Change in physical position of a pointing device.
/// This represents raw, unfiltered physical motion.
///
/// This is from winit's `DeviceEvent::MouseMotion`
MouseMotion {
device_id: DeviceId,
delta: (f64, f64),
},
/// The cursor has moved on the window. /// The cursor has moved on the window.
CursorMoved { CursorMoved {
device_id: DeviceId, device_id: DeviceId,
position: PhysicalPosition<f64>, position: PhysicalPosition<f64>,
modifiers: ModifiersState,
}, },
/// The cursor has entered the window. /// The cursor has entered the window.
@ -49,6 +59,8 @@ pub enum InputEvent {
device_id: DeviceId, device_id: DeviceId,
delta: MouseScrollDelta, delta: MouseScrollDelta,
phase: TouchPhase, phase: TouchPhase,
/// Deprecated in favor of WindowEvent::ModifiersChanged
modifiers: ModifiersState,
}, },
/// An mouse button press has been received. /// An mouse button press has been received.
@ -56,6 +68,51 @@ pub enum InputEvent {
device_id: DeviceId, device_id: DeviceId,
state: ElementState, state: ElementState,
button: MouseButton, button: MouseButton,
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
/// Touchpad magnification event with two-finger pinch gesture.
/// Positive delta values indicate magnification (zooming in) and negative delta values indicate shrinking (zooming out).
///
/// Note: Only available on macOS
TouchpadMagnify {
device_id: DeviceId,
delta: f64,
phase: TouchPhase,
},
/// Smart magnification event.
///
/// On a Mac, smart magnification is triggered by a double tap with two fingers
/// on the trackpad and is commonly used to zoom on a certain object
/// (e.g. a paragraph of a PDF) or (sort of like a toggle) to reset any zoom.
/// The gesture is also supported in Safari, Pages, etc.
///
/// The event is general enough that its generating gesture is allowed to vary
/// across platforms. It could also be generated by another device.
///
/// Unfortunatly, neither [Windows](https://support.microsoft.com/en-us/windows/touch-gestures-for-windows-a9d28305-4818-a5df-4e2b-e5590f850741)
/// nor [Wayland](https://wayland.freedesktop.org/libinput/doc/latest/gestures.html)
/// support this gesture or any other gesture with the same effect.
///
/// ## Platform-specific
///
/// - Only available on **macOS 10.8** and later.
SmartMagnify { device_id: DeviceId },
/// Touchpad rotation event with two-finger rotation gesture.
///
/// Positive delta values indicate rotation counterclockwise and
/// negative delta values indicate rotation clockwise.
///
/// ## Platform-specific
///
/// - Only available on **macOS**.
TouchpadRotate {
device_id: DeviceId,
delta: f32,
phase: TouchPhase,
}, },
/// Touchpad pressure event. /// Touchpad pressure event.
@ -79,62 +136,89 @@ pub enum InputEvent {
/// Touch event has been received /// Touch event has been received
/// ///
/// ## Platform-specific /// ## Platform-specific
/// - **Web**: Doesnt take into account CSS border, padding, or transform. ///
/// - **macOS:** Unsupported. /// - **macOS:** Unsupported.
Touch(Touch), Touch(Touch),
} }
impl InputEvent { #[derive(Debug, PartialEq)]
pub fn from_window_event(value: &WindowEvent) -> Option<Self> { pub enum InputEventConversionError<'a> {
FromError(&'a WindowEvent<'a>)
}
impl<'a> TryFrom<&'a WindowEvent<'a>> for InputEvent {
type Error = InputEventConversionError<'a>;
fn try_from(value: &'a WindowEvent<'a>) -> Result<Self, Self::Error> {
match value { match value {
WindowEvent::KeyboardInput { device_id, event, is_synthetic } => WindowEvent::KeyboardInput { device_id, input, is_synthetic } =>
Some(InputEvent::KeyboardInput { Ok(InputEvent::KeyboardInput {
device_id: *device_id, device_id: *device_id,
event: event.clone(), input: *input,
is_synthetic: *is_synthetic is_synthetic: *is_synthetic
}), }),
#[allow(deprecated, reason="Compatibility")] #[allow(deprecated, reason="Compatibility")]
WindowEvent::CursorMoved { device_id, position, } => WindowEvent::CursorMoved { device_id, position, modifiers } =>
Some(InputEvent::CursorMoved { Ok(InputEvent::CursorMoved {
device_id: *device_id, device_id: *device_id,
position: *position, position: *position,
modifiers: *modifiers
}), }),
WindowEvent::CursorEntered { device_id } => WindowEvent::CursorEntered { device_id } =>
Some(InputEvent::CursorEntered { Ok(InputEvent::CursorEntered {
device_id: *device_id device_id: *device_id
}), }),
WindowEvent::CursorLeft { device_id } => WindowEvent::CursorLeft { device_id } =>
Some(InputEvent::CursorLeft { Ok(InputEvent::CursorLeft {
device_id: *device_id device_id: *device_id
}), }),
#[allow(deprecated, reason="Compatibility")] #[allow(deprecated, reason="Compatibility")]
WindowEvent::MouseWheel { device_id, delta, phase } => WindowEvent::MouseWheel { device_id, delta, phase, modifiers } =>
Some(InputEvent::MouseWheel { Ok(InputEvent::MouseWheel {
device_id: *device_id, device_id: *device_id,
delta: *delta, delta: *delta,
phase: *phase, phase: *phase,
modifiers: *modifiers
}), }),
#[allow(deprecated, reason="Compatibility")] #[allow(deprecated, reason="Compatibility")]
WindowEvent::MouseInput { device_id, state, button } => WindowEvent::MouseInput { device_id, state, button, modifiers } =>
Some(InputEvent::MouseInput { Ok(InputEvent::MouseInput {
device_id: *device_id, device_id: *device_id,
state: *state, state: *state,
button: *button, button: *button,
modifiers: *modifiers
}),
WindowEvent::TouchpadMagnify { device_id, delta, phase } =>
Ok(InputEvent::TouchpadMagnify {
device_id: *device_id,
delta: *delta,
phase: *phase
}),
WindowEvent::SmartMagnify { device_id } =>
Ok(InputEvent::SmartMagnify {
device_id: *device_id
}),
WindowEvent::TouchpadRotate { device_id, delta, phase } =>
Ok(InputEvent::TouchpadRotate {
device_id: *device_id,
delta: *delta,
phase: *phase
}), }),
WindowEvent::TouchpadPressure { device_id, pressure, stage } => WindowEvent::TouchpadPressure { device_id, pressure, stage } =>
Some(InputEvent::TouchpadPressure { Ok(InputEvent::TouchpadPressure {
device_id: *device_id, device_id: *device_id,
pressure: *pressure, pressure: *pressure,
stage: *stage stage: *stage
}), }),
WindowEvent::AxisMotion { device_id, axis, value } => WindowEvent::AxisMotion { device_id, axis, value } =>
Some(InputEvent::AxisMotion { Ok(InputEvent::AxisMotion {
device_id: *device_id, device_id: *device_id,
axis: *axis, axis: *axis,
value: *value value: *value
}), }),
WindowEvent::Touch(t) => Some(InputEvent::Touch(*t)), WindowEvent::Touch(t) => Ok(InputEvent::Touch(*t)),
_ => None,
_ => Err(InputEventConversionError::FromError(value))
} }
} }
} }

View File

@ -13,7 +13,7 @@ pub use buttons::*;
pub mod action; pub mod action;
pub use action::*; pub use action::*;
pub type KeyCode = winit::keyboard::KeyCode; pub type KeyCode = winit::event::VirtualKeyCode;
/// Parses a [`KeyCode`] from a [`&str`]. /// Parses a [`KeyCode`] from a [`&str`].
/// ///
@ -24,42 +24,42 @@ pub fn keycode_from_str(s: &str) -> Option<KeyCode> {
let s = s.as_str(); let s = s.as_str();
match s { match s {
"1" => Some(KeyCode::Digit1), "1" => Some(KeyCode::Key1),
"2" => Some(KeyCode::Digit2), "2" => Some(KeyCode::Key2),
"3" => Some(KeyCode::Digit3), "3" => Some(KeyCode::Key3),
"4" => Some(KeyCode::Digit4), "4" => Some(KeyCode::Key4),
"5" => Some(KeyCode::Digit5), "5" => Some(KeyCode::Key5),
"6" => Some(KeyCode::Digit6), "6" => Some(KeyCode::Key6),
"7" => Some(KeyCode::Digit7), "7" => Some(KeyCode::Key7),
"8" => Some(KeyCode::Digit8), "8" => Some(KeyCode::Key8),
"9" => Some(KeyCode::Digit9), "9" => Some(KeyCode::Key9),
"0" => Some(KeyCode::Digit0), "0" => Some(KeyCode::Key0),
"a" => Some(KeyCode::KeyA), "a" => Some(KeyCode::A),
"b" => Some(KeyCode::KeyB), "b" => Some(KeyCode::B),
"c" => Some(KeyCode::KeyC), "c" => Some(KeyCode::C),
"d" => Some(KeyCode::KeyD), "d" => Some(KeyCode::D),
"e" => Some(KeyCode::KeyE), "e" => Some(KeyCode::E),
"f" => Some(KeyCode::KeyF), "f" => Some(KeyCode::F),
"g" => Some(KeyCode::KeyG), "g" => Some(KeyCode::G),
"h" => Some(KeyCode::KeyH), "h" => Some(KeyCode::H),
"i" => Some(KeyCode::KeyI), "i" => Some(KeyCode::I),
"j" => Some(KeyCode::KeyJ), "j" => Some(KeyCode::J),
"k" => Some(KeyCode::KeyK), "k" => Some(KeyCode::K),
"l" => Some(KeyCode::KeyL), "l" => Some(KeyCode::L),
"m" => Some(KeyCode::KeyM), "m" => Some(KeyCode::M),
"n" => Some(KeyCode::KeyN), "n" => Some(KeyCode::N),
"o" => Some(KeyCode::KeyO), "o" => Some(KeyCode::O),
"p" => Some(KeyCode::KeyP), "p" => Some(KeyCode::P),
"q" => Some(KeyCode::KeyQ), "q" => Some(KeyCode::Q),
"r" => Some(KeyCode::KeyR), "r" => Some(KeyCode::R),
"s" => Some(KeyCode::KeyS), "s" => Some(KeyCode::S),
"t" => Some(KeyCode::KeyT), "t" => Some(KeyCode::T),
"u" => Some(KeyCode::KeyU), "u" => Some(KeyCode::U),
"v" => Some(KeyCode::KeyV), "v" => Some(KeyCode::V),
"w" => Some(KeyCode::KeyW), "w" => Some(KeyCode::W),
"x" => Some(KeyCode::KeyX), "x" => Some(KeyCode::X),
"y" => Some(KeyCode::KeyY), "y" => Some(KeyCode::Y),
"z" => Some(KeyCode::KeyZ), "z" => Some(KeyCode::Z),
"escape" => Some(KeyCode::Escape), "escape" => Some(KeyCode::Escape),
"f1" => Some(KeyCode::F1), "f1" => Some(KeyCode::F1),
"f2" => Some(KeyCode::F2), "f2" => Some(KeyCode::F2),
@ -85,23 +85,25 @@ pub fn keycode_from_str(s: &str) -> Option<KeyCode> {
"f22" => Some(KeyCode::F22), "f22" => Some(KeyCode::F22),
"f23" => Some(KeyCode::F23), "f23" => Some(KeyCode::F23),
"f24" => Some(KeyCode::F24), "f24" => Some(KeyCode::F24),
"print_screen" => Some(KeyCode::PrintScreen), "snapshot" => Some(KeyCode::Snapshot),
"scroll_lock" => Some(KeyCode::ScrollLock), "scroll" => Some(KeyCode::Scroll),
"pause" => Some(KeyCode::Pause), "pause" => Some(KeyCode::Pause),
"insert" => Some(KeyCode::Insert), "insert" => Some(KeyCode::Insert),
"home" => Some(KeyCode::Home), "home" => Some(KeyCode::Home),
"delete" => Some(KeyCode::Delete), "delete" => Some(KeyCode::Delete),
"end" => Some(KeyCode::End), "end" => Some(KeyCode::End),
"page_down" => Some(KeyCode::PageDown), "pagedown" => Some(KeyCode::PageDown),
"page_up" => Some(KeyCode::PageUp), "pageup" => Some(KeyCode::PageUp),
"left" => Some(KeyCode::ArrowLeft), "left" => Some(KeyCode::Left),
"up" => Some(KeyCode::ArrowUp), "up" => Some(KeyCode::Up),
"right" => Some(KeyCode::ArrowRight), "right" => Some(KeyCode::Right),
"down" => Some(KeyCode::ArrowDown), "down" => Some(KeyCode::Down),
"backspace" => Some(KeyCode::Backspace), "back" => Some(KeyCode::Back),
"enter" => Some(KeyCode::Enter), "return" => Some(KeyCode::Return),
"space" => Some(KeyCode::Space), "space" => Some(KeyCode::Space),
"numlock" => Some(KeyCode::NumLock), "compose" => Some(KeyCode::Compose),
"caret" => Some(KeyCode::Caret),
"numlock" => Some(KeyCode::Numlock),
"numpad0" => Some(KeyCode::Numpad0), "numpad0" => Some(KeyCode::Numpad0),
"numpad1" => Some(KeyCode::Numpad1), "numpad1" => Some(KeyCode::Numpad1),
"numpad2" => Some(KeyCode::Numpad2), "numpad2" => Some(KeyCode::Numpad2),
@ -112,62 +114,76 @@ pub fn keycode_from_str(s: &str) -> Option<KeyCode> {
"numpad7" => Some(KeyCode::Numpad7), "numpad7" => Some(KeyCode::Numpad7),
"numpad8" => Some(KeyCode::Numpad8), "numpad8" => Some(KeyCode::Numpad8),
"numpad9" => Some(KeyCode::Numpad9), "numpad9" => Some(KeyCode::Numpad9),
"numpad_add" => Some(KeyCode::NumpadAdd), "numpadadd" => Some(KeyCode::NumpadAdd),
"numpad_divide" => Some(KeyCode::NumpadDivide), "numpaddivide" => Some(KeyCode::NumpadDivide),
"numpad_decimal" => Some(KeyCode::NumpadDecimal), "numpaddecimal" => Some(KeyCode::NumpadDecimal),
"numpad_comma" => Some(KeyCode::NumpadComma), "numpadcomma" => Some(KeyCode::NumpadComma),
"numpad_enter" => Some(KeyCode::NumpadEnter), "numpadenter" => Some(KeyCode::NumpadEnter),
"numpad_multiply" => Some(KeyCode::NumpadMultiply), "numpadequals" => Some(KeyCode::NumpadEquals),
"numpad_subtract" => Some(KeyCode::NumpadSubtract), "numpadmultiply" => Some(KeyCode::NumpadMultiply),
"numpad_star" => Some(KeyCode::NumpadStar), "numpadsubtract" => Some(KeyCode::NumpadSubtract),
"quote" => Some(KeyCode::Quote), "abntc1" => Some(KeyCode::AbntC1),
"launch_app1" => Some(KeyCode::LaunchApp1), "abntc2" => Some(KeyCode::AbntC2),
"launch_app1" => Some(KeyCode::LaunchApp2), "apostrophe" => Some(KeyCode::Apostrophe),
"apps" => Some(KeyCode::Apps),
"asterisk" => Some(KeyCode::Asterisk),
"at" => Some(KeyCode::At),
"ax" => Some(KeyCode::Ax),
"backslash" => Some(KeyCode::Backslash), "backslash" => Some(KeyCode::Backslash),
"caps_lock" => Some(KeyCode::CapsLock), "calculator" => Some(KeyCode::Calculator),
"capital" => Some(KeyCode::Capital),
"colon" => Some(KeyCode::Colon),
"comma" => Some(KeyCode::Comma), "comma" => Some(KeyCode::Comma),
"convert" => Some(KeyCode::Convert), "convert" => Some(KeyCode::Convert),
"equal" => Some(KeyCode::Equal), "equals" => Some(KeyCode::Equals),
"grave" | "backquote" => Some(KeyCode::Backquote), "grave" => Some(KeyCode::Grave),
"kana_mode" => Some(KeyCode::KanaMode), "kana" => Some(KeyCode::Kana),
"katakana" => Some(KeyCode::Katakana), "kanji" => Some(KeyCode::Kanji),
"alt_left" => Some(KeyCode::AltLeft), "lalt" => Some(KeyCode::LAlt),
"alt_right" => Some(KeyCode::AltRight), "lbracket" => Some(KeyCode::LBracket),
"bracket_left" => Some(KeyCode::BracketLeft), "lcontrol" => Some(KeyCode::LControl),
"bracket_right" => Some(KeyCode::BracketRight), "lshift" => Some(KeyCode::LShift),
"control_left" => Some(KeyCode::ControlLeft), "lwin" => Some(KeyCode::LWin),
"control-right" => Some(KeyCode::ControlRight), "mail" => Some(KeyCode::Mail),
"shift_left" => Some(KeyCode::ShiftLeft), "mediaselect" => Some(KeyCode::MediaSelect),
"shift_right" => Some(KeyCode::ShiftRight), "mediastop" => Some(KeyCode::MediaStop),
"meta" => Some(KeyCode::Meta),
"mail" => Some(KeyCode::LaunchMail),
"media_select" => Some(KeyCode::MediaSelect),
"media_stop" => Some(KeyCode::MediaStop),
"stop" => Some(KeyCode::MediaStop),
"track_next" => Some(KeyCode::MediaTrackNext),
"track_prev" => Some(KeyCode::MediaTrackPrevious),
"minus" => Some(KeyCode::Minus), "minus" => Some(KeyCode::Minus),
"mute" => Some(KeyCode::AudioVolumeMute), "mute" => Some(KeyCode::Mute),
"browser_forward" => Some(KeyCode::BrowserForward), "mycomputer" => Some(KeyCode::MyComputer),
"browser_back" => Some(KeyCode::BrowserBack), "navigateforward" => Some(KeyCode::NavigateForward),
"webfavorites" => Some(KeyCode::BrowserFavorites), "navigatebackward" => Some(KeyCode::NavigateBackward),
"webhome" => Some(KeyCode::BrowserHome), "nexttrack" => Some(KeyCode::NextTrack),
"webrefresh" => Some(KeyCode::BrowserRefresh), "noconvert" => Some(KeyCode::NoConvert),
"websearch" => Some(KeyCode::BrowserSearch), "oem102" => Some(KeyCode::OEM102),
"webstop" => Some(KeyCode::BrowserStop),
"non_convert" => Some(KeyCode::NonConvert),
"period" => Some(KeyCode::Period), "period" => Some(KeyCode::Period),
"play_pause" => Some(KeyCode::MediaPlayPause), "playpause" => Some(KeyCode::PlayPause),
"plus" => Some(KeyCode::NumpadAdd), "plus" => Some(KeyCode::Plus),
"power" => Some(KeyCode::Power), "power" => Some(KeyCode::Power),
"prevtrack" => Some(KeyCode::PrevTrack),
"ralt" => Some(KeyCode::RAlt),
"rbracket" => Some(KeyCode::RBracket),
"rcontrol" => Some(KeyCode::RControl),
"rshift" => Some(KeyCode::RShift),
"rwin" => Some(KeyCode::RWin),
"semicolon" => Some(KeyCode::Semicolon), "semicolon" => Some(KeyCode::Semicolon),
"slash" => Some(KeyCode::Slash), "slash" => Some(KeyCode::Slash),
"sleep" => Some(KeyCode::Sleep), "sleep" => Some(KeyCode::Sleep),
"stop" => Some(KeyCode::Stop),
"sysrq" => Some(KeyCode::Sysrq),
"tab" => Some(KeyCode::Tab), "tab" => Some(KeyCode::Tab),
"volume_down" => Some(KeyCode::AudioVolumeDown), "underline" => Some(KeyCode::Underline),
"volume_up" => Some(KeyCode::AudioVolumeUp), "unlabeled" => Some(KeyCode::Unlabeled),
"wake_up" => Some(KeyCode::WakeUp), "volumedown" => Some(KeyCode::VolumeDown),
"yen" => Some(KeyCode::IntlYen), "volumeup" => Some(KeyCode::VolumeUp),
"wake" => Some(KeyCode::Wake),
"webback" => Some(KeyCode::WebBack),
"webfavorites" => Some(KeyCode::WebFavorites),
"webforward" => Some(KeyCode::WebForward),
"webhome" => Some(KeyCode::WebHome),
"webrefresh" => Some(KeyCode::WebRefresh),
"websearch" => Some(KeyCode::WebSearch),
"webstop" => Some(KeyCode::WebStop),
"yen" => Some(KeyCode::Yen),
"copy" => Some(KeyCode::Copy), "copy" => Some(KeyCode::Copy),
"paste" => Some(KeyCode::Paste), "paste" => Some(KeyCode::Paste),
"cut" => Some(KeyCode::Cut), "cut" => Some(KeyCode::Cut),

View File

@ -2,7 +2,7 @@ use std::ptr::NonNull;
use glam::Vec2; use glam::Vec2;
use lyra_ecs::{World, system::IntoSystem}; use lyra_ecs::{World, system::IntoSystem};
use winit::{event::MouseScrollDelta, keyboard::PhysicalKey}; use winit::event::MouseScrollDelta;
use crate::{EventQueue, plugin::Plugin, game::GameStages}; use crate::{EventQueue, plugin::Plugin, game::GameStages};
@ -20,14 +20,21 @@ impl InputSystem {
let mut event_queue = event_queue.unwrap(); let mut event_queue = event_queue.unwrap();
match event { match event {
InputEvent::KeyboardInput { device_id, event, .. } => { InputEvent::KeyboardInput { input, .. } => {
if let PhysicalKey::Code(code) = event.physical_key { if let Some(code) = input.virtual_keycode {
drop(event_queue); drop(event_queue);
let mut e = world.get_resource_or_else(InputButtons::<winit::keyboard::KeyCode>::new); let mut e = world.get_resource_or_else(InputButtons::<winit::event::VirtualKeyCode>::new);
//let mut e = with_resource_mut(world, || InputButtons::<KeyCode>::new()); //let mut e = with_resource_mut(world, || InputButtons::<KeyCode>::new());
e.add_input_from_winit(code, event.state); e.add_input_from_winit(code, input.state);
} }
}, },
InputEvent::MouseMotion { delta, .. } => {
let delta = MouseMotion {
delta: Vec2::new(delta.0 as f32, delta.1 as f32)
};
event_queue.trigger_event(delta);
},
InputEvent::CursorMoved { position, .. } => { InputEvent::CursorMoved { position, .. } => {
let exact = MouseExact { let exact = MouseExact {
pos: Vec2::new(position.x as f32, position.y as f32) pos: Vec2::new(position.x as f32, position.y as f32)
@ -60,8 +67,6 @@ impl InputSystem {
winit::event::MouseButton::Left => MouseButton::Left, winit::event::MouseButton::Left => MouseButton::Left,
winit::event::MouseButton::Right => MouseButton::Right, winit::event::MouseButton::Right => MouseButton::Right,
winit::event::MouseButton::Middle => MouseButton::Middle, winit::event::MouseButton::Middle => MouseButton::Middle,
winit::event::MouseButton::Back => MouseButton::Back,
winit::event::MouseButton::Forward => MouseButton::Forward,
winit::event::MouseButton::Other(v) => MouseButton::Other(*v), winit::event::MouseButton::Other(v) => MouseButton::Other(*v),
}; };
@ -97,7 +102,7 @@ impl crate::ecs::system::System for InputSystem {
let queue = world.try_get_resource_mut::<EventQueue>() let queue = world.try_get_resource_mut::<EventQueue>()
.and_then(|q| q.read_events::<InputEvent>()); .and_then(|q| q.read_events::<InputEvent>());
let mut e = world.get_resource_or_else(InputButtons::<winit::keyboard::KeyCode>::new); let mut e = world.get_resource_or_else(InputButtons::<winit::event::VirtualKeyCode>::new);
e.update(); e.update();
drop(e); drop(e);
@ -135,7 +140,7 @@ impl IntoSystem<()> for InputSystem {
pub struct InputPlugin; pub struct InputPlugin;
impl Plugin for InputPlugin { impl Plugin for InputPlugin {
fn setup(&mut self, app: &mut crate::game::App) { fn setup(&self, game: &mut crate::game::Game) {
app.add_system_to_stage(GameStages::PreUpdate, "input", InputSystem, &[]); game.add_system_to_stage(GameStages::PreUpdate, "input", InputSystem, &[]);
} }
} }

View File

@ -9,7 +9,6 @@ pub mod game;
pub mod render; pub mod render;
pub mod resources; pub mod resources;
pub mod input; pub mod input;
pub mod winit;
pub mod as_any; pub mod as_any;
pub mod plugin; pub mod plugin;
pub mod change_tracker; pub mod change_tracker;

View File

@ -1,37 +1,35 @@
use lyra_ecs::CommandQueue; use lyra_ecs::CommandQueue;
use lyra_resource::ResourceManager; use lyra_resource::ResourceManager;
use crate::game::App;
use crate::winit::WinitPlugin;
use crate::EventsPlugin; use crate::EventsPlugin;
use crate::DeltaTimePlugin; use crate::DeltaTimePlugin;
use crate::game::Game;
use crate::input::InputPlugin; use crate::input::InputPlugin;
use crate::render::window::WindowPlugin; use crate::render::window::WindowPlugin;
/// A Plugin is something you can add to a `Game` that can be used to define systems, or spawn initial entities. /// A Plugin is something you can add to a `Game` that can be used to define systems, or spawn initial entities.
pub trait Plugin { pub trait Plugin {
/// Setup this plugin. This runs before the app has started /// Setup this plugin. This runs before the game has started
fn setup(&mut self, app: &mut App); fn setup(&self, game: &mut Game);
fn is_ready(&self, app: &mut App) -> bool { fn is_ready(&self, _game: &mut Game) -> bool {
let _ = app;
true true
} }
fn complete(&self, app: &mut App) { fn complete(&self, _game: &mut Game) {
let _ = app;
} }
fn cleanup(&self, app: &mut App) { fn cleanup(&self, _game: &mut Game) {
let _ = app;
} }
} }
impl<P> Plugin for P impl<P> Plugin for P
where P: Fn(&mut App) where P: Fn(&mut Game)
{ {
fn setup(&mut self, app: &mut App) { fn setup(&self, game: &mut Game) {
self(app); self(game);
} }
} }
@ -58,9 +56,9 @@ impl PluginSet {
} }
impl Plugin for PluginSet { impl Plugin for PluginSet {
fn setup(&mut self, app: &mut App) { fn setup(&self, game: &mut Game) {
for plugin in self.plugins.iter_mut() { for plugin in self.plugins.iter() {
plugin.setup(app); plugin.setup(game);
} }
} }
} }
@ -100,8 +98,8 @@ impl_tuple_plugin_set! { (C0, 0) (C1, 1) (C2, 2) (C3, 3) (C4, 4) (C5, 5) (C6, 6)
pub struct ResourceManagerPlugin; pub struct ResourceManagerPlugin;
impl Plugin for ResourceManagerPlugin { impl Plugin for ResourceManagerPlugin {
fn setup(&mut self, app: &mut App) { fn setup(&self, game: &mut Game) {
app.world.add_resource(ResourceManager::new()); game.world_mut().add_resource(ResourceManager::new());
} }
} }
@ -110,14 +108,13 @@ impl Plugin for ResourceManagerPlugin {
pub struct DefaultPlugins; pub struct DefaultPlugins;
impl Plugin for DefaultPlugins { impl Plugin for DefaultPlugins {
fn setup(&mut self, app: &mut App) { fn setup(&self, game: &mut Game) {
WinitPlugin::default().setup(app); CommandQueuePlugin.setup(game);
CommandQueuePlugin.setup(app); EventsPlugin.setup(game);
EventsPlugin.setup(app); InputPlugin.setup(game);
InputPlugin.setup(app); ResourceManagerPlugin.setup(game);
ResourceManagerPlugin.setup(app); WindowPlugin::default().setup(game);
WindowPlugin::default().setup(app); DeltaTimePlugin.setup(game);
DeltaTimePlugin.setup(app);
} }
} }
@ -127,7 +124,7 @@ impl Plugin for DefaultPlugins {
pub struct CommandQueuePlugin; pub struct CommandQueuePlugin;
impl Plugin for CommandQueuePlugin { impl Plugin for CommandQueuePlugin {
fn setup(&mut self, app: &mut App) { fn setup(&self, game: &mut Game) {
app.world.add_resource(CommandQueue::default()); game.world_mut().add_resource(CommandQueue::default());
} }
} }

View File

@ -22,7 +22,7 @@ pub struct RenderGraphContext<'a> {
pub device: Arc<wgpu::Device>, pub device: Arc<wgpu::Device>,
pub queue: Arc<wgpu::Queue>, pub queue: Arc<wgpu::Queue>,
pub(crate) buffer_writes: VecDeque<GraphBufferWrite>, pub(crate) buffer_writes: VecDeque<GraphBufferWrite>,
renderpass_desc: Vec<wgpu::RenderPassDescriptor<'a>>, renderpass_desc: Vec<wgpu::RenderPassDescriptor<'a, 'a>>,
/// The label of this Node. /// The label of this Node.
pub label: RenderGraphLabelValue, pub label: RenderGraphLabelValue,
} }
@ -41,7 +41,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, 'a>,
) -> wgpu::RenderPass { ) -> wgpu::RenderPass {
self.encoder self.encoder
.as_mut() .as_mut()

View File

@ -17,7 +17,7 @@ pub struct FxaaPass {
/// Store bind groups for the input textures. /// Store bind groups for the input textures.
/// The texture may change due to resizes, or changes to the view target chain /// The texture may change due to resizes, or changes to the view target chain
/// from other nodes. /// from other nodes.
bg_cache: HashMap<wgpu::Id<wgpu::TextureView>, wgpu::BindGroup>, bg_cache: HashMap<wgpu::Id, wgpu::BindGroup>,
} }
impl FxaaPass { impl FxaaPass {
@ -157,12 +157,10 @@ impl Node for FxaaPass {
resolve_target: None, resolve_target: None,
ops: wgpu::Operations { ops: wgpu::Operations {
load: wgpu::LoadOp::Load, load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store, store: true,
}, },
})], })],
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None, // TODO: occlusion queries
}); });
pass.set_pipeline(pipeline.as_render()); pass.set_pipeline(pipeline.as_render());

View File

@ -235,8 +235,6 @@ impl Node for LightCullComputePass {
], ],
shader, shader,
shader_entry_point: "cs_main".into(), shader_entry_point: "cs_main".into(),
cache: None,
compilation_options: Default::default(),
}); });
self.pipeline = Some(pipeline); self.pipeline = Some(pipeline);
@ -253,7 +251,6 @@ impl Node for LightCullComputePass {
let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor { let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("light_cull_pass"), label: Some("light_cull_pass"),
..Default::default()
}); });
pass.set_pipeline(pipeline); pass.set_pipeline(pipeline);

View File

@ -604,8 +604,8 @@ impl GpuMaterial {
&img.to_rgba8(), &img.to_rgba8(),
wgpu::ImageDataLayout { wgpu::ImageDataLayout {
offset: 0, offset: 0,
bytes_per_row: Some(4 * dim.0), bytes_per_row: std::num::NonZeroU32::new(4 * dim.0),
rows_per_image: Some(dim.1), rows_per_image: std::num::NonZeroU32::new(dim.1),
}, },
wgpu::Extent3d { wgpu::Extent3d {
width: dim.0, width: dim.0,

View File

@ -420,7 +420,7 @@ impl Node for MeshPass {
b: 0.3, b: 0.3,
a: 1.0, a: 1.0,
}), }),
store: wgpu::StoreOp::Store, store: true,
}, },
})], })],
// enable depth buffer // enable depth buffer
@ -428,11 +428,10 @@ impl Node for MeshPass {
view: depth_view, view: depth_view,
depth_ops: Some(wgpu::Operations { depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0), load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store, store: true,
}), }),
stencil_ops: None, stencil_ops: None,
}), }),
..Default::default()
}); });
pass.set_pipeline(pipeline); pass.set_pipeline(pipeline);

View File

@ -865,11 +865,10 @@ impl Node for ShadowMapsPass {
view: atlas.view(), view: atlas.view(),
depth_ops: Some(wgpu::Operations { depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0), load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store, store: true,
}), }),
stencil_ops: None, stencil_ops: None,
}), }),
..Default::default()
}); });
for light_depth_map in self.depth_maps.values() { for light_depth_map in self.depth_maps.values() {

View File

@ -17,7 +17,7 @@ pub struct TintPass {
/// Store bind groups for the input textures. /// Store bind groups for the input textures.
/// The texture may change due to resizes, or changes to the view target chain /// The texture may change due to resizes, or changes to the view target chain
/// from other nodes. /// from other nodes.
bg_cache: HashMap<wgpu::Id<wgpu::TextureView>, wgpu::BindGroup>, bg_cache: HashMap<wgpu::Id, wgpu::BindGroup>,
} }
impl TintPass { impl TintPass {
@ -152,12 +152,10 @@ impl Node for TintPass {
resolve_target: None, resolve_target: None,
ops: wgpu::Operations { ops: wgpu::Operations {
load: wgpu::LoadOp::Load, load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store, store: true,
}, },
})], })],
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
}); });
pass.set_pipeline(pipeline.as_render()); pass.set_pipeline(pipeline.as_render());

View File

@ -6,19 +6,10 @@ use crate::math;
enum RenderTargetInner { enum RenderTargetInner {
Surface { Surface {
/// The surface that will be rendered to. surface: wgpu::Surface,
///
/// You can create a new surface with a `'static` lifetime if you have an `Arc<Window>`:
/// ```nobuild
/// let window = Arc::new(window);
/// let surface = instance.create_surface(Arc::clone(&window))?;
/// ```
surface: wgpu::Surface<'static>,
/// the configuration of the surface render target..
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
}, },
Texture { Texture {
/// The texture that will be rendered to.
texture: Arc<wgpu::Texture>, texture: Arc<wgpu::Texture>,
} }
} }
@ -34,7 +25,7 @@ impl From<wgpu::Texture> for RenderTarget {
} }
impl RenderTarget { impl RenderTarget {
pub fn from_surface(surface: wgpu::Surface<'static>, config: wgpu::SurfaceConfiguration) -> Self { pub fn from_surface(surface: wgpu::Surface, config: wgpu::SurfaceConfiguration) -> Self {
Self(RenderTargetInner::Surface { surface, config }) Self(RenderTargetInner::Surface { surface, config })
} }

View File

@ -1,4 +1,4 @@
use std::mem; use std::{mem, num::{NonZeroU32, NonZeroU8}};
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct TextureViewDescriptor { pub struct TextureViewDescriptor {
@ -16,13 +16,13 @@ pub struct TextureViewDescriptor {
/// Mip level count. /// Mip level count.
/// If `Some(count)`, `base_mip_level + count` must be less or equal to underlying texture mip count. /// If `Some(count)`, `base_mip_level + count` must be less or equal to underlying texture mip count.
/// If `None`, considered to include the rest of the mipmap levels, but at least 1 in total. /// If `None`, considered to include the rest of the mipmap levels, but at least 1 in total.
pub mip_level_count: Option<u32>, pub mip_level_count: Option<NonZeroU32>,
/// Base array layer. /// Base array layer.
pub base_array_layer: u32, pub base_array_layer: u32,
/// Layer count. /// Layer count.
/// If `Some(count)`, `base_array_layer + count` must be less or equal to the underlying array count. /// If `Some(count)`, `base_array_layer + count` must be less or equal to the underlying array count.
/// If `None`, considered to include the rest of the array layers, but at least 1 in total. /// If `None`, considered to include the rest of the array layers, but at least 1 in total.
pub array_layer_count: Option<u32>, pub array_layer_count: Option<NonZeroU32>,
} }
impl TextureViewDescriptor { impl TextureViewDescriptor {
@ -79,7 +79,7 @@ pub struct SamplerDescriptor {
/// If this is enabled, this is a comparison sampler using the given comparison function. /// If this is enabled, this is a comparison sampler using the given comparison function.
pub compare: Option<wgpu::CompareFunction>, pub compare: Option<wgpu::CompareFunction>,
/// Valid values: 1, 2, 4, 8, and 16. /// Valid values: 1, 2, 4, 8, and 16.
pub anisotropy_clamp: u16, pub anisotropy_clamp: Option<NonZeroU8>,
/// Border color to use when address_mode is [`AddressMode::ClampToBorder`] /// Border color to use when address_mode is [`AddressMode::ClampToBorder`]
pub border_color: Option<wgpu::SamplerBorderColor>, pub border_color: Option<wgpu::SamplerBorderColor>,
} }

View File

@ -75,11 +75,10 @@ impl BasicRenderer {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), backends: wgpu::Backends::all(),
dx12_shader_compiler: Default::default(), dx12_shader_compiler: Default::default(),
flags: wgpu::InstanceFlags::default(),
gles_minor_version: wgpu::Gles3MinorVersion::Automatic,
}); });
let surface: wgpu::Surface::<'static> = instance.create_surface(window.clone()).unwrap(); // This should be safe since surface will live as long as the window that created it
let surface = unsafe { instance.create_surface(window.as_ref()) }.unwrap();
let adapter = instance.request_adapter( let adapter = instance.request_adapter(
&wgpu::RequestAdapterOptions { &wgpu::RequestAdapterOptions {
@ -91,12 +90,11 @@ impl BasicRenderer {
let (device, queue) = adapter.request_device( let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES | wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
required_features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES | wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
// WebGL does not support all wgpu features. // WebGL does not support all wgpu features.
// Not sure if the engine will ever completely support WASM, // Not sure if the engine will ever completely support WASM,
// but its here just in case // but its here just in case
required_limits: if cfg!(target_arch = "wasm32") { limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits { wgpu::Limits {
@ -104,7 +102,7 @@ impl BasicRenderer {
..Default::default() ..Default::default()
} }
}, },
memory_hints: wgpu::MemoryHints::MemoryUsage, label: None,
}, },
None, None,
).await.unwrap(); ).await.unwrap();
@ -115,7 +113,7 @@ impl BasicRenderer {
let surface_format = surface_caps.formats.iter() let surface_format = surface_caps.formats.iter()
.copied() .copied()
.find(|f| f.is_srgb()) .find(|f| f.describe().srgb)
.unwrap_or(surface_caps.formats[0]); .unwrap_or(surface_caps.formats[0]);
let config = wgpu::SurfaceConfiguration { let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
@ -124,7 +122,6 @@ impl BasicRenderer {
height: size.height, height: size.height,
present_mode: wgpu::PresentMode::default(), //wgpu::PresentMode::Mailbox, // "Fast Vsync" present_mode: wgpu::PresentMode::default(), //wgpu::PresentMode::Mailbox, // "Fast Vsync"
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
desired_maximum_frame_latency: 2,
view_formats: vec![], view_formats: vec![],
}; };
surface.configure(&device, &config); surface.configure(&device, &config);

View File

@ -2,25 +2,19 @@ use std::{ops::Deref, rc::Rc, sync::Arc};
use wgpu::PipelineLayout; use wgpu::PipelineLayout;
use super::{PipelineCompilationOptions, Shader}; use super::Shader;
//#[derive(Debug, Clone)] //#[derive(Debug, Clone)]
pub struct ComputePipelineDescriptor { pub struct ComputePipelineDescriptor {
pub label: Option<String>, pub label: Option<String>,
pub layouts: Vec<Arc<wgpu::BindGroupLayout>>, pub layouts: Vec<Arc<wgpu::BindGroupLayout>>,
pub push_constant_ranges: Vec<wgpu::PushConstantRange>,
// TODO: make this a ResHandle<Shader> // TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage. /// The compiled shader module for the stage.
pub shader: Rc<Shader>, pub shader: Rc<Shader>,
/// The entry point in the compiled shader. /// The entry point in the compiled shader.
/// There must be a function in the shader with the same name. /// There must be a function in the shader with the same name.
pub shader_entry_point: String, pub shader_entry_point: String,
/// Advanced options for when this pipeline is compiled
///
/// This implements `Default`, and for most users can be set to `Default::default()`
pub compilation_options: PipelineCompilationOptions,
pub push_constant_ranges: Vec<wgpu::PushConstantRange>,
/// The pipeline cache to use when creating this pipeline.
pub cache: Option<Arc<wgpu::PipelineCache>>,
} }
impl ComputePipelineDescriptor { impl ComputePipelineDescriptor {
@ -34,7 +28,7 @@ impl ComputePipelineDescriptor {
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)), label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)),
bind_group_layouts: &bgs, bind_group_layouts: &bgs,
push_constant_ranges: &self.push_constant_ranges, push_constant_ranges: &self.push_constant_ranges,
}) })
} }
@ -91,8 +85,6 @@ impl ComputePipeline {
layout: layout.as_ref(), layout: layout.as_ref(),
module: &compiled_shader, module: &compiled_shader,
entry_point: &desc.shader_entry_point, entry_point: &desc.shader_entry_point,
cache: desc.cache.as_ref().map(|c| &**c),
compilation_options: desc.compilation_options.as_wgpu(),
}; };
let pipeline = device.create_compute_pipeline(&desc); let pipeline = device.create_compute_pipeline(&desc);

View File

@ -1,6 +1,4 @@
mod shader; mod shader;
use std::collections::HashMap;
pub use shader::*; pub use shader::*;
mod pipeline; mod pipeline;
@ -13,21 +11,4 @@ mod render_pipeline;
pub use render_pipeline::*; pub use render_pipeline::*;
mod pass; mod pass;
pub use pass::*; pub use pass::*;
#[derive(Default, Clone)]
pub struct PipelineCompilationOptions {
pub constants: HashMap<String, f64>,
pub zero_initialize_workgroup_memory: bool,
pub vertex_pulling_transform: bool,
}
impl PipelineCompilationOptions {
pub fn as_wgpu(&self) -> wgpu::PipelineCompilationOptions {
wgpu::PipelineCompilationOptions {
constants: &self.constants,
zero_initialize_workgroup_memory: self.zero_initialize_workgroup_memory,
vertex_pulling_transform: self.vertex_pulling_transform,
}
}
}

View File

@ -97,7 +97,6 @@ impl RenderPipeline {
module: &vrtx_shad, module: &vrtx_shad,
entry_point: &desc.vertex.entry_point, entry_point: &desc.vertex.entry_point,
buffers: &vrtx_buffs, buffers: &vrtx_buffs,
compilation_options: Default::default(),
}; };
let frag_module = desc.fragment.as_ref().map(|f| { let frag_module = desc.fragment.as_ref().map(|f| {
@ -116,7 +115,6 @@ impl RenderPipeline {
module: fm.unwrap(), module: fm.unwrap(),
entry_point: &f.entry_point, entry_point: &f.entry_point,
targets: &f.targets, targets: &f.targets,
compilation_options: Default::default(),
}); });
let render_desc = wgpu::RenderPipelineDescriptor { let render_desc = wgpu::RenderPipelineDescriptor {
@ -128,7 +126,6 @@ impl RenderPipeline {
multisample: desc.multisample, multisample: desc.multisample,
fragment: fstate, fragment: fstate,
multiview: desc.multiview, multiview: desc.multiview,
cache: None,
}; };
let render_pipeline = device.create_render_pipeline(&render_desc); let render_pipeline = device.create_render_pipeline(&render_desc);

View File

@ -5,9 +5,9 @@ const LIGHT_TY_DIRECTIONAL = 0u;
const LIGHT_TY_POINT = 1u; const LIGHT_TY_POINT = 1u;
const LIGHT_TY_SPOT = 2u; const LIGHT_TY_SPOT = 2u;
alias vec2f = vec2<f32>; type vec2f = vec2<f32>;
alias vec3f = vec3<f32>; type vec3f = vec3<f32>;
alias vec4f = vec4<f32>; type vec4f = vec4<f32>;
struct CameraUniform { struct CameraUniform {
view: mat4x4<f32>, view: mat4x4<f32>,
@ -317,8 +317,8 @@ fn cone_inside_plane(cone: Cone, plane: Plane) -> bool {
return point_inside_plane(cone.tip, plane) && point_inside_plane(furthest, plane); return point_inside_plane(cone.tip, plane) && point_inside_plane(furthest, plane);
} }
fn cone_inside_frustum(cone: Cone, frustum_in: array<Plane, 6>) -> bool { fn cone_inside_frustum(cone: Cone, frustum: array<Plane, 6>) -> bool {
var frustum = frustum_in; var frustum = frustum;
for (var i = 0u; i < 4u; i++) { for (var i = 0u; i < 4u; i++) {
// TODO: better cone checking // TODO: better cone checking
if (sphere_inside_plane(cone.tip, cone.radius, frustum[i])) { if (sphere_inside_plane(cone.tip, cone.radius, frustum[i])) {

View File

@ -105,8 +105,8 @@ impl RenderTexture {
&rgba, &rgba,
wgpu::ImageDataLayout { wgpu::ImageDataLayout {
offset: 0, offset: 0,
bytes_per_row: Some(4 * dimensions.0), bytes_per_row: std::num::NonZeroU32::new(4 * dimensions.0),
rows_per_image: Some(dimensions.1), rows_per_image: std::num::NonZeroU32::new(dimensions.1),
}, },
size, size,
); );
@ -169,8 +169,8 @@ impl RenderTexture {
&rgba, &rgba,
wgpu::ImageDataLayout { wgpu::ImageDataLayout {
offset: 0, offset: 0,
bytes_per_row: Some(4 * dimensions.0), bytes_per_row: std::num::NonZeroU32::new(4 * dimensions.0),
rows_per_image: Some(dimensions.1), rows_per_image: std::num::NonZeroU32::new(dimensions.1),
}, },
size, size,
); );
@ -247,8 +247,8 @@ impl RenderTexture {
&rgba, &rgba,
wgpu::ImageDataLayout { wgpu::ImageDataLayout {
offset: 0, offset: 0,
bytes_per_row: Some(4 * dimensions.0), bytes_per_row: std::num::NonZeroU32::new(4 * dimensions.0),
rows_per_image: Some(dimensions.1), rows_per_image: std::num::NonZeroU32::new(dimensions.1),
}, },
size, size,
); );

View File

@ -1,506 +1,213 @@
use std::ops::{Deref, DerefMut}; use std::sync::Arc;
use lyra_ecs::{query::{filter::Changed, Entities, ResMut}, Component, World}; use glam::{Vec2, IVec2};
use lyra_resource::Image; use lyra_ecs::World;
use tracing::error; use tracing::{warn, error};
use winit::window::{CustomCursor, Fullscreen, Window}; use winit::{window::{Window, Fullscreen}, dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError};
pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel}; pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel};
use crate::{change_tracker::Ct, plugin::Plugin, winit::WinitWindows}; use crate::{plugin::Plugin, change_tracker::Ct, input::InputEvent, EventQueue};
#[derive(Default, Clone, Copy, PartialEq)] #[derive(Default, Clone)]
pub struct Area { pub enum WindowMode {
position: Position, /// The window will use the full size of the screen.
size: Size, Fullscreen,
} /// The window will be fullscreen with the full size of the screen without a border.
Borderless,
#[derive(Clone, Copy, PartialEq)] /// The window will not be fullscreen and will use the windows resolution size.
pub enum Size { #[default]
Physical { x: u32, y: u32 }, Windowed,
Logical { x: f64, y: f64 },
}
impl Default for Size {
fn default() -> Self {
Self::Physical { x: 0, y: 0 }
}
}
impl Into<winit::dpi::Size> for Size {
fn into(self) -> winit::dpi::Size {
match self {
Size::Physical { x, y } => winit::dpi::PhysicalSize::new(x, y).into(),
Size::Logical { x, y } => winit::dpi::LogicalSize::new(x, y).into(),
}
}
}
impl From<winit::dpi::Size> for Size {
fn from(value: winit::dpi::Size) -> Self {
match value {
winit::dpi::Size::Physical(physical_position) => Self::new_physical(physical_position.width, physical_position.height),
winit::dpi::Size::Logical(logical_position) => Self::new_logical(logical_position.width, logical_position.height),
}
}
}
impl Size {
pub fn new_physical(x: u32, y: u32) -> Self {
Self::Physical { x, y }
}
pub fn new_logical(x: f64, y: f64) -> Self {
Self::Logical { x, y }
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum Position {
Physical { x: i32, y: i32 },
Logical { x: f64, y: f64 },
}
impl Default for Position {
fn default() -> Self {
Self::Physical { x: 0, y: 0 }
}
}
impl Into<winit::dpi::Position> for Position {
fn into(self) -> winit::dpi::Position {
match self {
Position::Physical { x, y } => winit::dpi::PhysicalPosition::new(x, y).into(),
Position::Logical { x, y } => winit::dpi::LogicalPosition::new(x, y).into(),
}
}
}
impl From<winit::dpi::Position> for Position {
fn from(value: winit::dpi::Position) -> Self {
match value {
winit::dpi::Position::Physical(physical_position) => Self::new_physical(physical_position.x, physical_position.y),
winit::dpi::Position::Logical(logical_position) => Self::new_logical(logical_position.x, logical_position.y),
}
}
}
impl Position {
pub fn new_physical(x: i32, y: i32) -> Self {
Self::Physical { x, y }
}
pub fn new_logical(x: f64, y: f64) -> Self {
Self::Logical { x, y }
}
}
/// Flag component that
#[derive(Clone, Component)]
pub struct PrimaryWindow;
#[derive(Clone, PartialEq, Eq)]
pub enum CursorAppearance {
Icon(CursorIcon),
Custom(CustomCursor)
}
#[derive(Clone)]
pub struct Cursor {
/// Modifies the cursor icon of the window.
///
/// Platform-specific
/// * **iOS / Android / Orbital:** Unsupported.
/// * **Web:** Custom cursors have to be loaded and decoded first, until then the previous cursor is shown.
appearance: CursorAppearance,
/// Gets/sets the window's cursor grab mode
///
/// # Tip:
/// First try confining the cursor, and if it fails, try locking it instead.
grab: CursorGrabMode,
/// Gets/sets whether the window catches cursor events.
///
/// If `false`, events are passed through the window such that any other window behind it
/// receives them. By default hittest is enabled.
///
/// Platform-specific
/// * **iOS / Android / Web / Orbital:** Unsupported.
hittest: bool,
/// Gets/sets the cursor's visibility
///
/// Platform-specific
/// * **Windows / X11 / Wayland:** The cursor is only hidden within the confines of the window.
/// * **macOS:** The cursor is hidden as long as the window has input focus, even if the
/// cursor is outside of the window.
/// * **iOS / Android:** Unsupported.
visible: bool,
//cursor_position: Option<PhysicalPosition<i32>>,
} }
/// Options that the window will be created with. /// Options that the window will be created with.
#[derive(Clone, Component)] #[derive(Clone)]
pub struct WindowOptions { pub struct WindowOptions {
/// The enabled window buttons.
///
/// Platform-specific
/// * **Wayland / X11 / Orbital:** Not implemented. Always set to [`WindowButtons::all`].
/// * **Web / iOS / Android:** Unsupported. Always set to [`WindowButtons::all`].
pub enabled_buttons: WindowButtons,
/// Gets or sets if the window is in focus.
///
/// Platform-specific
/// * **iOS / Android / Wayland / Orbital:** Unsupported.
pub focused: bool,
/// Gets or sets the fullscreen setting.
///
/// If this is `None`, the window is windowed.
///
/// Platform-specific
/// * **iOS:** Can only be called on the main thread.
/// * **Android / Orbital:** Will always return None.
/// * **Wayland:** Can return Borderless(None) when there are no monitors.
/// * **Web:** Can only return None or Borderless(None).
pub fullscreen: Option<Fullscreen>,
/// Gets the position of the top-left hand corner of the windows client area relative to
/// 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. Note: this will be
/// the same value as [`WindowOptions::outer_position`].
/// * **Android / Wayland:** Unsupported.
pub inner_position: Option<Position>,
/// Gets/sets the size of the view in the window.
///
/// The size does not include the window title bars and borders.
///
/// Platform-specific
/// * **Web:** The size of the canvas element. Doesnt account for CSS `transform`.
pub size: Size,
/// Gets/sets if the window has decorations.
///
/// Platform-specific
/// * **iOS / Android / Web:** Always set to `true`.
pub decorated: bool,
/// Gets/sets the window's current maximized state
///
/// Platform-specific
/// * **iOS / Android / Web:** Unsupported.
pub maximized: bool,
/// Gets/sets the window's current minimized state.
///
/// Is `None` if the minimized state could not be determined.
///
/// Platform-specific
/// * **Wayland:** always `None`, un-minimize is unsupported.
/// * **iOS / Android / Web / Orbital:** Unsupported.
pub minimized: Option<bool>,
/// Gets/sets the window's current resizable state
///
/// If this is false, the window can still be resized by changing [`WindowOptions::size`].
///
/// Platform-specific
/// Setting this only has an affect on desktop platforms.
///
/// * **X11:** Due to a bug in XFCE, setting this has no effect..
/// * **iOS / Android / Web:** Unsupported.
pub resizable: bool,
/// Gets/sets the window's current visibility state.
///
/// `None` means it couldn't be determined.
///
/// Platform-specific
/// * **X11:** Not implemented.
/// * **Wayland / Android / Web:** Unsupported.
/// * **iOS:** Setting is not implemented, getting is unsupported.
pub visible: Option<bool>,
/// Gets/sets the position of the top-left hand corner of the window relative to
/// the top-left hand corner of the desktop.
///
/// Note that the top-left hand corner of the desktop is not necessarily the same
/// as the screen. If the user uses a desktop with multiple monitors, the top-left
/// hand corner of the desktop is the top-left hand corner of the monitor at the
/// top-left of the desktop.
///
/// If this is none, the position will be chosen by the windowing manager at creation, then set
/// when the window is created.
///
/// Platform-specific
/// * **iOS:** Value is the top left coordinates of the windows safe area in the screen
/// space coordinate system.
/// * **Web:** Value is the top-left coordinates relative to the viewport.
/// * **Android / Wayland:** Unsupported.
pub outer_position: Option<Position>,
/// Gets/sets the window resize increments.
///
/// This is a niche constraint hint usually employed by terminal emulators and other apps
/// that need “blocky” resizes.
///
/// Platform-specific
/// * **macOS:** Increments are converted to logical size and then macOS rounds them to whole numbers.
/// * **Wayland:** Not implemented, always `None`.
/// * **iOS / Android / Web / Orbital:** Unsupported.
pub resize_increments: Option<Size>,
/// Gets the scale factor.
///
/// The scale factor is the ratio of physical pixels to logical pixels.
/// See [winit docs](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.scale_factor)
/// for more information.
pub scale_factor: f64,
/// Gets/sets the window's blur state.
///
/// Platform-specific
/// * **Android / iOS / X11 / Web / Windows:** Unsupported.
/// * **Wayland:** Only works with org_kde_kwin_blur_manager protocol.
pub blur: bool,
/// Prevents the window contents from being captured by other apps. /// Prevents the window contents from being captured by other apps.
/// ///
/// Platform-specific /// Platform-specific:
/// * **macOS:** if false, [`NSWindowSharingNone`](https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone) /// * macOS: if false, NSWindowSharingNone is used but doesnt completely prevent all apps
/// is used but doesnt completely prevent all apps from reading the window content, /// from reading the window content, for instance, QuickTime.
/// for instance, QuickTime. /// * iOS / Android / x11 / Wayland / Web / Orbital: Unsupported.
/// * **iOS / Android / x11 / Wayland / Web / Orbital:** Unsupported.
pub content_protected: bool, pub content_protected: bool,
pub cursor: Cursor, /// Set grabbing mode on the cursor preventing it from leaving the window.
pub cursor_grab: CursorGrabMode,
/// Sets whether the window should get IME events /// Modifies whether the window catches cursor events.
///
/// When IME is allowed, the window will receive [`Ime`](winit::event::WindowEvent::Ime) /// If true, the window will catch the cursor events. If false, events are passed through
/// events, and during the preedit phase the window will NOT get KeyboardInput events. /// the window such that any otherwindow behind it receives them. By default hittest is enabled.
/// The window should allow IME while it is expecting text input. ///
/// /// Platform-specific:
/// When IME is not allowed, the window wont receive [`Ime`](winit::event::WindowEvent::Ime) /// * iOS / Android / Web / X11 / Orbital: Unsupported.
/// events, and will receive [`KeyboardInput`](winit::event::WindowEvent::KeyboardInput) events pub cursor_hittest: bool,
/// for every keypress instead. Not allowing IME is useful for games for example.
/// IME is not allowed by default. /// The cursor icon of the window.
/// ///
/// Platform-specific /// Platform-specific
/// * **macOS:** IME must be enabled to receive text-input where dead-key sequences are combined. /// * iOS / Android / Orbital: Unsupported.
/// * **iOS / Android / Web / Orbital:** Unsupported. pub cursor_icon: CursorIcon,
/// * **X11:** Enabling IME will disable dead keys reporting during compose.
/// The cursors visibility.
/// If false, this will hide the cursor. If true, this will show the cursor.
///
/// Platform-specific:
/// * Windows: The cursor is only hidden within the confines of the window.
/// * X11: The cursor is only hidden within the confines of the window.
/// * Wayland: The cursor is only hidden within the confines of the window.
/// * macOS: The cursor is hidden as long as the window has input focus, even if
/// the cursor is outside of the window.
/// * iOS / Android / Orbital: Unsupported.
pub cursor_visible: bool,
/// Turn window decorations on or off.
/// Enable/disable window decorations provided by the server or Winit. By default this is enabled.
///
/// Platform-specific
/// * iOS / Android / Web: No effect.
pub decorations: bool,
/// Sets the enabled window buttons.
///
/// Platform-specific:
/// * Wayland / X11 / Orbital: Not implemented.
/// * Web / iOS / Android: Unsupported.
pub enabled_buttons: WindowButtons,
/// The window mode. Can be used to set fullscreen and borderless.
pub mode: WindowMode,
/// Sets whether the window should get IME events.
///
/// If its allowed, the window will receive Ime events instead of KeyboardInput events.
/// This should only be allowed if the window is expecting text input.
///
/// Platform-specific:
/// * iOS / Android / Web / Orbital: Unsupported.
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 location of IME candidate box in client area coordinates relative to the top left.
/// ///
/// Platform-specific /// This is the window / popup / overlay that allows you to select the desired characters.
/// * **X11:** - area is not supported, only position. /// The look of this box may differ between input devices, even on the same platform.
/// * **iOS / Android / Web / Orbital:** Unsupported. pub ime_position: Vec2,
pub ime_cursor_area: Option<Area>,
/// Gets/sets the minimum size of the window. /// Modifies the inner size of the window.
/// ///
/// Platform-specific /// Platform-specific:
/// * **iOS / Android / Orbital:** Unsupported. /// * iOS / Android: Unsupported.
pub min_size: Option<Size>, /// * Web: Sets the size of the canvas element.
pub inner_size: IVec2,
/// Gets/sets the maximum size of the window.
///
/// Platform-specific
/// * **iOS / Android / Orbital:** Unsupported.
pub max_size: Option<Size>,
/// Gets/sets the current window theme. /// Sets a maximum dimension size for the window.
/// ///
/// Specify `None` to reset the theme to the system default. May also be `None` on unsupported /// Platform-specific:
/// platforms. /// * iOS / Android / Web / Orbital: Unsupported.
/// pub max_inner_size: Option<IVec2>,
/// Platform-specific
/// * **Wayland:** Sets the theme for the client side decorations. Using `None` will use dbus /// Sets a minimum dimension size for the window.
/// to get the system preference. ///
/// * **X11:** Sets `_GTK_THEME_VARIANT` hint to `dark` or `light` and if `None` is used, /// Platform-specific:
/// it will default to [`Theme::Dark`](winit::window::Theme::Dark). /// * iOS / Android / Web / Orbital: Unsupported.
/// * **iOS / Android / Web / Orbital:** Unsupported. pub min_inner_size: Option<IVec2>,
/// Sets the window to maximized or back.
///
/// Platform-specific:
/// * iOS / Android / Web / Orbital: Unsupported.
pub maximized: bool,
/// Sets the window to minimized or back.
///
/// Platform-specific:
/// * iOS / Android / Web / Orbital: Unsupported.
pub minimized: bool,
/// Modifies the position of the window.
///
/// Platform-specific:
/// * Web: Sets the top-left coordinates relative to the viewport.
/// * Android / Wayland: Unsupported.
//pub outer_position: Vec2,
/// Sets whether the window is resizable or not.
///
/// Platform-specific:
/// * X11: Due to a bug in XFCE, this has no effect on Xfwm.
/// * iOS / Android / Web: Unsupported.
pub resizeable: bool,
/// Sets window resize increments.
/// This is a niche constraint hint usually employed by terminal emulators and other apps that need “blocky” resizes.
///
/// Platform-specific:
/// * Wayland / Windows: Not implemented.
/// * iOS / Android / Web / Orbital: Unsupported.
pub resize_increments: Option<Vec2>,
/// Sets the current window theme. Use None to fallback to system default.
///
/// Platform-specific:
/// * macOS: This is an app-wide setting.
/// * x11: If None is used, it will default to Theme::Dark.
/// * iOS / Android / Web / x11 / Orbital: Unsupported.
pub theme: Option<Theme>, pub theme: Option<Theme>,
/// Gets/sets the title of the window. /// Modifies the title of the window.
/// ///
/// Platform-specific /// Platform-specific:
/// * **iOS / Android:** Unsupported. /// * iOS / Android: Unsupported.
/// * **X11 / Wayland / Web:** Cannot get, will always be an empty string.
pub title: String, pub title: String,
/// Gets/sets the window's transparency state. /// Sets the window icon.
/// /// On Windows and X11, this is typically the small icon in the top-left corner of the titlebar.
/// This is just a hint that may not change anything about the window transparency, however ///
/// doing a mismatch between the content of your window and this hint may result in visual
/// artifacts.
///
/// Platform-specific /// Platform-specific
/// * **macOS:** This will reset the windows background color. /// * iOS / Android / Web / Wayland / macOS / Orbital: Unsupported.
/// * **Web / iOS / Android:** Unsupported. /// * Windows: Sets ICON_SMALL. The base size for a window icon is 16x16, but its recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
/// * **X11:** Can only be set while building the window. /// * X11: Has no universal guidelines for icon sizes, so youre at the whims of the WM. That said, its usually in the same ballpark as on Windows.
pub transparent: bool, pub icon: Option<Icon>,
/// Sets the window's icon.
///
/// On Windows and X11, this is typically the small icon in the top-left corner of
/// the titlebar.
///
/// Platform-specific
/// * **iOS / Android / Web / Wayland / macOS / Orbital:** Unsupported.
/// * **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but its
/// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
/// * **X11:** Has no universal guidelines for icon sizes, so youre at the whims of
/// the WM. That said, its usually in the same ballpark as on Windows.
pub window_icon: Option<lyra_resource::ResHandle<Image>>,
/// Change the window level. /// Change the window level.
///
/// This is just a hint to the OS, and the system could ignore it. /// This is just a hint to the OS, and the system could ignore it.
/// pub level: WindowLevel,
/// See [`WindowLevel`] for details.
pub window_level: WindowLevel,
/// Show [window menu](https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu) /// Get/set the window's focused state.
/// at a specified position. pub focused: bool,
///
/// This is the context menu that is normally shown when interacting with the title bar. This is useful when implementing custom decorations.
/// Platform-specific
/// * **Android / iOS / macOS / Orbital / Wayland / Web / X11:** Unsupported.
pub show_window_menu: Option<Position>,
/// Gets the window's occluded state (completely hidden from view). /// Get whether or not the cursor is inside the window.
/// pub cursor_inside_window: bool,
/// This is different to window visibility as it depends on whether the window is
/// closed, minimised, set invisible, or fully occluded by another window.
///
/// Platform-specific
/// * **iOS:** this is set to `false` in response to an applicationWillEnterForeground
/// callback which means the application should start preparing its data.
/// Its `true` in response to an applicationDidEnterBackground callback which means
/// the application should free resources (according to the iOS application lifecycle).
/// * **Web:** Doesn't take into account CSS border, padding, or transform.
/// * **Android / Wayland / Windows / Orbital:** Unsupported.
pub occluded: bool,
}
impl From<winit::window::WindowAttributes> for WindowOptions {
fn from(value: winit::window::WindowAttributes) -> Self {
Self {
enabled_buttons: value.enabled_buttons,
focused: false,
fullscreen: value.fullscreen,
inner_position: None,
size: value.inner_size.map(|s| s.into())
.unwrap_or(Size::new_physical(1280, 720)),
decorated: value.decorations,
maximized: value.maximized,
minimized: None,
resizable: value.resizable,
visible: Some(value.visible),
outer_position: value.position.map(|p| p.into()),
resize_increments: value.resize_increments.map(|r| r.into()),
scale_factor: 1.0,
blur: value.blur,
content_protected: value.content_protected,
cursor: Cursor {
appearance: match value.cursor {
winit::window::Cursor::Icon(icon) => CursorAppearance::Icon(icon),
winit::window::Cursor::Custom(custom) => CursorAppearance::Custom(custom),
},
grab: CursorGrabMode::None,
hittest: true,
visible: true,
},
ime_allowed: false,
ime_cursor_area: None,
min_size: value.min_inner_size.map(|m| m.into()),
max_size: value.max_inner_size.map(|m| m.into()),
theme: value.preferred_theme,
title: value.title,
transparent: value.transparent,
window_icon: None,
window_level: value.window_level,
show_window_menu: None,
occluded: false,
}
}
} }
impl Default for WindowOptions { impl Default for WindowOptions {
fn default() -> Self { fn default() -> Self {
Self::from(Window::default_attributes()) Self {
} content_protected: false,
} cursor_grab: CursorGrabMode::None,
cursor_hittest: true,
impl WindowOptions { cursor_icon: CursorIcon::Default,
/// Create winit [`WindowAttributes`] from self. cursor_visible: true,
pub(crate) fn as_attributes(&self) -> winit::window::WindowAttributes { decorations: true,
let mut att = winit::window::Window::default_attributes(); enabled_buttons: WindowButtons::all(),
mode: WindowMode::Windowed,
att.enabled_buttons = self.enabled_buttons.clone(); ime_allowed: false,
att.fullscreen = self.fullscreen.clone(); ime_position: Default::default(),
att.inner_size = Some(self.size.into()); inner_size: glam::i32::IVec2::new(800, 600),
att.decorations = self.decorated; max_inner_size: None,
att.maximized = self.maximized; min_inner_size: None,
att.resizable = self.resizable; maximized: false,
att.visible = self.visible.unwrap_or(true); minimized: false,
att.position = self.outer_position.map(|p| p.into()); //outer_position: Default::default(),
att.resize_increments = self.resize_increments.map(|i| i.into()); resizeable: false,
att.blur = self.blur; resize_increments: None,
att.content_protected = self.content_protected; theme: None,
att.cursor = match self.cursor.appearance.clone() { title: "Lyra Engine Game".to_string(),
CursorAppearance::Icon(icon) => winit::window::Cursor::Icon(icon), icon: None,
CursorAppearance::Custom(custom) => winit::window::Cursor::Custom(custom), level: WindowLevel::Normal,
}; focused: false,
att.min_inner_size = self.min_size.map(|s| s.into()); cursor_inside_window: false,
att.max_inner_size = self.max_size.map(|s| s.into());
att.preferred_theme = self.theme;
att.title = self.title.clone();
att.transparent = self.transparent;
if self.window_icon.is_some() {
todo!("cannot set window attribute icon yet");
} }
att.window_level = self.window_level;
att
}
}
#[derive(Clone, Component)]
struct LastWindow {
last: WindowOptions,
}
impl Deref for LastWindow {
type Target = WindowOptions;
fn deref(&self) -> &Self::Target {
&self.last
}
}
impl DerefMut for LastWindow {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.last
} }
} }
@ -510,143 +217,163 @@ pub struct WindowPlugin {
create_options: WindowOptions, create_options: WindowOptions,
} }
/// A system that syncs Winit Windows with [`WindowOptions`] components. /// Convert an Vec2 to a LogicalPosition<f32>
pub fn window_sync_system(world: &mut World) -> anyhow::Result<()> { fn vec2_to_logical_pos(pos: Vec2) -> LogicalPosition<f32> {
for (entity, opts, mut last, windows) in world.filtered_view_iter::<(Entities, &WindowOptions, &mut LastWindow, ResMut<WinitWindows>), Changed<WindowOptions>>() { LogicalPosition { x: pos.x, y: pos.y }
let window = windows.get_entity_window(entity) }
.expect("entity's window is missing");
if opts.enabled_buttons != last.enabled_buttons { /// Convert an IVec2 to a LogicalSize<i32>
window.set_enabled_buttons(opts.enabled_buttons); fn ivec2_to_logical_size(size: IVec2) -> LogicalSize<i32> {
LogicalSize { width: size.x, height: size.y }
}
/// Convert an Option<IVec2> to an Option<LogicalSize<i32>>
fn ivec2_to_logical_size_op(size: Option<IVec2>) -> Option<LogicalSize<i32>> {
size.map(ivec2_to_logical_size)
}
/// Convert an Option<Vec2> to an Option<LogicalSize<f32>>
fn vec2_to_logical_size_op(size: Option<Vec2>) -> Option<LogicalSize<f32>> {
size.map(|size| LogicalSize { width: size.x, height: size.y } )
}
/// Set the cursor grab of a window depending on the platform.
/// This will also modify the parameter `grab` to ensure it matches what the platform can support
fn set_cursor_grab(window: &Window, grab: &mut CursorGrabMode) -> anyhow::Result<()> {
if *grab != CursorGrabMode::None {
if cfg!(unix) {
*grab = CursorGrabMode::Confined;
// TODO: Find a way to see if winit is using x11 or wayland. wayland supports Locked
} else if cfg!(wasm) {
*grab = CursorGrabMode::Locked;
} else if cfg!(windows) {
*grab = CursorGrabMode::Confined; // NOTE: May support Locked later
} else if cfg!(target_os = "macos") {
*grab = CursorGrabMode::Locked; // NOTE: May support Confined later
} else if cfg!(any(target_os = "android", target_os = "ios", target_os = "orbital")) {
warn!("CursorGrabMode is not supported on Android, IOS, or Oribital, skipping");
return Ok(())
} }
}
if opts.focused != last.focused && opts.focused { window.set_cursor_grab(*grab)?;
window.focus_window();
}
if opts.fullscreen != last.fullscreen { Ok(())
window.set_fullscreen(opts.fullscreen.clone()); }
}
if opts.size != last.size { /// if the window is set to confine the cursor, the cursor is invisible,
if window.request_inner_size(opts.size).is_some() { /// and the window is focused, set the cursor position to the center of the screen.
error!("request to increase window size failed"); fn center_mouse(window: &Window, options: &WindowOptions) {
if options.cursor_grab == CursorGrabMode::Confined && !options.cursor_visible
&& options.focused {
let size = window.inner_size();
let middle = PhysicalPosition {
x: size.width / 2,
y: size.height / 2,
};
window.set_cursor_position(middle).unwrap();
}
}
fn window_updater_system(world: &mut World) -> anyhow::Result<()> {
if let (Some(window), Some(opts)) = (world.try_get_resource::<Arc<Window>>(), world.try_get_resource::<Ct<WindowOptions>>()) {
// if the options changed, update the window
if opts.peek_changed() {
drop(opts); // drop the Ref, we're about to get a RefMut
// now we can get it mutable, this will trigger the ChangeTracker, so it will be reset at the end of this scope.
let mut opts = world.get_resource_mut::<Ct<WindowOptions>>();
if opts.focused {
window.focus_window();
} }
}
if opts.decorated != last.decorated {
window.set_decorations(opts.decorated);
}
if opts.maximized != last.maximized {
window.set_maximized(opts.maximized);
}
if opts.minimized != last.minimized && opts.minimized.is_some() {
window.set_minimized(opts.minimized.unwrap());
}
if opts.visible != last.visible && opts.visible.is_some() {
window.set_visible(opts.visible.unwrap());
}
if opts.outer_position != last.outer_position && opts.outer_position.is_some() {
window.set_outer_position(opts.outer_position.unwrap());
}
if opts.resize_increments != last.resize_increments {
window.set_resize_increments(opts.resize_increments);
}
if opts.blur != last.blur {
window.set_blur(opts.blur)
}
if opts.content_protected != last.content_protected {
window.set_content_protected(opts.content_protected); window.set_content_protected(opts.content_protected);
} set_cursor_grab(&window, &mut opts.cursor_grab)?;
match window.set_cursor_hittest(opts.cursor_hittest) {
if opts.cursor.appearance != last.cursor.appearance { Ok(()) => {},
match opts.cursor.appearance.clone() { Err(ExternalError::NotSupported(_)) => { /* ignore */ },
CursorAppearance::Icon(icon) => window.set_cursor(winit::window::Cursor::Icon(icon)), Err(e) => {
CursorAppearance::Custom(custom) => window.set_cursor(winit::window::Cursor::Custom(custom)), error!("OS error when setting cursor hittest: {:?}", e);
}
} }
} window.set_cursor_icon(opts.cursor_icon); // TODO: Handle unsupported platforms
window.set_cursor_visible(opts.cursor_visible); // TODO: Handle unsupported platforms
if opts.cursor.grab != last.cursor.grab { window.set_decorations(opts.decorations); // TODO: Handle unsupported platforms
if let Err(e) = window.set_cursor_grab(opts.cursor.grab) { window.set_enabled_buttons(opts.enabled_buttons); // TODO: Handle unsupported platforms
error!("could not set cursor grab mode: {}", e);
// Update the window mode. can only be done if the monitor is found
if let Some(monitor) = window.current_monitor()
.or_else(|| window.primary_monitor())
.or_else(|| window.available_monitors().next()) {
match opts.mode {
WindowMode::Borderless => window.set_fullscreen(Some(Fullscreen::Borderless(Some(monitor)))),
WindowMode::Fullscreen => window.set_fullscreen(Some(Fullscreen::Exclusive(monitor.video_modes().next().unwrap()))),
WindowMode::Windowed => window.set_fullscreen(None),
}
} else {
warn!("Failure to get monitor handle, could not update WindowMode");
} }
}
if opts.cursor.hittest != last.cursor.hittest {
if let Err(e) = window.set_cursor_hittest(opts.cursor.hittest) {
error!("could not set cursor hittest: {}", e);
}
}
if opts.cursor.visible != last.cursor.visible {
window.set_cursor_visible(opts.cursor.visible);
}
if opts.ime_allowed != last.ime_allowed {
window.set_ime_allowed(opts.ime_allowed); window.set_ime_allowed(opts.ime_allowed);
} window.set_ime_position(vec2_to_logical_pos(opts.ime_position));
window.set_inner_size(ivec2_to_logical_size(opts.inner_size));
if opts.ime_cursor_area != last.ime_cursor_area && opts.ime_cursor_area.is_some() { if opts.max_inner_size.is_some() {
let area = opts.ime_cursor_area.as_ref().unwrap(); window.set_max_inner_size(ivec2_to_logical_size_op(opts.max_inner_size));
window.set_ime_cursor_area(area.position, area.size); }
} if opts.min_inner_size.is_some() {
window.set_min_inner_size(ivec2_to_logical_size_op(opts.min_inner_size));
if opts.min_size != last.min_size { }
window.set_min_inner_size(opts.min_size); window.set_maximized(opts.maximized);
} window.set_minimized(opts.minimized);
window.set_resizable(opts.resizeable);
if opts.max_size != last.max_size { window.set_resize_increments(vec2_to_logical_size_op(opts.resize_increments));
window.set_max_inner_size(opts.max_size);
}
if opts.theme != last.theme {
window.set_theme(opts.theme); window.set_theme(opts.theme);
}
if opts.title != last.title {
window.set_title(&opts.title); window.set_title(&opts.title);
} window.set_window_icon(opts.icon.clone());
window.set_window_level(opts.level);
if opts.transparent != last.transparent { // reset the tracker after we mutably used it
window.set_transparent(opts.transparent); opts.reset();
}
// compare the resource version and uuid. These will get changed center_mouse(&window, &opts);
// when the image is reloaded } else {
let opts_icon = opts.window_icon.as_ref() drop(opts); // drop the Ref, we're about to get a RefMut
.map(|i| (i.version(), i.uuid())); let mut opts = world.get_resource_mut::<Ct<WindowOptions>>();
let last_icon = last.window_icon.as_ref()
.map(|i| (i.version(), i.uuid()));
if opts_icon != last_icon {
todo!("cannot set window icon yet");
}
if opts.window_level != last.window_level { if let Some(event_queue) = world.try_get_resource_mut::<EventQueue>() {
window.set_window_level(opts.window_level); if let Some(events) = event_queue.read_events::<InputEvent>() {
} for ev in events {
match ev {
InputEvent::CursorEntered { .. } => {
opts.cursor_inside_window = true;
},
InputEvent::CursorLeft { .. } => {
opts.cursor_inside_window = false;
},
_ => {},
}
}
}
}
if opts.show_window_menu != last.show_window_menu && opts.show_window_menu.is_some() { // update the stored state of the window to match the actual window
window.show_window_menu(opts.show_window_menu.unwrap());
}
last.last = opts.clone(); opts.focused = window.has_focus();
opts.reset();
center_mouse(&window, &opts);
}
} }
Ok(()) Ok(())
} }
impl Plugin for WindowPlugin { impl Plugin for WindowPlugin {
fn setup(&mut self, app: &mut crate::game::App) { fn setup(&self, game: &mut crate::game::Game) {
let window_options = WindowOptions::default(); let window_options = WindowOptions::default();
app.world.add_resource(Ct::new(window_options)); game.world_mut().add_resource(Ct::new(window_options));
app.with_system("window_sync", window_sync_system, &[]); game.with_system("window_updater", window_updater_system, &[]);
} }
} }

View File

@ -1,7 +1,7 @@
use glam::{EulerRot, Quat, Vec3}; use glam::{EulerRot, Quat, Vec3};
use lyra_ecs::{query::{Res, View}, Component}; use lyra_ecs::{query::{Res, View}, Component};
use crate::{game::App, input::ActionHandler, plugin::Plugin, DeltaTime}; use crate::{game::Game, input::ActionHandler, plugin::Plugin, DeltaTime};
use super::CameraComponent; use super::CameraComponent;
@ -97,7 +97,7 @@ pub fn free_fly_camera_controller(delta_time: Res<DeltaTime>, handler: Res<Actio
pub struct FreeFlyCameraPlugin; pub struct FreeFlyCameraPlugin;
impl Plugin for FreeFlyCameraPlugin { impl Plugin for FreeFlyCameraPlugin {
fn setup(&mut self, app: &mut App) { fn setup(&self, game: &mut Game) {
app.with_system("free_fly_camera_system", free_fly_camera_controller, &[]); game.with_system("free_fly_camera_system", free_fly_camera_controller, &[]);
} }
} }

View File

@ -1,234 +0,0 @@
use std::{collections::VecDeque, sync::Arc};
use async_std::task::block_on;
use glam::IVec2;
use lyra_ecs::Entity;
use rustc_hash::FxHashMap;
use tracing::{debug, error, info, warn};
use winit::{application::ApplicationHandler, event::WindowEvent, event_loop::{ActiveEventLoop, EventLoop}, window::{Window, WindowAttributes, WindowId}};
use crate::{game::{App, WindowState}, input::InputEvent, plugin::Plugin, render::{renderer::BasicRenderer, window::{PrimaryWindow, Size, WindowOptions}}, EventQueue};
pub struct WinitPlugin {
/// The primary window that will be created.
///
/// This will become `None` after the window is created. If you want to get the
/// primary world later, query for an entity with the [`PrimaryWindow`] and
/// [`WindowOptions`] components.
pub primary_window: Option<WindowOptions>,
}
impl Default for WinitPlugin {
fn default() -> Self {
Self { primary_window: Some(WindowOptions::default()) }
}
}
impl Plugin for WinitPlugin {
fn setup(&mut self, app: &mut crate::game::App) {
app.set_run_fn(winit_app_runner);
if let Some(prim) = self.primary_window.take() {
app.add_resource(WinitWindows::with_window(prim));
} else {
app.add_resource(WinitWindows::default());
}
}
fn is_ready(&self, _app: &mut crate::game::App) -> bool {
true
}
fn complete(&self, _app: &mut crate::game::App) {
}
fn cleanup(&self, _app: &mut crate::game::App) {
}
}
#[derive(Default)]
pub struct WinitWindows {
pub windows: FxHashMap<WindowId, Arc<Window>>,
pub entity_to_window: FxHashMap<Entity, WindowId>,
pub window_to_entity: FxHashMap<WindowId, Entity>,
/// windows that will be created when the Winit runner first starts.
window_queue: VecDeque<WindowOptions>,
}
impl WinitWindows {
pub fn with_window(window: WindowOptions) -> Self {
Self {
window_queue: vec![window].into(),
..Default::default()
}
}
pub fn create_window(&mut self, event_loop: &ActiveEventLoop, entity: Entity, attr: WindowAttributes) -> Result<WindowId, winit::error::OsError> {
let win = event_loop.create_window(attr)?;
let id = win.id();
self.windows.insert(id, Arc::new(win));
self.entity_to_window.insert(entity, id);
self.window_to_entity.insert(id, entity);
Ok(id)
}
pub fn get_entity_window(&self, entity: Entity) -> Option<&Arc<Window>> {
self.entity_to_window.get(&entity)
.and_then(|id| self.windows.get(id))
}
}
pub fn winit_app_runner(app: App) {
let evloop = EventLoop::new()
.expect("failed to create winit EventLoop");
let mut winit_runner = WinitRunner {
app,
};
evloop.run_app(&mut winit_runner)
.expect("loop error");
}
struct WinitRunner {
app: App
}
impl ApplicationHandler for WinitRunner {
fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
self.app.update();
let renderer = self.app.renderer.get_mut().expect("renderer was not initialized");
renderer.prepare(&mut self.app.world);
if let Some(mut event_queue) = self.app.world.try_get_resource_mut::<EventQueue>() {
event_queue.update_events();
}
match renderer.render() {
Ok(_) => {}
// Reconfigure the surface if lost
//Err(wgpu::SurfaceError::Lost) => self.on_resize(.surface_size()),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
error!("OOM");
event_loop.exit();
}
// All other errors (Outdated, Timeout) should be resolved by the next frame
Err(e) => eprintln!("{:?}", e),
}
let windows = self.app.world.get_resource::<WinitWindows>();
for window in windows.windows.values() {
window.request_redraw();
}
}
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let world = &mut self.app.world;
let mut windows = world.get_resource_mut::<WinitWindows>();
let to_create_window = windows.window_queue.pop_front().unwrap_or_default();
let window_attr = to_create_window.as_attributes();
drop(windows);
let en = world.spawn((to_create_window, PrimaryWindow));
let mut windows = world.get_resource_mut::<WinitWindows>();
let wid = windows.create_window(event_loop, en, window_attr).unwrap();
let window = windows.windows.get(&wid).unwrap().clone();
drop(windows);
debug!("Created window after resume");
let renderer = block_on(BasicRenderer::create_with_window(world, window));
if self.app.renderer.set(Box::new(renderer)).is_err() {
warn!("renderer was re-initialized");
}
}
fn window_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
window_id: winit::window::WindowId,
event: WindowEvent,
) {
let windows = self.app.world.get_resource::<WinitWindows>();
let window = match windows.windows.get(&window_id) {
Some(w) => w.clone(),
None => return,
};
drop(windows);
// If try_from failed, that means that the WindowEvent is not an
// input related event.
if let Some(input_ev) = InputEvent::from_window_event(&event) {
// Its possible to receive multiple input events before the update event for
// the InputSystem is called, so we must use a queue for the events.
if let Some(mut event_queue) = self.app.world.try_get_resource_mut::<EventQueue>() {
event_queue.trigger_event(input_ev.clone());
}
} else {
match event {
WindowEvent::ActivationTokenDone { serial, token } => todo!(),
WindowEvent::Resized(physical_size) => {
self.app.on_resize(physical_size);
let mut window_opts = self.app.world.get_resource::<WinitWindows>()
.window_to_entity.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.size = Size::new_physical(physical_size.width, physical_size.height);
},
WindowEvent::Moved(physical_position) => {
let mut state = self.app.world.get_resource_or_else(WindowState::new);
state.position = IVec2::new(physical_position.x, physical_position.y);
},
WindowEvent::CloseRequested => {
self.app.on_exit();
event_loop.exit();
},
WindowEvent::Destroyed => todo!(),
WindowEvent::DroppedFile(path_buf) => todo!(),
WindowEvent::HoveredFile(path_buf) => todo!(),
WindowEvent::HoveredFileCancelled => todo!(),
WindowEvent::Focused(focused) => {
let mut window_opts = self.app.world.get_resource::<WinitWindows>()
.window_to_entity.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.focused = focused;
},
WindowEvent::ModifiersChanged(modifiers) => debug!("modifiers changed: {:?}", modifiers),
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
let mut window_opts = self.app.world.get_resource::<WinitWindows>()
.window_to_entity.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.scale_factor = scale_factor;
},
WindowEvent::ThemeChanged(theme) => {
let mut window_opts = self.app.world.get_resource::<WinitWindows>()
.window_to_entity.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.theme = Some(theme);
},
WindowEvent::Occluded(occ) => {
let mut window_opts = self.app.world.get_resource::<WinitWindows>()
.window_to_entity.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.occluded = occ;
},
WindowEvent::RedrawRequested => {
//debug!("should redraw");
},
_ => {}
}
}
}
}

View File

@ -6,4 +6,4 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
glam = { version = "0.29.0" } glam = { version = "0.24.0" }

View File

@ -13,9 +13,9 @@ lyra-scene = { path = "../lyra-scene" }
anyhow = "1.0.75" anyhow = "1.0.75"
base64 = "0.21.4" base64 = "0.21.4"
crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] } crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] }
glam = "0.29.0" glam = "0.24.1"
gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness", "KHR_materials_specular"] } gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness", "KHR_materials_specular"] }
image = "0.25.2" image = "0.24.7"
# not using custom matcher, or file type from file path # not using custom matcher, or file type from file path
infer = { version = "0.15.0", default-features = false } infer = { version = "0.15.0", default-features = false }
mime = "0.3.17" mime = "0.3.17"

@ -1 +1 @@
Subproject commit a761f4094bc18190285b4687ec804161fea874b6 Subproject commit 54c9926a04cdef657289fd67730c0b85d1bdda3e

View File

@ -190,7 +190,7 @@ pub fn lua_script_last_stage_system(world: &mut World) -> anyhow::Result<()> {
pub struct LuaScriptingPlugin; pub struct LuaScriptingPlugin;
impl Plugin for LuaScriptingPlugin { impl Plugin for LuaScriptingPlugin {
fn setup(&mut self, game: &mut lyra_game::game::Game) { fn setup(&self, game: &mut lyra_game::game::Game) {
let world = game.world_mut(); let world = game.world_mut();
world.add_resource_default::<TypeRegistry>(); world.add_resource_default::<TypeRegistry>();

View File

@ -1,5 +1,4 @@
[toolchain] [toolchain]
#channel = "nightly-2023-11-21" channel = "nightly-2023-11-21"
channel = "nightly"
#date = "2023-11-21" #date = "2023-11-21"
targets = [ "x86_64-unknown-linux-gnu" ] targets = [ "x86_64-unknown-linux-gnu" ]