Created
June 27, 2020 18:13
-
-
Save zakarumych/d2c8f6324227168278deba53def24bbe to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use { | |
crate::{ | |
config::{AssetSource, Config}, | |
renderer::Renderer, | |
}, | |
alex::{Accessor, Schedule, World, WorldAccess}, | |
cfg_if::cfg_if, | |
color_eyre::Report, | |
goods::Cache, | |
lazy_static::lazy_static, | |
std::{ | |
cell::Cell, | |
future::Future, | |
marker::PhantomData, | |
pin::Pin, | |
rc::Rc, | |
sync::atomic::{AtomicBool, AtomicPtr, Ordering}, | |
task::{Context, Poll}, | |
}, | |
tokio::runtime::Runtime, | |
winit::{ | |
dpi::{PhysicalPosition, PhysicalSize}, | |
event::{Event as WinitEvent, WindowEvent as WinitWindowEvent}, | |
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, | |
window::{Theme, Window, WindowBuilder, WindowId}, | |
}, | |
}; | |
pub use winit::event::{ | |
AxisId, DeviceEvent, DeviceId, ElementState, KeyboardInput, ModifiersState, MouseButton, | |
MouseScrollDelta, Touch, TouchPhase, | |
}; | |
pub enum WindowEvent { | |
Resized(PhysicalSize<u32>), | |
Moved(PhysicalPosition<i32>), | |
CloseRequested, | |
Destroyed, | |
Focused(bool), | |
KeyboardInput { | |
device_id: DeviceId, | |
input: KeyboardInput, | |
is_synthetic: bool, | |
}, | |
ModifiersChanged(ModifiersState), | |
CursorMoved { | |
device_id: DeviceId, | |
position: PhysicalPosition<f64>, | |
modifiers: ModifiersState, | |
}, | |
CursorEntered { | |
device_id: DeviceId, | |
}, | |
CursorLeft { | |
device_id: DeviceId, | |
}, | |
MouseWheel { | |
device_id: DeviceId, | |
delta: MouseScrollDelta, | |
phase: TouchPhase, | |
modifiers: ModifiersState, | |
}, | |
MouseInput { | |
device_id: DeviceId, | |
state: ElementState, | |
button: MouseButton, | |
modifiers: ModifiersState, | |
}, | |
TouchpadPressure { | |
device_id: DeviceId, | |
pressure: f32, | |
stage: i64, | |
}, | |
AxisMotion { | |
device_id: DeviceId, | |
axis: AxisId, | |
value: f64, | |
}, | |
Touch(Touch), | |
ScaleFactorChanged { | |
scale_factor: f64, | |
}, | |
ThemeChanged(Theme), | |
} | |
pub enum Event { | |
WindowEvent { | |
window_id: WindowId, | |
event: WindowEvent, | |
}, | |
DeviceEvent { | |
device_id: DeviceId, | |
event: DeviceEvent, | |
}, | |
Suspended, | |
Resumed, | |
RedrawRequested(WindowId), | |
RedrawEventsCleared, | |
LoopDestroyed, | |
} | |
struct Shared { | |
event_loop_ptr: Cell<*const EventLoopWindowTarget<()>>, | |
waiting_for_event: Cell<bool>, | |
} | |
/// Root data structure for the game engine. | |
pub struct Engine { | |
world: World, | |
schedule: Schedule, | |
assets: Cache<String>, | |
events: flume::Receiver<Event>, | |
shared: Rc<Shared>, | |
} | |
impl Engine { | |
pub fn build_window(&mut self, builder: WindowBuilder) -> Result<Window, Report> { | |
let elwt = self.shared.event_loop_ptr.get(); | |
if elwt.is_null() { | |
unreachable!() | |
} | |
let elwt = unsafe { | |
// This block can be executed only within winit's event loop callback. | |
// when closure provided to `Engine::run` is polled, or | |
// on initial call to that closure. | |
// Because it takes `&mut self` and `Engine is not `Send` it cannot be | |
// sent to another thread. | |
// This function is not async so it is not possible that reference we creating | |
// here will be preserved over yielding. | |
&*elwt | |
}; | |
let window = builder.build(elwt)?; | |
Ok(window) | |
} | |
/// Adds a system to this engine. | |
pub fn add_system<A, S>(&mut self, accessor: A, system: S) -> &mut Self | |
where | |
A: for<'a> Accessor<'a> + Send + 'static, | |
S: FnMut(WorldAccess<'_>) + Send + 'static, | |
{ | |
self.schedule.add_system(accessor, system); | |
self | |
} | |
/// Asynchronously wait for next event. | |
pub async fn next(&mut self) -> Event { | |
self.shared.waiting_for_event.set(true); | |
let event = self.events.recv_async().await; | |
self.shared.waiting_for_event.set(false); | |
event.unwrap() | |
} | |
/// Runs an instance of an engine. | |
/// This function neven returns on success. | |
/// Instead it calls provided closure with create engine instance | |
/// and drive it to completion. | |
/// Along with polling winit's event-loop for window events. | |
pub fn run<F, A>(closure: F) -> Result<(), Report> | |
where | |
F: FnOnce(Self) -> A, | |
A: Future<Output = Result<(), Report>> + 'static, | |
{ | |
let mut runtime = Runtime::new()?; | |
// Setup basic logging first. | |
runtime.block_on(Self::init_logger())?; | |
let config = runtime.block_on(Self::load_config())?; | |
let registry = config | |
.sources | |
.iter() | |
.fold(goods::RegistryBuilder::new(), |builder, source| match source { | |
AssetSource::FileSystem { path } => { | |
cfg_if! { | |
if #[cfg(feature = "goods-fs")] { | |
builder.with(goods::FileSource::new(path.clone())) | |
} else { | |
tracing::error!("FileSystem asset source ignored w/o `goods-fs` feature"); | |
builder | |
} | |
} | |
} | |
}); | |
let assets = Cache::new(registry.build(), goods::Tokio(runtime.handle().clone())); | |
let (sender, receiver) = flume::unbounded(); | |
let shared = Rc::new(Shared { | |
event_loop_ptr: Cell::new(std::ptr::null()), | |
waiting_for_event: Cell::new(false), | |
}); | |
let engine = Engine { | |
assets, | |
schedule: Schedule::new(), | |
world: World::new(), | |
events: receiver, | |
shared: shared.clone(), | |
}; | |
let event_loop = EventLoop::new(); | |
shared.event_loop_ptr.set(&*event_loop); | |
let mut app = Box::pin(closure(engine)); | |
shared.event_loop_ptr.set(std::ptr::null()); | |
// Here goes magic | |
event_loop.run(move |event, el, flow| match event { | |
WinitEvent::MainEventsCleared => { | |
runtime.block_on(async { | |
// Set pointer. We ensure it is always valid while non-null. | |
shared.event_loop_ptr.set(el); | |
// Poll closure only once. | |
if let Poll::Ready(result) = (AppEventWaitFuture { app: app.as_mut() }.await) { | |
// No place where we could return this error. | |
// log and panic are only options. | |
if let Err(err) = result { | |
tracing::error!("Error: {}", err); | |
} | |
// Exit when closure resolves. | |
*flow = ControlFlow::Exit; | |
} else { | |
if shared.waiting_for_event.get() { | |
*flow = ControlFlow::Wait; | |
} else { | |
*flow = ControlFlow::Poll; | |
} | |
} | |
// Unset event loop before it is invalidated. | |
shared.event_loop_ptr.set(std::ptr::null()); | |
}); | |
} | |
rest => { | |
match rest { | |
WinitEvent::WindowEvent { window_id, event } => convert_window_event(event) | |
.map(|event| Event::WindowEvent { window_id, event }), | |
WinitEvent::DeviceEvent { device_id, event } => { | |
Event::DeviceEvent { device_id, event }.into() | |
} | |
WinitEvent::Suspended => Event::Suspended.into(), | |
WinitEvent::Resumed => Event::Resumed.into(), | |
WinitEvent::RedrawRequested(window) => Event::RedrawRequested(window).into(), | |
WinitEvent::RedrawEventsCleared => Event::RedrawEventsCleared.into(), | |
WinitEvent::LoopDestroyed => Event::LoopDestroyed.into(), | |
_ => None, | |
} | |
.map(|event| { | |
let _ = sender.send(event); | |
}); | |
} | |
}) | |
} | |
async fn init_logger() -> Result<(), Report> { | |
env_logger::try_init()?; | |
Ok(()) | |
} | |
async fn load_config() -> Result<Config, Report> { | |
tracing::info!("Running at {}", std::env::current_dir()?.display()); | |
// Now load config. | |
let config = Config::load_default().await?; | |
tracing::info!("Config loaded: {:?}", config); | |
Ok(config) | |
} | |
} | |
struct AppEventWaitFuture<'a, A> { | |
app: Pin<&'a mut A>, | |
} | |
impl<'a, A> Future for AppEventWaitFuture<'a, A> | |
where | |
A: Future<Output = Result<(), Report>>, | |
{ | |
type Output = Poll<Result<(), Report>>; | |
fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Poll<Result<(), Report>>> { | |
let poll = Future::poll(self.get_mut().app.as_mut(), ctx); | |
Poll::Ready(poll) | |
} | |
} | |
fn convert_window_event(event: WinitWindowEvent<'_>) -> Option<WindowEvent> { | |
match event { | |
WinitWindowEvent::Resized(size) => WindowEvent::Resized(size).into(), | |
WinitWindowEvent::Moved(position) => WindowEvent::Moved(position).into(), | |
WinitWindowEvent::CloseRequested => WindowEvent::CloseRequested.into(), | |
WinitWindowEvent::Destroyed => WindowEvent::Destroyed.into(), | |
WinitWindowEvent::Focused(focused) => WindowEvent::Focused(focused).into(), | |
WinitWindowEvent::KeyboardInput { | |
device_id, | |
input, | |
is_synthetic, | |
} => WindowEvent::KeyboardInput { | |
device_id, | |
input, | |
is_synthetic, | |
} | |
.into(), | |
WinitWindowEvent::ModifiersChanged(state) => WindowEvent::ModifiersChanged(state).into(), | |
WinitWindowEvent::CursorMoved { | |
device_id, | |
position, | |
modifiers, | |
} => WindowEvent::CursorMoved { | |
device_id, | |
position, | |
modifiers, | |
} | |
.into(), | |
WinitWindowEvent::CursorEntered { device_id } => { | |
WindowEvent::CursorEntered { device_id }.into() | |
} | |
WinitWindowEvent::CursorLeft { device_id } => WindowEvent::CursorLeft { device_id }.into(), | |
WinitWindowEvent::MouseWheel { | |
device_id, | |
delta, | |
phase, | |
modifiers, | |
} => WindowEvent::MouseWheel { | |
device_id, | |
delta, | |
phase, | |
modifiers, | |
} | |
.into(), | |
WinitWindowEvent::MouseInput { | |
device_id, | |
state, | |
button, | |
modifiers, | |
} => WindowEvent::MouseInput { | |
device_id, | |
state, | |
button, | |
modifiers, | |
} | |
.into(), | |
WinitWindowEvent::TouchpadPressure { | |
device_id, | |
pressure, | |
stage, | |
} => WindowEvent::TouchpadPressure { | |
device_id, | |
pressure, | |
stage, | |
} | |
.into(), | |
WinitWindowEvent::AxisMotion { | |
device_id, | |
axis, | |
value, | |
} => WindowEvent::AxisMotion { | |
device_id, | |
axis, | |
value, | |
} | |
.into(), | |
WinitWindowEvent::Touch(touch) => WindowEvent::Touch(touch).into(), | |
WinitWindowEvent::ScaleFactorChanged { scale_factor, .. } => { | |
WindowEvent::ScaleFactorChanged { scale_factor }.into() | |
} | |
WinitWindowEvent::ThemeChanged(theme) => WindowEvent::ThemeChanged(theme).into(), | |
_ => None, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment