Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_ore/src/seq/atom.rs

5.5 KiB, 81 runs

created by r1870400018:17908, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! The content layer: the bytes each splice brought into existence.
2//!
3//! An atom is immutable and is never divided. Only views of it divide, and a
4//! view is a [`crate::seq::slot::Slot`]. Byte `k` of the atom created by
5//! operation `a` is named `a+k` for as long as the history lasts, in every file,
6//! before and after any number of moves, which is the property the whole
7//! structure is built on.
8//!
9//! # Origin anchors
10//!
11//! A file's creation mints an atom too: one byte, [`ORIGIN`], born dead. It never
12//! renders, because [`crate::seq::claim::Dead`] buries it the moment it is made,
13//! and no frontend can name it, because nothing dead appears in a render. What it
14//! is for is that an empty file then has a byte in it, so a splice into an empty
15//! file anchors after a byte like every other splice, and every operation without
16//! exception is placed by the content it names rather than by a file it asserts.
17//!
18//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
19//! Anthropic Claude
20
21use crate::id::{
22 ContentRange,
23 OpId,
24};
25use crate::op::{
26 Op,
27 Placing,
28};
29
30use oxedyne_fe2o3_core::prelude::*;
31
32use std::collections::BTreeMap;
33use std::sync::Arc;
34
35
36// The byte a file's origin anchor holds. It is born dead and never renders, so
37// its value is arbitrary and nothing reads it; it is here so that the atom has a
38// length of one and the offset zero names something.
39pub const ORIGIN: u8 = 0;
40
41
42/// Every atom an operation set creates, keyed by the operation that created it.
43#[derive(Clone, Debug, Default, Eq, PartialEq)]
44pub struct Atoms {
45 map: BTreeMap<OpId, Arc<[u8]>>, // inserted bytes, by creating operation
46}
47
48impl Atoms {
49
50 pub fn new() -> Self {
51 Self { map: BTreeMap::new() }
52 }
53
54 /// Collects the atoms an operation set creates.
55 ///
56 /// A splice inserting nothing creates no atom, so no identifier is spent on
57 /// a pure deletion. A file's creation always creates one, of a single
58 /// [`ORIGIN`] byte, which is that file's origin anchor.
59 pub fn build(ops: &[(OpId, &Op)])
60 -> Outcome<Self>
61 {
62 let mut map: BTreeMap<OpId, Arc<[u8]>> = BTreeMap::new();
63 for (id, op) in ops {
64 let made = match op {
65 Op::FileCreate { .. } => Arc::from(vec![ORIGIN]),
66 // Shares the operation's buffer rather than copying it. Held three
67 // times -- here, in the log's record, and in the sequence's
68 // `Applied` -- the content of a real history cost 7.63 kB of
69 // resident memory per operation, 345,272 kB at 44,628 of them, and
70 // every verb that opens the repository paid it, `ore log` included,
71 // over no network at all. Sharing brought that to 5.79 kB and
72 // 263,136 kB. It did NOT reach the 5 kB aimed at: 258 MB is still
73 // resident with the content held once, and where the rest of it
74 // lives is a profiling question nobody has answered.
75 Op::Splice { insert, .. } if !insert.is_empty() => insert.clone(),
76 // A forgotten file still mints its origin anchor, and a forgotten
77 // insertion still occupies its length, so that every offset a
78 // later operation named inside it still names something. The
79 // bytes are gone; what stands in for them is never read, because
80 // [`crate::seq::claim::Dead`] buries the whole run.
81 Op::Forgotten { placing: Placing::File } => Arc::from(vec![ORIGIN]),
82 Op::Forgotten { placing: Placing::Splice { len, .. } } if *len > 0
83 => Arc::from(vec![0u8; *len as usize]),
84 _ => continue,
85 };
86 if map.insert(*id, made).is_some() {
87 return Err(err!(
88 "Two operations were given the identity {}; an operation \
89 identity names exactly one atom.", id;
90 Invalid, Input, Duplicate));
91 }
92 }
93 Ok(Self { map })
94 }
95
96 pub fn get(&self, id: &OpId)
97 -> Option<&[u8]>
98 {
99 self.map.get(id).map(|v| &v[..])
100 }
101
102 /// An operation that inserted nothing creates no atom, so its run is zero
103 /// rather than absent.
104 pub fn run_len(&self, id: &OpId) -> u64 {
105 self.map.get(id).map(|v| v.len() as u64).unwrap_or(0)
106 }
107
108 pub fn count(&self) -> usize {
109 self.map.len()
110 }
111
112 /// Bytes an atom can be read for, across every atom.
113 ///
114 /// NOT a measure of what this structure costs, and it used to be one.
115 ///
116 /// The buffers are shared with the records the atoms were built from, so what
117 /// this returns is bytes an atom can be READ for, not bytes this map is
118 /// keeping alive. Its marginal cost is the map itself; the content would be
119 /// resident whether these atoms existed or not. A caller summing it to learn
120 /// a memory figure is not over-reporting by some factor -- it is measuring
121 /// something else entirely. The meaning changed under the name on 2026-08-20,
122 /// when the buffer came to be shared; the arithmetic did not.
123 pub fn total(&self) -> u64 {
124 self.map.values().map(|v| v.len() as u64).sum()
125 }
126
127 /// In ascending order of creating operation.
128 pub fn iter(&self)
129 -> impl Iterator<Item = (&OpId, &[u8])>
130 {
131 self.map.iter().map(|(id, v)| (id, &v[..]))
132 }
133
134 /// Fails when the range names an atom the set does not hold, or reaches past
135 /// the end of one it does; either means the set is not causally complete.
136 pub fn slice(&self, range: &ContentRange)
137 -> Outcome<&[u8]>
138 {
139 let atom = match self.map.get(&range.op()) {
140 Some(a) => a,
141 None => return Err(err!(
142 "The content range {} names an atom that no operation in the set \
143 created.", range;
144 Invalid, Input, Missing)),
145 };
146 if range.to() > atom.len() as u64 {
147 return Err(err!(
148 "The content range {} reaches past the {} bytes its atom holds.",
149 range, atom.len();
150 Invalid, Input, Range));
151 }
152 Ok(&atom[range.from() as usize..range.to() as usize])
153 }
154}