1use std::cell::{Cell, RefCell};
8use std::rc::Rc;
9
10use oxframe_protocol::Meta;
11use rand::SeedableRng;
12use rand_chacha::ChaCha8Rng;
13
14use crate::CubicBezier;
15use crate::ease::presets;
16use crate::flow::Task;
17use crate::transition::Recorded;
18
19pub(crate) struct Runtime {
20 pub fps: f64,
21 pub frame: Cell<u32>,
22 pub task_time: Cell<f64>,
26 pub spawned: RefCell<Vec<Task>>,
27 pub meta: RefCell<Meta>,
28 pub meta_changed: Cell<bool>,
29 pub rng: RefCell<ChaCha8Rng>,
30 pub has_previous: Cell<bool>,
32 pub transition: RefCell<Option<Recorded>>,
34 pub sounds: RefCell<Vec<crate::SoundClip>>,
36 pub voices: RefCell<Vec<crate::VoiceLine>>,
38 pub slides: RefCell<Vec<crate::SlideMark>>,
40}
41
42impl Runtime {
43 pub fn new(fps: f64, meta: Meta, seed: u64) -> Rc<Self> {
44 assert!(fps > 0.0 && fps.is_finite(), "fps must be positive");
45 Rc::new(Self {
46 fps,
47 frame: Cell::new(0),
48 task_time: Cell::new(0.0),
49 spawned: RefCell::new(Vec::new()),
50 meta: RefCell::new(meta),
51 meta_changed: Cell::new(false),
52 rng: RefCell::new(ChaCha8Rng::seed_from_u64(seed)),
53 has_previous: Cell::new(false),
54 transition: RefCell::new(None),
55 sounds: RefCell::new(Vec::new()),
56 voices: RefCell::new(Vec::new()),
57 slides: RefCell::new(Vec::new()),
58 })
59 }
60
61 pub fn now(&self) -> f64 {
62 f64::from(self.frame.get()) / self.fps
63 }
64}
65
66thread_local! {
67 static CURRENT: RefCell<Option<Rc<Runtime>>> = const { RefCell::new(None) };
68}
69
70pub(crate) struct Installed(Option<Rc<Runtime>>);
72
73pub(crate) fn install(rt: &Rc<Runtime>) -> Installed {
74 Installed(CURRENT.with(|c| c.borrow_mut().replace(rt.clone())))
75}
76
77impl Drop for Installed {
78 fn drop(&mut self) {
79 let prev = self.0.take();
80 CURRENT.with(|c| *c.borrow_mut() = prev);
81 }
82}
83
84pub(crate) fn with<R>(f: impl FnOnce(&Runtime) -> R) -> R {
85 CURRENT.with(|c| {
86 let c = c.borrow();
87 let rt = c
88 .as_ref()
89 .expect("oxframe: this function can only be called while a scene is running");
90 f(rt)
91 })
92}
93
94pub fn frame() -> u32 {
96 with(|rt| rt.frame.get())
97}
98
99pub fn fps() -> f64 {
101 with(|rt| rt.fps)
102}
103
104pub fn dt() -> f64 {
106 with(|rt| 1.0 / rt.fps)
107}
108
109pub fn now() -> f64 {
111 with(|rt| rt.now())
112}
113
114pub fn has_previous() -> bool {
116 with(|rt| rt.has_previous.get())
117}
118
119pub fn begin_transition(mut spec: Recorded) -> f64 {
122 with(|rt| {
123 if rt.transition.borrow().is_some() {
124 panic!("oxframe: a scene can declare only one transition; call it once, at the start");
125 }
126 let stored = rt.meta.borrow().transition.as_ref().map(|t| t.seconds);
127 if let Some(seconds) = stored {
128 spec.seconds = if seconds.is_finite() {
129 seconds.max(0.0)
130 } else {
131 0.0
132 };
133 } else if spec.seconds.is_finite() && spec.seconds > 0.0 {
134 let seconds = spec.seconds;
135 rt.meta.borrow_mut().transition = Some(oxframe_protocol::TransitionMeta { seconds });
136 rt.meta_changed.set(true);
137 } else {
138 spec.seconds = 0.0;
139 }
140 let seconds = spec.seconds;
141 if seconds > 0.0 {
142 *rt.transition.borrow_mut() = Some(spec);
143 }
144 seconds
145 })
146}
147
148pub fn time() -> f64 {
150 with(|rt| rt.task_time.get())
151}
152
153pub fn tunable(name: &str, default: f64) -> f64 {
156 with(|rt| {
157 let mut meta = rt.meta.borrow_mut();
158 if let Some(v) = meta.tunables.get(name) {
159 return *v;
160 }
161 meta.tunables.insert(name.to_owned(), default);
162 rt.meta_changed.set(true);
163 default
164 })
165}
166
167pub fn ease_named(name: &str, default: Option<CubicBezier>) -> CubicBezier {
170 with(|rt| {
171 let mut meta = rt.meta.borrow_mut();
172 if let Some(v) = meta.eases.get(name) {
173 return CubicBezier::from_array(*v);
174 }
175 let default = default.unwrap_or(presets::DEFAULT);
176 meta.eases.insert(name.to_owned(), default.to_array());
177 rt.meta_changed.set(true);
178 default
179 })
180}
181
182pub(crate) fn marker(name: &str) -> f64 {
185 with(|rt| {
186 let mut meta = rt.meta.borrow_mut();
187 if let Some(v) = meta.markers.get(name) {
188 return *v;
189 }
190 let t = rt.task_time.get();
191 meta.markers.insert(name.to_owned(), t);
192 rt.meta_changed.set(true);
193 t
194 })
195}
196
197pub fn meta_snapshot() -> Meta {
199 with(|rt| rt.meta.borrow().clone())
200}
201
202pub fn with_rng<R>(f: impl FnOnce(&mut ChaCha8Rng) -> R) -> R {
206 with(|rt| f(&mut rt.rng.borrow_mut()))
207}
208
209pub fn random() -> f64 {
211 use rand::RngExt;
212 with_rng(|r| r.random::<f64>())
213}
214
215pub fn random_range(min: f64, max: f64) -> f64 {
217 min + (max - min) * random()
218}
219
220#[derive(Clone)]
224pub struct SeededRng(ChaCha8Rng);
225
226impl SeededRng {
227 pub fn new(seed: u64) -> Self {
229 Self(ChaCha8Rng::seed_from_u64(seed))
230 }
231
232 pub fn next_f64(&mut self) -> f64 {
234 use rand::RngExt;
235 self.0.random::<f64>()
236 }
237
238 pub fn range(&mut self, span: std::ops::Range<f64>) -> f64 {
240 let w = span.end - span.start;
241 span.start + w * self.next_f64()
242 }
243}