1use std::collections::{HashMap, HashSet, BinaryHeap};
12use std::cmp::Ordering;
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 id: usize,
77}
78
79impl Activation {
80 pub fn new(rule_name: String, salience: i32) -> Self {
82 Self {
83 rule_name,
84 salience,
85 activation_group: None,
86 agenda_group: "MAIN".to_string(),
87 ruleflow_group: None,
88 no_loop: true,
89 lock_on_active: false,
90 auto_focus: false,
91 created_at: std::time::Instant::now(),
92 condition_count: 1, id: 0,
94 }
95 }
96
97 pub fn with_condition_count(mut self, count: usize) -> Self {
99 self.condition_count = count;
100 self
101 }
102
103 pub fn with_activation_group(mut self, group: String) -> Self {
105 self.activation_group = Some(group);
106 self
107 }
108
109 pub fn with_agenda_group(mut self, group: String) -> Self {
111 self.agenda_group = group;
112 self
113 }
114
115 pub fn with_ruleflow_group(mut self, group: String) -> Self {
117 self.ruleflow_group = Some(group);
118 self
119 }
120
121 pub fn with_no_loop(mut self, no_loop: bool) -> Self {
123 self.no_loop = no_loop;
124 self
125 }
126
127 pub fn with_lock_on_active(mut self, lock: bool) -> Self {
129 self.lock_on_active = lock;
130 self
131 }
132
133 pub fn with_auto_focus(mut self, auto_focus: bool) -> Self {
135 self.auto_focus = auto_focus;
136 self
137 }
138}
139
140impl PartialEq for Activation {
142 fn eq(&self, other: &Self) -> bool {
143 self.id == other.id
144 }
145}
146
147impl Eq for Activation {}
148
149impl PartialOrd for Activation {
150 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
151 Some(self.cmp(other))
152 }
153}
154
155impl Ord for Activation {
156 fn cmp(&self, other: &Self) -> Ordering {
157 match self.salience.cmp(&other.salience) {
159 Ordering::Equal => {
160 other.created_at.cmp(&self.created_at)
162 }
163 other_order => other_order,
164 }
165 }
166}
167
168pub struct AdvancedAgenda {
170 activations: HashMap<String, BinaryHeap<Activation>>,
172 focus: String,
174 focus_stack: Vec<String>,
176 fired_rules: HashSet<String>,
178 fired_activation_groups: HashSet<String>,
180 locked_groups: HashSet<String>,
182 active_ruleflow_groups: HashSet<String>,
184 next_id: usize,
186 strategy: ConflictResolutionStrategy,
188}
189
190impl AdvancedAgenda {
191 pub fn new() -> Self {
193 let mut agenda = Self {
194 activations: HashMap::new(),
195 focus: "MAIN".to_string(),
196 focus_stack: Vec::new(),
197 fired_rules: HashSet::new(),
198 fired_activation_groups: HashSet::new(),
199 locked_groups: HashSet::new(),
200 active_ruleflow_groups: HashSet::new(),
201 next_id: 0,
202 strategy: ConflictResolutionStrategy::Salience, };
204 agenda.activations.insert("MAIN".to_string(), BinaryHeap::new());
205 agenda
206 }
207
208 pub fn set_strategy(&mut self, strategy: ConflictResolutionStrategy) {
210 self.strategy = strategy;
211 let current_strategy = self.strategy; for heap in self.activations.values_mut() {
214 let mut activations: Vec<_> = heap.drain().collect();
215 Self::sort_with_strategy(current_strategy, &mut activations);
216 *heap = activations.into_iter().collect();
217 }
218 }
219
220 pub fn strategy(&self) -> ConflictResolutionStrategy {
222 self.strategy
223 }
224
225 fn sort_with_strategy(strategy: ConflictResolutionStrategy, activations: &mut [Activation]) {
227 match strategy {
228 ConflictResolutionStrategy::Salience => {
229 activations.sort_by(|a, b| {
231 match b.salience.cmp(&a.salience) {
232 Ordering::Equal => b.created_at.cmp(&a.created_at),
233 other => other,
234 }
235 });
236 }
237 ConflictResolutionStrategy::LEX => {
238 activations.sort_by(|a, b| b.created_at.cmp(&a.created_at));
240 }
241 ConflictResolutionStrategy::MEA => {
242 activations.sort_by(|a, b| {
244 match b.created_at.cmp(&a.created_at) {
245 Ordering::Equal => b.condition_count.cmp(&a.condition_count),
246 other => other,
247 }
248 });
249 }
250 ConflictResolutionStrategy::Depth => {
251 activations.sort_by(|a, b| {
253 match b.salience.cmp(&a.salience) {
254 Ordering::Equal => b.created_at.cmp(&a.created_at),
255 other => other,
256 }
257 });
258 }
259 ConflictResolutionStrategy::Breadth => {
260 activations.sort_by(|a, b| {
262 match b.salience.cmp(&a.salience) {
263 Ordering::Equal => b.created_at.cmp(&a.created_at),
264 other => other,
265 }
266 });
267 }
268 ConflictResolutionStrategy::Simplicity => {
269 activations.sort_by(|a, b| {
271 match a.condition_count.cmp(&b.condition_count) {
272 Ordering::Equal => b.created_at.cmp(&a.created_at),
273 other => other,
274 }
275 });
276 }
277 ConflictResolutionStrategy::Complexity => {
278 activations.sort_by(|a, b| {
280 match b.condition_count.cmp(&a.condition_count) {
281 Ordering::Equal => b.created_at.cmp(&a.created_at),
282 other => other,
283 }
284 });
285 }
286 ConflictResolutionStrategy::Random => {
287 fastrand::shuffle(activations);
289 }
290 }
291 }
292
293 pub fn add_activation(&mut self, mut activation: Activation) {
295 if activation.auto_focus && activation.agenda_group != self.focus {
297 self.set_focus(activation.agenda_group.clone());
298 }
299
300 if let Some(ref group) = activation.activation_group {
302 if self.fired_activation_groups.contains(group) {
303 return; }
305 }
306
307 if let Some(ref group) = activation.ruleflow_group {
309 if !self.active_ruleflow_groups.contains(group) {
310 return; }
312 }
313
314 activation.id = self.next_id;
316 self.next_id += 1;
317
318 self.activations
320 .entry(activation.agenda_group.clone())
321 .or_insert_with(BinaryHeap::new)
322 .push(activation);
323 }
324
325 pub fn get_next_activation(&mut self) -> Option<Activation> {
327 loop {
328 if let Some(heap) = self.activations.get_mut(&self.focus) {
330 while let Some(activation) = heap.pop() {
331 if activation.no_loop && self.fired_rules.contains(&activation.rule_name) {
333 continue;
334 }
335
336 if activation.lock_on_active && self.locked_groups.contains(&activation.agenda_group) {
338 continue;
339 }
340
341 if let Some(ref group) = activation.activation_group {
343 if self.fired_activation_groups.contains(group) {
344 continue;
345 }
346 }
347
348 return Some(activation);
349 }
350 }
351
352 if let Some(prev_focus) = self.focus_stack.pop() {
354 self.focus = prev_focus;
355 } else {
356 return None; }
358 }
359 }
360
361 pub fn mark_rule_fired(&mut self, activation: &Activation) {
363 self.fired_rules.insert(activation.rule_name.clone());
364
365 if let Some(ref group) = activation.activation_group {
367 self.fired_activation_groups.insert(group.clone());
368 }
369
370 if activation.lock_on_active {
372 self.locked_groups.insert(activation.agenda_group.clone());
373 }
374 }
375
376 pub fn set_focus(&mut self, group: String) {
378 if group != self.focus {
379 self.focus_stack.push(self.focus.clone());
380 self.focus = group;
381 }
382 }
383
384 pub fn get_focus(&self) -> &str {
386 &self.focus
387 }
388
389 pub fn clear(&mut self) {
391 self.activations.clear();
392 self.activations.insert("MAIN".to_string(), BinaryHeap::new());
393 self.focus = "MAIN".to_string();
394 self.focus_stack.clear();
395 self.fired_rules.clear();
396 self.fired_activation_groups.clear();
397 self.locked_groups.clear();
398 }
399
400 pub fn reset_fired_flags(&mut self) {
402 self.fired_rules.clear();
403 self.fired_activation_groups.clear();
404 self.locked_groups.clear();
405 }
406
407 pub fn activate_ruleflow_group(&mut self, group: String) {
409 self.active_ruleflow_groups.insert(group);
410 }
411
412 pub fn deactivate_ruleflow_group(&mut self, group: &str) {
414 self.active_ruleflow_groups.remove(group);
415 }
416
417 pub fn is_ruleflow_group_active(&self, group: &str) -> bool {
419 self.active_ruleflow_groups.contains(group)
420 }
421
422 pub fn stats(&self) -> AgendaStats {
424 let total_activations: usize = self.activations.values().map(|heap| heap.len()).sum();
425 let groups = self.activations.len();
426
427 AgendaStats {
428 total_activations,
429 groups,
430 focus: self.focus.clone(),
431 fired_rules: self.fired_rules.len(),
432 fired_activation_groups: self.fired_activation_groups.len(),
433 active_ruleflow_groups: self.active_ruleflow_groups.len(),
434 }
435 }
436}
437
438impl Default for AdvancedAgenda {
439 fn default() -> Self {
440 Self::new()
441 }
442}
443
444#[derive(Debug, Clone)]
446pub struct AgendaStats {
447 pub total_activations: usize,
448 pub groups: usize,
449 pub focus: String,
450 pub fired_rules: usize,
451 pub fired_activation_groups: usize,
452 pub active_ruleflow_groups: usize,
453}
454
455impl std::fmt::Display for AgendaStats {
456 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
457 write!(
458 f,
459 "Agenda Stats: {} activations, {} groups, focus='{}', {} fired rules",
460 self.total_activations, self.groups, self.focus, self.fired_rules
461 )
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
470 fn test_basic_activation() {
471 let mut agenda = AdvancedAgenda::new();
472
473 let act1 = Activation::new("Rule1".to_string(), 10);
474 let act2 = Activation::new("Rule2".to_string(), 20);
475
476 agenda.add_activation(act1);
477 agenda.add_activation(act2);
478
479 let next = agenda.get_next_activation().unwrap();
481 assert_eq!(next.rule_name, "Rule2");
482 }
483
484 #[test]
485 fn test_activation_groups() {
486 let mut agenda = AdvancedAgenda::new();
487
488 let act1 = Activation::new("Rule1".to_string(), 10)
489 .with_activation_group("group1".to_string());
490 let act2 = Activation::new("Rule2".to_string(), 20)
491 .with_activation_group("group1".to_string());
492
493 agenda.add_activation(act1);
494 agenda.add_activation(act2);
495
496 let first = agenda.get_next_activation().unwrap();
498 agenda.mark_rule_fired(&first);
499
500 let second = agenda.get_next_activation();
502 assert!(second.is_none());
503 }
504
505 #[test]
506 fn test_agenda_groups() {
507 let mut agenda = AdvancedAgenda::new();
508
509 let act1 = Activation::new("Rule1".to_string(), 10)
510 .with_agenda_group("group_a".to_string());
511 let act2 = Activation::new("Rule2".to_string(), 20)
512 .with_agenda_group("group_b".to_string());
513
514 agenda.add_activation(act1);
515 agenda.add_activation(act2);
516
517 assert!(agenda.get_next_activation().is_none());
519
520 agenda.set_focus("group_a".to_string());
522 let next = agenda.get_next_activation().unwrap();
523 assert_eq!(next.rule_name, "Rule1");
524 }
525
526 #[test]
527 fn test_auto_focus() {
528 let mut agenda = AdvancedAgenda::new();
529
530 let act = Activation::new("Rule1".to_string(), 10)
531 .with_agenda_group("special".to_string())
532 .with_auto_focus(true);
533
534 agenda.add_activation(act);
535
536 assert_eq!(agenda.get_focus(), "special");
538 }
539
540 #[test]
541 fn test_ruleflow_groups() {
542 let mut agenda = AdvancedAgenda::new();
543
544 let act = Activation::new("Rule1".to_string(), 10)
545 .with_ruleflow_group("flow1".to_string());
546
547 agenda.add_activation(act.clone());
549 assert_eq!(agenda.stats().total_activations, 0);
550
551 agenda.activate_ruleflow_group("flow1".to_string());
553 agenda.add_activation(act);
554 assert_eq!(agenda.stats().total_activations, 1);
555 }
556}