#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ActivationOrder {
#[default]
Sequential,
ShuffleChunks,
ShuffleFull,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct RunContext {
pub simulation_seed: u64,
pub tick: u64,
pub system_id: crate::engine::types::SystemID,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct ActivationContext {
pub(crate) order: ActivationOrder,
pub(crate) seed: u64,
pub(crate) system_id: crate::engine::types::SystemID,
}
impl Default for ActivationContext {
fn default() -> Self {
Self {
order: ActivationOrder::Sequential,
seed: 0,
system_id: 0,
}
}
}
thread_local! {
static CURRENT_ACTIVATION: std::cell::Cell<ActivationContext> =
const { std::cell::Cell::new(ActivationContext {
order: ActivationOrder::Sequential,
seed: 0,
system_id: 0,
}) };
static CURRENT_RUN_CONTEXT: std::cell::Cell<RunContext> =
const { std::cell::Cell::new(RunContext {
simulation_seed: 0,
tick: 0,
system_id: 0,
}) };
}
pub(crate) fn current_activation_context() -> ActivationContext {
CURRENT_ACTIVATION.with(std::cell::Cell::get)
}
struct RestoreOnDrop<'a, T: Copy + 'static> {
cell: &'a std::cell::Cell<T>,
previous: T,
}
impl<'a, T: Copy + 'static> RestoreOnDrop<'a, T> {
fn install(cell: &'a std::cell::Cell<T>, context: T) -> Self {
let previous = cell.replace(context);
Self { cell, previous }
}
}
impl<T: Copy + 'static> Drop for RestoreOnDrop<'_, T> {
fn drop(&mut self) {
self.cell.set(self.previous);
}
}
pub(crate) fn with_activation_context<R>(context: ActivationContext, f: impl FnOnce() -> R) -> R {
CURRENT_ACTIVATION.with(|cell| {
let _restore = RestoreOnDrop::install(cell, context);
f()
})
}
pub(crate) fn current_run_context() -> RunContext {
CURRENT_RUN_CONTEXT.with(std::cell::Cell::get)
}
pub(crate) fn with_run_context<R>(context: RunContext, f: impl FnOnce() -> R) -> R {
CURRENT_RUN_CONTEXT.with(|cell| {
let _restore = RestoreOnDrop::install(cell, context);
f()
})
}
#[cfg(test)]
mod context_tests {
use super::*;
#[test]
fn unwinding_restores_the_previous_run_context() {
let outer = RunContext {
simulation_seed: 7,
tick: 3,
system_id: 11,
};
with_run_context(outer, || {
let inner = RunContext {
simulation_seed: 99,
tick: 99,
system_id: 99,
};
let panicked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
with_run_context(inner, || panic!("system blew up"));
}));
assert!(panicked.is_err());
assert_eq!(current_run_context(), outer);
});
assert_eq!(current_run_context(), RunContext::default());
}
#[test]
fn unwinding_restores_the_previous_activation_context() {
let outer = ActivationContext {
order: ActivationOrder::ShuffleFull,
seed: 5,
system_id: 2,
};
with_activation_context(outer, || {
let inner = ActivationContext {
order: ActivationOrder::ShuffleChunks,
seed: 42,
system_id: 8,
};
let panicked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
with_activation_context(inner, || panic!("system blew up"));
}));
assert!(panicked.is_err());
assert_eq!(current_activation_context(), outer);
});
assert_eq!(current_activation_context(), ActivationContext::default());
}
}