1use std::cmp::Ordering;
12use std::collections::{BinaryHeap, HashMap, HashSet};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ConflictResolutionStrategy {
20 Salience,
22
23 LEX,
26
27 MEA,
30
31 Depth,
34
35 Breadth,
38
39 Simplicity,
42
43 Complexity,
46
47 Random,
50}
51
52#[derive(Debug, Clone)]
54pub struct Activation {
55 pub rule_name: String,
57 pub salience: i32,
59 pub activation_group: Option<String>,
61 pub agenda_group: String,
63 pub ruleflow_group: Option<String>,
65 pub no_loop: bool,
67 pub lock_on_active: bool,
69 pub auto_focus: bool,
71 pub created_at: std::time::Instant,
73 pub condition_count: usize,
75 pub matched_fact_handle: Option<super::FactHandle>,
77 id: usize,
79}
80
81impl Activation {
82 pub fn new(rule_name: String, salience: i32) -> Self {
84 Self {
85 rule_name,
86 salience,
87 activation_group: None,
88 agenda_group: "MAIN".to_string(),
89 ruleflow_group: None,
90 no_loop: true,
91 lock_on_active: false,
92 auto_focus: false,
93 created_at: std::time::Instant::now(),
94 condition_count: 1, matched_fact_handle: None,
96 id: 0,
97 }
98 }
99
100 pub fn with_matched_fact(mut self, handle: super::FactHandle) -> Self {
102 self.matched_fact_handle = Some(handle);
103 self
104 }
105
106 pub fn with_condition_count(mut self, count: usize) -> Self {
108 self.condition_count = count;
109 self
110 }
111
112 pub fn with_activation_group(mut self, group: String) -> Self {
114 self.activation_group = Some(group);
115 self
116 }
117
118 pub fn with_agenda_group(mut self, group: String) -> Self {
120 self.agenda_group = group;
121 self
122 }
123
124 pub fn with_ruleflow_group(mut self, group: String) -> Self {
126 self.ruleflow_group = Some(group);
127 self
128 }
129
130 pub fn with_no_loop(mut self, no_loop: bool) -> Self {
132 self.no_loop = no_loop;
133 self
134 }
135
136 pub fn with_lock_on_active(mut self, lock: bool) -> Self {
138 self.lock_on_active = lock;
139 self
140 }
141
142 pub fn with_auto_focus(mut self, auto_focus: bool) -> Self {
144 self.auto_focus = auto_focus;
145 self
146 }
147}
148
149impl PartialEq for Activation {
151 fn eq(&self, other: &Self) -> bool {
152 self.id == other.id
153 }
154}
155
156impl Eq for Activation {}
157
158impl PartialOrd for Activation {
159 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
160 Some(self.cmp(other))
161 }
162}
163
164impl Ord for Activation {
165 fn cmp(&self, other: &Self) -> Ordering {
166 match self.salience.cmp(&other.salience) {
168 Ordering::Equal => {
169 other.created_at.cmp(&self.created_at)
171 }
172 other_order => other_order,
173 }
174 }
175}
176
177pub struct AdvancedAgenda {
179 activations: HashMap<String, BinaryHeap<Activation>>,
181 focus: String,
183 focus_stack: Vec<String>,
185 fired_rules: HashSet<String>,
187 fired_activation_groups: HashSet<String>,
189 locked_groups: HashSet<String>,
191 active_ruleflow_groups: HashSet<String>,
193 next_id: usize,
195 strategy: ConflictResolutionStrategy,
197}
198
199impl AdvancedAgenda {
200 pub fn new() -> Self {
202 let mut agenda = Self {
203 activations: HashMap::new(),
204 focus: "MAIN".to_string(),
205 focus_stack: Vec::new(),
206 fired_rules: HashSet::new(),
207 fired_activation_groups: HashSet::new(),
208 locked_groups: HashSet::new(),
209 active_ruleflow_groups: HashSet::new(),
210 next_id: 0,
211 strategy: ConflictResolutionStrategy::Salience, };
213 agenda
214 .activations
215 .insert("MAIN".to_string(), BinaryHeap::new());
216 agenda
217 }
218
219 pub fn set_strategy(&mut self, strategy: ConflictResolutionStrategy) {
221 self.strategy = strategy;
222 let current_strategy = self.strategy; for heap in self.activations.values_mut() {
225 let mut activations: Vec<_> = heap.drain().collect();
226 Self::sort_with_strategy(current_strategy, &mut activations);
227 *heap = activations.into_iter().collect();
228 }
229 }
230
231 pub fn strategy(&self) -> ConflictResolutionStrategy {
233 self.strategy
234 }
235
236 fn sort_with_strategy(strategy: ConflictResolutionStrategy, activations: &mut [Activation]) {
238 match strategy {
239 ConflictResolutionStrategy::Salience => {
240 activations.sort_by(|a, b| match b.salience.cmp(&a.salience) {
242 Ordering::Equal => b.created_at.cmp(&a.created_at),
243 other => other,
244 });
245 }
246 ConflictResolutionStrategy::LEX => {
247 activations.sort_by_key(|b| std::cmp::Reverse(b.created_at));
250 }
251 ConflictResolutionStrategy::MEA => {
252 activations.sort_by(|a, b| match b.created_at.cmp(&a.created_at) {
254 Ordering::Equal => b.condition_count.cmp(&a.condition_count),
255 other => other,
256 });
257 }
258 ConflictResolutionStrategy::Depth => {
259 activations.sort_by(|a, b| match b.salience.cmp(&a.salience) {
261 Ordering::Equal => b.created_at.cmp(&a.created_at),
262 other => other,
263 });
264 }
265 ConflictResolutionStrategy::Breadth => {
266 activations.sort_by(|a, b| match b.salience.cmp(&a.salience) {
268 Ordering::Equal => b.created_at.cmp(&a.created_at),
269 other => other,
270 });
271 }
272 ConflictResolutionStrategy::Simplicity => {
273 activations.sort_by(|a, b| match a.condition_count.cmp(&b.condition_count) {
275 Ordering::Equal => b.created_at.cmp(&a.created_at),
276 other => other,
277 });
278 }
279 ConflictResolutionStrategy::Complexity => {
280 activations.sort_by(|a, b| match b.condition_count.cmp(&a.condition_count) {
282 Ordering::Equal => b.created_at.cmp(&a.created_at),
283 other => other,
284 });
285 }
286 ConflictResolutionStrategy::Random => {
287 use std::collections::hash_map::RandomState;
290 use std::hash::{BuildHasher, Hash, Hasher};
291
292 let hasher_builder = RandomState::new();
293 activations.sort_by_cached_key(|a| {
294 let mut hasher = hasher_builder.build_hasher();
295 a.rule_name.hash(&mut hasher);
296 a.created_at.hash(&mut hasher);
297 hasher.finish()
298 });
299 }
300 }
301 }
302
303 pub fn add_activation(&mut self, mut activation: Activation) {
305 if activation.auto_focus && activation.agenda_group != self.focus {
307 self.set_focus(activation.agenda_group.clone());
308 }
309
310 if let Some(ref group) = activation.activation_group {
312 if self.fired_activation_groups.contains(group) {
313 return; }
315 }
316
317 if let Some(ref group) = activation.ruleflow_group {
319 if !self.active_ruleflow_groups.contains(group) {
320 return; }
322 }
323
324 activation.id = self.next_id;
326 self.next_id += 1;
327
328 self.activations
330 .entry(activation.agenda_group.clone())
331 .or_default()
332 .push(activation);
333 }
334
335 pub fn get_next_activation(&mut self) -> Option<Activation> {
337 loop {
338 if let Some(heap) = self.activations.get_mut(&self.focus) {
340 while let Some(activation) = heap.pop() {
341 if activation.no_loop && self.fired_rules.contains(&activation.rule_name) {
343 continue;
344 }
345
346 if activation.lock_on_active
348 && self.locked_groups.contains(&activation.agenda_group)
349 {
350 continue;
351 }
352
353 if let Some(ref group) = activation.activation_group {
355 if self.fired_activation_groups.contains(group) {
356 continue;
357 }
358 }
359
360 return Some(activation);
361 }
362 }
363
364 if let Some(prev_focus) = self.focus_stack.pop() {
366 self.focus = prev_focus;
367 } else {
368 return None; }
370 }
371 }
372
373 pub fn mark_rule_fired(&mut self, activation: &Activation) {
375 self.fired_rules.insert(activation.rule_name.clone());
376
377 if let Some(ref group) = activation.activation_group {
379 self.fired_activation_groups.insert(group.clone());
380 }
381
382 if activation.lock_on_active {
384 self.locked_groups.insert(activation.agenda_group.clone());
385 }
386 }
387
388 pub fn has_fired(&self, rule_name: &str) -> bool {
390 self.fired_rules.contains(rule_name)
391 }
392
393 pub fn set_focus(&mut self, group: String) {
395 if group != self.focus {
396 self.focus_stack.push(self.focus.clone());
397 self.focus = group;
398 }
399 }
400
401 pub fn get_focus(&self) -> &str {
403 &self.focus
404 }
405
406 pub fn clear(&mut self) {
408 self.activations.clear();
409 self.activations
410 .insert("MAIN".to_string(), BinaryHeap::new());
411 self.focus = "MAIN".to_string();
412 self.focus_stack.clear();
413 self.fired_rules.clear();
414 self.fired_activation_groups.clear();
415 self.locked_groups.clear();
416 }
417
418 pub fn reset_fired_flags(&mut self) {
420 self.fired_rules.clear();
421 self.fired_activation_groups.clear();
422 self.locked_groups.clear();
423 }
424
425 pub fn activate_ruleflow_group(&mut self, group: String) {
427 self.active_ruleflow_groups.insert(group);
428 }
429
430 pub fn deactivate_ruleflow_group(&mut self, group: &str) {
432 self.active_ruleflow_groups.remove(group);
433 }
434
435 pub fn is_ruleflow_group_active(&self, group: &str) -> bool {
437 self.active_ruleflow_groups.contains(group)
438 }
439
440 pub fn stats(&self) -> AgendaStats {
442 let total_activations: usize = self.activations.values().map(|heap| heap.len()).sum();
443 let groups = self.activations.len();
444
445 AgendaStats {
446 total_activations,
447 groups,
448 focus: self.focus.clone(),
449 fired_rules: self.fired_rules.len(),
450 fired_activation_groups: self.fired_activation_groups.len(),
451 active_ruleflow_groups: self.active_ruleflow_groups.len(),
452 }
453 }
454}
455
456impl Default for AdvancedAgenda {
457 fn default() -> Self {
458 Self::new()
459 }
460}
461
462#[derive(Debug, Clone)]
464pub struct AgendaStats {
465 pub total_activations: usize,
466 pub groups: usize,
467 pub focus: String,
468 pub fired_rules: usize,
469 pub fired_activation_groups: usize,
470 pub active_ruleflow_groups: usize,
471}
472
473impl std::fmt::Display for AgendaStats {
474 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
475 write!(
476 f,
477 "Agenda Stats: {} activations, {} groups, focus='{}', {} fired rules",
478 self.total_activations, self.groups, self.focus, self.fired_rules
479 )
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486
487 #[test]
488 fn test_basic_activation() {
489 let mut agenda = AdvancedAgenda::new();
490
491 let act1 = Activation::new("Rule1".to_string(), 10);
492 let act2 = Activation::new("Rule2".to_string(), 20);
493
494 agenda.add_activation(act1);
495 agenda.add_activation(act2);
496
497 let next = agenda.get_next_activation().unwrap();
499 assert_eq!(next.rule_name, "Rule2");
500 }
501
502 #[test]
503 fn test_activation_groups() {
504 let mut agenda = AdvancedAgenda::new();
505
506 let act1 =
507 Activation::new("Rule1".to_string(), 10).with_activation_group("group1".to_string());
508 let act2 =
509 Activation::new("Rule2".to_string(), 20).with_activation_group("group1".to_string());
510
511 agenda.add_activation(act1);
512 agenda.add_activation(act2);
513
514 let first = agenda.get_next_activation().unwrap();
516 agenda.mark_rule_fired(&first);
517
518 let second = agenda.get_next_activation();
520 assert!(second.is_none());
521 }
522
523 #[test]
524 fn test_agenda_groups() {
525 let mut agenda = AdvancedAgenda::new();
526
527 let act1 =
528 Activation::new("Rule1".to_string(), 10).with_agenda_group("group_a".to_string());
529 let act2 =
530 Activation::new("Rule2".to_string(), 20).with_agenda_group("group_b".to_string());
531
532 agenda.add_activation(act1);
533 agenda.add_activation(act2);
534
535 assert!(agenda.get_next_activation().is_none());
537
538 agenda.set_focus("group_a".to_string());
540 let next = agenda.get_next_activation().unwrap();
541 assert_eq!(next.rule_name, "Rule1");
542 }
543
544 #[test]
545 fn test_auto_focus() {
546 let mut agenda = AdvancedAgenda::new();
547
548 let act = Activation::new("Rule1".to_string(), 10)
549 .with_agenda_group("special".to_string())
550 .with_auto_focus(true);
551
552 agenda.add_activation(act);
553
554 assert_eq!(agenda.get_focus(), "special");
556 }
557
558 #[test]
559 fn test_ruleflow_groups() {
560 let mut agenda = AdvancedAgenda::new();
561
562 let act = Activation::new("Rule1".to_string(), 10).with_ruleflow_group("flow1".to_string());
563
564 agenda.add_activation(act.clone());
566 assert_eq!(agenda.stats().total_activations, 0);
567
568 agenda.activate_ruleflow_group("flow1".to_string());
570 agenda.add_activation(act);
571 assert_eq!(agenda.stats().total_activations, 1);
572 }
573}