Compare commits

...

3 Commits

Author SHA1 Message Date
SeanOMik 2107b8f7b0
engine: move winit ApplicationHandler to winit plugin 2024-09-19 17:30:30 -04:00
SeanOMik 8b1077cab7
engine: get a window showing and things rendered 2024-09-18 21:45:15 -04:00
SeanOMik 45fd190409
update wgpu and winit to latest versions
need to make a WinitPlugin though, so no window currently
2024-09-18 19:47:55 -04:00
42 changed files with 7496 additions and 1699 deletions

2658
Cargo.lock generated

File diff suppressed because it is too large Load Diff

4999
Cargo.lock.old Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,8 @@
use std::{sync::Arc, collections::VecDeque, ptr::NonNull};
use std::{cell::OnceCell, collections::VecDeque, ptr::NonNull};
use async_std::task::block_on;
use lyra_ecs::{World, system::{System, IntoSystem}};
use tracing::{error, info, Level};
use lyra_ecs::{system::{IntoSystem, System}, ResourceObject, World};
use lyra_math::IVec2;
use tracing::{info, error, Level};
use tracing_appender::non_blocking;
use tracing_subscriber::{
layer::SubscriberExt,
@ -11,9 +10,7 @@ use tracing_subscriber::{
util::SubscriberInitExt, fmt,
};
use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode, DeviceEvent}, event_loop::{EventLoop, ControlFlow}};
use crate::{render::{renderer::{Renderer, BasicRenderer}, window::WindowOptions}, input::InputEvent, plugin::Plugin, change_tracker::Ct, EventQueue, StagedExecutor, Stage};
use crate::{plugin::Plugin, render::renderer::Renderer, Stage, StagedExecutor};
#[derive(Clone, Copy, Hash, Debug)]
pub enum GameStages {
@ -37,8 +34,13 @@ pub struct Controls<'a> {
#[derive(Clone, Default)]
pub struct WindowState {
pub is_focused: bool,
pub is_cursor_inside_window: bool,
/// Indicates if the window is currently focused.
pub focused: bool,
/// Indicates if the window is currently occluded.
pub occluded: bool,
/// Indicates if the cursor is inside of the window.
pub cursor_inside_window: bool,
pub position: IVec2,
}
impl WindowState {
@ -47,185 +49,41 @@ 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 {
impl App {
pub fn new() -> Self {
// init logging
let (stdout_layer, stdout_nb) = non_blocking(std::io::stdout());
{
let t = tracing_subscriber::registry()
.with(fmt::layer().with_writer(stdout_layer));
#[cfg(feature = "tracy")]
let t = t.with(tracing_tracy::TracyLayer::default());
t.with(filter::Targets::new()
// done by prefix, so it includes all lyra subpackages
.with_target("lyra", Level::DEBUG)
.with_target("wgsl_preprocessor", Level::DEBUG)
.with_target("wgpu", Level::WARN)
.with_target("winit", Level::DEBUG)
.with_default(Level::INFO))
.init();
}
// store the logger worker guard to ensure logging still happens
let mut world = World::new();
world.add_resource(stdout_nb);
// initialize ecs system stages
let mut staged = StagedExecutor::new();
staged.add_stage(GameStages::First);
staged.add_stage_after(GameStages::First, GameStages::PreUpdate);
@ -234,29 +92,35 @@ impl Default for Game {
staged.add_stage_after(GameStages::PostUpdate, GameStages::Last);
Self {
world: Some(World::new()),
plugins: VecDeque::new(),
system_exec: Some(staged),
startup_systems: VecDeque::new(),
renderer: OnceCell::new(),
world,
plugins: Default::default(),
startup_systems: Default::default(),
staged_exec: staged,
run_fn: OnceCell::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()
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);
}
}
/// Get the world of this game
pub fn world(&self) -> &World {
// world is always `Some`, so unwrapping is safe
self.world.as_ref().unwrap()
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
@ -265,8 +129,7 @@ impl Game {
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.staged_exec.add_system_to_stage(GameStages::Update, name, system.into_system(), depends);
self
}
@ -275,8 +138,7 @@ impl Game {
///
/// 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.staged_exec.add_stage(stage);
self
}
@ -287,8 +149,7 @@ impl Game {
/// * `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.staged_exec.add_stage_after(before, after);
self
}
@ -304,8 +165,7 @@ impl Game {
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.staged_exec.add_system_to_stage(stage, name, system.into_system(), depends);
self
}
@ -337,53 +197,21 @@ impl Game {
///
/// 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.world = world;
self
}
/// Start the game
pub async fn run(&mut self) {
// init logging
let (stdout_layer, _stdout_nb) = non_blocking(std::io::stdout());
pub fn set_run_fn<F>(&self, f: F)
where
F: FnOnce(App) + 'static
{
let t = tracing_subscriber::registry()
.with(fmt::layer().with_writer(stdout_layer));
#[cfg(feature = "tracy")]
let t = t.with(tracing_tracy::TracyLayer::default());
t.with(filter::Targets::new()
// done by prefix, so it includes all lyra subpackages
.with_target("lyra", Level::DEBUG)
.with_target("wgsl_preprocessor", Level::DEBUG)
.with_target("wgpu", Level::WARN)
.with_target("winit", Level::DEBUG)
.with_default(Level::INFO))
.init();
// ignore if a runner function was already set
let _ = self.run_fn.set(Box::new(f));
}
let world = self.world.take().unwrap_or_default();
// run startup systems
while let Some(mut startup) = self.startup_systems.pop_front() {
let startup = startup.as_mut();
let world_ptr = NonNull::from(&world);
startup.setup(world_ptr).expect("World returned an error!");
startup.execute(world_ptr).expect("World returned an error!");
}
// start winit event loops
let event_loop = EventLoop::new();
let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap());
let system_dispatcher = self.system_exec.take().unwrap();
let mut g_loop = GameLoop::new(Arc::clone(&window), world, system_dispatcher).await;
g_loop.on_init().await;
event_loop.run(move |event, _, control_flow| {
g_loop.run_sync(event, control_flow);
});
pub fn run(mut self) {
let f = self.run_fn.take()
.expect("No run function set");
f(self);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,35 +1,37 @@
use lyra_ecs::CommandQueue;
use lyra_resource::ResourceManager;
use crate::game::App;
use crate::winit::WinitPlugin;
use crate::EventsPlugin;
use crate::DeltaTimePlugin;
use crate::game::Game;
use crate::input::InputPlugin;
use crate::render::window::WindowPlugin;
/// A Plugin is something you can add to a `Game` that can be used to define systems, or spawn initial entities.
pub trait Plugin {
/// Setup this plugin. This runs before the game has started
fn setup(&self, game: &mut Game);
/// Setup this plugin. This runs before the app has started
fn setup(&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(&self, app: &mut App) {
self(app);
}
}
@ -56,9 +58,9 @@ impl PluginSet {
}
impl Plugin for PluginSet {
fn setup(&self, game: &mut Game) {
fn setup(&self, app: &mut App) {
for plugin in self.plugins.iter() {
plugin.setup(game);
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(&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(&self, app: &mut App) {
WinitPlugin.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(&self, app: &mut App) {
app.world.add_resource(CommandQueue::default());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,27 +1,91 @@
use std::sync::Arc;
use glam::{Vec2, IVec2};
use lyra_ecs::World;
use tracing::{warn, error};
use winit::{window::{Window, Fullscreen}, dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError};
use glam::{IVec2, Vec2};
use lyra_ecs::{query::{Entities, ResMut, TickOf}, Component, World};
use tracing::{error, warn};
use winit::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError, monitor::VideoModeHandle, window::{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, input::InputEvent, plugin::Plugin, winit::WinitWindows, EventQueue};
#[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)]
pub struct Area {
position: Position,
size: Size,
}
#[derive(Clone, Copy)]
pub enum Size {
Physical { x: i32, y: i32 },
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 Size {
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 }
}
}
#[derive(Clone, Copy)]
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 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;
/// 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.
///
@ -43,11 +107,13 @@ pub struct WindowOptions {
/// * iOS / Android / Web / X11 / Orbital: Unsupported.
pub cursor_hittest: bool,
/// The cursor icon of the window.
/// Modifies the cursor icon of the window.
///
/// Platform-specific
/// Platform-specific:
/// * iOS / Android / Orbital: Unsupported.
pub cursor_icon: CursorIcon,
/// * Web: Custom cursors have to be loaded and decoded first, until then the previous
/// cursor is shown.
pub cursor: winit::window::Cursor,
/// The cursors visibility.
/// If false, this will hide the cursor. If true, this will show the cursor.
@ -61,6 +127,37 @@ pub struct WindowOptions {
/// * iOS / Android / Orbital: Unsupported.
pub cursor_visible: bool,
/// The windows current visibility state.
///
/// Platform-specific
/// * **X11:** Not implemented.
/// * **Wayland / iOS / Android / Web:** Unsupported.
pub visible: bool,
/// 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.
///
/// The default value follows the [`winit::window::WindowAttributes::with_transparent`].
///
/// Platform-specific
/// * **macOS:** This will reset the windows background color.
/// * **Web / iOS / Android:** Unsupported.
/// * **X11:** Can only be set while building the window, with
/// [`winit::window::WindowAttributes::with_transparent`].
pub transparent: bool,
/// The current blur state of the window
///
/// If `true`, this will make the transparent window background blurry.
///
/// Platform-specific
/// * **Android / iOS / X11 / Web / Windows:** Unsupported.
/// * **Wayland:** Only works with org_kde_kwin_blur_manager protocol.
pub blur: bool,
/// Turn window decorations on or off.
/// Enable/disable window decorations provided by the server or Winit. By default this is enabled.
///
@ -75,8 +172,10 @@ pub struct WindowOptions {
/// * Web / iOS / Android: Unsupported.
pub enabled_buttons: WindowButtons,
/// The window mode. Can be used to set fullscreen and borderless.
pub mode: WindowMode,
/// The fullscreen settings for the monitor.
///
/// Set to `None` for windowed.
pub fullscreen: Option<Fullscreen>,
/// Sets whether the window should get IME events.
///
@ -87,30 +186,67 @@ pub struct WindowOptions {
/// * iOS / Android / Web / Orbital: Unsupported.
pub ime_allowed: bool,
/// Sets location of IME candidate box in client area coordinates relative to the top left.
/// Set the IME cursor editing area, where the `position` is the top left corner of that
/// area and `size` is the size of this area starting from the position. An example of such
/// area could be a input field in the UI or line in the editor.
///
/// 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,
/// The windowing system could place a candidate box close to that area, but try to not
/// obscure the specified area, so the user input to it stays visible.
///
/// The candidate box 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.
///
/// (Apples official term is “candidate window”, see their chinese and japanese guides).
///
/// Platform-specific
/// * **X11:** - area is not supported, only position.
/// * **iOS / Android / Web / Orbital:** Unsupported.
pub ime_cursor_area: Area,
/// Modifies the inner size of the window.
///
/// Platform-specific:
/// * iOS / Android: Unsupported.
/// * Web: Sets the size of the canvas element.
pub inner_size: IVec2,
pub inner_size: Size,
/// Sets a maximum dimension size for the window.
///
/// Platform-specific:
/// * iOS / Android / Web / Orbital: Unsupported.
pub max_inner_size: Option<IVec2>,
pub max_inner_size: Option<Size>,
/// Sets a minimum dimension size for the window.
///
/// Platform-specific:
/// * iOS / Android / Web / Orbital: Unsupported.
pub min_inner_size: Option<IVec2>,
pub min_inner_size: Option<Size>,
/// 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.
///
/// Platform-specific
/// * **iOS:** Can only be called on the main thread. Returns the top left coordinates of
/// the window in the screen space coordinate system.
/// * **Web:** Returns the top-left coordinates relative to the viewport.
/// * **Android / Wayland:** Always returns NotSupportedError.
pub outer_position: Option<Position>,
/// Returns the position of the top-left hand corner of the windows client area relative to
/// the top-left hand corner of the desktop.
///
/// The same conditions that apply to `WindowOptions::outer_position` apply to this.
///
/// Platform-specific
/// * **iOS:** Can only be called on the main thread. Returns the top left coordinates of the
/// windows safe area in the screen space coordinate system.
/// * **Web:** Returns the top-left coordinates relative to the viewport.
/// * **Android / Wayland:** Always returns NotSupportedError.
pub inner_position: Option<Position>,
/// Sets the window to maximized or back.
///
@ -144,7 +280,7 @@ pub struct WindowOptions {
/// Platform-specific:
/// * Wayland / Windows: Not implemented.
/// * iOS / Android / Web / Orbital: Unsupported.
pub resize_increments: Option<Vec2>,
pub resize_increments: Option<Size>,
/// Sets the current window theme. Use None to fallback to system default.
///
@ -186,14 +322,14 @@ impl Default for WindowOptions {
content_protected: false,
cursor_grab: CursorGrabMode::None,
cursor_hittest: true,
cursor_icon: CursorIcon::Default,
cursor: Default::default(),
cursor_visible: true,
decorations: true,
enabled_buttons: WindowButtons::all(),
mode: WindowMode::Windowed,
fullscreen: None,
ime_allowed: false,
ime_position: Default::default(),
inner_size: glam::i32::IVec2::new(800, 600),
ime_cursor_area: Area::default(),
inner_size: Size::new_physical(800, 600),
max_inner_size: None,
min_inner_size: None,
maximized: false,
@ -207,10 +343,74 @@ impl Default for WindowOptions {
level: WindowLevel::Normal,
focused: false,
cursor_inside_window: false,
blur: false,
inner_position: None,
outer_position: None,
transparent: false,
visible: true,
}
}
}
impl WindowOptions {
pub(crate) fn as_winit_attributes(&self) -> winit::window::WindowAttributes {
let mut att = Window::default_attributes();
att.inner_size = Some(self.inner_size.into());
if let Some(min_inner_size) = self.min_inner_size {
att.min_inner_size = Some(min_inner_size.into());
}
if let Some(max_inner_size) = self.max_inner_size {
att.max_inner_size = Some(max_inner_size.into());
}
if let Some(position) = self.outer_position.clone()
.or(self.inner_position.clone())
{
att.position = Some(position.into());
}
att.resizable = self.resizeable;
att.enabled_buttons = self.enabled_buttons.clone();
att.title = self.title.clone();
att.maximized = self.maximized;
att.visible = self.visible;
att.transparent = self.transparent;
att.blur = self.blur;
att.decorations = self.decorations;
//att.window_icon = self.icon.clone
todo!()
/* winit::window::WindowAttributes {
inner_size: Some(self.inner_size.into()),
min_inner_size: self.min_inner_size.map(|v| v.into()),
max_inner_size: self.max_inner_size.map(|v| v.into()),
position: self.outer_position.clone().or(self.inner_position.clone()).map(|v| v.into()), // TODO: sync in system
resizable: self.resizeable,
enabled_buttons: self.enabled_buttons.clone(),
title: self.title.clone(),
maximized: self.maximized,
visible: self.visible, // TODO: sync in system
transparent: self.transparent, // TODO: sync in system
blur: self.blur, // TODO: sync in system
decorations: self.decorations,
window_icon: self.icon.clone(),
preferred_theme: self.theme,
resize_increments: self.resize_increments.map(|v| v.into()),
content_protected: self.content_protected,
window_level: self.level,
active: false, // TODO
cursor: self.cursor.clone(),
fullscreen: self.fullscreen.clone(),
parent_window: todo!(),
platform_specific: todo!(),
} */
}
}
#[derive(Default)]
pub struct WindowPlugin {
#[allow(dead_code)]
@ -224,7 +424,10 @@ fn vec2_to_logical_pos(pos: Vec2) -> LogicalPosition<f32> {
/// Convert an IVec2 to a LogicalSize<i32>
fn ivec2_to_logical_size(size: IVec2) -> LogicalSize<i32> {
LogicalSize { width: size.x, height: size.y }
LogicalSize {
width: size.x,
height: size.y,
}
}
/// Convert an Option<IVec2> to an Option<LogicalSize<i32>>
@ -234,7 +437,10 @@ fn ivec2_to_logical_size_op(size: Option<IVec2>) -> Option<LogicalSize<i32>> {
/// 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 } )
size.map(|size| LogicalSize {
width: size.x,
height: size.y,
})
}
/// Set the cursor grab of a window depending on the platform.
@ -250,9 +456,13 @@ fn set_cursor_grab(window: &Window, grab: &mut CursorGrabMode) -> anyhow::Result
*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")) {
} 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(())
return Ok(());
}
}
@ -264,8 +474,8 @@ fn set_cursor_grab(window: &Window, grab: &mut CursorGrabMode) -> anyhow::Result
/// 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 {
if options.cursor_grab == CursorGrabMode::Confined && !options.cursor_visible && options.focused
{
let size = window.inner_size();
let middle = PhysicalPosition {
x: size.width / 2,
@ -276,92 +486,71 @@ fn center_mouse(window: &Window, options: &WindowOptions) {
}
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 let (Some(window), Some(opts)) = (
world.try_get_resource::<Arc<Window>>(),
world.try_get_resource::<Ct<WindowOptions>>(),
) { */
let tick = world.tick();
for (entity, mut opts, window_tick, windows) in world.view_iter::<(Entities, &mut WindowOptions, TickOf<WindowOptions>, ResMut<WinitWindows>)>() {
let window = windows.get_entity_window(entity)
.expect("entity's window is missing");
if window_tick == tick {
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 */ },
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(opts.cursor.clone()); // 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");
}
window.set_fullscreen(opts.fullscreen.clone());
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));
window.set_ime_cursor_area(opts.ime_cursor_area.position, opts.ime_cursor_area.size);
window.request_inner_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));
window.set_max_inner_size(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));
window.set_min_inner_size(opts.min_inner_size);
}
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_resize_increments(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);
}
}
@ -370,10 +559,10 @@ fn window_updater_system(world: &mut World) -> anyhow::Result<()> {
}
impl Plugin for WindowPlugin {
fn setup(&self, game: &mut crate::game::Game) {
fn setup(&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_updater", window_updater_system, &[]);
}
}

View File

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

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

@ -0,0 +1,175 @@
use std::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, WindowOptions}}, EventQueue};
#[derive(Default)]
pub struct WinitPlugin;
impl Plugin for WinitPlugin {
fn setup(&self, app: &mut crate::game::App) {
app.set_run_fn(winit_app_runner);
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>,
}
impl WinitWindows {
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);
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) {
debug!("update now");
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),
}
}
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let world = &mut self.app.world;
let en = world.spawn((WindowOptions::default(), PrimaryWindow));
let attr = Window::default_attributes();
let mut windows = world.get_resource_mut::<WinitWindows>();
let wid = windows.create_window(event_loop, en, attr).unwrap();
let window = windows.windows.get(&wid).unwrap().clone();
drop(windows);
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);
},
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 state = self.app.world.get_resource_or_else(WindowState::new);
state.focused = focused;
},
WindowEvent::ModifiersChanged(modifiers) => debug!("modifiers changed: {:?}", modifiers),
WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer } => {
info!("changed scale to {scale_factor}");
},
WindowEvent::ThemeChanged(theme) => todo!(),
WindowEvent::Occluded(occ) => {
let mut state = self.app.world.get_resource_or_else(WindowState::new);
state.occluded = occ;
},
WindowEvent::RedrawRequested => {
debug!("should redraw");
},
_ => {}
}
}
}
}

View File

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

View File

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

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

View File

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