Commit 6282dd5a authored by neosam's avatar neosam
Browse files

Merge branch 'rahix/wip' into 'master'

Colliders and Keyboard Events

See merge request !11
parents 10dc1fcf b2b2360f
......@@ -47,4 +47,8 @@ features = [
"NodeList",
"SvgElement",
"CssStyleDeclaration",
"KeyboardEvent",
]
[profile.dev]
opt-level = 3
......@@ -2,9 +2,11 @@ use crate::components;
use legion::IntoQuery;
use ncollide2d::pipeline;
use ncollide2d::shape;
use std::sync::mpsc;
pub struct Collider {
handle: Option<ncollide2d::pipeline::CollisionObjectSlabHandle>,
drop_notifier: Option<mpsc::SyncSender<ncollide2d::pipeline::CollisionObjectSlabHandle>>,
shape: shape::ShapeHandle<f32>,
groups: pipeline::CollisionGroups,
}
......@@ -13,6 +15,7 @@ impl Collider {
pub fn new(shape: shape::ShapeHandle<f32>, groups: pipeline::CollisionGroups) -> Collider {
Collider {
handle: None,
drop_notifier: None,
shape,
groups,
}
......@@ -40,14 +43,30 @@ impl Collider {
}
}
impl Drop for Collider {
fn drop(&mut self) {
if let (Some(drop_notifier), Some(handle)) = (self.drop_notifier.take(), self.handle.take())
{
if let Err(e) = drop_notifier.try_send(handle) {
crate::console_warn!("Failed to schedule collider drop!! Error: {}", e);
}
}
}
}
pub struct CollisionWorld {
world: ncollide2d::world::CollisionWorld<f32, legion::Entity>,
drop_notifier: mpsc::SyncSender<ncollide2d::pipeline::CollisionObjectSlabHandle>,
drop_receiver: mpsc::Receiver<ncollide2d::pipeline::CollisionObjectSlabHandle>,
}
impl CollisionWorld {
pub fn new() -> CollisionWorld {
let (drop_notifier, drop_receiver) = mpsc::sync_channel(32);
CollisionWorld {
world: ncollide2d::world::CollisionWorld::new(0.5),
drop_notifier,
drop_receiver,
}
}
......@@ -78,8 +97,13 @@ pub fn synchronize_collisision_world(
*ent,
);
collider.handle = Some(handle);
collider.drop_notifier = Some(collision_world.drop_notifier.clone());
}
}
while let Ok(handle) = collision_world.drop_receiver.try_recv() {
collision_world.world.remove(&[handle]);
}
}
#[legion::system]
......
#![allow(unused_attributes)]
#![rustfmt::skip]
use crate::resources::Color;
pub const BACKGROUND: Color = Color { red: 0.063, green: 0.055, blue: 0.137, alpha: 1.0};
pub const PRIMARY1_SHADE1: Color = Color { red: 0.698, green: 0.224, blue: 1.000, alpha: 1.0};
pub const PRIMARY1_SHADE2: Color = Color { red: 0.404, green: 0.008, blue: 0.624, alpha: 1.0};
pub const PRIMARY1_SHADE3: Color = Color { red: 0.267, green: 0.000, blue: 0.412, alpha: 1.0};
pub const PRIMARY1_SHADE4: Color = Color { red: 0.141, green: 0.000, blue: 0.220, alpha: 1.0};
pub const PRIMARY2_SHADE1: Color = Color { red: 0.408, green: 0.000, blue: 0.906, alpha: 1.0};
pub const PRIMARY2_SHADE2: Color = Color { red: 0.255, green: 0.000, blue: 0.545, alpha: 1.0};
pub const PRIMARY2_SHADE3: Color = Color { red: 0.165, green: 0.000, blue: 0.369, alpha: 1.0};
pub const PRIMARY2_SHADE4: Color = Color { red: 0.078, green: 0.000, blue: 0.184, alpha: 1.0};
pub const PRIMARY3_SHADE1: Color = Color { red: 0.020, green: 0.725, blue: 0.925, alpha: 1.0};
pub const PRIMARY3_SHADE2: Color = Color { red: 0.000, green: 0.463, blue: 0.663, alpha: 1.0};
pub const PRIMARY3_SHADE3: Color = Color { red: 0.008, green: 0.365, blue: 0.518, alpha: 1.0};
pub const PRIMARY3_SHADE4: Color = Color { red: 0.000, green: 0.165, blue: 0.227, alpha: 1.0};
use crate::colors;
use crate::components;
use crate::resources;
use legion::IntoQuery;
......@@ -28,7 +29,7 @@ pub fn draw_edges(
rendering.begin_path();
rendering.set_line_width(7.0);
rendering.set_stroke_style(&resources::Color::with_rgb(0.164, 0.0, 0.367));
rendering.set_stroke_style(&colors::PRIMARY2_SHADE3);
rendering.move_to(pos1.0.x as f64, pos1.0.y as f64);
rendering.line_to(pos2.0.x as f64, pos2.0.y as f64);
rendering.stroke();
......
use crate::colors;
use crate::components;
use crate::resources;
......@@ -54,7 +55,7 @@ pub fn draw_nodes(
) {
// TODO: Replace by proper API
rendering.begin_path();
rendering.set_fill_style(&resources::Color::with_rgb(0.63, 0.54, 0.137));
rendering.set_fill_style(&colors::BACKGROUND);
rendering.arc(
pos.0.x as f64,
pos.0.y as f64,
......@@ -66,7 +67,7 @@ pub fn draw_nodes(
rendering.begin_path();
rendering.set_line_width(5.0);
rendering.set_stroke_style(&resources::Color::with_rgb(0.254, 0.0, 0.543));
rendering.set_stroke_style(&colors::PRIMARY2_SHADE2);
rendering.arc(
pos.0.x as f64,
pos.0.y as f64,
......@@ -77,7 +78,7 @@ pub fn draw_nodes(
rendering.stroke();
let time = clock.wall_time();
let node_color = resources::Color::with_hex_string("#6800e7").unwrap();
let node_color = colors::PRIMARY2_SHADE1;
if let Some(amount_completed) = node.amount_completed(time) {
rendering.begin_path();
rendering.set_stroke_style(&node_color);
......
use crate::colors;
use crate::components;
use crate::resources;
......@@ -12,7 +13,7 @@ pub fn draw_thesun(
) {
// TODO: Replace by proper API
rendering.begin_path();
rendering.set_fill_style(&resources::Color::with_rgb(0.695, 0.223, 1.0));
rendering.set_fill_style(&colors::PRIMARY1_SHADE1);
rendering.arc(
pos.0.x as f64,
pos.0.y as f64,
......@@ -26,7 +27,7 @@ pub fn draw_thesun(
rendering.set_line_width(10.0);
rendering.begin_path();
rendering.set_stroke_style(&resources::Color::with_rgb(0.402, 0.008, 0.621));
rendering.set_stroke_style(&colors::PRIMARY1_SHADE2);
rendering.arc(
pos.0.x as f64,
pos.0.y as f64,
......@@ -37,7 +38,7 @@ pub fn draw_thesun(
rendering.stroke();
rendering.begin_path();
rendering.set_stroke_style(&resources::Color::with_rgb(0.266, 0.0, 0.410));
rendering.set_stroke_style(&colors::PRIMARY1_SHADE3);
rendering.arc(
pos.0.x as f64,
pos.0.y as f64,
......
use std::cell::RefCell;
use std::rc::Rc;
use crate::utils;
use wasm_bindgen::closure;
pub enum Transition {
/// Schedule a call of [`State::update`] with `requestAnimationFrame`.
Loop,
/// Sleep and wait for any registered event-handler to fire.
Sleep,
/// Either `Loop` or `Sleep`, whatever happened last.
Keep,
/// Replace this state with a new one.
Replace(Box<dyn State>),
/// Push a new state on the stack.
Push(Box<dyn State>),
/// Pop this state from the stack and return to the last one.
Pop,
}
impl std::fmt::Debug for Transition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Transition::Loop => "Loop",
Transition::Sleep => "Sleep",
Transition::Keep => "Keep",
Transition::Replace(_) => "Replace(..)",
Transition::Push(_) => "Push(..)",
Transition::Pop => "Pop",
}
)
}
}
impl Transition {
pub fn replace<S: State + 'static>(new: S) -> Transition {
Transition::Replace(Box::new(new))
}
pub fn push<S: State + 'static>(new: S) -> Transition {
Transition::Push(Box::new(new))
}
}
pub trait State {
fn init(&mut self, init: StateInitializer) -> Transition;
fn deinit(&mut self) {}
fn update(&mut self) -> Transition {
Transition::Keep
}
#[allow(unused_variables)]
fn event(&mut self, event: Event) -> Transition {
Transition::Keep
}
}
#[derive(Debug, Clone)]
pub enum Event<'a> {
MouseClick {
target: &'a str,
coords: nalgebra::Point2<f32>,
},
KeyDown(&'a str),
KeyUp(&'a str),
}
struct EventHandlers {
pub mouse_handlers:
std::collections::HashMap<String, closure::Closure<dyn FnMut(wasm_bindgen::JsValue)>>,
pub key_handlers: Option<(
closure::Closure<dyn FnMut(wasm_bindgen::JsValue)>,
closure::Closure<dyn FnMut(wasm_bindgen::JsValue)>,
)>,
}
impl EventHandlers {
pub fn new() -> EventHandlers {
EventHandlers {
mouse_handlers: std::collections::HashMap::new(),
key_handlers: None,
}
}
pub fn register_onclick<F: FnMut(&web_sys::MouseEvent) + 'static>(
&mut self,
id: &str,
mut f: F,
) {
use wasm_bindgen::JsCast;
let f = closure::Closure::wrap(Box::new(move |event: wasm_bindgen::JsValue| {
let event = event
.dyn_ref::<web_sys::MouseEvent>()
.expect("no mouse event?");
f(event)
}) as Box<dyn FnMut(wasm_bindgen::JsValue)>);
if let Ok(el) = utils::get_element_by_id::<web_sys::SvgElement>(id) {
el.set_onclick(Some(f.as_ref().unchecked_ref()));
} else if let Ok(el) = utils::get_element_by_id::<web_sys::HtmlElement>(id) {
el.set_onclick(Some(f.as_ref().unchecked_ref()));
} else {
panic!("Cannot find appropriate element {:?}", id);
}
self.mouse_handlers.insert(id.to_owned(), f);
}
pub fn register_keyevents<F: FnMut(bool, &web_sys::KeyboardEvent) + 'static>(&mut self, f: F) {
use wasm_bindgen::JsCast;
let f = Rc::new(RefCell::new(f));
let f2 = f.clone();
let fdown = closure::Closure::wrap(Box::new(move |event: wasm_bindgen::JsValue| {
let event = event
.dyn_ref::<web_sys::KeyboardEvent>()
.expect("no keyboard event?");
f.borrow_mut()(true, event)
}) as Box<dyn FnMut(wasm_bindgen::JsValue)>);
let fup = closure::Closure::wrap(Box::new(move |event: wasm_bindgen::JsValue| {
let event = event
.dyn_ref::<web_sys::KeyboardEvent>()
.expect("no keyboard event?");
f2.borrow_mut()(false, event)
}) as Box<dyn FnMut(wasm_bindgen::JsValue)>);
let body = utils::document().body().unwrap();
body.set_onkeydown(Some(fdown.as_ref().unchecked_ref()));
body.set_onkeyup(Some(fup.as_ref().unchecked_ref()));
self.key_handlers = Some((fdown, fup));
}
pub fn deregister_all(&mut self) {
if let Some(_) = self.key_handlers {
let body = utils::document().body().unwrap();
body.set_onkeydown(None);
body.set_onkeyup(None);
}
for id in self.mouse_handlers.keys() {
if let Ok(el) = utils::get_element_by_id::<web_sys::SvgElement>(id) {
el.set_onclick(None);
} else if let Ok(el) = utils::get_element_by_id::<web_sys::HtmlElement>(id) {
el.set_onclick(None);
} else {
panic!("Cannot find appropriate element {:?}", id);
}
}
}
}
#[derive(Clone)]
pub struct StateMachineHandle(Rc<RefCell<StateMachine>>);
impl StateMachineHandle {
pub fn new(state_machine: &Rc<RefCell<StateMachine>>) -> StateMachineHandle {
StateMachineHandle(state_machine.clone())
}
pub fn do_transition(&self, t: Transition) {
StateMachine::do_transition(&self.0, t)
}
pub fn do_event(&self, event: Event) {
let t = StateMachine::active(&self.0).event(event);
self.do_transition(t)
}
}
pub struct StateInitializer<'a> {
handle: StateMachineHandle,
event_handlers: &'a mut EventHandlers,
}
impl<'a> StateInitializer<'a> {
fn new(
state_machine: &Rc<RefCell<StateMachine>>,
event_handlers: &'a mut EventHandlers,
) -> StateInitializer<'a> {
StateInitializer {
handle: StateMachineHandle::new(state_machine),
event_handlers,
}
}
pub fn register_onclick(&mut self, id: &str) {
let handle = self.handle.clone();
let id2 = id.to_owned();
let canvas = utils::get_element_by_id::<web_sys::HtmlCanvasElement>("game-canvas").unwrap();
self.event_handlers.register_onclick(id, move |event| {
let click = nalgebra::Point2::new(event.client_x() as f64, event.client_y() as f64);
let canvas_click = utils::screen_to_contain_canvas(&canvas, click);
let canvas_click = nalgebra::Point2::new(canvas_click.x as f32, canvas_click.y as f32);
handle.do_event(Event::MouseClick {
target: &id2,
coords: canvas_click,
});
});
}
pub fn register_keyevents(&mut self) {
let handle = self.handle.clone();
self.event_handlers
.register_keyevents(move |keydown, event| {
if keydown {
handle.do_event(Event::KeyDown(&event.key()));
} else {
handle.do_event(Event::KeyUp(&event.key()));
}
});
}
}
/// "Container" for a stored state
struct StateStorage {
pub state: Box<dyn State>,
pub do_loop: Option<bool>,
pub event_handlers: EventHandlers,
}
impl StateStorage {
pub fn new<S: State + 'static>(state: S) -> StateStorage {
StateStorage::with_boxed(Box::new(state))
}
pub fn with_boxed(state: Box<dyn State>) -> StateStorage {
StateStorage {
state,
do_loop: None,
event_handlers: EventHandlers::new(),
}
}
// ----------
pub fn init(&mut self, sm: &Rc<RefCell<StateMachine>>) -> Transition {
self.state
.init(StateInitializer::new(sm, &mut self.event_handlers))
}
pub fn deinit(&mut self) {
self.state.deinit();
self.event_handlers.deregister_all();
}
pub fn update(&mut self) -> Transition {
self.state.update()
}
fn event(&mut self, event: Event) -> Transition {
self.state.event(event)
}
}
pub struct StateMachine {
states: Vec<StateStorage>,
loop_handler: Option<closure::Closure<dyn FnMut(wasm_bindgen::JsValue)>>,
loop_scheduled: bool,
}
impl StateMachine {
pub fn launch<S: State + 'static>(initial: S) {
let this = Rc::new(RefCell::new(StateMachine {
states: vec![StateStorage::new(initial)],
loop_handler: None,
loop_scheduled: false,
}));
// Sadly Rc::new_cyclic() isn't stable yet ...
let loop_handler = {
let this = this.clone();
closure::Closure::wrap(Box::new(move |_timestamp| {
{
let mut sm = this.borrow_mut();
if !sm.loop_scheduled {
// Running without being scheduled??
crate::console_warn!("Loop handler running without being scheduled!");
return;
}
sm.loop_scheduled = false;
}
// First, check if the event is still wanted by the current state
let t = {
let mut active = StateMachine::active(&this);
if active.do_loop != Some(true) {
// If not, just pretend nothing ever happened.
return;
}
active.update()
};
StateMachine::do_transition(&this, t);
}) as Box<dyn FnMut(wasm_bindgen::JsValue)>)
};
this.borrow_mut().loop_handler = Some(loop_handler);
let t = StateMachine::active(&this).init(&this);
StateMachine::do_transition(&this, t);
}
fn active(this: &Rc<RefCell<StateMachine>>) -> std::cell::RefMut<StateStorage> {
std::cell::RefMut::map(this.borrow_mut(), |sm| sm.states.last_mut().unwrap())
}
pub fn do_event(this: &Rc<RefCell<StateMachine>>, event: Event) {
let t = StateMachine::active(this).event(event);
StateMachine::do_transition(this, t);
}
pub fn do_transition(this: &Rc<RefCell<StateMachine>>, t: Transition) {
// Iteratively perform all transitions until there is no more left to do and hopefully
// events/loop frames are scheduled.
let mut next = Some(t);
while let Some(t) = next {
next = match t {
Transition::Keep => {
if StateMachine::active(this)
.do_loop
.expect("no previous `Loop` or `Sleep` transition")
{
Some(Transition::Loop)
} else {
Some(Transition::Sleep)
}
}
Transition::Loop => {
use wasm_bindgen::JsCast;
StateMachine::active(this).do_loop = Some(true);
// Only schedule if a loop was not already scheduled. Otherwise we'll end up
// with a lot of requestAnimationFrame() loops running in parallel.
if !this.borrow().loop_scheduled {
let mut sm = this.borrow_mut();
let loop_handler = sm.loop_handler.as_ref().unwrap();
utils::window()
.request_animation_frame(loop_handler.as_ref().unchecked_ref())
.unwrap();
sm.loop_scheduled = true;
}
None
}
Transition::Sleep => {
StateMachine::active(this).do_loop = Some(false);
// We hope that at least some event handler/active promise is present which will
// wake us up again in the future. Otherwise, we'll get stuck from here ...
None
}
Transition::Replace(new) => {
let mut active = StateMachine::active(this);
active.deinit();
*active = StateStorage::with_boxed(new);
Some(active.init(this))
}
Transition::Push(new) => {
StateMachine::active(this).deinit();
this.borrow_mut().states.push(StateStorage::with_boxed(new));
Some(StateMachine::active(this).init(this))
}
Transition::Pop => {
StateMachine::active(this).deinit();
this.borrow_mut().states.pop().unwrap();
Some(StateMachine::active(this).init(this))
}
};
}
}
}
use wasm_bindgen::prelude::*;
pub mod colliders;
pub mod colors;
pub mod components;
pub mod gamestate;
pub mod resources;
pub mod state;
pub mod states;
pub mod svg_loader;
pub mod systems;
......@@ -12,6 +13,6 @@ pub mod utils;
#[wasm_bindgen]
pub fn start() -> Result<(), JsValue> {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
state::StateMachine::launch(states::MainMenuState::new());
gamestate::StateMachine::launch(states::MainMenuState::new());
Ok(())
}
use crate::utils;
use std::cell;
use std::rc;
use wasm_bindgen::JsCast;
pub enum Transition {
/// Schedule a call of [`State::update`] with `requestAnimationFrame`.
Loop,
/// Sleep and wait for any registered event-handler to fire.
Sleep,
/// Either `Loop` or `Sleep`, whatever happened last.
Keep,
/// Replace this state with a new one.
Replace(Box<dyn State>),
/// Push a new state on the stack.
Push(Box<dyn State>),
/// Pop this state from the stack and return to the last one.
Pop,
}
impl Transition {
pub fn replace<S: State + 'static>(new: S) -> Transition {
Transition::Replace(Box::new(new))
}
pub fn push<S: State + 'static>(new: S) -> Transition {
Transition::Push(Box::new(new))
}
}
#[derive(Debug, Clone)]
pub enum Event {
MouseClick(nalgebra::Point2<f32>),
}
pub trait State {
fn init(&mut self, init: StateInitializer) -> Transition;
fn deinit(&mut self) {}
fn update(&mut self) -> Transition {
Transition::Keep
}