Commit 1aa9b988 authored by Simon Goller's avatar Simon Goller Committed by Rahix

Use more error handling

Replace most of the panic calls by error handling with anyhow.
parent 0218844a
......@@ -5,6 +5,7 @@ use crate::resources;
use crate::sprites;
use crate::svg_loader;
use crate::utils;
use anyhow::Context;
pub struct BottleAngelShift {
hours: usize,
......@@ -12,26 +13,29 @@ pub struct BottleAngelShift {
}
impl BottleAngelShift {
pub fn generate(rng: &mut impl rand::Rng) -> BottleAngelShift {
pub fn generate(rng: &mut impl rand::Rng) -> anyhow::Result<BottleAngelShift> {
use rand::seq::SliceRandom;
let compatible_levels = ["assembly-hall-1.svg", "ccl-ground-level.svg"];
BottleAngelShift {
Ok(BottleAngelShift {
hours: rng.gen_range(1, 3),
level: compatible_levels.choose(rng).unwrap().to_string(),
}
level: compatible_levels
.choose(rng)
.context("Compatible level not found")?
.to_string(),
})
}
}
impl super::AngelShiftImpl for BottleAngelShift {
fn metadata(&self) -> super::ShiftMetadata {
super::ShiftMetadata {
fn metadata(&self) -> anyhow::Result<super::ShiftMetadata> {
Ok(super::ShiftMetadata {
title: "Bottle Angel".to_owned(),
description: "Collect bottles from all bottle drop points in the designated area."
.to_owned(),
hours: self.hours,
}
})
}
fn level_name(&self) -> &str {
......@@ -44,17 +48,16 @@ impl super::AngelShiftImpl for BottleAngelShift {
resources: &mut legion::Resources,
schedule_builder: &mut legion::systems::Builder,
level: &svg_loader::SvgLevel,
) {
) -> anyhow::Result<()> {
// Display objective
let objective = utils::get_element_by_id::<web_sys::Element>("ingame-objective").unwrap();
let objective = utils::get_element_by_id::<web_sys::Element>("ingame-objective")?;
objective.set_inner_html(
r#"
<text x="30" y="0" class="stats-label">Collect drop points:</text>
<text id="ingame-bottle-angel-stats" x="430" y="0" class="stats-number">0/4</text>
"#,
);
let stats =
utils::get_element_by_id::<web_sys::Element>("ingame-bottle-angel-stats").unwrap();
let stats = utils::get_element_by_id::<web_sys::Element>("ingame-bottle-angel-stats")?;
entities::create_drop_points(world, level);
resources.insert(BottleAngelState::new(4, stats));
......@@ -62,6 +65,7 @@ impl super::AngelShiftImpl for BottleAngelShift {
schedule_builder
.add_thread_local(collect_bottledrops_system())
.add_thread_local(update_bottle_shift_system(self.hours));
Ok(())
}
}
......@@ -98,24 +102,29 @@ pub fn collect_bottledrops(
#[resource] player: &resources::Player,
#[resource] collision_world: &colliders::CollisionWorld,
#[resource] bottle_angel_state: &mut BottleAngelState,
) {
) -> anyhow::Result<()> {
use legion::IntoQuery;
let collider = <&colliders::Collider>::query()
.get(world, player.0)
.unwrap();
.context("Couldn't find player")?;
let mut bottledrops = <(&components::Matebottledrop, &mut components::Sprite)>::query();
for pair in collision_world
.world
.proximities_with(collider.handle.unwrap(), false)
.unwrap()
.proximities_with(collider.handle.context("No collider handler")?, false)
.context("Proximities not found")?
{
if pair.3 != ncollide2d::query::Proximity::Intersecting {
continue;
}
let entity = *collision_world.world.objects.get(pair.1).unwrap().data();
let entity = *collision_world
.world
.objects
.get(pair.1)
.context("Couldn't find counterpart")?
.data();
if let Ok((_, sprite)) = bottledrops.get_mut(world, entity) {
*sprite = components::Sprite::new(sprites::Sprite::BottleDropPointEmpty);
cmd.remove_component::<components::Matebottledrop>(entity);
......@@ -123,6 +132,7 @@ pub fn collect_bottledrops(
bottle_angel_state.update_stats();
}
}
Ok(())
}
#[legion::system]
......@@ -133,15 +143,16 @@ pub fn update_bottle_shift(
#[resource] player: &mut resources::Player,
#[resource] bottle_angel_state: &mut BottleAngelState,
#[resource] game_manager: &mut resources::GameManager,
) {
) -> anyhow::Result<()> {
use legion::IntoQuery;
if bottle_angel_state.collected_drops >= bottle_angel_state.drops_in_map {
let player = <&mut components::Player>::query()
.get_mut(world, player.0)
.unwrap();
.context("Player not found")?;
player.collected_hours += *hours_to_award as u32;
*hours_to_award = 0;
game_manager.request_return_to_heaven();
}
Ok(())
}
......@@ -4,7 +4,7 @@ pub trait AngelShiftImpl {
/// Return metadata about this shift.
///
/// Used in heaven to display what task is up next.
fn metadata(&self) -> ShiftMetadata;
fn metadata(&self) -> anyhow::Result<ShiftMetadata>;
/// Return name of the selected level for this shift.
fn level_name(&self) -> &str;
......@@ -20,7 +20,7 @@ pub trait AngelShiftImpl {
resources: &mut legion::Resources,
schedule_builder: &mut legion::systems::Builder,
level: &svg_loader::SvgLevel,
);
) -> anyhow::Result<()>;
}
pub struct ShiftMetadata {
......@@ -35,7 +35,7 @@ pub struct ShiftMetadata {
pub struct AngelShift(pub Box<dyn AngelShiftImpl>);
impl AngelShift {
pub fn metadata(&self) -> ShiftMetadata {
pub fn metadata(&self) -> anyhow::Result<ShiftMetadata> {
self.0.metadata()
}
......@@ -49,8 +49,8 @@ impl AngelShift {
resources: &mut legion::Resources,
schedule_builder: &mut legion::systems::Builder,
level: &svg_loader::SvgLevel,
) {
) -> anyhow::Result<()> {
self.0
.init_gameworld(world, resources, schedule_builder, level);
.init_gameworld(world, resources, schedule_builder, level)
}
}
......@@ -6,10 +6,10 @@ pub use definitions::AngelShift;
pub use definitions::AngelShiftImpl;
pub use definitions::ShiftMetadata;
pub fn generate_random_shift(rng: &mut impl rand::Rng) -> AngelShift {
AngelShift(match rng.gen_range(0usize, 2) {
0 => Box::new(bottle_angel::BottleAngelShift::generate(rng)),
1 => Box::new(network_switch::NetworkSwitchShift::generate(rng)),
pub fn generate_random_shift(rng: &mut impl rand::Rng) -> anyhow::Result<AngelShift> {
Ok(AngelShift(match rng.gen_range(0usize, 2) {
0 => Box::new(bottle_angel::BottleAngelShift::generate(rng)?),
1 => Box::new(network_switch::NetworkSwitchShift::generate(rng)?),
_ => unreachable!(),
})
}))
}
......@@ -5,6 +5,7 @@ use crate::resources;
use crate::sprites;
use crate::svg_loader;
use crate::utils;
use anyhow::Context;
pub struct NetworkSwitchShift {
hours: usize,
......@@ -12,15 +13,18 @@ pub struct NetworkSwitchShift {
}
impl NetworkSwitchShift {
pub fn generate(rng: &mut impl rand::Rng) -> NetworkSwitchShift {
pub fn generate(rng: &mut impl rand::Rng) -> anyhow::Result<NetworkSwitchShift> {
use rand::seq::SliceRandom;
let compatible_levels = ["assembly-hall-1.svg"];
NetworkSwitchShift {
Ok(NetworkSwitchShift {
hours: rng.gen_range(1, 3),
level: compatible_levels.choose(rng).unwrap().to_string(),
}
level: compatible_levels
.choose(rng)
.context("Compatible level not found")?
.to_string(),
})
}
}
......@@ -48,12 +52,12 @@ impl NetworkSwitchAngelState {
}
impl super::AngelShiftImpl for NetworkSwitchShift {
fn metadata(&self) -> super::ShiftMetadata {
super::ShiftMetadata {
fn metadata(&self) -> anyhow::Result<super::ShiftMetadata> {
Ok(super::ShiftMetadata {
title: "Fix the internet".to_owned(),
description: "Check what's wrong with the network switches.".to_owned(),
hours: self.hours,
}
})
}
fn level_name(&self) -> &str {
......@@ -66,22 +70,23 @@ impl super::AngelShiftImpl for NetworkSwitchShift {
resources: &mut legion::Resources,
schedule_builder: &mut legion::systems::Builder,
level: &svg_loader::SvgLevel,
) {
) -> anyhow::Result<()> {
// Display objective
let objective = utils::get_element_by_id::<web_sys::Element>("ingame-objective").unwrap();
let objective = utils::get_element_by_id::<web_sys::Element>("ingame-objective")?;
objective.set_inner_html(
r#"
<text x="30" y="0" class="stats-label">Switches online:</text>
<text id="ingame-network-stats" x="430" y="0" class="stats-number">0/4</text>
"#,
);
let stats = utils::get_element_by_id::<web_sys::Element>("ingame-network-stats").unwrap();
let stats = utils::get_element_by_id::<web_sys::Element>("ingame-network-stats")?;
entities::create_network_switches(world, level);
resources.insert(NetworkSwitchAngelState::new(4, stats));
schedule_builder
.add_thread_local(reconnect_switches_system())
.add_thread_local(update_network_switch_shift_system(self.hours));
Ok(())
}
}
......@@ -95,24 +100,32 @@ pub fn reconnect_switches(
#[resource] player: &resources::Player,
#[resource] collision_world: &colliders::CollisionWorld,
#[resource] angel_state: &mut NetworkSwitchAngelState,
) {
) -> anyhow::Result<()> {
use legion::IntoQuery;
let collider = <&colliders::Collider>::query()
.get(world, player.0)
.unwrap();
.context("Player not found")?;
let mut switches = <(&components::NetworkSwitch, &mut components::Sprite)>::query();
for pair in collision_world
.world
.proximities_with(collider.handle.unwrap(), false)
.unwrap()
.proximities_with(
collider.handle.context("Collider handler not found")?,
false,
)
.context("Proximities not found")?
{
if pair.3 != ncollide2d::query::Proximity::Intersecting {
continue;
}
let entity = *collision_world.world.objects.get(pair.1).unwrap().data();
let entity = *collision_world
.world
.objects
.get(pair.1)
.context("Couldn't find counterpart")?
.data();
if let Ok((_, sprite)) = switches.get_mut(world, entity) {
*sprite = components::Sprite::new(sprites::Sprite::NetworkSwitchConnected);
cmd.remove_component::<components::NetworkSwitch>(entity);
......@@ -120,6 +133,7 @@ pub fn reconnect_switches(
angel_state.update_stats();
}
}
Ok(())
}
#[legion::system]
......@@ -130,15 +144,16 @@ pub fn update_network_switch_shift(
#[resource] player: &mut resources::Player,
#[resource] angel_state: &mut NetworkSwitchAngelState,
#[resource] game_manager: &mut resources::GameManager,
) {
) -> anyhow::Result<()> {
use legion::IntoQuery;
if angel_state.reconnected_switches >= angel_state.switches_in_map {
let player = <&mut components::Player>::query()
.get_mut(world, player.0)
.unwrap();
.context("Player not found")?;
player.collected_hours += *hours_to_award as u32;
*hours_to_award = 0;
game_manager.request_return_to_heaven();
}
Ok(())
}
......@@ -59,7 +59,7 @@ pub fn cheat_set_sanity(val: f32) -> Result<(), wasm_bindgen::JsValue> {
match || -> anyhow::Result<()> {
// this is nice
let state = get_cheat_state()?;
state.do_cheat(CheatCommand::SetSanity(val));
state.do_cheat(CheatCommand::SetSanity(val))?;
Ok(())
}() {
// this is not nice
......@@ -72,7 +72,7 @@ pub fn cheat_set_sanity(val: f32) -> Result<(), wasm_bindgen::JsValue> {
pub fn cheat_set_shifts(val: u32) -> Result<(), wasm_bindgen::JsValue> {
match || -> anyhow::Result<()> {
let state = get_cheat_state()?;
state.do_cheat(CheatCommand::SetShifts(val));
state.do_cheat(CheatCommand::SetShifts(val))?;
Ok(())
}() {
// this is not nice
......@@ -84,7 +84,7 @@ pub fn cheat_set_shifts(val: u32) -> Result<(), wasm_bindgen::JsValue> {
pub fn cheat_get_player() -> Result<(), wasm_bindgen::JsValue> {
match || -> anyhow::Result<()> {
let state = get_cheat_state()?;
state.do_cheat(CheatCommand::GetPlayer());
state.do_cheat(CheatCommand::GetPlayer())?;
Ok(())
}() {
// this is not nice
......
......@@ -20,12 +20,12 @@ impl Edge {
pub fn draw_edges(
world: &legion::world::SubWorld,
#[resource] rendering: &mut resources::Rendering,
) {
) -> anyhow::Result<()> {
// TODO: Replace by proper API
for edge in <&Edge>::query().iter(world) {
let mut positions = <&components::Position>::query();
let pos1 = positions.get(world, edge.node1).unwrap();
let pos2 = positions.get(world, edge.node2).unwrap();
let pos1 = positions.get(world, edge.node1)?;
let pos2 = positions.get(world, edge.node2)?;
rendering.begin_path();
rendering.set_line_width(7.0);
......@@ -34,4 +34,6 @@ pub fn draw_edges(
rendering.line_to(pos2.0.x as f64, pos2.0.y as f64);
rendering.stroke();
}
Ok(())
}
use anyhow::Result;
use wasm_bindgen::JsValue;
use web_sys;
pub trait AnyhowWebExt<T> {
fn to_anyhow(self) -> anyhow::Result<T>;
}
impl<T> AnyhowWebExt<T> for Result<T, JsValue> {
fn to_anyhow(self) -> anyhow::Result<T> {
self.map_err(|js_value| {
let error_message = match js_value.as_string() {
Some(string) => string,
None => "Unknown error".to_string(),
};
anyhow::anyhow!("{}", error_message)
})
}
}
impl<T> AnyhowWebExt<T> for Result<T, web_sys::Element> {
fn to_anyhow(self) -> anyhow::Result<T> {
self.map_err(|js_value| {
let error_message = match js_value.as_string() {
Some(string) => string,
None => "Unknown error".to_string(),
};
anyhow::anyhow!("{}", error_message)
})
}
}
......@@ -4,6 +4,10 @@ use std::rc::Rc;
use crate::utils;
use wasm_bindgen::closure;
use crate::error::AnyhowWebExt;
use anyhow::Context;
pub enum Transition {
/// Schedule a call of [`State::update`] with `requestAnimationFrame`.
Loop,
......@@ -47,15 +51,17 @@ impl Transition {
}
pub trait State {
fn init(&mut self, init: StateInitializer) -> Transition;
fn deinit(&mut self) {}
fn init(&mut self, init: StateInitializer) -> anyhow::Result<Transition>;
fn deinit(&mut self) -> anyhow::Result<()> {
Ok(())
}
fn update(&mut self, _timestamp: f64) -> Transition {
Transition::Keep
fn update(&mut self, _timestamp: f64) -> anyhow::Result<Transition> {
Ok(Transition::Keep)
}
#[allow(unused_variables)]
fn event(&mut self, event: Event) -> Transition {
Transition::Keep
fn event(&mut self, event: Event) -> anyhow::Result<Transition> {
Ok(Transition::Keep)
}
}
......@@ -111,7 +117,10 @@ impl EventHandlers {
self.mouse_handlers.insert(id.to_owned(), f);
}
pub fn register_keyevents<F: FnMut(bool, &web_sys::KeyboardEvent) + 'static>(&mut self, f: F) {
pub fn register_keyevents<F: FnMut(bool, &web_sys::KeyboardEvent) + 'static>(
&mut self,
f: F,
) -> anyhow::Result<()> {
use wasm_bindgen::JsCast;
let f = Rc::new(RefCell::new(f));
......@@ -136,16 +145,21 @@ impl EventHandlers {
f2.borrow_mut()(false, event)
}) as Box<dyn FnMut(wasm_bindgen::JsValue)>);
let body = utils::document().body().unwrap();
let body = utils::document()
.body()
.context("document.body is undefined")?;
body.set_onkeydown(Some(fdown.as_ref().unchecked_ref()));
body.set_onkeyup(Some(fup.as_ref().unchecked_ref()));
self.key_handlers = Some((fdown, fup));
Ok(())
}
pub fn deregister_all(&mut self) {
pub fn deregister_all(&mut self) -> anyhow::Result<()> {
if self.key_handlers.is_some() {
let body = utils::document().body().unwrap();
let body = utils::document()
.body()
.context("document.body is undefined")?;
body.set_onkeydown(None);
body.set_onkeyup(None);
}
......@@ -156,9 +170,10 @@ impl EventHandlers {
} 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);
return Err(anyhow::anyhow!("Cannot find appropriate element {:?}", id));
}
}
Ok(())
}
}
......@@ -170,16 +185,16 @@ impl StateMachineHandle {
StateMachineHandle(state_machine.clone())
}
pub fn do_transition(&self, t: Transition) {
pub fn do_transition(&self, t: Transition) -> anyhow::Result<()> {
StateMachine::do_transition(&self.0, t)
}
pub fn do_event(&self, event: Event) {
let t = StateMachine::active(&self.0).event(event);
pub fn do_event(&self, event: Event) -> anyhow::Result<()> {
let t = StateMachine::active(&self.0).event(event)?;
self.do_transition(t)
}
pub fn do_cheat(&self, cheat: crate::cheats::CheatCommand) {
pub fn do_cheat(&self, cheat: crate::cheats::CheatCommand) -> anyhow::Result<()>{
self.do_event(Event::Cheat(cheat))
}
......@@ -204,19 +219,22 @@ impl<'a> StateInitializer<'a> {
}
}
pub fn register_onclick(&mut self, id: &str) {
pub fn register_onclick(&mut self, id: &str) -> anyhow::Result<()> {
let handle = self.handle.clone();
let id2 = id.to_owned();
let canvas = utils::get_element_by_id::<web_sys::HtmlCanvasElement>("game-canvas").unwrap();
let canvas = utils::get_element_by_id::<web_sys::HtmlCanvasElement>("game-canvas")?;
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,
});
handle
.do_event(Event::MouseClick {
target: &id2,
coords: canvas_click,
})
.unwrap(); // ignore-unwrap - unwrap inside a closure.
});
Ok(())
}
pub fn register_keyevents(&mut self) {
......@@ -224,11 +242,12 @@ impl<'a> StateInitializer<'a> {
self.event_handlers
.register_keyevents(move |keydown, event| {
if keydown {
handle.do_event(Event::KeyDown(&event.key()));
handle.do_event(Event::KeyDown(&event.key())).unwrap(); // ignore-unwrap - unwrap inside a closure
} else {
handle.do_event(Event::KeyUp(&event.key()));
handle.do_event(Event::KeyUp(&event.key())).unwrap(); // ignore-unwrap - unwrap inside a closure
}
});
})
.unwrap();
}
pub fn get_handle(&self) -> StateMachineHandle {
......@@ -258,21 +277,22 @@ impl StateStorage {
// ----------
pub fn init(&mut self, sm: &Rc<RefCell<StateMachine>>) -> Transition {
pub fn init(&mut self, sm: &Rc<RefCell<StateMachine>>) -> anyhow::Result<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 deinit(&mut self) -> anyhow::Result<()> {
self.state.deinit()?;
self.event_handlers.deregister_all()?;
Ok(())
}
pub fn update(&mut self, timestamp: f64) -> Transition {
pub fn update(&mut self, timestamp: f64) -> anyhow::Result<Transition> {
self.state.update(timestamp)
}
fn event(&mut self, event: Event) -> Transition {
fn event(&mut self, event: Event) -> anyhow::Result<Transition> {
self.state.event(event)
}
}
......@@ -284,7 +304,7 @@ pub struct StateMachine {
}
impl StateMachine {
pub fn launch<S: State + 'static>(initial: S) {
pub fn launch<S: State + 'static>(initial: S) -> anyhow::Result<()> {
let this = Rc::new(RefCell::new(StateMachine {
states: vec![StateStorage::new(initial)],
loop_handler: None,
......@@ -315,28 +335,31 @@ impl StateMachine {
return;
}
active.update(timestamp)
active.update(timestamp).unwrap() // ignore-unwrap - unwrap inside a closure
};
StateMachine::do_transition(&this, t);
StateMachine::do_transition(&this, t).unwrap();
}) as Box<dyn FnMut(f64)>)
};
this.borrow_mut().loop_handler = Some(loop_handler);
let t = StateMachine::active(&this).init(&this);
StateMachine::do_transition(&this, t);
let t = StateMachine::active(&this).init(&this)?;
StateMachine::do_transition(&this, t)?;
Ok(())
}
fn active(this: &Rc<RefCell<StateMachine>>) -> std::cell::RefMut<StateStorage> {
std::cell::RefMut::map(this.borrow_mut(), |sm| sm.states.last_mut().unwrap())
// ignore-unwrap - unwrap inside a closure
}
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_event(this: &Rc<RefCell<StateMachine>>, event: Event) -> anyhow::Result<()> {
let t = StateMachine::active(this).event(event)?;
StateMachine::do_transition(this, t)?;
Ok(())
}