Update wgpu to 0.20.1 and winit to 0.30.3 #26
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>();
|
||||
|
||||
|
|
|
@ -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,75 +31,73 @@ 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
|
||||
.request::<Gltf>("../assets/AntiqueCamera.glb")
|
||||
.unwrap();
|
||||
|
@ -146,4 +144,4 @@ fn setup_scene_plugin(game: &mut Game) {
|
|||
let mut camera = CameraComponent::new_3d();
|
||||
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
|
||||
world.spawn((camera, FreeFlyCamera::default()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![feature(associated_type_defaults)]
|
||||
|
||||
extern crate self as lyra_ecs;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
|
|
|
@ -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.iter();
|
||||
|
||||
let mut row_iter = view_row.row.iter();
|
||||
|
||||
let dynamic_type = row_iter.next().unwrap();
|
||||
|
||||
let dynamic_type = row_iter.next().unwrap();
|
||||
let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() };
|
||||
assert_eq!(*component_data, 50);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -5,4 +5,7 @@ mod or;
|
|||
pub use or::*;
|
||||
|
||||
mod not;
|
||||
pub use not::*;
|
||||
pub use not::*;
|
||||
|
||||
mod changed;
|
||||
pub use changed::*;
|
|
@ -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};
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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) });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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, &[]);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
@ -362,28 +78,140 @@ impl Game {
|
|||
.with_default(Level::INFO))
|
||||
.init();
|
||||
}
|
||||
|
||||
|
||||
let world = self.world.take().unwrap_or_default();
|
||||
// store the logger worker guard to ensure logging still happens
|
||||
let mut world = World::new();
|
||||
world.add_resource(stdout_nb);
|
||||
|
||||
// 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!");
|
||||
// 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);
|
||||
|
||||
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());
|
||||
|
||||
let system_dispatcher = self.system_exec.take().unwrap();
|
||||
let mut g_loop = GameLoop::new(Arc::clone(&window), world, system_dispatcher).await;
|
||||
g_loop.on_init().await;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
g_loop.run_sync(event, control_flow);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
let wptr = NonNull::from(&self.world);
|
||||
|
||||
if let Err(e) = self.staged_exec.execute(wptr, true) {
|
||||
error!("Error when executing staged systems: '{}'", e);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, &[]);
|
||||
}
|
||||
}
|
|
@ -88,6 +88,8 @@ pub enum MouseButton {
|
|||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Back,
|
||||
Forward,
|
||||
Other(u16),
|
||||
}
|
||||
|
||||
|
|
|
@ -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**: Doesn’t 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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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, &[]);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
@ -28,7 +34,7 @@ impl ComputePipelineDescriptor {
|
|||
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)),
|
||||
bind_group_layouts: &bgs,
|
||||
bind_group_layouts: &bgs,
|
||||
push_constant_ranges: &self.push_constant_ranges,
|
||||
})
|
||||
}
|
||||
|
@ -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);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
mod shader;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use shader::*;
|
||||
|
||||
mod pipeline;
|
||||
|
@ -11,4 +13,21 @@ mod render_pipeline;
|
|||
pub use render_pipeline::*;
|
||||
|
||||
mod pass;
|
||||
pub use pass::*;
|
||||
pub use pass::*;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct PipelineCompilationOptions {
|
||||
pub constants: HashMap<String, f64>,
|
||||
pub zero_initialize_workgroup_memory: bool,
|
||||
pub vertex_pulling_transform: bool,
|
||||
}
|
||||
|
||||
impl PipelineCompilationOptions {
|
||||
pub fn as_wgpu(&self) -> wgpu::PipelineCompilationOptions {
|
||||
wgpu::PipelineCompilationOptions {
|
||||
constants: &self.constants,
|
||||
zero_initialize_workgroup_memory: self.zero_initialize_workgroup_memory,
|
||||
vertex_pulling_transform: self.vertex_pulling_transform,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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])) {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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 doesn’t 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 cursor’s 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 it’s recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
|
||||
/// * X11: Has no universal guidelines for icon sizes, so you’re at the whims of the WM. That said, it’s 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 window’s 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 window’s 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. Doesn’t 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 window’s 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 doesn’t 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 won’t 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 window’s 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 it’s
|
||||
/// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
|
||||
/// * **X11:** Has no universal guidelines for icon sizes, so you’re at the whims of
|
||||
/// the WM. That said, it’s 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();
|
||||
}
|
||||
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.size != last.size {
|
||||
if window.request_inner_size(opts.size).is_some() {
|
||||
error!("request to increase window size failed");
|
||||
}
|
||||
}
|
||||
|
||||
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.decorated != last.decorated {
|
||||
window.set_decorations(opts.decorated);
|
||||
}
|
||||
|
||||
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, &[]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, &[]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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" }
|
|
@ -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
|
|
@ -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>();
|
||||
|
|
|
@ -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" ]
|
Loading…
Reference in New Issue