Skip to main content

geop_core_program/
kept.rs

1//! What a program built, kept between processes: reopening a program whose
2//! steps and inputs did not change builds nothing.
3//!
4//! Every node knows how it was made, in terms that mean the same in every
5//! process ([`Derivation`]): a digest of its arguments, and for each thing it
6//! read, what it read ([`KeptRead`]) and a digest of what that was — of the
7//! node it read, of an input's or a state's value, of a file's text. A
8//! node's own digest ([`Digest`]) is one of how it was made, so two nodes of
9//! one digest hold one value, in any process: makes are plain functions of
10//! their arguments and what they read (see [`crate::part::Make`]). A node
11//! made again into a value equal to the last keeps its digest, as it keeps
12//! its version.
13//!
14//! A target whose type says how its values are written ([`Keeper`]) has its
15//! nodes kept in the cache file of the program they were built by, beside it
16//! (`part.geop.geopc`, see [`crate::cache`]): the value, how it was made and
17//! its digest ([`Record`]). A node a run would make is taken from there
18//! instead when its arguments are the same and every read reads the same
19//! now — the same digest, in this process — and only then: what a record
20//! says is never trusted, it is checked. Parts of values shared between
21//! records — the faces two revisions of a model have alike — are written
22//! once ([`Pool`]).
23//!
24//! What is made of a part outside its program — a drawing of it, which an
25//! editor makes of what a reader reads — is kept the same way, as made of
26//! the program as a whole rather than by one of its steps ([`made_kept`]).
27//!
28//! A cache file is of one build of the kernel and one version of its format
29//! ([`FORMAT`]): any other is not read, and is replaced when it is written
30//! again.
31
32use std::{
33    collections::{BTreeMap, BTreeSet},
34    fmt::Write as _,
35    rc::Rc,
36};
37
38use geop_core_math::geop_error::{GeopError, GeopResult};
39use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned};
40use serde_json::value::RawValue;
41
42use crate::{
43    reader::{Reader, Reads},
44    runner::Runner,
45};
46
47/// What `make` makes of what `reader` reads — what an editor draws of the
48/// part — kept between processes as `key` of the program `runner` runs,
49/// whose part `reader` reads: read back as `keeper` wrote it where an
50/// earlier process made it of what reads the same now, else made, and
51/// noted to be kept. With what it read ([`Reader::recorded`]).
52pub fn made_kept<T: 'static>(
53    reader: &Reader,
54    runner: &Runner,
55    key: &str,
56    keeper: Keeper<T>,
57    make: impl FnOnce(&Reader) -> GeopResult<T>,
58) -> (GeopResult<Rc<T>>, Reads) {
59    let (part, before, own) = (reader.part(), reader.position(), reader.own());
60    let path = &runner.location.file;
61    let Some(cache) = runner.workspace.as_deref().and_then(|w| w.cache()) else {
62        let (made, reads) = reader.recorded(make);
63        return (made.map(Rc::new), reads);
64    };
65    let kind = std::any::type_name::<T>();
66    let args = digest_debug("made of the program", key);
67    let records = cache.builds(path, |builds| builds.records("", key));
68    for record in records.iter().flatten().rev() {
69        if record.kind != kind || record.derivation.args != args {
70            continue;
71        }
72        let reads = (record.derivation.reads.iter())
73            .map(|(kept, digest)| {
74                let (read, now) =
75                    part.resolve(kept, before, own, runner, reader.outside_reader())?;
76                (now == *digest).then_some(read)
77            })
78            .collect::<Option<Vec<_>>>();
79        let Some(reads) = reads else {
80            continue;
81        };
82        if let Some(Ok(value)) = cache.builds(path, |builds| builds.value(record, &keeper)) {
83            cache.builds(path, |builds| builds.used(record));
84            runner.count_restored();
85            return (Ok(Rc::new(value)), Reads(reads));
86        }
87    }
88    let (made, reads) = reader.recorded(make);
89    let made = made.map(Rc::new);
90    let derivation = (reads.0.iter())
91        .map(|read| part.read_digest(read, before, own, runner, reader.outside_reader()))
92        .collect::<Option<Vec<_>>>()
93        .map(|reads| Derivation { args, reads });
94    if let (Ok(value), Some(derivation)) = (&made, derivation) {
95        let digest = derivation.digest("", key, kind);
96        let value = value.clone();
97        cache.builds(path, |builds| {
98            if !builds.has("", key, &derivation, digest) {
99                builds.add(Made {
100                    step: String::new(),
101                    target: key.to_string(),
102                    kind,
103                    derivation: Rc::new(derivation),
104                    digest,
105                    write: Box::new(move |pool| (keeper.write)(&value, pool)),
106                });
107            }
108        });
109    }
110    (made, reads)
111}
112
113/// The version of the format a build is kept in: a cache file of another
114/// version is not read.
115pub const FORMAT: u32 = 1;
116
117/// How many records of one target of one step a cache file keeps — those
118/// used last: the builds of a program with as many sets of inputs (the sizes
119/// of a screw placed in an assembly), and of the edits before the last.
120const KEPT_RECORDS: usize = 16;
121
122/// How the values of a target type are written to be read back exactly, in
123/// another process (see the [module](self)): a target without one is made
124/// again in every process.
125pub struct Keeper<T> {
126    /// The value as JSON, its parts that other values may share put in the
127    /// pool.
128    pub write: fn(&T, &mut Pool) -> GeopResult<String>,
129    /// The value `write` wrote, its parts taken from the pool.
130    pub read: fn(&str, &Pool) -> GeopResult<T>,
131}
132
133impl<T> Clone for Keeper<T> {
134    fn clone(&self) -> Self {
135        *self
136    }
137}
138
139impl<T> Copy for Keeper<T> {}
140
141impl<T: Serialize + DeserializeOwned> Keeper<T> {
142    /// A value written by its own serde, which must write it exactly
143    /// ([`geop_core_math::exact`]): read back, it is the value written.
144    pub const SERDE: Self = Self {
145        write: write_serde::<T>,
146        read: read_serde::<T>,
147    };
148}
149
150fn write_serde<T: Serialize>(value: &T, _: &mut Pool) -> GeopResult<String> {
151    to_text(value)
152}
153
154fn read_serde<T: DeserializeOwned>(text: &str, _: &Pool) -> GeopResult<T> {
155    from_text(text)
156}
157
158/// `value` as JSON.
159fn to_text<T: Serialize>(value: &T) -> GeopResult<String> {
160    serde_json::to_string(value).map_err(|e| GeopError::new(format!("writing a kept value: {e}")))
161}
162
163/// The value the JSON `text` holds.
164fn from_text<T: DeserializeOwned>(text: &str) -> GeopResult<T> {
165    serde_json::from_str(text).map_err(|e| GeopError::new(format!("reading a kept value: {e}")))
166}
167
168/// Values the records of one cache file share, each written once, by the
169/// digest of its text: a model's faces, which every revision of the model
170/// that kept them holds alike.
171#[derive(Default)]
172pub struct Pool {
173    values: BTreeMap<String, Box<RawValue>>,
174    /// What was put since the last [`Pool::take_used`]: what the record
175    /// being written uses.
176    used: BTreeSet<String>,
177    /// What was read of it shared ([`Pool::shared`]), by key.
178    read: std::cell::RefCell<BTreeMap<String, Rc<dyn std::any::Any>>>,
179}
180
181impl Pool {
182    /// Puts `value`, and gives what it is taken by ([`Pool::get`]).
183    pub fn put<V: Serialize>(&mut self, value: &V) -> GeopResult<String> {
184        let text = to_text(value)?;
185        let key = format!("{:032x}", digest_text(&text));
186        if !self.values.contains_key(&key) {
187            let raw = RawValue::from_string(text)
188                .map_err(|e| GeopError::new(format!("writing a kept value: {e}")))?;
189            self.values.insert(key.clone(), raw);
190        }
191        self.used.insert(key.clone());
192        Ok(key)
193    }
194
195    /// The value put as `key`.
196    pub fn get<V: DeserializeOwned>(&self, key: &str) -> GeopResult<V> {
197        let raw = (self.values.get(key)).ok_or_else(|| {
198            GeopError::new(format!("a kept value names {key}, which is not kept"))
199        })?;
200        from_text(raw.get())
201    }
202
203    /// What `read` reads of the value put as `key`, read once for every
204    /// record that reads it so: what many records hold alike — the drawing
205    /// of a part placed a thousand times — is one value in memory. `read`
206    /// reads the same of the same key, as a `V`.
207    pub fn shared<V: 'static>(
208        &self,
209        key: &str,
210        read: impl FnOnce() -> GeopResult<V>,
211    ) -> GeopResult<Rc<V>> {
212        let at = format!("{}:{key}", std::any::type_name::<V>());
213        if let Some(kept) = self.read.borrow().get(&at)
214            && let Ok(kept) = kept.clone().downcast::<V>()
215        {
216            return Ok(kept);
217        }
218        let kept = Rc::new(read()?);
219        (self.read.borrow_mut()).insert(at, kept.clone());
220        Ok(kept)
221    }
222
223    /// How many values it holds.
224    pub fn len(&self) -> usize {
225        self.values.len()
226    }
227
228    /// Whether it holds none.
229    pub fn is_empty(&self) -> bool {
230        self.values.is_empty()
231    }
232
233    /// What was put since the last time it was asked.
234    fn take_used(&mut self) -> BTreeSet<String> {
235        std::mem::take(&mut self.used)
236    }
237}
238
239/// A digest of how a node was made, or of what it read: the same in every
240/// process for the same thing (see the [module](self)). Written as hex.
241#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
242pub struct Digest(pub u128);
243
244impl Serialize for Digest {
245    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
246        serializer.collect_str(&format_args!("{:032x}", self.0))
247    }
248}
249
250impl<'de> Deserialize<'de> for Digest {
251    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
252        let text = String::deserialize(deserializer)?;
253        u128::from_str_radix(&text, 16)
254            .map(Digest)
255            .map_err(serde::de::Error::custom)
256    }
257}
258
259/// A 128-bit FNV-1a hash, fed piece by piece: written out here rather than
260/// the standard library's hasher, which may change between Rust versions.
261pub(crate) struct Hasher(u128);
262
263impl Default for Hasher {
264    fn default() -> Self {
265        Self(0x6c62272e07bb014262b821756295c58d)
266    }
267}
268
269impl Hasher {
270    pub(crate) fn bytes(&mut self, bytes: &[u8]) {
271        const PRIME: u128 = 0x0000000001000000000000000000013B;
272        for &byte in bytes {
273            self.0 ^= u128::from(byte);
274            self.0 = self.0.wrapping_mul(PRIME);
275        }
276    }
277
278    /// `text`, told from what follows: its length first.
279    pub(crate) fn text(&mut self, text: &str) -> &mut Self {
280        self.bytes(&(text.len() as u64).to_le_bytes());
281        self.bytes(text.as_bytes());
282        self
283    }
284
285    pub(crate) fn digest(&mut self, digest: Digest) -> &mut Self {
286        self.bytes(&digest.0.to_le_bytes());
287        self
288    }
289
290    pub(crate) fn finish(&self) -> Digest {
291        Digest(self.0)
292    }
293}
294
295impl std::fmt::Write for Hasher {
296    fn write_str(&mut self, s: &str) -> std::fmt::Result {
297        self.bytes(s.as_bytes());
298        Ok(())
299    }
300}
301
302/// The digest of `text`.
303pub(crate) fn digest_text(text: &str) -> u128 {
304    let mut hasher = Hasher::default();
305    hasher.bytes(text.as_bytes());
306    hasher.0
307}
308
309/// The digest of `value` as `Debug` writes it: of a node's arguments, an
310/// input's or a state's value. `Debug` is derived for them, every field
311/// written, and a number written exactly — an interval's both bounds, to
312/// the bit — so values that write alike are equal.
313pub(crate) fn digest_debug<T: std::fmt::Debug + ?Sized>(what: &str, value: &T) -> Digest {
314    let mut hasher = Hasher::default();
315    hasher.text(what);
316    // Writing into a hasher does not fail.
317    let _ = write!(hasher, "{value:?}");
318    hasher.finish()
319}
320
321/// What a node read, as a [`crate::target::Read`] says it, in terms that
322/// mean the same in another process: no version, no type id.
323#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
324pub(crate) enum KeptRead {
325    /// Whatever node of the target the reader sees, or that there is none.
326    Target(String),
327    Input(String),
328    State(String),
329    File(String),
330    /// Which targets of the type of that name there are.
331    Listed(String),
332    Inside(String, Box<KeptRead>),
333}
334
335/// How a node was made, in terms that mean the same in every process: its
336/// arguments' digest, and each read with the digest of what it read.
337#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
338pub(crate) struct Derivation {
339    pub args: Digest,
340    pub reads: Vec<(KeptRead, Digest)>,
341}
342
343impl Derivation {
344    /// The digest of the node `step` made so of `target`, a `kind`.
345    pub(crate) fn digest(&self, step: &str, target: &str, kind: &str) -> Digest {
346        let mut hasher = Hasher::default();
347        hasher.text(step).text(target).text(kind).digest(self.args);
348        for (read, digest) in &self.reads {
349            let _ = write!(hasher, "{read:?}");
350            hasher.digest(*digest);
351        }
352        hasher.finish()
353    }
354}
355
356/// A node kept: the step and target it is of, the type of its value, how
357/// it was made and its digest — and its value, as its type's [`Keeper`]
358/// wrote it, with the parts of the pool it uses.
359#[derive(Serialize, Deserialize)]
360pub(crate) struct Record {
361    pub step: String,
362    pub target: String,
363    pub kind: String,
364    pub derivation: Derivation,
365    pub digest: Digest,
366    value: Box<RawValue>,
367    uses: BTreeSet<String>,
368}
369
370impl Record {
371    /// Whether it is the record of a node so made, of that digest.
372    fn is(&self, derivation: &Derivation, digest: Digest) -> bool {
373        self.digest == digest && self.derivation == *derivation
374    }
375}
376
377/// A node made in this process, to be kept when the cache is next written
378/// ([`crate::cache::Cache::flush`]).
379pub(crate) struct Made {
380    pub step: String,
381    pub target: String,
382    pub kind: &'static str,
383    pub derivation: Rc<Derivation>,
384    pub digest: Digest,
385    /// Writes the value made, as its type's keeper does.
386    pub write: WriteMade,
387}
388
389/// What writes the value of a node made, to keep it ([`Made::write`]).
390pub(crate) type WriteMade = Box<dyn Fn(&mut Pool) -> GeopResult<String>>;
391
392/// A cache file of builds, as it is written.
393#[derive(Serialize, Deserialize)]
394struct File {
395    format: u32,
396    /// The build of the kernel that wrote it ([`crate::cache::CacheStore::build`]).
397    build: String,
398    records: Vec<Record>,
399    pool: BTreeMap<String, Box<RawValue>>,
400}
401
402/// What a program built, as kept: the records of its cache file — by step
403/// and target, the one used last last — and what was made since, to be
404/// kept too.
405#[derive(Default)]
406pub(crate) struct Builds {
407    records: BTreeMap<(String, String), Vec<Rc<Record>>>,
408    pool: Pool,
409    made: Vec<Made>,
410    /// The steps of the program, as its runs since the file was read or
411    /// written had them — a program placed with steps after its own, a
412    /// mirrored part, has those too: a record of a step none had is not
413    /// kept.
414    order: Option<BTreeSet<String>>,
415    /// Whether `order` was noted since the file was read or written: what
416    /// the next run's steps are added to, not put in place of.
417    ordered: bool,
418    changed: bool,
419}
420
421impl Builds {
422    /// The builds the cache file `text` keeps, if it is of this format and
423    /// of the kernel's build `build`; none otherwise.
424    pub(crate) fn read(text: Option<&str>, build: &str) -> Self {
425        let file = text.and_then(|text| serde_json::from_str::<File>(text).ok());
426        let Some(file) = file.filter(|file| file.format == FORMAT && file.build == build) else {
427            return Self::default();
428        };
429        let mut records: BTreeMap<_, Vec<Rc<Record>>> = BTreeMap::new();
430        for record in file.records {
431            let key = (record.step.clone(), record.target.clone());
432            records.entry(key).or_default().push(Rc::new(record));
433        }
434        Self {
435            records,
436            pool: Pool {
437                values: file.pool,
438                ..Pool::default()
439            },
440            ..Self::default()
441        }
442    }
443
444    /// The records of the target `target` of the step `step`.
445    pub(crate) fn records(&self, step: &str, target: &str) -> Vec<Rc<Record>> {
446        let key = (step.to_string(), target.to_string());
447        self.records.get(&key).cloned().unwrap_or_default()
448    }
449
450    /// The value `record` keeps, read by `keeper`.
451    pub(crate) fn value<T>(&self, record: &Record, keeper: &Keeper<T>) -> GeopResult<T> {
452        (keeper.read)(record.value.get(), &self.pool).map_err(|e| {
453            e.with_context(format!(
454                "reading the kept target `{}` of step `{}`",
455                record.target, record.step
456            ))
457        })
458    }
459
460    /// Notes that `record` was used: it is kept before records used longer
461    /// ago.
462    pub(crate) fn used(&mut self, record: &Rc<Record>) {
463        let key = (record.step.clone(), record.target.clone());
464        if let Some(records) = self.records.get_mut(&key)
465            && let Some(at) = records.iter().position(|r| Rc::ptr_eq(r, record))
466        {
467            let record = records.remove(at);
468            records.push(record);
469        }
470    }
471
472    /// Whether a node so made, of that digest, is kept or to be kept.
473    pub(crate) fn has(
474        &self,
475        step: &str,
476        target: &str,
477        derivation: &Derivation,
478        digest: Digest,
479    ) -> bool {
480        let key = (step.to_string(), target.to_string());
481        let kept = (self.records.get(&key).into_iter().flatten()).any(|r| r.is(derivation, digest));
482        kept || (self.made.iter()).any(|m| {
483            m.step == step
484                && m.target == target
485                && m.digest == digest
486                && *m.derivation == *derivation
487        })
488    }
489
490    /// Notes `made`, to be kept: of the nodes of one target of one step
491    /// made since the file was written, the [`KEPT_RECORDS`] made last.
492    pub(crate) fn add(&mut self, made: Made) {
493        let same = |m: &Made| m.step == made.step && m.target == made.target;
494        if self.made.iter().filter(|m| same(m)).count() >= KEPT_RECORDS
495            && let Some(at) = self.made.iter().position(same)
496        {
497            self.made.remove(at);
498        }
499        self.made.push(made);
500        self.changed = true;
501    }
502
503    /// Notes the steps of the program, as a run had them: with those of
504    /// the runs since the file was read or written.
505    pub(crate) fn order(&mut self, order: &[String]) {
506        let mut order: BTreeSet<String> = order.iter().cloned().collect();
507        if std::mem::replace(&mut self.ordered, true)
508            && let Some(noted) = &self.order
509        {
510            order.extend(noted.iter().cloned());
511        }
512        if self.order.as_ref() != Some(&order) {
513            self.changed = true;
514            self.order = Some(order);
515        }
516    }
517
518    /// The cache file to write, if anything changed since it was read or
519    /// written: what was made since written into its records, of the steps
520    /// the program still has, the [`KEPT_RECORDS`] of each target used last,
521    /// and of the pool only what those use. A value that cannot be written
522    /// — an enclosure gone infinite — is only not kept.
523    pub(crate) fn write(&mut self, build: &str) -> Option<String> {
524        if !std::mem::take(&mut self.changed) {
525            return None;
526        }
527        self.ordered = false;
528        for made in std::mem::take(&mut self.made) {
529            let key = (made.step.clone(), made.target.clone());
530            let records = self.records.entry(key).or_default();
531            if let Some(at) = records
532                .iter()
533                .position(|r| r.is(&made.derivation, made.digest))
534            {
535                let record = records.remove(at);
536                records.push(record);
537                continue;
538            }
539            self.pool.take_used();
540            let Ok(text) = (made.write)(&mut self.pool) else {
541                continue;
542            };
543            let Ok(value) = RawValue::from_string(text) else {
544                continue;
545            };
546            records.push(Rc::new(Record {
547                step: made.step,
548                target: made.target,
549                kind: made.kind.to_string(),
550                derivation: (*made.derivation).clone(),
551                digest: made.digest,
552                value,
553                uses: self.pool.take_used(),
554            }));
555        }
556        if let Some(order) = &self.order {
557            // What was made of the program as a whole is of no step.
558            (self.records).retain(|(step, _), _| step.is_empty() || order.contains(step));
559        }
560        for records in self.records.values_mut() {
561            let excess = records.len().saturating_sub(KEPT_RECORDS);
562            records.drain(..excess);
563        }
564        let used: BTreeSet<&String> = (self.records.values().flatten())
565            .flat_map(|record| &record.uses)
566            .collect();
567        let used: BTreeSet<String> = used.into_iter().cloned().collect();
568        self.pool.values.retain(|key, _| used.contains(key));
569        let file = FileRef {
570            format: FORMAT,
571            build,
572            records: self.records.values().flatten().map(|r| &**r).collect(),
573            pool: &self.pool.values,
574        };
575        serde_json::to_string(&file).ok()
576    }
577}
578
579/// [`File`], borrowed, to be written.
580#[derive(Serialize)]
581struct FileRef<'a> {
582    format: u32,
583    build: &'a str,
584    records: Vec<&'a Record>,
585    pool: &'a BTreeMap<String, Box<RawValue>>,
586}