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}
39
40impl Runtime {
41 pub fn new(fps: f64, meta: Meta, seed: u64) -> Rc<Self> {
42 assert!(fps > 0.0 && fps.is_finite(), "fps must be positive");
43 Rc::new(Self {
44 fps,
45 frame: Cell::new(0),
46 task_time: Cell::new(0.0),
47 spawned: RefCell::new(Vec::new()),
48 meta: RefCell::new(meta),
49 meta_changed: Cell::new(false),
50 rng: RefCell::new(ChaCha8Rng::seed_from_u64(seed)),
51 has_previous: Cell::new(false),
52 transition: RefCell::new(None),
53 sounds: RefCell::new(Vec::new()),
54 voices: RefCell::new(Vec::new()),
55 })
56 }
57
58 pub fn now(&self) -> f64 {
59 f64::from(self.frame.get()) / self.fps
60 }
61}
62
63thread_local! {
64 static CURRENT: RefCell<Option<Rc<Runtime>>> = const { RefCell::new(None) };
65}
66
67pub(crate) struct Installed(Option<Rc<Runtime>>);
69
70pub(crate) fn install(rt: &Rc<Runtime>) -> Installed {
71 Installed(CURRENT.with(|c| c.borrow_mut().replace(rt.clone())))
72}
73
74impl Drop for Installed {
75 fn drop(&mut self) {
76 let prev = self.0.take();
77 CURRENT.with(|c| *c.borrow_mut() = prev);
78 }
79}
80
81pub(crate) fn with<R>(f: impl FnOnce(&Runtime) -> R) -> R {
82 CURRENT.with(|c| {
83 let c = c.borrow();
84 let rt = c
85 .as_ref()
86 .expect("oxframe: this function can only be called while a scene is running");
87 f(rt)
88 })
89}
90
91pub fn frame() -> u32 {
93 with(|rt| rt.frame.get())
94}
95
96pub fn fps() -> f64 {
98 with(|rt| rt.fps)
99}
100
101pub fn dt() -> f64 {
103 with(|rt| 1.0 / rt.fps)
104}
105
106pub fn now() -> f64 {
108 with(|rt| rt.now())
109}
110
111pub fn has_previous() -> bool {
113 with(|rt| rt.has_previous.get())
114}
115
116pub fn begin_transition(mut spec: Recorded) -> f64 {
119 with(|rt| {
120 if rt.transition.borrow().is_some() {
121 panic!("oxframe: a scene can declare only one transition; call it once, at the start");
122 }
123 let stored = rt.meta.borrow().transition.as_ref().map(|t| t.seconds);
124 if let Some(seconds) = stored {
125 spec.seconds = if seconds.is_finite() {
126 seconds.max(0.0)
127 } else {
128 0.0
129 };
130 } else if spec.seconds.is_finite() && spec.seconds > 0.0 {
131 let seconds = spec.seconds;
132 rt.meta.borrow_mut().transition = Some(oxframe_protocol::TransitionMeta { seconds });
133 rt.meta_changed.set(true);
134 } else {
135 spec.seconds = 0.0;
136 }
137 let seconds = spec.seconds;
138 if seconds > 0.0 {
139 *rt.transition.borrow_mut() = Some(spec);
140 }
141 seconds
142 })
143}
144
145pub fn time() -> f64 {
147 with(|rt| rt.task_time.get())
148}
149
150pub fn tunable(name: &str, default: f64) -> f64 {
153 with(|rt| {
154 let mut meta = rt.meta.borrow_mut();
155 if let Some(v) = meta.tunables.get(name) {
156 return *v;
157 }
158 meta.tunables.insert(name.to_owned(), default);
159 rt.meta_changed.set(true);
160 default
161 })
162}
163
164pub fn ease_named(name: &str, default: Option<CubicBezier>) -> CubicBezier {
167 with(|rt| {
168 let mut meta = rt.meta.borrow_mut();
169 if let Some(v) = meta.eases.get(name) {
170 return CubicBezier::from_array(*v);
171 }
172 let default = default.unwrap_or(presets::DEFAULT);
173 meta.eases.insert(name.to_owned(), default.to_array());
174 rt.meta_changed.set(true);
175 default
176 })
177}
178
179pub(crate) fn marker(name: &str) -> f64 {
182 with(|rt| {
183 let mut meta = rt.meta.borrow_mut();
184 if let Some(v) = meta.markers.get(name) {
185 return *v;
186 }
187 let t = rt.task_time.get();
188 meta.markers.insert(name.to_owned(), t);
189 rt.meta_changed.set(true);
190 t
191 })
192}
193
194pub fn meta_snapshot() -> Meta {
196 with(|rt| rt.meta.borrow().clone())
197}
198
199pub fn with_rng<R>(f: impl FnOnce(&mut ChaCha8Rng) -> R) -> R {
203 with(|rt| f(&mut rt.rng.borrow_mut()))
204}
205
206pub fn random() -> f64 {
208 use rand::RngExt;
209 with_rng(|r| r.random::<f64>())
210}
211
212pub fn random_range(min: f64, max: f64) -> f64 {
214 min + (max - min) * random()
215}
216
217#[derive(Clone)]
221pub struct SeededRng(ChaCha8Rng);
222
223impl SeededRng {
224 pub fn new(seed: u64) -> Self {
226 Self(ChaCha8Rng::seed_from_u64(seed))
227 }
228
229 pub fn next_f64(&mut self) -> f64 {
231 use rand::RngExt;
232 self.0.random::<f64>()
233 }
234
235 pub fn range(&mut self, span: std::ops::Range<f64>) -> f64 {
237 let w = span.end - span.start;
238 span.start + w * self.next_f64()
239 }
240}