Commit ab7a5bbb authored by Rahix's avatar Rahix 🦀

Merge branch 'neosam/error-handling' into 'master'

Use more error handling

See merge request !61
parents 0218844a 804cf58a
......@@ -110,6 +110,7 @@ dependencies = [
"console_error_panic_hook",
"js-sys",
"legion",
"macro-impl",
"nalgebra",
"ncollide2d",
"rand",
......@@ -255,6 +256,15 @@ dependencies = [
"cfg-if 0.1.10",
]
[[package]]
name = "macro-impl"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "matrixmultiply"
version = "0.2.3"
......
......@@ -11,6 +11,9 @@ build = "src/build.rs"
crate-type = ["cdylib"]
path = "src/main.rs"
[workspace]
members = ["macro-impl"]
[dependencies]
js-sys = "0.3.46"
wasm-bindgen = "0.2.69"
......@@ -20,6 +23,7 @@ ncollide2d = "0.26.1"
console_error_panic_hook = "0.1.6"
anyhow = "1.0"
rand = "0.7.3"
macro-impl = {path = "macro-impl"}
[dependencies.legion]
version = "0.3.1"
......
[package]
name = "macro-impl"
version = "0.1.0"
authors = ["Simon Goller <neosam+github@posteo.de>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
syn = {version="1.0", features = ["full"] }
quote = "1.0"
proc-macro2 = "1.0"
\ No newline at end of file
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn angel_system(args: TokenStream, input: TokenStream) -> TokenStream {
let f = syn::parse_macro_input!(input as syn::ItemFn);
let fname = f.sig.ident;
let attrs = f.attrs;
let inputs = f.sig.inputs;
let block = f.block;
let stmts = block.stmts;
let rtype = f.sig.output;
let vis = f.vis;
let legion_attr = if !args.is_empty() {
let args: proc_macro2::TokenStream = args.into();
quote::quote!(#[legion::system(#args)])
} else {
quote::quote!(#[legion::system])
};
quote::quote!(
#legion_attr
#(#attrs)*
#vis fn #fname(#inputs) {
crate::error::handle_system_result(|| #rtype {
#(#stmts)*
});
}
)
.into()
}
#[proc_macro_attribute]
pub fn wasm_bindgen_anyhow(_args: TokenStream, input: TokenStream) -> TokenStream {
let f = syn::parse_macro_input!(input as syn::ItemFn);
let fname = f.sig.ident;
let attrs = f.attrs;
let inputs = f.sig.inputs;
let block = f.block;
let stmts = block.stmts;
let rtype = f.sig.output;
let vis = f.vis;
quote::quote!(
#[wasm_bindgen]
#(#attrs)*
#vis fn #fname(#inputs) -> Result<(), wasm_bindgen::JsValue> {
match || #rtype {
#(#stmts)*
}() {
Ok(()) => Ok(()),
Err(err) => Err(
wasm_bindgen::JsValue::from_str(&format!("{}", err))
),
}
}
)
.into()
}
......@@ -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(())
}
}
......@@ -88,7 +92,7 @@ impl BottleAngelState {
}
}
#[legion::system]
#[macro_impl::angel_system]
#[read_component(colliders::Collider)]
#[read_component(components::Matebottledrop)]
#[write_component(components::Sprite)]
......@@ -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,9 +132,10 @@ pub fn collect_bottledrops(
bottle_angel_state.update_stats();
}
}
Ok(())
}
#[legion::system]
#[macro_impl::angel_system]
#[write_component(components::Player)]
pub fn update_bottle_shift(
#[state] hours_to_award: &mut usize,
......@@ -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,26 +70,27 @@ 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(())
}
}
#[legion::system]
#[macro_impl::angel_system]
#[read_component(colliders::Collider)]
#[read_component(components::NetworkSwitch)]
#[write_component(components::Sprite)]
......@@ -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,9 +133,10 @@ pub fn reconnect_switches(
angel_state.update_stats();
}
}
Ok(())
}
#[legion::system]
#[macro_impl::angel_system]
#[write_component(components::Player)]
pub fn update_network_switch_shift(
#[state] hours_to_award: &mut usize,
......@@ -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(())
}
......@@ -54,41 +54,23 @@ pub fn cheat_enable(password: &str) {
}
}
#[wasm_bindgen]
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));
Ok(())
}() {
// this is not nice
Ok(()) => Ok(()),
Err(err) => Err(wasm_bindgen::JsValue::from_str(&format!("{}", err))),
}
#[macro_impl::wasm_bindgen_anyhow]
pub fn cheat_set_sanity(val: f32) -> anyhow::Result<()> {
let state = get_cheat_state()?;
state.do_cheat(CheatCommand::SetSanity(val))?;
Ok(())
}
#[wasm_bindgen]
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));
Ok(())
}() {
// this is not nice
Ok(()) => Ok(()),
Err(err) => Err(wasm_bindgen::JsValue::from_str(&format!("{}", err))),
}
#[macro_impl::wasm_bindgen_anyhow]
pub fn cheat_set_shifts(val: u32) -> anyhow::Result<()> {
let state = get_cheat_state()?;
state.do_cheat(CheatCommand::SetShifts(val))?;
Ok(())
}
#[wasm_bindgen]
pub fn cheat_get_player() -> Result<(), wasm_bindgen::JsValue> {
match || -> anyhow::Result<()> {
let state = get_cheat_state()?;
state.do_cheat(CheatCommand::GetPlayer());
Ok(())
}() {
// this is not nice
Ok(()) => Ok(()),
Err(err) => Err(wasm_bindgen::JsValue::from_str(&format!("{}", err))),
}
#[macro_impl::wasm_bindgen_anyhow]
pub fn cheat_get_player() -> anyhow::Result<()> {
let state = get_cheat_state()?;
state.do_cheat(CheatCommand::GetPlayer())?;
Ok(())
}
......@@ -14,18 +14,18 @@ impl Edge {
}
}
#[legion::system]
#[macro_impl::angel_system]
#[read_component(Edge)]
#[read_component(components::Position)]
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(())
}
......@@ -46,13 +46,14 @@ pub fn update_nodes(node: &mut Node, #[resource] clock: &resources::Clock) {
}
}
#[legion::system(for_each)]
//#[legion::system(for_each)]
#[macro_impl::angel_system(for_each)]
pub fn draw_nodes(
node: &Node,
pos: &components::Position,
#[resource] rendering: &mut resources::Rendering,
#[resource] clock: &resources::Clock,
) {
) -> anyhow::Result<()> {
// TODO: Replace by proper API
rendering.begin_path();
rendering.set_fill_style(&colors::BACKGROUND);
......@@ -62,7 +63,7 @@ pub fn draw_nodes(
10.0,
0.0,
std::f64::consts::TAU,
);
)?;
rendering.fill();
rendering.begin_path();
......@@ -74,7 +75,7 @@ pub fn draw_nodes(
10.0,
0.0,
std::f64::consts::TAU,
);
)?;
rendering.stroke();
let time = clock.wall_time();
......@@ -88,7 +89,7 @@ pub fn draw_nodes(
18.0,
0.0,
std::f64::consts::TAU * amount_completed,
);
)?;
rendering.stroke();
}
......@@ -106,7 +107,8 @@ pub fn draw_nodes(
radius,
0.0,
std::f64::consts::TAU,
);
)?;
rendering.stroke();
}
Ok(())
}
......@@ -4,13 +4,13 @@ use crate::resources;
pub struct TheSun;
#[legion::system(for_each)]
#[macro_impl::angel_system(for_each)]
pub fn draw_thesun(
_: &TheSun,
pos: &components::Position,
#[resource] rendering: &mut resources::Rendering,
#[resource] clock: &resources::Clock,
) {
) -> anyhow::Result<()> {
// TODO: Replace by proper API
rendering.begin_path();
rendering.set_fill_style(&colors::PRIMARY1_SHADE1);
......@@ -20,7 +20,7 @@ pub fn draw_thesun(
30.0,
0.0,
std::f64::consts::TAU,
);
)?;
rendering.fill();
let now = clock.wall_time();
......