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

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);