1use std::collections::{HashMap, HashSet};
9use super::working_memory::{WorkingMemory, FactHandle};
10use super::network::{ReteUlNode, TypedReteUlRule};
11use super::facts::TypedFacts;
12use super::agenda::{AdvancedAgenda, Activation};
13use super::template::TemplateRegistry;
14use super::globals::GlobalsRegistry;
15
16#[derive(Debug)]
18pub struct RuleDependencyGraph {
19 fact_type_to_rules: HashMap<String, HashSet<usize>>,
21 rule_to_fact_types: HashMap<usize, HashSet<String>>,
23}
24
25impl RuleDependencyGraph {
26 pub fn new() -> Self {
28 Self {
29 fact_type_to_rules: HashMap::new(),
30 rule_to_fact_types: HashMap::new(),
31 }
32 }
33
34 pub fn add_dependency(&mut self, rule_idx: usize, fact_type: String) {
36 self.fact_type_to_rules
37 .entry(fact_type.clone())
38 .or_insert_with(HashSet::new)
39 .insert(rule_idx);
40
41 self.rule_to_fact_types
42 .entry(rule_idx)
43 .or_insert_with(HashSet::new)
44 .insert(fact_type);
45 }
46
47 pub fn get_affected_rules(&self, fact_type: &str) -> HashSet<usize> {
49 self.fact_type_to_rules
50 .get(fact_type)
51 .cloned()
52 .unwrap_or_else(HashSet::new)
53 }
54
55 pub fn get_rule_dependencies(&self, rule_idx: usize) -> HashSet<String> {
57 self.rule_to_fact_types
58 .get(&rule_idx)
59 .cloned()
60 .unwrap_or_else(HashSet::new)
61 }
62}
63
64impl Default for RuleDependencyGraph {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70pub struct IncrementalEngine {
73 working_memory: WorkingMemory,
75 rules: Vec<TypedReteUlRule>,
77 dependencies: RuleDependencyGraph,
79 agenda: AdvancedAgenda,
81 rule_matched_facts: HashMap<usize, HashSet<FactHandle>>,
83 templates: TemplateRegistry,
85 globals: GlobalsRegistry,
87}
88
89impl IncrementalEngine {
90 pub fn new() -> Self {
92 Self {
93 working_memory: WorkingMemory::new(),
94 rules: Vec::new(),
95 dependencies: RuleDependencyGraph::new(),
96 agenda: AdvancedAgenda::new(),
97 rule_matched_facts: HashMap::new(),
98 templates: TemplateRegistry::new(),
99 globals: GlobalsRegistry::new(),
100 }
101 }
102
103 pub fn add_rule(&mut self, rule: TypedReteUlRule, depends_on: Vec<String>) {
105 let rule_idx = self.rules.len();
106
107 for fact_type in depends_on {
109 self.dependencies.add_dependency(rule_idx, fact_type);
110 }
111
112 self.rules.push(rule);
113 }
114
115 pub fn insert(&mut self, fact_type: String, data: TypedFacts) -> FactHandle {
117 let handle = self.working_memory.insert(fact_type.clone(), data);
118
119 self.propagate_changes_for_type(&fact_type);
121
122 handle
123 }
124
125 pub fn update(&mut self, handle: FactHandle, data: TypedFacts) -> Result<(), String> {
127 let fact_type = self.working_memory
129 .get(&handle)
130 .map(|f| f.fact_type.clone())
131 .ok_or_else(|| format!("FactHandle {} not found", handle))?;
132
133 self.working_memory.update(handle, data)?;
134
135 self.propagate_changes_for_type(&fact_type);
137
138 Ok(())
139 }
140
141 pub fn retract(&mut self, handle: FactHandle) -> Result<(), String> {
143 let fact_type = self.working_memory
145 .get(&handle)
146 .map(|f| f.fact_type.clone())
147 .ok_or_else(|| format!("FactHandle {} not found", handle))?;
148
149 self.working_memory.retract(handle)?;
150
151 self.propagate_changes_for_type(&fact_type);
153
154 Ok(())
155 }
156
157 fn propagate_changes_for_type(&mut self, fact_type: &str) {
159 let affected_rules = self.dependencies.get_affected_rules(fact_type);
161
162 if affected_rules.is_empty() {
163 return; }
165
166 let facts = self.working_memory.to_typed_facts();
168
169 for &rule_idx in &affected_rules {
171 let rule = &self.rules[rule_idx];
172
173 let matches = super::network::evaluate_rete_ul_node_typed(&rule.node, &facts);
175
176 if matches {
177 let activation = Activation::new(rule.name.clone(), rule.priority)
179 .with_no_loop(rule.no_loop);
180
181 self.agenda.add_activation(activation);
182 }
183 }
184 }
185
186 pub fn fire_all(&mut self) -> Vec<String> {
188 let mut fired_rules = Vec::new();
189
190 while let Some(activation) = self.agenda.get_next_activation() {
191 if let Some((idx, rule)) = self.rules
193 .iter_mut()
194 .enumerate()
195 .find(|(_, r)| r.name == activation.rule_name)
196 {
197 let mut facts = self.working_memory.to_typed_facts();
199 (rule.action)(&mut facts);
200
201 fired_rules.push(activation.rule_name.clone());
203 self.agenda.mark_rule_fired(&activation);
204
205 }
208 }
209
210 fired_rules
211 }
212
213 pub fn working_memory(&self) -> &WorkingMemory {
215 &self.working_memory
216 }
217
218 pub fn working_memory_mut(&mut self) -> &mut WorkingMemory {
220 &mut self.working_memory
221 }
222
223 pub fn agenda(&self) -> &AdvancedAgenda {
225 &self.agenda
226 }
227
228 pub fn agenda_mut(&mut self) -> &mut AdvancedAgenda {
230 &mut self.agenda
231 }
232
233 pub fn stats(&self) -> IncrementalEngineStats {
235 IncrementalEngineStats {
236 rules: self.rules.len(),
237 working_memory: self.working_memory.stats(),
238 agenda: self.agenda.stats(),
239 dependencies: self.dependencies.fact_type_to_rules.len(),
240 }
241 }
242
243 pub fn reset(&mut self) {
245 self.agenda.reset_fired_flags();
246 }
247
248 pub fn templates(&self) -> &TemplateRegistry {
250 &self.templates
251 }
252
253 pub fn templates_mut(&mut self) -> &mut TemplateRegistry {
255 &mut self.templates
256 }
257
258 pub fn globals(&self) -> &GlobalsRegistry {
260 &self.globals
261 }
262
263 pub fn globals_mut(&mut self) -> &mut GlobalsRegistry {
265 &mut self.globals
266 }
267
268 pub fn insert_with_template(
270 &mut self,
271 template_name: &str,
272 data: TypedFacts,
273 ) -> crate::errors::Result<FactHandle> {
274 self.templates.validate(template_name, &data)?;
276
277 Ok(self.insert(template_name.to_string(), data))
279 }
280}
281
282impl Default for IncrementalEngine {
283 fn default() -> Self {
284 Self::new()
285 }
286}
287
288#[derive(Debug)]
290pub struct IncrementalEngineStats {
291 pub rules: usize,
292 pub working_memory: super::working_memory::WorkingMemoryStats,
293 pub agenda: super::agenda::AgendaStats,
294 pub dependencies: usize,
295}
296
297impl std::fmt::Display for IncrementalEngineStats {
298 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299 write!(
300 f,
301 "Engine Stats: {} rules, {} fact types tracked\nWM: {}\nAgenda: {}",
302 self.rules,
303 self.dependencies,
304 self.working_memory,
305 self.agenda
306 )
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use crate::rete::network::ReteUlNode;
314 use crate::rete::alpha::AlphaNode;
315
316 #[test]
317 fn test_dependency_graph() {
318 let mut graph = RuleDependencyGraph::new();
319
320 graph.add_dependency(0, "Person".to_string());
321 graph.add_dependency(1, "Person".to_string());
322 graph.add_dependency(1, "Order".to_string());
323
324 let affected = graph.get_affected_rules("Person");
325 assert_eq!(affected.len(), 2);
326 assert!(affected.contains(&0));
327 assert!(affected.contains(&1));
328
329 let deps = graph.get_rule_dependencies(1);
330 assert_eq!(deps.len(), 2);
331 assert!(deps.contains("Person"));
332 assert!(deps.contains("Order"));
333 }
334
335 #[test]
336 fn test_incremental_propagation() {
337 let mut engine = IncrementalEngine::new();
338
339 let node = ReteUlNode::UlAlpha(AlphaNode {
341 field: "Person.age".to_string(),
342 operator: ">".to_string(),
343 value: "18".to_string(),
344 });
345
346 let rule = TypedReteUlRule {
347 name: "IsAdult".to_string(),
348 node,
349 priority: 0,
350 no_loop: true,
351 action: Box::new(|_| {}),
352 };
353
354 engine.add_rule(rule, vec!["Person".to_string()]);
355
356 let mut person = TypedFacts::new();
358 person.set("age", 25i64);
359 let handle = engine.insert("Person".to_string(), person);
360
361 let stats = engine.stats();
363 assert!(stats.agenda.total_activations > 0);
364
365 let mut updated = TypedFacts::new();
367 updated.set("age", 15i64); engine.update(handle, updated).unwrap();
369
370 }
372}