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 self.focus = self.focus_stack.pop()?;
366 }
367 }
368
369 pub fn mark_rule_fired(&mut self, activation: &Activation) {
371 self.fired_rules.insert(activation.rule_name.clone());
372
373 if let Some(ref group) = activation.activation_group {
375 self.fired_activation_groups.insert(group.clone());
376 }
377
378 if activation.lock_on_active {
380 self.locked_groups.insert(activation.agenda_group.clone());
381 }
382 }
383
384 pub fn has_fired(&self, rule_name: &str) -> bool {
386 self.fired_rules.contains(rule_name)
387 }
388
389 pub fn set_focus(&mut self, group: String) {
391 if group != self.focus {
392 self.focus_stack.push(self.focus.clone());
393 self.focus = group;
394 }
395 }
396
397 pub fn get_focus(&self) -> &str {
399 &self.focus
400 }
401
402 pub fn clear(&mut self) {
404 self.activations.clear();
405 self.activations
406 .insert("MAIN".to_string(), BinaryHeap::new());
407 self.focus = "MAIN".to_string();
408 self.focus_stack.clear();
409 self.fired_rules.clear();
410 self.fired_activation_groups.clear();
411 self.locked_groups.clear();
412 }
413
414 pub fn reset_fired_flags(&mut self) {
416 self.fired_rules.clear();
417 self.fired_activation_groups.clear();
418 self.locked_groups.clear();
419 }
420
421 pub fn activate_ruleflow_group(&mut self, group: String) {
423 self.active_ruleflow_groups.insert(group);
424 }
425
426 pub fn deactivate_ruleflow_group(&mut self, group: &str) {
428 self.active_ruleflow_groups.remove(group);
429 }
430
431 pub fn is_ruleflow_group_active(&self, group: &str) -> bool {
433 self.active_ruleflow_groups.contains(group)
434 }
435
436 pub fn stats(&self) -> AgendaStats {
438 let total_activations: usize = self.activations.values().map(|heap| heap.len()).sum();
439 let groups = self.activations.len();
440
441 AgendaStats {
442 total_activations,
443 groups,
444 focus: self.focus.clone(),
445 fired_rules: self.fired_rules.len(),
446 fired_activation_groups: self.fired_activation_groups.len(),
447 active_ruleflow_groups: self.active_ruleflow_groups.len(),
448 }
449 }
450}
451
452impl Default for AdvancedAgenda {
453 fn default() -> Self {
454 Self::new()
455 }
456}
457
458#[derive(Debug, Clone)]
460pub struct AgendaStats {
461 pub total_activations: usize,
462 pub groups: usize,
463 pub focus: String,
464 pub fired_rules: usize,
465 pub fired_activation_groups: usize,
466 pub active_ruleflow_groups: usize,
467}
468
469impl std::fmt::Display for AgendaStats {
470 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471 write!(
472 f,
473 "Agenda Stats: {} activations, {} groups, focus='{}', {} fired rules",
474 self.total_activations, self.groups, self.focus, self.fired_rules
475 )
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482
483 #[test]
484 fn test_basic_activation() {
485 let mut agenda = AdvancedAgenda::new();
486
487 let act1 = Activation::new("Rule1".to_string(), 10);
488 let act2 = Activation::new("Rule2".to_string(), 20);
489
490 agenda.add_activation(act1);
491 agenda.add_activation(act2);
492
493 let next = agenda.get_next_activation().unwrap();
495 assert_eq!(next.rule_name, "Rule2");
496 }
497
498 #[test]
499 fn test_activation_groups() {
500 let mut agenda = AdvancedAgenda::new();
501
502 let act1 =
503 Activation::new("Rule1".to_string(), 10).with_activation_group("group1".to_string());
504 let act2 =
505 Activation::new("Rule2".to_string(), 20).with_activation_group("group1".to_string());
506
507 agenda.add_activation(act1);
508 agenda.add_activation(act2);
509
510 let first = agenda.get_next_activation().unwrap();
512 agenda.mark_rule_fired(&first);
513
514 let second = agenda.get_next_activation();
516 assert!(second.is_none());
517 }
518
519 #[test]
520 fn test_agenda_groups() {
521 let mut agenda = AdvancedAgenda::new();
522
523 let act1 =
524 Activation::new("Rule1".to_string(), 10).with_agenda_group("group_a".to_string());
525 let act2 =
526 Activation::new("Rule2".to_string(), 20).with_agenda_group("group_b".to_string());
527
528 agenda.add_activation(act1);
529 agenda.add_activation(act2);
530
531 assert!(agenda.get_next_activation().is_none());
533
534 agenda.set_focus("group_a".to_string());
536 let next = agenda.get_next_activation().unwrap();
537 assert_eq!(next.rule_name, "Rule1");
538 }
539
540 #[test]
541 fn test_auto_focus() {
542 let mut agenda = AdvancedAgenda::new();
543
544 let act = Activation::new("Rule1".to_string(), 10)
545 .with_agenda_group("special".to_string())
546 .with_auto_focus(true);
547
548 agenda.add_activation(act);
549
550 assert_eq!(agenda.get_focus(), "special");
552 }
553
554 #[test]
555 fn test_ruleflow_groups() {
556 let mut agenda = AdvancedAgenda::new();
557
558 let act = Activation::new("Rule1".to_string(), 10).with_ruleflow_group("flow1".to_string());
559
560 agenda.add_activation(act.clone());
562 assert_eq!(agenda.stats().total_activations, 0);
563
564 agenda.activate_ruleflow_group("flow1".to_string());
566 agenda.add_activation(act);
567 assert_eq!(agenda.stats().total_activations, 1);
568 }
569}