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 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.activations.insert("MAIN".to_string(), BinaryHeap::new());
214 agenda
215 }
216
217 pub fn set_strategy(&mut self, strategy: ConflictResolutionStrategy) {
219 self.strategy = strategy;
220 let current_strategy = self.strategy; for heap in self.activations.values_mut() {
223 let mut activations: Vec<_> = heap.drain().collect();
224 Self::sort_with_strategy(current_strategy, &mut activations);
225 *heap = activations.into_iter().collect();
226 }
227 }
228
229 pub fn strategy(&self) -> ConflictResolutionStrategy {
231 self.strategy
232 }
233
234 fn sort_with_strategy(strategy: ConflictResolutionStrategy, activations: &mut [Activation]) {
236 match strategy {
237 ConflictResolutionStrategy::Salience => {
238 activations.sort_by(|a, b| {
240 match b.salience.cmp(&a.salience) {
241 Ordering::Equal => b.created_at.cmp(&a.created_at),
242 other => other,
243 }
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| {
253 match b.created_at.cmp(&a.created_at) {
254 Ordering::Equal => b.condition_count.cmp(&a.condition_count),
255 other => other,
256 }
257 });
258 }
259 ConflictResolutionStrategy::Depth => {
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::Breadth => {
269 activations.sort_by(|a, b| {
271 match b.salience.cmp(&a.salience) {
272 Ordering::Equal => b.created_at.cmp(&a.created_at),
273 other => other,
274 }
275 });
276 }
277 ConflictResolutionStrategy::Simplicity => {
278 activations.sort_by(|a, b| {
280 match a.condition_count.cmp(&b.condition_count) {
281 Ordering::Equal => b.created_at.cmp(&a.created_at),
282 other => other,
283 }
284 });
285 }
286 ConflictResolutionStrategy::Complexity => {
287 activations.sort_by(|a, b| {
289 match b.condition_count.cmp(&a.condition_count) {
290 Ordering::Equal => b.created_at.cmp(&a.created_at),
291 other => other,
292 }
293 });
294 }
295 ConflictResolutionStrategy::Random => {
296 fastrand::shuffle(activations);
298 }
299 }
300 }
301
302 pub fn add_activation(&mut self, mut activation: Activation) {
304 if activation.auto_focus && activation.agenda_group != self.focus {
306 self.set_focus(activation.agenda_group.clone());
307 }
308
309 if let Some(ref group) = activation.activation_group {
311 if self.fired_activation_groups.contains(group) {
312 return; }
314 }
315
316 if let Some(ref group) = activation.ruleflow_group {
318 if !self.active_ruleflow_groups.contains(group) {
319 return; }
321 }
322
323 activation.id = self.next_id;
325 self.next_id += 1;
326
327 self.activations
329 .entry(activation.agenda_group.clone())
330 .or_insert_with(BinaryHeap::new)
331 .push(activation);
332 }
333
334 pub fn get_next_activation(&mut self) -> Option<Activation> {
336 loop {
337 if let Some(heap) = self.activations.get_mut(&self.focus) {
339 while let Some(activation) = heap.pop() {
340 if activation.no_loop && self.fired_rules.contains(&activation.rule_name) {
342 continue;
343 }
344
345 if activation.lock_on_active && self.locked_groups.contains(&activation.agenda_group) {
347 continue;
348 }
349
350 if let Some(ref group) = activation.activation_group {
352 if self.fired_activation_groups.contains(group) {
353 continue;
354 }
355 }
356
357 return Some(activation);
358 }
359 }
360
361 if let Some(prev_focus) = self.focus_stack.pop() {
363 self.focus = prev_focus;
364 } else {
365 return None; }
367 }
368 }
369
370 pub fn mark_rule_fired(&mut self, activation: &Activation) {
372 self.fired_rules.insert(activation.rule_name.clone());
373
374 if let Some(ref group) = activation.activation_group {
376 self.fired_activation_groups.insert(group.clone());
377 }
378
379 if activation.lock_on_active {
381 self.locked_groups.insert(activation.agenda_group.clone());
382 }
383 }
384
385 pub fn set_focus(&mut self, group: String) {
387 if group != self.focus {
388 self.focus_stack.push(self.focus.clone());
389 self.focus = group;
390 }
391 }
392
393 pub fn get_focus(&self) -> &str {
395 &self.focus
396 }
397
398 pub fn clear(&mut self) {
400 self.activations.clear();
401 self.activations.insert("MAIN".to_string(), BinaryHeap::new());
402 self.focus = "MAIN".to_string();
403 self.focus_stack.clear();
404 self.fired_rules.clear();
405 self.fired_activation_groups.clear();
406 self.locked_groups.clear();
407 }
408
409 pub fn reset_fired_flags(&mut self) {
411 self.fired_rules.clear();
412 self.fired_activation_groups.clear();
413 self.locked_groups.clear();
414 }
415
416 pub fn activate_ruleflow_group(&mut self, group: String) {
418 self.active_ruleflow_groups.insert(group);
419 }
420
421 pub fn deactivate_ruleflow_group(&mut self, group: &str) {
423 self.active_ruleflow_groups.remove(group);
424 }
425
426 pub fn is_ruleflow_group_active(&self, group: &str) -> bool {
428 self.active_ruleflow_groups.contains(group)
429 }
430
431 pub fn stats(&self) -> AgendaStats {
433 let total_activations: usize = self.activations.values().map(|heap| heap.len()).sum();
434 let groups = self.activations.len();
435
436 AgendaStats {
437 total_activations,
438 groups,
439 focus: self.focus.clone(),
440 fired_rules: self.fired_rules.len(),
441 fired_activation_groups: self.fired_activation_groups.len(),
442 active_ruleflow_groups: self.active_ruleflow_groups.len(),
443 }
444 }
445}
446
447impl Default for AdvancedAgenda {
448 fn default() -> Self {
449 Self::new()
450 }
451}
452
453#[derive(Debug, Clone)]
455pub struct AgendaStats {
456 pub total_activations: usize,
457 pub groups: usize,
458 pub focus: String,
459 pub fired_rules: usize,
460 pub fired_activation_groups: usize,
461 pub active_ruleflow_groups: usize,
462}
463
464impl std::fmt::Display for AgendaStats {
465 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
466 write!(
467 f,
468 "Agenda Stats: {} activations, {} groups, focus='{}', {} fired rules",
469 self.total_activations, self.groups, self.focus, self.fired_rules
470 )
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477
478 #[test]
479 fn test_basic_activation() {
480 let mut agenda = AdvancedAgenda::new();
481
482 let act1 = Activation::new("Rule1".to_string(), 10);
483 let act2 = Activation::new("Rule2".to_string(), 20);
484
485 agenda.add_activation(act1);
486 agenda.add_activation(act2);
487
488 let next = agenda.get_next_activation().unwrap();
490 assert_eq!(next.rule_name, "Rule2");
491 }
492
493 #[test]
494 fn test_activation_groups() {
495 let mut agenda = AdvancedAgenda::new();
496
497 let act1 = Activation::new("Rule1".to_string(), 10)
498 .with_activation_group("group1".to_string());
499 let act2 = Activation::new("Rule2".to_string(), 20)
500 .with_activation_group("group1".to_string());
501
502 agenda.add_activation(act1);
503 agenda.add_activation(act2);
504
505 let first = agenda.get_next_activation().unwrap();
507 agenda.mark_rule_fired(&first);
508
509 let second = agenda.get_next_activation();
511 assert!(second.is_none());
512 }
513
514 #[test]
515 fn test_agenda_groups() {
516 let mut agenda = AdvancedAgenda::new();
517
518 let act1 = Activation::new("Rule1".to_string(), 10)
519 .with_agenda_group("group_a".to_string());
520 let act2 = Activation::new("Rule2".to_string(), 20)
521 .with_agenda_group("group_b".to_string());
522
523 agenda.add_activation(act1);
524 agenda.add_activation(act2);
525
526 assert!(agenda.get_next_activation().is_none());
528
529 agenda.set_focus("group_a".to_string());
531 let next = agenda.get_next_activation().unwrap();
532 assert_eq!(next.rule_name, "Rule1");
533 }
534
535 #[test]
536 fn test_auto_focus() {
537 let mut agenda = AdvancedAgenda::new();
538
539 let act = Activation::new("Rule1".to_string(), 10)
540 .with_agenda_group("special".to_string())
541 .with_auto_focus(true);
542
543 agenda.add_activation(act);
544
545 assert_eq!(agenda.get_focus(), "special");
547 }
548
549 #[test]
550 fn test_ruleflow_groups() {
551 let mut agenda = AdvancedAgenda::new();
552
553 let act = Activation::new("Rule1".to_string(), 10)
554 .with_ruleflow_group("flow1".to_string());
555
556 agenda.add_activation(act.clone());
558 assert_eq!(agenda.stats().total_activations, 0);
559
560 agenda.activate_ruleflow_group("flow1".to_string());
562 agenda.add_activation(act);
563 assert_eq!(agenda.stats().total_activations, 1);
564 }
565}