Skip to main content

nifty_rt/
logic.rs

1//! Compiled code as the runtime engine's [`Logic`]: each template's dynamic parts are
2//! compiled functions, which this calls with the app's context and the template's frame.
3
4use std::fmt;
5use std::rc::{Rc, Weak};
6use std::sync::Arc;
7
8use nifty_download::Downloads;
9use nifty_runtime::{
10    Backend, Dialogs, Engine, Logic, Shown, StateSet, Templates, Ui, Waker, WindowRequest,
11};
12
13use crate::context::{AppError, Context};
14use crate::dialog::Answer;
15use crate::download::Targets;
16use crate::program::{ActionFn, ExpandFn, FrameDesc, ReadFn, WriteFn};
17use crate::show::Sink;
18use crate::value::{
19    BOOL, INT, LIST_OF_STRINGS, STRING, Slot, TypeDesc, Value, as_list, as_str, int, int_slot,
20    release, retain, string_slot, strings_slot,
21};
22
23/// The locals visible to a piece of UI. A frame owns its values, released when it drops.
24pub struct Frame {
25    slots: Box<[Slot]>,
26    types: &'static [&'static TypeDesc],
27}
28
29impl Frame {
30    /// A frame for `desc`'s locals, all empty.
31    pub(crate) fn empty(desc: &'static FrameDesc) -> Self {
32        let types = desc.locals.items();
33        Self {
34            slots: vec![0; types.len()].into(),
35            types,
36        }
37    }
38
39    pub(crate) fn as_ptr(&self) -> *const Slot {
40        self.slots.as_ptr()
41    }
42
43    pub(crate) fn as_mut_ptr(&mut self) -> *mut Slot {
44        self.slots.as_mut_ptr()
45    }
46}
47
48impl Clone for Frame {
49    fn clone(&self) -> Self {
50        for (slot, ty) in self.slots.iter().zip(self.types) {
51            retain(*slot, ty);
52        }
53        Self {
54            slots: self.slots.clone(),
55            types: self.types,
56        }
57    }
58}
59
60impl Drop for Frame {
61    fn drop(&mut self) {
62        for (slot, ty) in self.slots.iter().zip(self.types) {
63            release(*slot, ty);
64        }
65    }
66}
67
68impl fmt::Debug for Frame {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        f.debug_struct("Frame")
71            .field("locals", &self.slots.len())
72            .finish_non_exhaustive()
73    }
74}
75
76/// A text field's or checkbox's place: how to read it and how to write the user's edits.
77#[derive(Clone, Copy, Debug)]
78pub struct Binding {
79    pub(crate) read: ReadFn,
80    pub(crate) write: WriteFn,
81}
82
83/// An `if`, `for` or view call: compiled code picks which of `lists` it shows.
84#[derive(Debug)]
85pub struct Region {
86    pub(crate) expand: ExpandFn,
87    pub(crate) lists: Vec<Templates<Compiled>>,
88}
89
90#[derive(Debug)]
91pub(crate) struct ViewTemplate {
92    pub body: Templates<Compiled>,
93    pub frame: &'static FrameDesc,
94}
95
96pub struct Compiled {
97    context: Context,
98    /// Indexed by view id.
99    views: Vec<ViewTemplate>,
100    downloads: Downloads<Targets>,
101    dialogs: Dialogs<Answer>,
102}
103
104impl Compiled {
105    /// Runs compiled code against `context` for `engine`, which shows file dialogs and
106    /// receives their answers and download reports through `ui`.
107    pub(crate) fn new<B: Backend>(
108        context: Context,
109        views: Vec<ViewTemplate>,
110        ui: &Ui<B>,
111        engine: Weak<Engine<Self, B>>,
112    ) -> Self {
113        let waker: Arc<dyn Waker> = Arc::new(ui.waker(Box::new(move || {
114            if let Some(engine) = engine.upgrade() {
115                engine.logic().dialogs.show_asked();
116                engine.handle_event(Self::receive);
117            }
118        })));
119        Self {
120            context,
121            views,
122            downloads: Downloads::new(Arc::clone(&waker)),
123            dialogs: Dialogs::new(ui.dialogs(), waker),
124        }
125    }
126
127    #[cfg(test)]
128    pub(crate) fn context(&self) -> &Context {
129        &self.context
130    }
131
132    /// Waits until every running download has sent its last report, without applying them.
133    #[cfg(any(test, feature = "testing"))]
134    pub fn wait_for_downloads(&self) {
135        self.downloads.wait();
136    }
137
138    /// Waits until a download sends a report, and wakes the UI thread to apply it.
139    #[cfg(any(test, feature = "testing"))]
140    pub fn wait_for_download_report(&self) {
141        self.downloads.wait_for_report();
142    }
143
144    /// Runs code for an event, then starts the downloads and asks for the dialogs it asked
145    /// for, even if it failed after asking.
146    fn event(&self, run: impl FnOnce() -> Result<(), AppError>) -> Result<(), AppError> {
147        let result = run();
148        for request in self.context.take_downloads() {
149            self.downloads.start(request);
150        }
151        for (command, id) in self.context.take_download_commands() {
152            self.downloads.command(&id, command);
153        }
154        self.downloads
155            .cancel_unheld(|targets| self.context.follow_download(targets));
156        for (dialog, answer) in self.context.take_dialogs() {
157            self.dialogs.ask(dialog, answer);
158        }
159        result
160    }
161
162    fn receive(&self) -> Result<(), AppError> {
163        self.downloads.receive(
164            |targets| self.context.follow_download(targets),
165            |targets, report| self.context.report_download(targets, report),
166        )?;
167        self.dialogs
168            .receive(|answer, picked| self.context.answer_dialog(answer, picked))
169    }
170
171    /// Runs a compiled call, turning its `false` into the failure it recorded.
172    fn call(&self, run: impl FnOnce(*const Context) -> bool) -> Result<(), AppError> {
173        if run(&raw const self.context) {
174            Ok(())
175        } else {
176            Err(self.context.take_error())
177        }
178    }
179
180    /// The value a compiled read gives, owned by the caller.
181    fn read(&self, read: ReadFn, frame: &Frame, ty: &'static TypeDesc) -> Result<Value, AppError> {
182        let mut out = 0;
183        // SAFETY: compiled reads take the context and their template's frame, and write an
184        // owned value of the template's type to `out`.
185        self.call(|context| unsafe { read(context, frame.as_ptr(), &raw mut out) })?;
186        Ok(Value::owned(out, ty))
187    }
188
189    fn write(&self, binding: &Binding, frame: &Frame, value: Slot) -> Result<(), AppError> {
190        // SAFETY: compiled writes take the context, their template's frame and an owned
191        // value of the bound place's type.
192        self.event(|| {
193            self.call(|context| unsafe { (binding.write)(context, frame.as_ptr(), value) })
194        })
195    }
196}
197
198impl Logic for Compiled {
199    type Frame = Frame;
200    type Value = ReadFn;
201    type Binding = Binding;
202    type Action = ActionFn;
203    type Region = Region;
204    type Error = AppError;
205
206    fn text(&self, text: &ReadFn, frame: &Frame) -> Result<String, AppError> {
207        let value = self.read(*text, frame, &STRING)?;
208        Ok(as_str(value.slot()).to_owned())
209    }
210
211    fn number(&self, number: &ReadFn, frame: &Frame) -> Result<i64, AppError> {
212        Ok(int(self.read(*number, frame, &INT)?.slot()))
213    }
214
215    fn flag(&self, flag: &ReadFn, frame: &Frame) -> Result<bool, AppError> {
216        Ok(self.read(*flag, frame, &BOOL)?.slot() != 0)
217    }
218
219    fn texts(&self, texts: &ReadFn, frame: &Frame) -> Result<Vec<String>, AppError> {
220        let list = self.read(*texts, frame, &LIST_OF_STRINGS)?;
221        Ok(as_list(list.slot())
222            .items
223            .iter()
224            .map(|item| as_str(*item).to_owned())
225            .collect())
226    }
227
228    fn read_number(&self, binding: &Binding, frame: &Frame) -> Result<i64, AppError> {
229        self.number(&binding.read, frame)
230    }
231
232    fn read_text(&self, binding: &Binding, frame: &Frame) -> Result<String, AppError> {
233        self.text(&binding.read, frame)
234    }
235
236    fn read_checked(&self, binding: &Binding, frame: &Frame) -> Result<bool, AppError> {
237        self.flag(&binding.read, frame)
238    }
239
240    fn write_text(&self, binding: &Binding, frame: &Frame, text: &str) -> Result<(), AppError> {
241        self.write(binding, frame, string_slot(text))
242    }
243
244    fn write_checked(
245        &self,
246        binding: &Binding,
247        frame: &Frame,
248        checked: bool,
249    ) -> Result<(), AppError> {
250        self.write(binding, frame, Slot::from(checked))
251    }
252
253    fn write_number(&self, binding: &Binding, frame: &Frame, number: i64) -> Result<(), AppError> {
254        self.write(binding, frame, int_slot(number))
255    }
256
257    fn write_texts(
258        &self,
259        binding: &Binding,
260        frame: &Frame,
261        texts: Vec<String>,
262    ) -> Result<(), AppError> {
263        self.write(binding, frame, strings_slot(texts))
264    }
265
266    fn run(&self, action: &ActionFn, frame: &Frame) -> Result<(), AppError> {
267        let mut scratch = frame.clone();
268        // SAFETY: compiled actions take the context and a frame of their locals to change.
269        self.event(|| self.call(|context| unsafe { action(context, scratch.as_mut_ptr()) }))
270    }
271
272    fn expand(&self, region: &Region, frame: &Rc<Frame>) -> Result<Vec<Shown<Self>>, AppError> {
273        let mut sink = Sink::new(region, &self.views, frame);
274        // SAFETY: compiled expansions take the context, their region's frame and a sink
275        // they only pass to `nf_show_*`.
276        self.call(|context| unsafe { (region.expand)(context, frame.as_ptr(), &raw mut sink) })?;
277        Ok(sink.finish())
278    }
279
280    fn take_changed(&self) -> StateSet {
281        self.context.take_changed()
282    }
283
284    fn take_window_requests(&self) -> Vec<WindowRequest> {
285        self.context.take_window_requests()
286    }
287}
288
289impl fmt::Debug for Compiled {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        f.debug_struct("Compiled")
292            .field("context", &self.context)
293            .finish_non_exhaustive()
294    }
295}