1use std::cmp::Reverse;
15use std::collections::BinaryHeap;
16
17use log::info;
18use serde::{Deserialize, Serialize};
19
20use crate::trigger::{ScriptedAction, TriggerCondition};
21
22#[cfg(test)]
23const PLACEHOLDER_THRESHOLD: usize = 4;
24#[cfg(not(test))]
25const PLACEHOLDER_THRESHOLD: usize = 64;
26
27#[derive(Debug, Clone, Serialize, Deserialize, Default)]
31pub struct Scheduler {
32 pub heap: BinaryHeap<Reverse<(usize, usize)>>, pub events: Vec<ScheduledEvent>,
34}
35impl Scheduler {
36 pub fn schedule_in(&mut self, now: usize, turns_ahead: usize, actions: Vec<ScriptedAction>, note: Option<String>) {
38 let idx = self.events.len();
39 let on_turn = now + turns_ahead;
40 let log_msg = match ¬e {
41 Some(msg) => msg.as_str(),
42 None => "<no note provided>",
43 };
44 info!("scheduling event (turn now/due = {now}/{on_turn}): \"{log_msg}\"");
45 self.heap.push(Reverse((on_turn, idx)));
46 self.events.push(ScheduledEvent {
47 on_turn,
48 actions,
49 note,
50 condition: None,
51 on_false: OnFalsePolicy::Cancel,
52 });
53 }
54
55 pub fn schedule_on(&mut self, on_turn: usize, actions: Vec<ScriptedAction>, note: Option<String>) {
57 let idx = self.events.len();
58 let log_msg = match ¬e {
59 Some(note) => note.as_str(),
60 None => "<no note provided>",
61 };
62 info!("scheduling event (turn due = {on_turn}): \"{log_msg}\"");
63 self.heap.push(Reverse((on_turn, idx)));
64 self.events.push(ScheduledEvent {
65 on_turn,
66 actions,
67 note,
68 condition: None,
69 on_false: OnFalsePolicy::Cancel,
70 });
71 }
72
73 pub fn schedule_in_if(
78 &mut self,
79 now: usize,
80 turns_ahead: usize,
81 condition: Option<EventCondition>,
82 on_false: OnFalsePolicy,
83 actions: Vec<ScriptedAction>,
84 note: Option<String>,
85 ) {
86 let idx = self.events.len();
87 let on_turn = now + turns_ahead;
88 let log_msg = match ¬e {
89 Some(msg) => msg.as_str(),
90 None => "<no note provided>",
91 };
92 info!("scheduling conditional event (turn now/due = {now}/{on_turn}): \"{log_msg}\"");
93 self.heap.push(Reverse((on_turn, idx)));
94 self.events.push(ScheduledEvent {
95 on_turn,
96 actions,
97 note,
98 condition,
99 on_false,
100 });
101 }
102
103 pub fn schedule_on_if(
105 &mut self,
106 on_turn: usize,
107 condition: Option<EventCondition>,
108 on_false: OnFalsePolicy,
109 actions: Vec<ScriptedAction>,
110 note: Option<String>,
111 ) {
112 let idx = self.events.len();
113 let log_msg = match ¬e {
114 Some(note) => note.as_str(),
115 None => "<no note provided>",
116 };
117 info!("scheduling conditional event (turn due = {on_turn}): \"{log_msg}\"");
118 self.heap.push(Reverse((on_turn, idx)));
119 self.events.push(ScheduledEvent {
120 on_turn,
121 actions,
122 note,
123 condition,
124 on_false,
125 });
126 }
127
128 pub fn pop_due(&mut self, now: usize) -> Option<ScheduledEvent> {
132 if let Some(Reverse((turn_due, idx))) = self.heap.peek().copied()
133 && now >= turn_due
134 {
135 self.heap.pop();
136 let event = std::mem::take(&mut self.events[idx]);
139 self.compact_if_needed();
140 return Some(event);
141 }
142 None
143 }
144
145 fn compact_if_needed(&mut self) {
147 let placeholder_count = self.events.iter().filter(|e| e.is_placeholder()).count();
148 if placeholder_count > PLACEHOLDER_THRESHOLD {
149 let old_events = std::mem::take(&mut self.events);
150 let mut index_map = vec![0; old_events.len()];
151 for (old_idx, event) in old_events.into_iter().enumerate() {
152 if event.is_placeholder() {
153 continue;
154 }
155 let new_idx = self.events.len();
156 index_map[old_idx] = new_idx;
157 self.events.push(event);
158 }
159 let mut new_heap = BinaryHeap::with_capacity(self.heap.len());
160 while let Some(Reverse((turn_due, old_idx))) = self.heap.pop() {
161 let new_idx = index_map[old_idx];
162 new_heap.push(Reverse((turn_due, new_idx)));
163 }
164 self.heap = new_heap;
165 }
166 }
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
176pub struct ScheduledEvent {
177 pub on_turn: usize,
178 pub actions: Vec<ScriptedAction>,
179 pub note: Option<String>,
180 pub condition: Option<EventCondition>,
182 pub on_false: OnFalsePolicy,
184}
185
186impl ScheduledEvent {
187 fn is_placeholder(&self) -> bool {
189 *self == ScheduledEvent::default()
190 }
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
195pub enum OnFalsePolicy {
196 #[default]
198 Cancel,
199 RetryAfter(usize),
201 RetryNextTurn,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
207pub enum EventCondition {
208 Trigger(TriggerCondition),
210 All(Vec<EventCondition>),
212 Any(Vec<EventCondition>),
214}
215
216impl EventCondition {
217 pub fn eval_with_events(&self, world: &crate::world::AmbleWorld, events: &[TriggerCondition]) -> bool {
219 match self {
220 EventCondition::Trigger(tc) => tc.matches_event_in(events) || tc.is_ongoing(world),
221 EventCondition::All(conds) => conds.iter().all(|c| c.eval_with_events(world, events)),
222 EventCondition::Any(conds) => conds.iter().any(|c| c.eval_with_events(world, events)),
223 }
224 }
225
226 pub fn eval(&self, world: &crate::world::AmbleWorld) -> bool {
228 self.eval_with_events(world, &[])
229 }
230
231 pub fn eval_ambient(&self, world: &crate::world::AmbleWorld) -> bool {
234 match self {
235 EventCondition::Trigger(tc) => match tc {
236 TriggerCondition::Ambient { room_ids, .. } => world
237 .player
238 .location
239 .room_id()
240 .is_ok_and(|room_id| room_ids.is_empty() || room_ids.contains(&room_id)),
241 _ => tc.is_ongoing(world),
242 },
243 EventCondition::All(conds) => conds.iter().all(|c| c.eval_ambient(world)),
244 EventCondition::Any(conds) => conds.iter().any(|c| c.eval_ambient(world)),
245 }
246 }
247
248 pub fn any_trigger<F>(&self, matcher: F) -> bool
250 where
251 F: FnMut(&TriggerCondition) -> bool,
252 {
253 let mut matcher = matcher;
254 self.any_trigger_inner(&mut matcher)
255 }
256
257 pub fn for_each_condition<F>(&self, visitor: F)
259 where
260 F: FnMut(&TriggerCondition),
261 {
262 let mut visitor = visitor;
263 self.for_each_trigger_inner(&mut visitor);
264 }
265
266 fn any_trigger_inner<F>(&self, matcher: &mut F) -> bool
267 where
268 F: FnMut(&TriggerCondition) -> bool,
269 {
270 match self {
271 EventCondition::Trigger(tc) => matcher(tc),
272 EventCondition::All(conds) | EventCondition::Any(conds) => {
273 conds.iter().any(|c| c.any_trigger_inner(matcher))
274 },
275 }
276 }
277
278 fn for_each_trigger_inner<F>(&self, visitor: &mut F)
279 where
280 F: FnMut(&TriggerCondition),
281 {
282 match self {
283 EventCondition::Trigger(tc) => visitor(tc),
284 EventCondition::All(conds) | EventCondition::Any(conds) => {
285 for cond in conds {
286 cond.for_each_trigger_inner(visitor);
287 }
288 },
289 }
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::trigger::TriggerAction;
297
298 fn create_test_action() -> ScriptedAction {
299 ScriptedAction::new(TriggerAction::ShowMessage("Test message".to_string()))
300 }
301
302 fn create_test_actions(count: usize) -> Vec<ScriptedAction> {
303 (0..count)
304 .map(|i| ScriptedAction::new(TriggerAction::ShowMessage(format!("Message {i}"))))
305 .collect()
306 }
307
308 #[test]
309 fn scheduler_new_is_empty() {
310 let scheduler = Scheduler::default();
311 assert!(scheduler.heap.is_empty());
312 assert!(scheduler.events.is_empty());
313 }
314
315 #[test]
316 fn schedule_in_adds_event_correctly() {
317 let mut scheduler = Scheduler::default();
318 let actions = vec![create_test_action()];
319 let note = Some("Test event".to_string());
320
321 scheduler.schedule_in(5, 3, actions.clone(), note.clone());
322
323 assert_eq!(scheduler.events.len(), 1);
324 assert_eq!(scheduler.heap.len(), 1);
325
326 let event = &scheduler.events[0];
327 assert_eq!(event.on_turn, 8); assert_eq!(event.actions.len(), 1);
329 assert_eq!(event.note, note);
330 }
331
332 #[test]
333 fn schedule_on_adds_event_correctly() {
334 let mut scheduler = Scheduler::default();
335 let actions = vec![create_test_action()];
336 let note = Some("Direct schedule test".to_string());
337
338 scheduler.schedule_on(10, actions.clone(), note.clone());
339
340 assert_eq!(scheduler.events.len(), 1);
341 assert_eq!(scheduler.heap.len(), 1);
342
343 let event = &scheduler.events[0];
344 assert_eq!(event.on_turn, 10);
345 assert_eq!(event.actions.len(), 1);
346 assert_eq!(event.note, note);
347 }
348
349 #[test]
350 fn schedule_multiple_events() {
351 let mut scheduler = Scheduler::default();
352
353 scheduler.schedule_in(0, 5, vec![create_test_action()], Some("Event 1".to_string()));
354 scheduler.schedule_in(0, 3, vec![create_test_action()], Some("Event 2".to_string()));
355 scheduler.schedule_on(10, vec![create_test_action()], Some("Event 3".to_string()));
356
357 assert_eq!(scheduler.events.len(), 3);
358 assert_eq!(scheduler.heap.len(), 3);
359 }
360
361 #[test]
362 fn pop_due_returns_none_when_nothing_due() {
363 let mut scheduler = Scheduler::default();
364 scheduler.schedule_in(5, 5, vec![create_test_action()], None);
365
366 let result = scheduler.pop_due(8); assert!(result.is_none());
368 assert_eq!(scheduler.heap.len(), 1); }
370
371 #[test]
372 fn pop_due_returns_event_when_due() {
373 let mut scheduler = Scheduler::default();
374 let actions = vec![create_test_action()];
375 let note = Some("Due event".to_string());
376
377 scheduler.schedule_in(5, 3, actions.clone(), note.clone());
378
379 let result = scheduler.pop_due(8); assert!(result.is_some());
381
382 let event = result.unwrap();
383 assert_eq!(event.on_turn, 8);
384 assert_eq!(event.note, note);
385 assert_eq!(event.actions.len(), 1);
386
387 assert!(scheduler.heap.is_empty());
389 }
390
391 #[test]
392 fn pop_due_returns_event_when_overdue() {
393 let mut scheduler = Scheduler::default();
394 scheduler.schedule_in(5, 3, vec![create_test_action()], Some("Overdue event".to_string()));
395
396 let result = scheduler.pop_due(10); assert!(result.is_some());
398
399 let event = result.unwrap();
400 assert_eq!(event.on_turn, 8);
401 }
402
403 #[test]
404 fn events_fire_in_correct_order() {
405 let mut scheduler = Scheduler::default();
406
407 scheduler.schedule_on(15, create_test_actions(1), Some("Third".to_string()));
409 scheduler.schedule_on(5, create_test_actions(1), Some("First".to_string()));
410 scheduler.schedule_on(10, create_test_actions(1), Some("Second".to_string()));
411
412 let first = scheduler.pop_due(5).unwrap();
414 assert_eq!(first.note, Some("First".to_string()));
415 assert_eq!(first.on_turn, 5);
416
417 let second = scheduler.pop_due(10).unwrap();
418 assert_eq!(second.note, Some("Second".to_string()));
419 assert_eq!(second.on_turn, 10);
420
421 let third = scheduler.pop_due(15).unwrap();
422 assert_eq!(third.note, Some("Third".to_string()));
423 assert_eq!(third.on_turn, 15);
424
425 assert!(scheduler.pop_due(20).is_none());
427 }
428
429 #[test]
430 fn events_with_same_turn_fire_in_fifo_order() {
431 let mut scheduler = Scheduler::default();
432
433 scheduler.schedule_on(10, create_test_actions(1), Some("First scheduled".to_string()));
435 scheduler.schedule_on(10, create_test_actions(1), Some("Second scheduled".to_string()));
436 scheduler.schedule_on(10, create_test_actions(1), Some("Third scheduled".to_string()));
437
438 let first = scheduler.pop_due(10).unwrap();
440 assert_eq!(first.note, Some("First scheduled".to_string()));
441
442 let second = scheduler.pop_due(10).unwrap();
443 assert_eq!(second.note, Some("Second scheduled".to_string()));
444
445 let third = scheduler.pop_due(10).unwrap();
446 assert_eq!(third.note, Some("Third scheduled".to_string()));
447 }
448
449 #[test]
450 fn pop_due_multiple_events_same_turn() {
451 let mut scheduler = Scheduler::default();
452
453 scheduler.schedule_on(5, create_test_actions(1), Some("Event A".to_string()));
454 scheduler.schedule_on(5, create_test_actions(1), Some("Event B".to_string()));
455 scheduler.schedule_on(10, create_test_actions(1), Some("Event C".to_string()));
456
457 let mut events_turn_5 = Vec::new();
459 while let Some(event) = scheduler.pop_due(5) {
460 if event.on_turn == 5 {
461 events_turn_5.push(event);
462 } else {
463 break;
464 }
465 }
466
467 assert_eq!(events_turn_5.len(), 2);
468 assert!(events_turn_5.iter().any(|e| e.note == Some("Event A".to_string())));
469 assert!(events_turn_5.iter().any(|e| e.note == Some("Event B".to_string())));
470
471 let event_c = scheduler.pop_due(10).unwrap();
473 assert_eq!(event_c.note, Some("Event C".to_string()));
474 }
475
476 #[test]
477 fn schedule_with_no_note() {
478 let mut scheduler = Scheduler::default();
479 scheduler.schedule_in(0, 5, vec![create_test_action()], None);
480
481 let event = scheduler.pop_due(5).unwrap();
482 assert_eq!(event.note, None);
483 }
484
485 #[test]
486 fn schedule_with_empty_actions() {
487 let mut scheduler = Scheduler::default();
488 scheduler.schedule_in(0, 5, vec![], Some("Empty actions".to_string()));
489
490 let event = scheduler.pop_due(5).unwrap();
491 assert!(event.actions.is_empty());
492 assert_eq!(event.note, Some("Empty actions".to_string()));
493 }
494
495 #[test]
496 fn schedule_with_multiple_actions() {
497 let mut scheduler = Scheduler::default();
498 let actions = create_test_actions(5);
499
500 scheduler.schedule_in(0, 3, actions.clone(), Some("Multi-action event".to_string()));
501
502 let event = scheduler.pop_due(3).unwrap();
503 assert_eq!(event.actions.len(), 5);
504 }
505
506 #[test]
507 fn scheduled_event_default() {
508 let event = ScheduledEvent::default();
509 assert_eq!(event.on_turn, 0);
510 assert!(event.actions.is_empty());
511 assert_eq!(event.note, None);
512 }
513
514 #[test]
515 fn mem_take_leaves_default_placeholder() {
516 let mut scheduler = Scheduler::default();
517 scheduler.schedule_in(0, 5, vec![create_test_action()], Some("Test".to_string()));
518
519 let _event = scheduler.pop_due(5).unwrap();
520
521 assert_eq!(scheduler.events.len(), 1);
523 let placeholder = &scheduler.events[0];
524 assert_eq!(placeholder.on_turn, 0);
525 assert!(placeholder.actions.is_empty());
526 assert_eq!(placeholder.note, None);
527 }
528
529 #[test]
530 fn compact_events_when_placeholder_threshold_exceeded() {
531 let mut scheduler = Scheduler::default();
532
533 for i in 1..=6 {
534 scheduler.schedule_on(i, create_test_actions(1), Some(format!("Event {i}")));
535 }
536
537 for turn in 1..=5 {
538 let ev = scheduler.pop_due(turn).unwrap();
539 assert_eq!(ev.note, Some(format!("Event {turn}")));
540 }
541
542 assert_eq!(scheduler.events.len(), 1);
543 assert_eq!(scheduler.heap.len(), 1);
544 assert_eq!(scheduler.events[0].note, Some("Event 6".to_string()));
545
546 let final_event = scheduler.pop_due(6).unwrap();
547 assert_eq!(final_event.note, Some("Event 6".to_string()));
548 }
549
550 #[test]
551 fn edge_case_turn_zero() {
552 let mut scheduler = Scheduler::default();
553 scheduler.schedule_on(0, vec![create_test_action()], Some("Turn zero".to_string()));
554
555 let event = scheduler.pop_due(0).unwrap();
556 assert_eq!(event.on_turn, 0);
557 }
558
559 #[test]
560 fn edge_case_large_turn_numbers() {
561 let mut scheduler = Scheduler::default();
562 let large_turn = usize::MAX - 1000;
563
564 scheduler.schedule_on(large_turn, vec![create_test_action()], Some("Large turn".to_string()));
565
566 let event = scheduler.pop_due(large_turn).unwrap();
567 assert_eq!(event.on_turn, large_turn);
568 }
569
570 #[test]
571 fn serialization_roundtrip() {
572 let mut scheduler = Scheduler::default();
573 scheduler.schedule_in(5, 10, create_test_actions(3), Some("Serialization test".to_string()));
574 scheduler.schedule_on(20, create_test_actions(2), None);
575
576 let serialized = serde_json::to_string(&scheduler).expect("Failed to serialize");
578
579 let deserialized: Scheduler = serde_json::from_str(&serialized).expect("Failed to deserialize");
581
582 assert_eq!(deserialized.events.len(), scheduler.events.len());
584 assert_eq!(deserialized.heap.len(), scheduler.heap.len());
585
586 let mut des_scheduler = deserialized;
588 let event1 = des_scheduler.pop_due(15).unwrap();
589 assert_eq!(event1.on_turn, 15);
590 assert_eq!(event1.actions.len(), 3);
591
592 let event2 = des_scheduler.pop_due(20).unwrap();
593 assert_eq!(event2.on_turn, 20);
594 assert_eq!(event2.actions.len(), 2);
595 assert_eq!(event2.note, None);
596 }
597}