weavatrix_memory/projection/memory/
parts.rs1use super::{
2 MemoryProjection,
3 state::{NodeHistory, NodeRevision},
4};
5use crate::{MemoryError, MemoryFact, MemoryNode, Result, Timestamp};
6use std::thread;
7
8#[derive(Clone, Copy)]
9struct PreparedFact {
10 source: usize,
11 target: usize,
12 id_hash: u64,
13}
14
15impl MemoryProjection {
16 pub fn try_from_parts(
29 nodes: Vec<MemoryNode>,
30 facts: Vec<MemoryFact>,
31 known_at: Timestamp,
32 source_position: Option<u64>,
33 ) -> Result<Self> {
34 let mut projection = Self::with_capacity(nodes.len(), facts.len());
35 for (position, node) in nodes.into_iter().enumerate() {
36 node.validate()?;
37 let index = projection.nodes.len();
38 if projection
39 .node_lookup
40 .insert(node.id.clone(), index)
41 .is_some()
42 {
43 return Err(MemoryError::InvalidValue {
44 field: "nodes",
45 reason: "node identifiers must be unique",
46 });
47 }
48 projection.nodes.push(NodeHistory::new(NodeRevision {
49 node,
50 recorded_at: known_at,
51 position: u64::try_from(position).map_err(|_| MemoryError::CapacityOverflow)?,
52 }));
53 }
54 let prepared = prepare_facts(&projection, &facts, known_at)?;
55 for (index, (fact, prepared)) in facts.iter().zip(&prepared).enumerate() {
56 if projection
57 .fact_lookup
58 .insert_hashed(fact.id.clone(), index, prepared.id_hash)
59 .is_some()
60 {
61 return Err(MemoryError::ConflictingFact {
62 id: fact.id.to_string(),
63 });
64 }
65 }
66 for fact in &facts {
67 if let Some(prior) = &fact.supersedes {
68 projection.apply_supersession(prior, fact)?;
69 }
70 }
71 let endpoints = prepared
72 .into_iter()
73 .map(|fact| (fact.source, fact.target))
74 .collect::<Vec<_>>();
75 projection.facts = facts;
76 projection.set_incidents(&endpoints)?;
77 projection.last_global_position = source_position;
78 Ok(projection)
79 }
80}
81
82fn prepare_facts(
83 projection: &MemoryProjection,
84 facts: &[MemoryFact],
85 known_at: Timestamp,
86) -> Result<Vec<PreparedFact>> {
87 let parallelism = thread::available_parallelism().map_or(1, usize::from);
88 let workers = parallelism.min(facts.len().div_ceil(16_384)).max(1);
89 if workers == 1 {
90 return facts
91 .iter()
92 .map(|fact| prepare_fact(projection, fact, known_at))
93 .collect();
94 }
95 let chunk_size = facts.len().div_ceil(workers);
96 thread::scope(|scope| {
97 let handles = facts
98 .chunks(chunk_size)
99 .map(|chunk| {
100 scope.spawn(move || {
101 chunk
102 .iter()
103 .map(|fact| prepare_fact(projection, fact, known_at))
104 .collect::<Result<Vec<_>>>()
105 })
106 })
107 .collect::<Vec<_>>();
108 let mut prepared = Vec::with_capacity(facts.len());
109 for handle in handles {
110 let mut chunk = handle.join().map_err(|_| MemoryError::InvalidValue {
111 field: "facts",
112 reason: "parallel fact preparation panicked",
113 })??;
114 prepared.append(&mut chunk);
115 }
116 Ok(prepared)
117 })
118}
119
120fn prepare_fact(
121 projection: &MemoryProjection,
122 fact: &MemoryFact,
123 known_at: Timestamp,
124) -> Result<PreparedFact> {
125 if fact.recorded_at > known_at {
126 return Err(MemoryError::InvalidValue {
127 field: "facts",
128 reason: "fact recorded_at must not exceed known_at",
129 });
130 }
131 fact.validate()?;
132 Ok(PreparedFact {
133 source: projection.require_entity(&fact.source)?,
134 target: projection.require_entity(&fact.target)?,
135 id_hash: projection.fact_lookup.hash(&fact.id),
136 })
137}