fix(cxxengine): catch errors
This commit is contained in:
parent
4ea4b1249b
commit
c780f8bfe4
1 changed files with 62 additions and 35 deletions
|
|
@ -9,10 +9,28 @@
|
||||||
//! and events are mirrored back before being applied — exactly as in
|
//! and events are mirrored back before being applied — exactly as in
|
||||||
//! pyengine.rs.
|
//! pyengine.rs.
|
||||||
|
|
||||||
|
use std::panic::{self, AssertUnwindSafe};
|
||||||
|
|
||||||
use crate::dice::Dice;
|
use crate::dice::Dice;
|
||||||
use crate::game::{GameEvent, GameState, Stage, TurnStage};
|
use crate::game::{GameEvent, GameState, Stage, TurnStage};
|
||||||
use crate::training_common::{get_valid_action_indices, TrictracAction};
|
use crate::training_common::{get_valid_action_indices, TrictracAction};
|
||||||
|
|
||||||
|
/// Catch any Rust panic and convert it to anyhow::Error so it never
|
||||||
|
/// crosses the C FFI boundary as undefined behaviour.
|
||||||
|
fn catch_panics<F, T>(f: F) -> anyhow::Result<T>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> anyhow::Result<T> + panic::UnwindSafe,
|
||||||
|
{
|
||||||
|
panic::catch_unwind(f).unwrap_or_else(|e| {
|
||||||
|
let msg = e
|
||||||
|
.downcast_ref::<String>()
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
.or_else(|| e.downcast_ref::<&str>().copied())
|
||||||
|
.unwrap_or("unknown panic payload");
|
||||||
|
Err(anyhow::anyhow!("Rust panic in FFI: {}", msg))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ── cxx bridge declaration ────────────────────────────────────────────────────
|
// ── cxx bridge declaration ────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[cxx::bridge(namespace = "trictrac_engine")]
|
#[cxx::bridge(namespace = "trictrac_engine")]
|
||||||
|
|
@ -98,7 +116,9 @@ pub fn new_trictrac_engine() -> Box<TricTracEngine> {
|
||||||
let mut game_state = GameState::new(false); // schools_enabled = false
|
let mut game_state = GameState::new(false); // schools_enabled = false
|
||||||
game_state.init_player("player1");
|
game_state.init_player("player1");
|
||||||
game_state.init_player("player2");
|
game_state.init_player("player2");
|
||||||
game_state.consume(&GameEvent::BeginGame { goes_first: 1 });
|
game_state
|
||||||
|
.consume(&GameEvent::BeginGame { goes_first: 1 })
|
||||||
|
.expect("BeginGame failed during engine initialization");
|
||||||
Box::new(TricTracEngine { game_state })
|
Box::new(TricTracEngine { game_state })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,13 +147,16 @@ impl TricTracEngine {
|
||||||
if player_idx != self.current_player_idx() {
|
if player_idx != self.current_player_idx() {
|
||||||
return Ok(vec![]);
|
return Ok(vec![]);
|
||||||
}
|
}
|
||||||
if player_idx == 0 {
|
catch_panics(AssertUnwindSafe(|| {
|
||||||
get_valid_action_indices(&self.game_state)
|
if player_idx == 0 {
|
||||||
.map(|v| v.into_iter().map(|i| i as u64).collect())
|
get_valid_action_indices(&self.game_state)
|
||||||
} else {
|
.map(|v| v.into_iter().map(|i| i as u64).collect())
|
||||||
let mirror = self.game_state.mirror();
|
} else {
|
||||||
get_valid_action_indices(&mirror).map(|v| v.into_iter().map(|i| i as u64).collect())
|
let mirror = self.game_state.mirror();
|
||||||
}
|
get_valid_action_indices(&mirror)
|
||||||
|
.map(|v| v.into_iter().map(|i| i as u64).collect())
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_to_string(&self, player_idx: u64, action_idx: u64) -> String {
|
fn action_to_string(&self, player_idx: u64, action_idx: u64) -> String {
|
||||||
|
|
@ -188,38 +211,42 @@ impl TricTracEngine {
|
||||||
let dice = Dice {
|
let dice = Dice {
|
||||||
values: (dice.die1, dice.die2),
|
values: (dice.die1, dice.die2),
|
||||||
};
|
};
|
||||||
self.game_state
|
catch_panics(AssertUnwindSafe(|| {
|
||||||
.consume(&GameEvent::RollResult { player_id, dice });
|
self.game_state
|
||||||
Ok(())
|
.consume(&GameEvent::RollResult { player_id, dice })
|
||||||
|
.map_err(|e| anyhow::anyhow!(e))
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_action(&mut self, action_idx: u64) -> anyhow::Result<()> {
|
fn apply_action(&mut self, action_idx: u64) -> anyhow::Result<()> {
|
||||||
let needs_mirror = self.game_state.active_player_id == 2;
|
catch_panics(AssertUnwindSafe(|| {
|
||||||
|
let needs_mirror = self.game_state.active_player_id == 2;
|
||||||
|
|
||||||
let event = TrictracAction::from_action_index(action_idx as usize).and_then(|a| {
|
let event = TrictracAction::from_action_index(action_idx as usize).and_then(|a| {
|
||||||
let state = if needs_mirror {
|
let state = if needs_mirror {
|
||||||
&self.game_state.mirror()
|
&self.game_state.mirror()
|
||||||
} else {
|
} else {
|
||||||
&self.game_state
|
&self.game_state
|
||||||
};
|
};
|
||||||
a.to_event(state)
|
a.to_event(state)
|
||||||
.map(|e| if needs_mirror { e.get_mirror(false) } else { e })
|
.map(|e| if needs_mirror { e.get_mirror(false) } else { e })
|
||||||
});
|
});
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Some(evt) if self.game_state.validate(&evt) => {
|
Some(evt) if self.game_state.validate(&evt) => self
|
||||||
self.game_state.consume(&evt);
|
.game_state
|
||||||
Ok(())
|
.consume(&evt)
|
||||||
|
.map_err(|e| anyhow::anyhow!(e)),
|
||||||
|
Some(evt) => anyhow::bail!(
|
||||||
|
"apply_action: event {:?} is not valid in current state {}",
|
||||||
|
evt,
|
||||||
|
self.game_state
|
||||||
|
),
|
||||||
|
None => anyhow::bail!(
|
||||||
|
"apply_action: could not build event from action index {}",
|
||||||
|
action_idx
|
||||||
|
),
|
||||||
}
|
}
|
||||||
Some(evt) => anyhow::bail!(
|
}))
|
||||||
"apply_action: event {:?} is not valid in current state {}",
|
|
||||||
evt,
|
|
||||||
self.game_state
|
|
||||||
),
|
|
||||||
None => anyhow::bail!(
|
|
||||||
"apply_action: could not build event from action index {}",
|
|
||||||
action_idx
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue