Compare commits

...

8 Commits

51 changed files with 3147 additions and 2044 deletions

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,97 @@
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

@ -6,3 +6,6 @@ pub use or::*;
mod not;
pub use not::*;
mod changed;
pub use changed::*;

View File

@ -89,6 +89,12 @@ pub trait AsQuery {
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 {
fn into_query(self) -> Self;
}
@ -125,10 +131,22 @@ impl Query for () {
}
}
impl Filter for () {
type Item<'a> = bool;
}
impl AsQuery for () {
type Query = ();
}
pub trait Filter: Query {
type Item<'a> = bool;
}
impl AsFilter for () {
type Filter = ();
}
#[cfg(test)]
mod tests {
use crate::{World, archetype::Archetype, tests::Vec2};

View File

@ -1,8 +1,8 @@
use crate::World;
use super::{Query, Fetch, AsQuery};
use super::{Query, Fetch, AsQuery, Filter, AsFilter};
impl<'a, F1> Fetch<'a> for (F1,)
/* impl<'a, F1> Fetch<'a> for (F1,)
where
F1: Fetch<'a>,
{
@ -102,7 +102,7 @@ where
Q2: AsQuery,
{
type Query = (Q1::Query, Q2::Query);
}
} */
macro_rules! impl_bundle_tuple {
( $($name: ident),+ ) => (
@ -154,10 +154,39 @@ macro_rules! impl_bundle_tuple {
impl<$($name: AsQuery),+> AsQuery for ($($name,)+) {
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
impl_bundle_tuple! { Q1 }
impl_bundle_tuple! { Q1, Q2 }
impl_bundle_tuple! { Q1, Q2, Q3 }
impl_bundle_tuple! { Q1, Q2, Q3, Q4 }
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 super::{Query, Fetch, AsQuery};
use super::{AsFilter, AsQuery, Fetch, Filter, Query};
pub type View<'a, Q, F = ()> = ViewState<'a, <Q as AsQuery>::Query, <F as AsQuery>::Query>;
pub struct ViewState<'a, Q: Query, F: Query> {
pub struct ViewState<'a, Q: Query, F: Filter> {
world: &'a World,
query: Q,
filter: F,
@ -16,7 +16,7 @@ pub struct ViewState<'a, Q: Query, F: Query> {
impl<'a, Q, F> ViewState<'a, Q, F>
where
Q: Query,
F: Query,
F: Filter,
{
pub fn new(world: &'a World, query: Q, filter: F, archetypes: Vec<&'a Archetype>) -> Self {
Self {
@ -38,7 +38,7 @@ where
}
/// Consumes `self`, adding a filter to the view.
pub fn with<U: AsQuery>(self, filter: U::Query) -> ViewState<'a, Q, (F, U::Query)> {
pub fn with<U: AsFilter>(self, filter: U::Filter) -> ViewState<'a, Q, (F, U::Filter)> {
ViewState::new(self.world, self.query, (self.filter, filter), self.archetypes)
}
}
@ -46,18 +46,19 @@ where
impl<'a, Q, F> IntoIterator for ViewState<'a, Q, F>
where
Q: Query,
F: Query,
F: Filter,
{
type Item = Q::Item<'a>;
type IntoIter = ViewIter<'a, Q, F>;
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 {
world: self.world,
tick,
tick: self.world.current_tick(),
has_ticked: false,
query: self.query,
filter: self.filter,
fetcher: None,
@ -69,9 +70,10 @@ where
}
}
pub struct ViewIter<'a, Q: Query, F: Query> {
pub struct ViewIter<'a, Q: Query, F: Filter> {
world: &'a World,
tick: Tick,
has_ticked: bool,
query: Q,
filter: F,
fetcher: Option<Q::Fetch<'a>>,
@ -84,7 +86,7 @@ pub struct ViewIter<'a, Q: Query, F: Query> {
impl<'a, Q, F> Iterator for ViewIter<'a, Q, F>
where
Q: Query,
F: Query,
F: Filter,
{
type Item = Q::Item<'a>;
@ -110,6 +112,13 @@ where
let entity_index = ArchetypeEntityId(entity_index);
if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(entity_index) {
// only tick the world if the filter has fetched, and when the world hasn't
// been ticked yet.
if Q::MUTATES && !self.has_ticked {
self.has_ticked = true;
self.tick = self.world.tick();
}
let i = unsafe { fetcher.get_item(entity_index) };
return Some(i);
}
@ -147,17 +156,17 @@ pub struct ViewOne<'a, Q: Query> {
impl<'a, Q: Query> ViewOne<'a, Q> {
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 {
world,
tick,
tick: world.current_tick(),
entity,
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) {
let arch = self.world.archetypes.get(&record.id)
.expect("An invalid record was specified for an entity");
@ -165,6 +174,9 @@ impl<'a, Q: Query> ViewOne<'a, Q> {
if self.query.can_visit_archetype(arch) {
let mut fetch = unsafe { self.query.fetch(self.world, arch, self.tick) };
if fetch.can_visit_item(record.index) {
// only tick the world when something is actually fetched.
self.world.tick();
return Some(unsafe { fetch.get_item(record.index) });
}
}

View File

@ -2,6 +2,7 @@ use std::marker::PhantomData;
use lyra_ecs_derive::Component;
use crate::query::Filter;
use crate::query::Query;
use crate::query::ViewState;
use crate::Entity;
@ -98,7 +99,7 @@ impl World {
impl<'a, Q, F> ViewState<'a, Q, F>
where
Q: Query,
F: Query,
F: Filter,
{
/// 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>

View File

@ -1,7 +1,7 @@
use std::{any::Any, marker::PhantomData, ptr::NonNull};
use paste::paste;
use crate::{World, Access, ResourceObject, query::{Query, ViewState, ResMut, Res}};
use crate::{World, Access, ResourceObject, query::{Query, Filter, ViewState, ResMut, Res}};
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>
where
Q: Query + 'static,
F: Query + 'static,
F: Filter + 'static,
{
type State = (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 crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker};
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsFilter, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker};
/// 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.
pub fn filtered_view<Q: AsQuery, F: AsQuery>(&self) -> ViewState<Q::Query, F::Query> {
pub fn filtered_view<Q: AsQuery, F: AsFilter>(&self) -> ViewState<Q::Query, F::Filter> {
let archetypes = self.archetypes.values().collect();
ViewState::<Q::Query, F::Query>::new(self, Q::Query::new(), F::Query::new(), archetypes)
ViewState::<Q::Query, F::Filter>::new(self, Q::Query::new(), F::Filter::new(), archetypes)
}
/// 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.
pub fn filtered_view_iter<Q: AsQuery, F: AsQuery>(&self) -> ViewIter<Q::Query, F::Query> {
pub fn filtered_view_iter<Q: AsQuery, F: AsFilter>(&self) -> ViewIter<Q::Query, F::Filter> {
let archetypes = self.archetypes.values().collect();
let v = ViewState::new(self, Q::Query::new(), F::Query::new(), archetypes);
let v = ViewState::new(self, Q::Query::new(), F::Filter::new(), archetypes);
v.into_iter()
}

View File

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

View File

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

View File

@ -1,9 +1,8 @@
use std::{sync::Arc, collections::VecDeque, ptr::NonNull};
use std::{cell::OnceCell, collections::VecDeque, ptr::NonNull};
use async_std::task::block_on;
use lyra_ecs::{World, system::{System, IntoSystem}};
use tracing::{error, info, Level};
use lyra_ecs::{system::{IntoSystem, System}, ResourceObject, World};
use lyra_math::IVec2;
use tracing::{info, error, Level};
use tracing_appender::non_blocking;
use tracing_subscriber::{
layer::SubscriberExt,
@ -11,9 +10,7 @@ use tracing_subscriber::{
util::SubscriberInitExt, fmt,
};
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};
use crate::{plugin::Plugin, render::renderer::Renderer, Stage, StagedExecutor};
#[derive(Clone, Copy, Hash, Debug)]
pub enum GameStages {
@ -37,8 +34,13 @@ pub struct Controls<'a> {
#[derive(Clone, Default)]
pub struct WindowState {
pub is_focused: bool,
pub is_cursor_inside_window: bool,
/// Indicates if the window is currently focused.
pub focused: 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 {
@ -47,305 +49,19 @@ impl WindowState {
}
}
struct GameLoop {
window: Arc<Window>,
renderer: Box<dyn Renderer>,
world: World,
staged_exec: StagedExecutor,
}
impl GameLoop {
pub async fn new(window: Arc<Window>, mut world: World, staged_exec: StagedExecutor) -> Self {
let renderer = BasicRenderer::create_with_window(&mut world, window.clone()).await;
Self {
window: Arc::clone(&window),
renderer: Box::new(renderer),
world,
staged_exec,
}
}
pub async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
self.renderer.on_resize(&mut self.world, new_size);
}
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>,
pub struct App {
pub(crate) renderer: OnceCell<Box<dyn Renderer>>,
pub world: World,
plugins: VecDeque<Box<dyn Plugin>>,
system_exec: Option<StagedExecutor>,
startup_systems: VecDeque<Box<dyn System>>,
staged_exec: StagedExecutor,
run_fn: OnceCell<Box<dyn FnOnce(App)>>,
}
impl Default for Game {
fn default() -> Self {
let mut staged = StagedExecutor::new();
staged.add_stage(GameStages::First);
staged.add_stage_after(GameStages::First, GameStages::PreUpdate);
staged.add_stage_after(GameStages::PreUpdate, GameStages::Update);
staged.add_stage_after(GameStages::Update, GameStages::PostUpdate);
staged.add_stage_after(GameStages::PostUpdate, GameStages::Last);
Self {
world: Some(World::new()),
plugins: VecDeque::new(),
system_exec: Some(staged),
startup_systems: VecDeque::new(),
}
}
}
impl Game {
pub async fn initialize() -> Game {
Self::default()
}
/// Get the world of this game
pub fn world_mut(&mut self) -> &mut World {
// world is always `Some`, so unwrapping is safe
self.world.as_mut().unwrap()
}
/// Get the world of this game
pub fn world(&self) -> &World {
// world is always `Some`, so unwrapping is safe
self.world.as_ref().unwrap()
}
/// Add a system to the ecs world
pub fn with_system<S, A>(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self
where
S: IntoSystem<A>,
<S as IntoSystem<A>>::System: 'static
{
let system_dispatcher = self.system_exec.as_mut().unwrap();
system_dispatcher.add_system_to_stage(GameStages::Update, name, system.into_system(), depends);
self
}
/// Add a stage.
///
/// 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 {
let system_dispatcher = self.system_exec.as_mut().unwrap();
system_dispatcher.add_stage(stage);
self
}
/// Add a stage that executes after another one.
///
/// Parameters:
/// * `before` - The stage that will run before `after`.
/// * `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 {
let system_dispatcher = self.system_exec.as_mut().unwrap();
system_dispatcher.add_stage_after(before, after);
self
}
/// Add a system to an already existing stage.
///
/// # Panics
/// Panics if the stage was not already added to the executor
pub fn add_system_to_stage<T, S, A>(&mut self, stage: T,
name: &str, system: S, depends: &[&str]) -> &mut Self
where
T: Stage,
S: IntoSystem<A>,
<S as IntoSystem<A>>::System: 'static
{
let system_dispatcher = self.system_exec.as_mut().unwrap();
system_dispatcher.add_system_to_stage(stage, name, system.into_system(), depends);
self
}
/// Add a startup system that will be ran right after plugins are setup.
/// They will only be ran once
pub fn with_startup_system<S>(&mut self, system: S) -> &mut Self
where
S: System + 'static
{
self.startup_systems.push_back(Box::new(system));
self
}
/// Add a plugin to the game. These are executed as they are added.
pub fn with_plugin<P>(&mut self, plugin: P) -> &mut Self
where
P: Plugin + 'static
{
let plugin = Box::new(plugin);
plugin.as_ref().setup(self);
self.plugins.push_back(plugin);
self
}
/// Override the default (empty) world
///
/// 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 {
self.world = Some(world);
self
}
/// Start the game
pub async fn run(&mut self) {
impl App {
pub fn new() -> Self {
// init logging
let (stdout_layer, _stdout_nb) = non_blocking(std::io::stdout());
let (stdout_layer, stdout_nb) = non_blocking(std::io::stdout());
{
let t = tracing_subscriber::registry()
.with(fmt::layer().with_writer(stdout_layer));
@ -363,27 +79,139 @@ impl Game {
.init();
}
// store the logger worker guard to ensure logging still happens
let mut world = World::new();
world.add_resource(stdout_nb);
let world = self.world.take().unwrap_or_default();
// initialize ecs system stages
let mut staged = StagedExecutor::new();
staged.add_stage(GameStages::First);
staged.add_stage_after(GameStages::First, GameStages::PreUpdate);
staged.add_stage_after(GameStages::PreUpdate, GameStages::Update);
staged.add_stage_after(GameStages::Update, GameStages::PostUpdate);
staged.add_stage_after(GameStages::PostUpdate, GameStages::Last);
// 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!");
Self {
renderer: OnceCell::new(),
world,
plugins: Default::default(),
startup_systems: Default::default(),
staged_exec: staged,
run_fn: OnceCell::new(),
}
}
// start winit event loops
let event_loop = EventLoop::new();
let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap());
pub fn update(&mut self) {
let wptr = NonNull::from(&self.world);
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;
if let Err(e) = self.staged_exec.execute(wptr, true) {
error!("Error when executing staged systems: '{}'", e);
}
}
event_loop.run(move |event, _, control_flow| {
g_loop.run_sync(event, control_flow);
});
pub(crate) fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
self.renderer.get_mut()
.expect("renderer was not initialized")
.on_resize(&mut self.world, new_size);
}
pub(crate) fn on_exit(&mut self) {
info!("On exit!");
}
pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
self.world.add_resource(data);
}
/// Add a system to the ecs world
pub fn with_system<S, A>(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self
where
S: IntoSystem<A>,
<S as IntoSystem<A>>::System: 'static
{
self.staged_exec.add_system_to_stage(GameStages::Update, name, system.into_system(), depends);
self
}
/// Add a stage.
///
/// 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 {
self.staged_exec.add_stage(stage);
self
}
/// Add a stage that executes after another one.
///
/// Parameters:
/// * `before` - The stage that will run before `after`.
/// * `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 {
self.staged_exec.add_stage_after(before, after);
self
}
/// Add a system to an already existing stage.
///
/// # Panics
/// Panics if the stage was not already added to the executor
pub fn add_system_to_stage<T, S, A>(&mut self, stage: T,
name: &str, system: S, depends: &[&str]) -> &mut Self
where
T: Stage,
S: IntoSystem<A>,
<S as IntoSystem<A>>::System: 'static
{
self.staged_exec.add_system_to_stage(stage, name, system.into_system(), depends);
self
}
/// Add a startup system that will be ran right after plugins are setup.
/// They will only be ran once
pub fn with_startup_system<S>(&mut self, system: S) -> &mut Self
where
S: System + 'static
{
self.startup_systems.push_back(Box::new(system));
self
}
/// 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
where
P: Plugin + 'static
{
plugin.setup(self);
let plugin = Box::new(plugin);
self.plugins.push_back(plugin);
self
}
/// Override the default (empty) world
///
/// 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 {
self.world = world;
self
}
pub fn set_run_fn<F>(&self, f: F)
where
F: FnOnce(App) + 'static
{
// ignore if a runner function was already set
let _ = self.run_fn.set(Box::new(f));
}
pub fn run(mut self) {
let f = self.run_fn.take()
.expect("No run function set");
f(self);
}
}

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use winit::{event::{DeviceId, KeyboardInput, ModifiersState, MouseScrollDelta, TouchPhase, MouseButton, AxisId, Touch, WindowEvent, ElementState}, dpi::PhysicalPosition};
use winit::{dpi::PhysicalPosition, event::{AxisId, DeviceId, ElementState, KeyEvent, MouseButton, MouseScrollDelta, Touch, TouchPhase, WindowEvent}};
/// Wrapper around events from `winit::WindowEvent` that are specific to input related events.
///
@ -19,7 +19,7 @@ pub enum InputEvent {
/// An event from the keyboard has been received.
KeyboardInput {
device_id: DeviceId,
input: KeyboardInput,
event: KeyEvent,
/// If true, the event was generated synthetically by winit in one of the following circumstances:
/// Synthetic key press events are generated for all keys pressed when a window gains focus.
@ -28,20 +28,10 @@ pub enum InputEvent {
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.
CursorMoved {
device_id: DeviceId,
position: PhysicalPosition<f64>,
modifiers: ModifiersState,
},
/// The cursor has entered the window.
@ -59,8 +49,6 @@ pub enum InputEvent {
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
/// Deprecated in favor of WindowEvent::ModifiersChanged
modifiers: ModifiersState,
},
/// An mouse button press has been received.
@ -68,51 +56,6 @@ pub enum InputEvent {
device_id: DeviceId,
state: ElementState,
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.
@ -136,89 +79,62 @@ pub enum InputEvent {
/// Touch event has been received
///
/// ## Platform-specific
///
/// - **Web**: Doesnt take into account CSS border, padding, or transform.
/// - **macOS:** Unsupported.
Touch(Touch),
}
#[derive(Debug, PartialEq)]
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> {
impl InputEvent {
pub fn from_window_event(value: &WindowEvent) -> Option<Self> {
match value {
WindowEvent::KeyboardInput { device_id, input, is_synthetic } =>
Ok(InputEvent::KeyboardInput {
WindowEvent::KeyboardInput { device_id, event, is_synthetic } =>
Some(InputEvent::KeyboardInput {
device_id: *device_id,
input: *input,
event: event.clone(),
is_synthetic: *is_synthetic
}),
#[allow(deprecated, reason="Compatibility")]
WindowEvent::CursorMoved { device_id, position, modifiers } =>
Ok(InputEvent::CursorMoved {
WindowEvent::CursorMoved { device_id, position, } =>
Some(InputEvent::CursorMoved {
device_id: *device_id,
position: *position,
modifiers: *modifiers
}),
WindowEvent::CursorEntered { device_id } =>
Ok(InputEvent::CursorEntered {
Some(InputEvent::CursorEntered {
device_id: *device_id
}),
WindowEvent::CursorLeft { device_id } =>
Ok(InputEvent::CursorLeft {
Some(InputEvent::CursorLeft {
device_id: *device_id
}),
#[allow(deprecated, reason="Compatibility")]
WindowEvent::MouseWheel { device_id, delta, phase, modifiers } =>
Ok(InputEvent::MouseWheel {
WindowEvent::MouseWheel { device_id, delta, phase } =>
Some(InputEvent::MouseWheel {
device_id: *device_id,
delta: *delta,
phase: *phase,
modifiers: *modifiers
}),
#[allow(deprecated, reason="Compatibility")]
WindowEvent::MouseInput { device_id, state, button, modifiers } =>
Ok(InputEvent::MouseInput {
WindowEvent::MouseInput { device_id, state, button } =>
Some(InputEvent::MouseInput {
device_id: *device_id,
state: *state,
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 } =>
Ok(InputEvent::TouchpadPressure {
Some(InputEvent::TouchpadPressure {
device_id: *device_id,
pressure: *pressure,
stage: *stage
}),
WindowEvent::AxisMotion { device_id, axis, value } =>
Ok(InputEvent::AxisMotion {
Some(InputEvent::AxisMotion {
device_id: *device_id,
axis: *axis,
value: *value
}),
WindowEvent::Touch(t) => Ok(InputEvent::Touch(*t)),
_ => Err(InputEventConversionError::FromError(value))
WindowEvent::Touch(t) => Some(InputEvent::Touch(*t)),
_ => None,
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -865,10 +865,11 @@ impl Node for ShadowMapsPass {
view: atlas.view(),
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: true,
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
..Default::default()
});
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.
/// The texture may change due to resizes, or changes to the view target chain
/// from other nodes.
bg_cache: HashMap<wgpu::Id, wgpu::BindGroup>,
bg_cache: HashMap<wgpu::Id<wgpu::TextureView>, wgpu::BindGroup>,
}
impl TintPass {
@ -152,10 +152,12 @@ impl Node for TintPass {
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: true,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
pass.set_pipeline(pipeline.as_render());

View File

@ -6,10 +6,19 @@ use crate::math;
enum RenderTargetInner {
Surface {
surface: wgpu::Surface,
/// The surface that will be rendered to.
///
/// 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,
},
Texture {
/// The texture that will be rendered to.
texture: Arc<wgpu::Texture>,
}
}
@ -25,7 +34,7 @@ impl From<wgpu::Texture> for RenderTarget {
}
impl RenderTarget {
pub fn from_surface(surface: wgpu::Surface, config: wgpu::SurfaceConfiguration) -> Self {
pub fn from_surface(surface: wgpu::Surface<'static>, config: wgpu::SurfaceConfiguration) -> Self {
Self(RenderTargetInner::Surface { surface, config })
}

View File

@ -1,4 +1,4 @@
use std::{mem, num::{NonZeroU32, NonZeroU8}};
use std::mem;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TextureViewDescriptor {
@ -16,13 +16,13 @@ pub struct TextureViewDescriptor {
/// Mip level 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.
pub mip_level_count: Option<NonZeroU32>,
pub mip_level_count: Option<u32>,
/// Base array layer.
pub base_array_layer: u32,
/// Layer 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.
pub array_layer_count: Option<NonZeroU32>,
pub array_layer_count: Option<u32>,
}
impl TextureViewDescriptor {
@ -79,7 +79,7 @@ pub struct SamplerDescriptor {
/// If this is enabled, this is a comparison sampler using the given comparison function.
pub compare: Option<wgpu::CompareFunction>,
/// Valid values: 1, 2, 4, 8, and 16.
pub anisotropy_clamp: Option<NonZeroU8>,
pub anisotropy_clamp: u16,
/// Border color to use when address_mode is [`AddressMode::ClampToBorder`]
pub border_color: Option<wgpu::SamplerBorderColor>,
}

View File

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

View File

@ -2,19 +2,25 @@ use std::{ops::Deref, rc::Rc, sync::Arc};
use wgpu::PipelineLayout;
use super::Shader;
use super::{PipelineCompilationOptions, Shader};
//#[derive(Debug, Clone)]
pub struct ComputePipelineDescriptor {
pub label: Option<String>,
pub layouts: Vec<Arc<wgpu::BindGroupLayout>>,
pub push_constant_ranges: Vec<wgpu::PushConstantRange>,
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage.
pub shader: Rc<Shader>,
/// The entry point in the compiled shader.
/// There must be a function in the shader with the same name.
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 {
@ -85,6 +91,8 @@ impl ComputePipeline {
layout: layout.as_ref(),
module: &compiled_shader,
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);

View File

@ -1,4 +1,6 @@
mod shader;
use std::collections::HashMap;
pub use shader::*;
mod pipeline;
@ -12,3 +14,20 @@ pub use render_pipeline::*;
mod 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,6 +97,7 @@ impl RenderPipeline {
module: &vrtx_shad,
entry_point: &desc.vertex.entry_point,
buffers: &vrtx_buffs,
compilation_options: Default::default(),
};
let frag_module = desc.fragment.as_ref().map(|f| {
@ -115,6 +116,7 @@ impl RenderPipeline {
module: fm.unwrap(),
entry_point: &f.entry_point,
targets: &f.targets,
compilation_options: Default::default(),
});
let render_desc = wgpu::RenderPipelineDescriptor {
@ -126,6 +128,7 @@ impl RenderPipeline {
multisample: desc.multisample,
fragment: fstate,
multiview: desc.multiview,
cache: None,
};
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_SPOT = 2u;
type vec2f = vec2<f32>;
type vec3f = vec3<f32>;
type vec4f = vec4<f32>;
alias vec2f = vec2<f32>;
alias vec3f = vec3<f32>;
alias vec4f = vec4<f32>;
struct CameraUniform {
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);
}
fn cone_inside_frustum(cone: Cone, frustum: array<Plane, 6>) -> bool {
var frustum = frustum;
fn cone_inside_frustum(cone: Cone, frustum_in: array<Plane, 6>) -> bool {
var frustum = frustum_in;
for (var i = 0u; i < 4u; i++) {
// TODO: better cone checking
if (sphere_inside_plane(cone.tip, cone.radius, frustum[i])) {

View File

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

View File

@ -1,213 +1,506 @@
use std::sync::Arc;
use std::ops::{Deref, DerefMut};
use glam::{Vec2, IVec2};
use lyra_ecs::World;
use tracing::{warn, error};
use winit::{window::{Window, Fullscreen}, dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError};
use lyra_ecs::{query::{filter::Changed, Entities, ResMut}, Component, World};
use lyra_resource::Image;
use tracing::error;
use winit::window::{CustomCursor, Fullscreen, Window};
pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel};
use crate::{plugin::Plugin, change_tracker::Ct, input::InputEvent, EventQueue};
use crate::{change_tracker::Ct, plugin::Plugin, winit::WinitWindows};
#[derive(Default, Clone)]
pub enum WindowMode {
/// The window will use the full size of the screen.
Fullscreen,
/// The window will be fullscreen with the full size of the screen without a border.
Borderless,
/// The window will not be fullscreen and will use the windows resolution size.
#[default]
Windowed,
#[derive(Default, Clone, Copy, PartialEq)]
pub struct Area {
position: Position,
size: Size,
}
#[derive(Clone, Copy, PartialEq)]
pub enum Size {
Physical { x: u32, y: u32 },
Logical { x: f64, y: f64 },
}
impl Default for Size {
fn default() -> Self {
Self::Physical { x: 0, y: 0 }
}
}
impl Into<winit::dpi::Size> for Size {
fn into(self) -> winit::dpi::Size {
match self {
Size::Physical { x, y } => winit::dpi::PhysicalSize::new(x, y).into(),
Size::Logical { x, y } => winit::dpi::LogicalSize::new(x, y).into(),
}
}
}
impl From<winit::dpi::Size> for Size {
fn from(value: winit::dpi::Size) -> Self {
match value {
winit::dpi::Size::Physical(physical_position) => Self::new_physical(physical_position.width, physical_position.height),
winit::dpi::Size::Logical(logical_position) => Self::new_logical(logical_position.width, logical_position.height),
}
}
}
impl Size {
pub fn new_physical(x: u32, y: u32) -> Self {
Self::Physical { x, y }
}
pub fn new_logical(x: f64, y: f64) -> Self {
Self::Logical { x, y }
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum Position {
Physical { x: i32, y: i32 },
Logical { x: f64, y: f64 },
}
impl Default for Position {
fn default() -> Self {
Self::Physical { x: 0, y: 0 }
}
}
impl Into<winit::dpi::Position> for Position {
fn into(self) -> winit::dpi::Position {
match self {
Position::Physical { x, y } => winit::dpi::PhysicalPosition::new(x, y).into(),
Position::Logical { x, y } => winit::dpi::LogicalPosition::new(x, y).into(),
}
}
}
impl From<winit::dpi::Position> for Position {
fn from(value: winit::dpi::Position) -> Self {
match value {
winit::dpi::Position::Physical(physical_position) => Self::new_physical(physical_position.x, physical_position.y),
winit::dpi::Position::Logical(logical_position) => Self::new_logical(logical_position.x, logical_position.y),
}
}
}
impl Position {
pub fn new_physical(x: i32, y: i32) -> Self {
Self::Physical { x, y }
}
pub fn new_logical(x: f64, y: f64) -> Self {
Self::Logical { x, y }
}
}
/// Flag component that
#[derive(Clone, Component)]
pub struct PrimaryWindow;
#[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.
#[derive(Clone)]
#[derive(Clone, Component)]
pub struct WindowOptions {
/// Prevents the window contents from being captured by other apps.
///
/// Platform-specific:
/// * macOS: if false, NSWindowSharingNone is used but doesnt completely prevent all apps
/// from reading the window content, for instance, QuickTime.
/// * iOS / Android / x11 / Wayland / Web / Orbital: Unsupported.
pub content_protected: bool,
/// Set grabbing mode on the cursor preventing it from leaving the window.
pub cursor_grab: CursorGrabMode,
/// Modifies whether the window catches cursor events.
/// If true, the window will catch the cursor events. If false, events are passed through
/// the window such that any otherwindow behind it receives them. By default hittest is enabled.
///
/// Platform-specific:
/// * iOS / Android / Web / X11 / Orbital: Unsupported.
pub cursor_hittest: bool,
/// The cursor icon of the window.
/// The enabled window buttons.
///
/// Platform-specific
/// * iOS / Android / Orbital: Unsupported.
pub cursor_icon: CursorIcon,
/// 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.
/// * **Wayland / X11 / Orbital:** Not implemented. Always set to [`WindowButtons::all`].
/// * **Web / iOS / Android:** Unsupported. Always set to [`WindowButtons::all`].
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,
/// Sets location of IME candidate box in client area coordinates relative to the top left.
///
/// This is the window / popup / overlay that allows you to select the desired characters.
/// The look of this box may differ between input devices, even on the same platform.
pub ime_position: Vec2,
/// Modifies the inner size of the window.
///
/// Platform-specific:
/// * iOS / Android: Unsupported.
/// * Web: Sets the size of the canvas element.
pub inner_size: IVec2,
/// Sets a maximum dimension size for the window.
///
/// Platform-specific:
/// * iOS / Android / Web / Orbital: Unsupported.
pub max_inner_size: Option<IVec2>,
/// Sets a minimum dimension size for the window.
///
/// Platform-specific:
/// * 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>,
/// Modifies the title of the window.
///
/// Platform-specific:
/// * iOS / Android: Unsupported.
pub title: String,
/// Sets the window icon.
/// On Windows and X11, this is typically the small icon in the top-left corner of the titlebar.
/// Gets or sets if the window is in focus.
///
/// 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 icon: Option<Icon>,
/// Change the window level.
/// This is just a hint to the OS, and the system could ignore it.
pub level: WindowLevel,
/// Get/set the window's focused state.
/// * **iOS / Android / Wayland / Orbital:** Unsupported.
pub focused: bool,
/// Get whether or not the cursor is inside the window.
pub cursor_inside_window: 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.
///
/// Platform-specific
/// * **macOS:** if false, [`NSWindowSharingNone`](https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone)
/// is used but doesnt completely prevent all apps from reading the window content,
/// for instance, QuickTime.
/// * **iOS / Android / x11 / Wayland / Web / Orbital:** Unsupported.
pub content_protected: bool,
pub cursor: Cursor,
/// Sets whether the window should get IME events
///
/// When IME is allowed, the window will receive [`Ime`](winit::event::WindowEvent::Ime)
/// events, and during the preedit phase the window will NOT get KeyboardInput events.
/// The window should allow IME while it is expecting text input.
///
/// When IME is not allowed, the window wont receive [`Ime`](winit::event::WindowEvent::Ime)
/// events, and will receive [`KeyboardInput`](winit::event::WindowEvent::KeyboardInput) events
/// for every keypress instead. Not allowing IME is useful for games for example.
/// IME is not allowed by default.
///
/// Platform-specific
/// * **macOS:** IME must be enabled to receive text-input where dead-key sequences are combined.
/// * **iOS / Android / Web / Orbital:** Unsupported.
/// * **X11:** Enabling IME will disable dead keys reporting during compose.
pub ime_allowed: bool,
/// Sets area of IME candidate box in window client area coordinates relative to the top left.
///
/// Platform-specific
/// * **X11:** - area is not supported, only position.
/// * **iOS / Android / Web / Orbital:** Unsupported.
pub ime_cursor_area: Option<Area>,
/// Gets/sets the minimum size of the window.
///
/// Platform-specific
/// * **iOS / Android / Orbital:** Unsupported.
pub min_size: Option<Size>,
/// 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.
///
/// Specify `None` to reset the theme to the system default. May also be `None` on unsupported
/// platforms.
///
/// Platform-specific
/// * **Wayland:** Sets the theme for the client side decorations. Using `None` will use dbus
/// to get the system preference.
/// * **X11:** Sets `_GTK_THEME_VARIANT` hint to `dark` or `light` and if `None` is used,
/// it will default to [`Theme::Dark`](winit::window::Theme::Dark).
/// * **iOS / Android / Web / Orbital:** Unsupported.
pub theme: Option<Theme>,
/// Gets/sets the title of the window.
///
/// Platform-specific
/// * **iOS / Android:** Unsupported.
/// * **X11 / Wayland / Web:** Cannot get, will always be an empty string.
pub title: String,
/// Gets/sets the window's transparency state.
///
/// 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
/// * **macOS:** This will reset the windows background color.
/// * **Web / iOS / Android:** Unsupported.
/// * **X11:** Can only be set while building the window.
pub transparent: bool,
/// 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.
///
/// This is just a hint to the OS, and the system could ignore it.
///
/// See [`WindowLevel`] for details.
pub window_level: WindowLevel,
/// Show [window menu](https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu)
/// at a specified position.
///
/// This is the context menu that is normally shown when interacting with the title bar. This is useful when implementing custom decorations.
/// Platform-specific
/// * **Android / iOS / macOS / Orbital / Wayland / Web / X11:** Unsupported.
pub show_window_menu: Option<Position>,
/// Gets the window's occluded state (completely hidden from view).
///
/// 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 {
fn default() -> Self {
Self {
content_protected: false,
cursor_grab: CursorGrabMode::None,
cursor_hittest: true,
cursor_icon: CursorIcon::Default,
cursor_visible: true,
decorations: true,
enabled_buttons: WindowButtons::all(),
mode: WindowMode::Windowed,
ime_allowed: false,
ime_position: Default::default(),
inner_size: glam::i32::IVec2::new(800, 600),
max_inner_size: None,
min_inner_size: None,
maximized: false,
minimized: false,
//outer_position: Default::default(),
resizeable: false,
resize_increments: None,
theme: None,
title: "Lyra Engine Game".to_string(),
icon: None,
level: WindowLevel::Normal,
focused: false,
cursor_inside_window: false,
Self::from(Window::default_attributes())
}
}
impl WindowOptions {
/// Create winit [`WindowAttributes`] from self.
pub(crate) fn as_attributes(&self) -> winit::window::WindowAttributes {
let mut att = winit::window::Window::default_attributes();
att.enabled_buttons = self.enabled_buttons.clone();
att.fullscreen = self.fullscreen.clone();
att.inner_size = Some(self.size.into());
att.decorations = self.decorated;
att.maximized = self.maximized;
att.resizable = self.resizable;
att.visible = self.visible.unwrap_or(true);
att.position = self.outer_position.map(|p| p.into());
att.resize_increments = self.resize_increments.map(|i| i.into());
att.blur = self.blur;
att.content_protected = self.content_protected;
att.cursor = match self.cursor.appearance.clone() {
CursorAppearance::Icon(icon) => winit::window::Cursor::Icon(icon),
CursorAppearance::Custom(custom) => winit::window::Cursor::Custom(custom),
};
att.min_inner_size = self.min_size.map(|s| s.into());
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
}
}
@ -217,163 +510,143 @@ pub struct WindowPlugin {
create_options: WindowOptions,
}
/// Convert an Vec2 to a LogicalPosition<f32>
fn vec2_to_logical_pos(pos: Vec2) -> LogicalPosition<f32> {
LogicalPosition { x: pos.x, y: pos.y }
}
/// A system that syncs Winit Windows with [`WindowOptions`] components.
pub fn window_sync_system(world: &mut World) -> anyhow::Result<()> {
for (entity, opts, mut last, windows) in world.filtered_view_iter::<(Entities, &WindowOptions, &mut LastWindow, ResMut<WinitWindows>), Changed<WindowOptions>>() {
let window = windows.get_entity_window(entity)
.expect("entity's window is missing");
/// Convert an IVec2 to a LogicalSize<i32>
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.enabled_buttons != last.enabled_buttons {
window.set_enabled_buttons(opts.enabled_buttons);
}
}
window.set_cursor_grab(*grab)?;
if opts.focused != last.focused && opts.focused {
window.focus_window();
}
Ok(())
}
if opts.fullscreen != last.fullscreen {
window.set_fullscreen(opts.fullscreen.clone());
}
/// if the window is set to confine the cursor, the cursor is invisible,
/// and the window is focused, set the cursor position to the center of the screen.
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.size != last.size {
if window.request_inner_size(opts.size).is_some() {
error!("request to increase window size failed");
}
window.set_content_protected(opts.content_protected);
set_cursor_grab(&window, &mut opts.cursor_grab)?;
match window.set_cursor_hittest(opts.cursor_hittest) {
Ok(()) => {},
Err(ExternalError::NotSupported(_)) => { /* ignore */ },
Err(e) => {
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
window.set_decorations(opts.decorations); // TODO: Handle unsupported platforms
window.set_enabled_buttons(opts.enabled_buttons); // TODO: Handle unsupported platforms
}
// 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.decorated != last.decorated {
window.set_decorations(opts.decorated);
}
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.max_inner_size.is_some() {
window.set_max_inner_size(ivec2_to_logical_size_op(opts.max_inner_size));
}
if opts.min_inner_size.is_some() {
window.set_min_inner_size(ivec2_to_logical_size_op(opts.min_inner_size));
}
if opts.maximized != last.maximized {
window.set_maximized(opts.maximized);
window.set_minimized(opts.minimized);
window.set_resizable(opts.resizeable);
window.set_resize_increments(vec2_to_logical_size_op(opts.resize_increments));
window.set_theme(opts.theme);
window.set_title(&opts.title);
window.set_window_icon(opts.icon.clone());
window.set_window_level(opts.level);
// reset the tracker after we mutably used it
opts.reset();
center_mouse(&window, &opts);
} else {
drop(opts); // drop the Ref, we're about to get a RefMut
let mut opts = world.get_resource_mut::<Ct<WindowOptions>>();
if let Some(event_queue) = world.try_get_resource_mut::<EventQueue>() {
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;
},
_ => {},
}
}
}
}
// update the stored state of the window to match the actual window
opts.focused = window.has_focus();
opts.reset();
center_mouse(&window, &opts);
}
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);
}
if opts.cursor.appearance != last.cursor.appearance {
match opts.cursor.appearance.clone() {
CursorAppearance::Icon(icon) => window.set_cursor(winit::window::Cursor::Icon(icon)),
CursorAppearance::Custom(custom) => window.set_cursor(winit::window::Cursor::Custom(custom)),
}
}
if opts.cursor.grab != last.cursor.grab {
if let Err(e) = window.set_cursor_grab(opts.cursor.grab) {
error!("could not set cursor grab mode: {}", e);
}
}
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);
}
if opts.ime_cursor_area != last.ime_cursor_area && opts.ime_cursor_area.is_some() {
let area = opts.ime_cursor_area.as_ref().unwrap();
window.set_ime_cursor_area(area.position, area.size);
}
if opts.min_size != last.min_size {
window.set_min_inner_size(opts.min_size);
}
if opts.max_size != last.max_size {
window.set_max_inner_size(opts.max_size);
}
if opts.theme != last.theme {
window.set_theme(opts.theme);
}
if opts.title != last.title {
window.set_title(&opts.title);
}
if opts.transparent != last.transparent {
window.set_transparent(opts.transparent);
}
// compare the resource version and uuid. These will get changed
// when the image is reloaded
let opts_icon = opts.window_icon.as_ref()
.map(|i| (i.version(), i.uuid()));
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 {
window.set_window_level(opts.window_level);
}
if opts.show_window_menu != last.show_window_menu && opts.show_window_menu.is_some() {
window.show_window_menu(opts.show_window_menu.unwrap());
}
last.last = opts.clone();
}
Ok(())
}
impl Plugin for WindowPlugin {
fn setup(&self, game: &mut crate::game::Game) {
fn setup(&mut self, app: &mut crate::game::App) {
let window_options = WindowOptions::default();
game.world_mut().add_resource(Ct::new(window_options));
game.with_system("window_updater", window_updater_system, &[]);
app.world.add_resource(Ct::new(window_options));
app.with_system("window_sync", window_sync_system, &[]);
}
}

View File

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

234
lyra-game/src/winit/mod.rs Normal file
View File

@ -0,0 +1,234 @@
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
[dependencies]
glam = { version = "0.24.0" }
glam = { version = "0.29.0" }

View File

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

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

View File

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

View File

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