Skip to main content

nifty_rt/
place.rs

1//! Places in state that compiled code names for writes that happen later, such as where a
2//! download reports or a file dialog answers: a state, then a path of fields and items.
3
4use std::slice;
5
6use nifty_download::{StatePlace, StateReader};
7use nifty_runtime::{Failure, Step};
8
9use crate::context::Context;
10use crate::value::{self, Slot, TypeDesc};
11
12/// Where a value lives inside a state, with list positions already checked not to be
13/// negative.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub(crate) struct Location {
16    state: usize,
17    path: Vec<Step>,
18}
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21#[repr(u8)]
22pub enum StepKind {
23    Field,
24    Index,
25}
26
27/// One step of a path: a struct's field, or a list's item, by position.
28#[derive(Clone, Copy, Debug)]
29#[repr(C)]
30pub struct PathStep {
31    pub kind: StepKind,
32    pub index: i64,
33}
34
35/// A place in state, as compiled code builds one for a call: state `state`, then `len`
36/// steps at `steps`.
37#[derive(Debug)]
38#[repr(C)]
39pub struct PlaceDesc {
40    pub state: u32,
41    pub steps: *const PathStep,
42    pub len: usize,
43}
44
45impl Location {
46    /// # Safety
47    /// `place.steps` points to `place.len` readable steps.
48    pub(crate) unsafe fn from_place(place: &PlaceDesc) -> Result<Self, Failure> {
49        let steps = if place.len == 0 {
50            &[]
51        } else {
52            // SAFETY: by the caller's contract.
53            unsafe { slice::from_raw_parts(place.steps, place.len) }
54        };
55        let path = steps
56            .iter()
57            .map(|step| {
58                let index = usize::try_from(step.index)
59                    .map_err(|_| Failure::NegativePosition(step.index))?;
60                Ok(match step.kind {
61                    StepKind::Field => Step::Field(index),
62                    StepKind::Index => Step::Index(index),
63                })
64            })
65            .collect::<Result<_, _>>()?;
66        Ok(Self {
67            state: place.state as usize,
68            path,
69        })
70    }
71}
72
73impl StatePlace for Location {
74    fn steps(&self) -> &[Step] {
75        &self.path
76    }
77
78    fn with_steps(&self, steps: Vec<Step>) -> Self {
79        Self {
80            state: self.state,
81            path: steps,
82        }
83    }
84
85    fn same_root(&self, other: &Self) -> bool {
86        self.state == other.state
87    }
88}
89
90/// A context's state, read for finding where a download's id went. Download bindings are
91/// typed by the checker, so the place above a list position holds a list, and an id's
92/// place a string.
93pub(crate) struct StateOf<'a>(pub &'a Context);
94
95impl StateReader for StateOf<'_> {
96    type Place = Location;
97
98    fn list_len(&self, place: &Location) -> Option<usize> {
99        Some(value::as_list(self.0.read_location(place)?).items.len())
100    }
101
102    fn holds_text(&self, place: &Location, text: &str) -> bool {
103        self.0
104            .read_location(place)
105            .is_some_and(|slot| value::as_str(slot) == text)
106    }
107}
108
109impl Context {
110    /// The value at `location`, borrowed; `None` once a list position on the way is gone.
111    fn read_location(&self, location: &Location) -> Option<Slot> {
112        let mut slot = self.state(location.state);
113        for step in &location.path {
114            slot = match *step {
115                Step::Field(index) => value::as_struct(slot).fields[index],
116                Step::Index(position) => *value::as_list(slot).items.get(position)?,
117            };
118        }
119        Some(slot)
120    }
121
122    /// Replaces the value at `location` with `value`, of type `ty`, which it takes
123    /// ownership of, and marks its state changed.
124    pub(crate) fn write_location(
125        &self,
126        location: &Location,
127        value: Slot,
128        ty: &TypeDesc,
129    ) -> Result<(), Failure> {
130        // SAFETY: state slots stay valid while the context lives, and nothing else holds
131        // one while the UI thread runs this.
132        let mut slot = unsafe { &mut *self.state_mut(location.state) };
133        for step in &location.path {
134            slot = match *step {
135                Step::Field(index) => &mut value::struct_mut(slot).fields[index],
136                Step::Index(position) => {
137                    let items = &mut value::list_mut(slot).items;
138                    let len = items.len();
139                    let Some(item) = items.get_mut(position) else {
140                        value::release(value, ty);
141                        return Err(Failure::NoItem { position, len });
142                    };
143                    item
144                }
145            };
146        }
147        value::release(std::mem::replace(slot, value), ty);
148        Ok(())
149    }
150}