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 use std::collections::hash_map::RandomState;
289 use std::hash::{BuildHasher, Hash, Hasher};
290
291 let hasher_builder = RandomState::new();
292 activations.sort_by_cached_key(|a| {
293 let mut hasher = hasher_builder.build_hasher();
294 a.rule_name.hash(&mut hasher);
295 a.created_at.hash(&mut hasher);
296 hasher.finish()
297 });
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_default()
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
347 && self.locked_groups.contains(&activation.agenda_group)
348 {
349 continue;
350 }
351
352 if let Some(ref group) = activation.activation_group {
354 if self.fired_activation_groups.contains(group) {
355 continue;
356 }
357 }
358
359 return Some(activation);
360 }
361 }
362
363 if let Some(prev_focus) = self.focus_stack.pop() {
365 self.focus = prev_focus;
366 } else {
367 return None; }
369 }
370 }
371
372 pub fn mark_rule_fired(&mut self, activation: &Activation) {
374 self.fired_rules.insert(activation.rule_name.clone());
375
376 if let Some(ref group) = activation.activation_group {
378 self.fired_activation_groups.insert(group.clone());
379 }
380
381 if activation.lock_on_active {
383 self.locked_groups.insert(activation.agenda_group.clone());
384 }
385 }
386
387 pub fn has_fired(&self, rule_name: &str) -> bool {
389 self.fired_rules.contains(rule_name)
390 }
391
392 pub fn set_focus(&mut self, group: String) {
394 if group != self.focus {
395 self.focus_stack.push(self.focus.clone());
396 self.focus = group;
397 }
398 }
399
400 pub fn get_focus(&self) -> &str {
402 &self.focus
403 }
404
405 pub fn clear(&mut self) {
407 self.activations.clear();
408 self.activations
409 .insert("MAIN".to_string(), BinaryHeap::new());
410 self.focus = "MAIN".to_string();
411 self.focus_stack.clear();
412 self.fired_rules.clear();
413 self.fired_activation_groups.clear();
414 self.locked_groups.clear();
415 }
416
417 pub fn reset_fired_flags(&mut self) {
419 self.fired_rules.clear();
420 self.fired_activation_groups.clear();
421 self.locked_groups.clear();
422 }
423
424 pub fn activate_ruleflow_group(&mut self, group: String) {
426 self.active_ruleflow_groups.insert(group);
427 }
428
429 pub fn deactivate_ruleflow_group(&mut self, group: &str) {
431 self.active_ruleflow_groups.remove(group);
432 }
433
434 pub fn is_ruleflow_group_active(&self, group: &str) -> bool {
436 self.active_ruleflow_groups.contains(group)
437 }
438
439 pub fn stats(&self) -> AgendaStats {
441 let total_activations: usize = self.activations.values().map(|heap| heap.len()).sum();
442 let groups = self.activations.len();
443
444 AgendaStats {
445 total_activations,
446 groups,
447 focus: self.focus.clone(),
448 fired_rules: self.fired_rules.len(),
449 fired_activation_groups: self.fired_activation_groups.len(),
450 active_ruleflow_groups: self.active_ruleflow_groups.len(),
451 }
452 }
453}
454
455impl Default for AdvancedAgenda {
456 fn default() -> Self {
457 Self::new()
458 }
459}
460
461#[derive(Debug, Clone)]
463pub struct AgendaStats {
464 pub total_activations: usize,
465 pub groups: usize,
466 pub focus: String,
467 pub fired_rules: usize,
468 pub fired_activation_groups: usize,
469 pub active_ruleflow_groups: usize,
470}
471
472impl std::fmt::Display for AgendaStats {
473 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474 write!(
475 f,
476 "Agenda Stats: {} activations, {} groups, focus='{}', {} fired rules",
477 self.total_activations, self.groups, self.focus, self.fired_rules
478 )
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485
486 #[test]
487 fn test_basic_activation() {
488 let mut agenda = AdvancedAgenda::new();
489
490 let act1 = Activation::new("Rule1".to_string(), 10);
491 let act2 = Activation::new("Rule2".to_string(), 20);
492
493 agenda.add_activation(act1);
494 agenda.add_activation(act2);
495
496 let next = agenda.get_next_activation().unwrap();
498 assert_eq!(next.rule_name, "Rule2");
499 }
500
501 #[test]
502 fn test_activation_groups() {
503 let mut agenda = AdvancedAgenda::new();
504
505 let act1 =
506 Activation::new("Rule1".to_string(), 10).with_activation_group("group1".to_string());
507 let act2 =
508 Activation::new("Rule2".to_string(), 20).with_activation_group("group1".to_string());
509
510 agenda.add_activation(act1);
511 agenda.add_activation(act2);
512
513 let first = agenda.get_next_activation().unwrap();
515 agenda.mark_rule_fired(&first);
516
517 let second = agenda.get_next_activation();
519 assert!(second.is_none());
520 }
521
522 #[test]
523 fn test_agenda_groups() {
524 let mut agenda = AdvancedAgenda::new();
525
526 let act1 =
527 Activation::new("Rule1".to_string(), 10).with_agenda_group("group_a".to_string());
528 let act2 =
529 Activation::new("Rule2".to_string(), 20).with_agenda_group("group_b".to_string());
530
531 agenda.add_activation(act1);
532 agenda.add_activation(act2);
533
534 assert!(agenda.get_next_activation().is_none());
536
537 agenda.set_focus("group_a".to_string());
539 let next = agenda.get_next_activation().unwrap();
540 assert_eq!(next.rule_name, "Rule1");
541 }
542
543 #[test]
544 fn test_auto_focus() {
545 let mut agenda = AdvancedAgenda::new();
546
547 let act = Activation::new("Rule1".to_string(), 10)
548 .with_agenda_group("special".to_string())
549 .with_auto_focus(true);
550
551 agenda.add_activation(act);
552
553 assert_eq!(agenda.get_focus(), "special");
555 }
556
557 #[test]
558 fn test_ruleflow_groups() {
559 let mut agenda = AdvancedAgenda::new();
560
561 let act = Activation::new("Rule1".to_string(), 10).with_ruleflow_group("flow1".to_string());
562
563 agenda.add_activation(act.clone());
565 assert_eq!(agenda.stats().total_activations, 0);
566
567 agenda.activate_ruleflow_group("flow1".to_string());
569 agenda.add_activation(act);
570 assert_eq!(agenda.stats().total_activations, 1);
571 }
572}