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(|a, b| b.created_at.cmp(&a.created_at));
249 }
250 ConflictResolutionStrategy::MEA => {
251 activations.sort_by(|a, b| match b.created_at.cmp(&a.created_at) {
253 Ordering::Equal => b.condition_count.cmp(&a.condition_count),
254 other => other,
255 });
256 }
257 ConflictResolutionStrategy::Depth => {
258 activations.sort_by(|a, b| match b.salience.cmp(&a.salience) {
260 Ordering::Equal => b.created_at.cmp(&a.created_at),
261 other => other,
262 });
263 }
264 ConflictResolutionStrategy::Breadth => {
265 activations.sort_by(|a, b| match b.salience.cmp(&a.salience) {
267 Ordering::Equal => b.created_at.cmp(&a.created_at),
268 other => other,
269 });
270 }
271 ConflictResolutionStrategy::Simplicity => {
272 activations.sort_by(|a, b| match a.condition_count.cmp(&b.condition_count) {
274 Ordering::Equal => b.created_at.cmp(&a.created_at),
275 other => other,
276 });
277 }
278 ConflictResolutionStrategy::Complexity => {
279 activations.sort_by(|a, b| match b.condition_count.cmp(&a.condition_count) {
281 Ordering::Equal => b.created_at.cmp(&a.created_at),
282 other => other,
283 });
284 }
285 ConflictResolutionStrategy::Random => {
286 fastrand::shuffle(activations);
288 }
289 }
290 }
291
292 pub fn add_activation(&mut self, mut activation: Activation) {
294 if activation.auto_focus && activation.agenda_group != self.focus {
296 self.set_focus(activation.agenda_group.clone());
297 }
298
299 if let Some(ref group) = activation.activation_group {
301 if self.fired_activation_groups.contains(group) {
302 return; }
304 }
305
306 if let Some(ref group) = activation.ruleflow_group {
308 if !self.active_ruleflow_groups.contains(group) {
309 return; }
311 }
312
313 activation.id = self.next_id;
315 self.next_id += 1;
316
317 self.activations
319 .entry(activation.agenda_group.clone())
320 .or_default()
321 .push(activation);
322 }
323
324 pub fn get_next_activation(&mut self) -> Option<Activation> {
326 loop {
327 if let Some(heap) = self.activations.get_mut(&self.focus) {
329 while let Some(activation) = heap.pop() {
330 if activation.no_loop && self.fired_rules.contains(&activation.rule_name) {
332 continue;
333 }
334
335 if activation.lock_on_active
337 && self.locked_groups.contains(&activation.agenda_group)
338 {
339 continue;
340 }
341
342 if let Some(ref group) = activation.activation_group {
344 if self.fired_activation_groups.contains(group) {
345 continue;
346 }
347 }
348
349 return Some(activation);
350 }
351 }
352
353 if let Some(prev_focus) = self.focus_stack.pop() {
355 self.focus = prev_focus;
356 } else {
357 return None; }
359 }
360 }
361
362 pub fn mark_rule_fired(&mut self, activation: &Activation) {
364 self.fired_rules.insert(activation.rule_name.clone());
365
366 if let Some(ref group) = activation.activation_group {
368 self.fired_activation_groups.insert(group.clone());
369 }
370
371 if activation.lock_on_active {
373 self.locked_groups.insert(activation.agenda_group.clone());
374 }
375 }
376
377 pub fn has_fired(&self, rule_name: &str) -> bool {
379 self.fired_rules.contains(rule_name)
380 }
381
382 pub fn set_focus(&mut self, group: String) {
384 if group != self.focus {
385 self.focus_stack.push(self.focus.clone());
386 self.focus = group;
387 }
388 }
389
390 pub fn get_focus(&self) -> &str {
392 &self.focus
393 }
394
395 pub fn clear(&mut self) {
397 self.activations.clear();
398 self.activations
399 .insert("MAIN".to_string(), BinaryHeap::new());
400 self.focus = "MAIN".to_string();
401 self.focus_stack.clear();
402 self.fired_rules.clear();
403 self.fired_activation_groups.clear();
404 self.locked_groups.clear();
405 }
406
407 pub fn reset_fired_flags(&mut self) {
409 self.fired_rules.clear();
410 self.fired_activation_groups.clear();
411 self.locked_groups.clear();
412 }
413
414 pub fn activate_ruleflow_group(&mut self, group: String) {
416 self.active_ruleflow_groups.insert(group);
417 }
418
419 pub fn deactivate_ruleflow_group(&mut self, group: &str) {
421 self.active_ruleflow_groups.remove(group);
422 }
423
424 pub fn is_ruleflow_group_active(&self, group: &str) -> bool {
426 self.active_ruleflow_groups.contains(group)
427 }
428
429 pub fn stats(&self) -> AgendaStats {
431 let total_activations: usize = self.activations.values().map(|heap| heap.len()).sum();
432 let groups = self.activations.len();
433
434 AgendaStats {
435 total_activations,
436 groups,
437 focus: self.focus.clone(),
438 fired_rules: self.fired_rules.len(),
439 fired_activation_groups: self.fired_activation_groups.len(),
440 active_ruleflow_groups: self.active_ruleflow_groups.len(),
441 }
442 }
443}
444
445impl Default for AdvancedAgenda {
446 fn default() -> Self {
447 Self::new()
448 }
449}
450
451#[derive(Debug, Clone)]
453pub struct AgendaStats {
454 pub total_activations: usize,
455 pub groups: usize,
456 pub focus: String,
457 pub fired_rules: usize,
458 pub fired_activation_groups: usize,
459 pub active_ruleflow_groups: usize,
460}
461
462impl std::fmt::Display for AgendaStats {
463 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
464 write!(
465 f,
466 "Agenda Stats: {} activations, {} groups, focus='{}', {} fired rules",
467 self.total_activations, self.groups, self.focus, self.fired_rules
468 )
469 }
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475
476 #[test]
477 fn test_basic_activation() {
478 let mut agenda = AdvancedAgenda::new();
479
480 let act1 = Activation::new("Rule1".to_string(), 10);
481 let act2 = Activation::new("Rule2".to_string(), 20);
482
483 agenda.add_activation(act1);
484 agenda.add_activation(act2);
485
486 let next = agenda.get_next_activation().unwrap();
488 assert_eq!(next.rule_name, "Rule2");
489 }
490
491 #[test]
492 fn test_activation_groups() {
493 let mut agenda = AdvancedAgenda::new();
494
495 let act1 =
496 Activation::new("Rule1".to_string(), 10).with_activation_group("group1".to_string());
497 let act2 =
498 Activation::new("Rule2".to_string(), 20).with_activation_group("group1".to_string());
499
500 agenda.add_activation(act1);
501 agenda.add_activation(act2);
502
503 let first = agenda.get_next_activation().unwrap();
505 agenda.mark_rule_fired(&first);
506
507 let second = agenda.get_next_activation();
509 assert!(second.is_none());
510 }
511
512 #[test]
513 fn test_agenda_groups() {
514 let mut agenda = AdvancedAgenda::new();
515
516 let act1 =
517 Activation::new("Rule1".to_string(), 10).with_agenda_group("group_a".to_string());
518 let act2 =
519 Activation::new("Rule2".to_string(), 20).with_agenda_group("group_b".to_string());
520
521 agenda.add_activation(act1);
522 agenda.add_activation(act2);
523
524 assert!(agenda.get_next_activation().is_none());
526
527 agenda.set_focus("group_a".to_string());
529 let next = agenda.get_next_activation().unwrap();
530 assert_eq!(next.rule_name, "Rule1");
531 }
532
533 #[test]
534 fn test_auto_focus() {
535 let mut agenda = AdvancedAgenda::new();
536
537 let act = Activation::new("Rule1".to_string(), 10)
538 .with_agenda_group("special".to_string())
539 .with_auto_focus(true);
540
541 agenda.add_activation(act);
542
543 assert_eq!(agenda.get_focus(), "special");
545 }
546
547 #[test]
548 fn test_ruleflow_groups() {
549 let mut agenda = AdvancedAgenda::new();
550
551 let act = Activation::new("Rule1".to_string(), 10).with_ruleflow_group("flow1".to_string());
552
553 agenda.add_activation(act.clone());
555 assert_eq!(agenda.stats().total_activations, 0);
556
557 agenda.activate_ruleflow_group("flow1".to_string());
559 agenda.add_activation(act);
560 assert_eq!(agenda.stats().total_activations, 1);
561 }
562}