1extern crate self as pasaka;
23
24use std::{
25 collections::HashMap,
26 sync::{LazyLock, Mutex},
27};
28
29mod choice;
30pub use choice::ChoiceHandle;
31pub use choice::ChoiceResult;
32pub use choice::PassageHandle;
33pub use choice::PassageResult;
34
35mod engine;
36pub use engine::Engine;
37
38mod runner;
39pub use runner::*;
40
41pub trait PassageImpl: 'static
42where
43 Self::State: serde::Serialize + for<'a> serde::Deserialize<'a>,
44{
45 type State;
46
47 fn run(&self, h: PassageHandle, state: Self::State) -> PassageResult;
48
49 fn with_state(&self, state: Self::State) -> Passage {
50 Passage {
51 state: serde_json::to_value(state).unwrap(),
52 registry_key: self.registry_key(),
53 }
54 }
55
56 fn name(&self) -> &'static str;
57
58 fn module_path(&self) -> &'static str;
59
60 fn registry_key(&self) -> String {
61 format!("{}::{}", self.module_path(), self.name())
62 }
63}
64
65#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
69pub struct Passage {
70 state: serde_json::Value,
71 registry_key: String,
72}
73
74impl Passage {
75 pub fn run(self, h: PassageHandle) -> PassageResult {
76 let guard = PASSAGE_REGISTRY
77 .lock()
78 .expect("accessing passage registry should not panic");
79 let f = guard.get(&self.registry_key).expect(&format!(
80 "passage {} should be registered using #[passage]",
81 self.registry_key
82 ));
83
84 f(h, self.state)
85 }
86
87 pub fn state<S: for<'a> serde::Deserialize<'a>>(&self) -> Option<S> {
88 serde_json::from_value(self.state.clone()).ok()
89 }
90}
91
92type BoxedPassage = Box<dyn Fn(PassageHandle, serde_json::Value) -> PassageResult + Send + Sync>;
93
94static PASSAGE_REGISTRY: LazyLock<Mutex<HashMap<String, BoxedPassage>>> =
95 LazyLock::new(|| Mutex::new(HashMap::new()));
96
97pub fn register_passage<P: PassageImpl + Send + Sync>(passage: P) {
101 PASSAGE_REGISTRY
102 .lock()
103 .expect("registering passage registry should not panic")
104 .insert(
105 passage.registry_key(),
106 Box::new(move |h, value| {
107 let state: P::State = serde_json::from_value(value)
108 .expect("deserialized value should match passage state");
109 passage.run(h, state)
110 }),
111 );
112}
113
114pub use pasaka_macro::passage;
115
116pub mod macro_support {
118 pub mod ctor {
120 pub use ctor::*;
121 }
122
123 pub mod serde {
125 pub use serde::*;
126 }
127
128 #[cfg(feature = "web")]
130 pub mod yew {
131 pub use yew::*;
132 }
133}