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;
17
18pub(crate) struct Runtime {
19 pub fps: f64,
20 pub frame: Cell<u32>,
21 pub task_time: Cell<f64>,
25 pub spawned: RefCell<Vec<Task>>,
26 pub meta: RefCell<Meta>,
27 pub meta_changed: Cell<bool>,
28 pub rng: RefCell<ChaCha8Rng>,
29}
30
31impl Runtime {
32 pub fn new(fps: f64, meta: Meta, seed: u64) -> Rc<Self> {
33 assert!(fps > 0.0 && fps.is_finite(), "fps must be positive");
34 Rc::new(Self {
35 fps,
36 frame: Cell::new(0),
37 task_time: Cell::new(0.0),
38 spawned: RefCell::new(Vec::new()),
39 meta: RefCell::new(meta),
40 meta_changed: Cell::new(false),
41 rng: RefCell::new(ChaCha8Rng::seed_from_u64(seed)),
42 })
43 }
44
45 pub fn now(&self) -> f64 {
46 f64::from(self.frame.get()) / self.fps
47 }
48}
49
50thread_local! {
51 static CURRENT: RefCell<Option<Rc<Runtime>>> = const { RefCell::new(None) };
52}
53
54pub(crate) struct Installed(Option<Rc<Runtime>>);
56
57pub(crate) fn install(rt: &Rc<Runtime>) -> Installed {
58 Installed(CURRENT.with(|c| c.borrow_mut().replace(rt.clone())))
59}
60
61impl Drop for Installed {
62 fn drop(&mut self) {
63 let prev = self.0.take();
64 CURRENT.with(|c| *c.borrow_mut() = prev);
65 }
66}
67
68pub(crate) fn with<R>(f: impl FnOnce(&Runtime) -> R) -> R {
69 CURRENT.with(|c| {
70 let c = c.borrow();
71 let rt = c
72 .as_ref()
73 .expect("oxframe: this function can only be called while a scene is running");
74 f(rt)
75 })
76}
77
78pub fn frame() -> u32 {
80 with(|rt| rt.frame.get())
81}
82
83pub fn fps() -> f64 {
85 with(|rt| rt.fps)
86}
87
88pub fn dt() -> f64 {
90 with(|rt| 1.0 / rt.fps)
91}
92
93pub fn now() -> f64 {
95 with(|rt| rt.now())
96}
97
98pub fn time() -> f64 {
100 with(|rt| rt.task_time.get())
101}
102
103pub fn tunable(name: &str, default: f64) -> f64 {
106 with(|rt| {
107 let mut meta = rt.meta.borrow_mut();
108 if let Some(v) = meta.tunables.get(name) {
109 return *v;
110 }
111 meta.tunables.insert(name.to_owned(), default);
112 rt.meta_changed.set(true);
113 default
114 })
115}
116
117pub fn ease_named(name: &str, default: Option<CubicBezier>) -> CubicBezier {
120 with(|rt| {
121 let mut meta = rt.meta.borrow_mut();
122 if let Some(v) = meta.eases.get(name) {
123 return CubicBezier::from_array(*v);
124 }
125 let default = default.unwrap_or(presets::DEFAULT);
126 meta.eases.insert(name.to_owned(), default.to_array());
127 rt.meta_changed.set(true);
128 default
129 })
130}
131
132pub(crate) fn marker(name: &str) -> f64 {
135 with(|rt| {
136 let mut meta = rt.meta.borrow_mut();
137 if let Some(v) = meta.markers.get(name) {
138 return *v;
139 }
140 let t = rt.task_time.get();
141 meta.markers.insert(name.to_owned(), t);
142 rt.meta_changed.set(true);
143 t
144 })
145}
146
147pub fn meta_snapshot() -> Meta {
149 with(|rt| rt.meta.borrow().clone())
150}
151
152pub fn with_rng<R>(f: impl FnOnce(&mut ChaCha8Rng) -> R) -> R {
156 with(|rt| f(&mut rt.rng.borrow_mut()))
157}
158
159pub fn random() -> f64 {
161 use rand::RngExt;
162 with_rng(|r| r.random::<f64>())
163}
164
165pub fn random_range(min: f64, max: f64) -> f64 {
167 min + (max - min) * random()
168}