1#![cfg_attr(
2 not(test),
3 expect(
4 dead_code,
5 reason = "custom scheduler control hooks are exercised by tests and reserved for keyed runtimes"
6 )
7)]
8
9use std::{collections::VecDeque, sync::Arc};
10
11use crate::command::brigadier::{CommandSyntaxError, ContextChainStage};
12
13use super::{
14 CommandResultCallback, ExecutionCommandSource, SteelContextChain, SteelExecutor, SteelModifier,
15};
16
17const MAX_COMMAND_QUEUE_DEPTH: usize = 10_000_000;
18
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
21pub(crate) struct ChainModifiers(u8);
22
23impl ChainModifiers {
24 const FORKED: u8 = 1;
25 const RETURN: u8 = 2;
26
27 pub(crate) const fn is_forked(self) -> bool {
28 self.0 & Self::FORKED != 0
29 }
30
31 pub(crate) const fn is_return(self) -> bool {
32 self.0 & Self::RETURN != 0
33 }
34
35 pub(crate) const fn with_forked(self) -> Self {
36 Self(self.0 | Self::FORKED)
37 }
38
39 pub(crate) const fn with_return(self) -> Self {
40 Self(self.0 | Self::RETURN)
41 }
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub(crate) enum ExecutionStop {
47 Completed,
48 Suspended,
49 CommandLimit,
50 QueueOverflow,
51}
52
53#[derive(Clone)]
54pub(crate) struct Frame {
55 depth: usize,
56 return_value_consumer: CommandResultCallback,
57 discard: FrameDiscard,
58}
59
60#[derive(Clone, Copy)]
61enum FrameDiscard {
62 All,
63 AtOrAbove(usize),
64}
65
66impl Frame {
67 pub(crate) const fn depth(&self) -> usize {
68 self.depth
69 }
70
71 pub(crate) fn return_success(&self, value: i32) {
72 self.return_value_consumer.on_result(true, value);
73 }
74
75 pub(crate) fn return_failure(&self) {
76 self.return_value_consumer.on_result(false, 0);
77 }
78}
79
80pub(crate) trait EntryAction<S>: Send + 'static
81where
82 S: ExecutionCommandSource,
83{
84 fn execute(self: Box<Self>, context: &mut CommandExecutionContext<S>, frame: Frame);
85
86 fn runs_after_command_limit(&self) -> bool {
87 false
88 }
89
90 fn cancel(&mut self) {}
91}
92
93pub(crate) enum CommandResultSuspensionPoll {
95 Pending,
96 Ready(Result<i32, CommandSyntaxError>),
97}
98
99#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
101pub enum CommandSuspensionOrder {
102 #[default]
104 Source,
105 Global,
107}
108
109pub(crate) trait CommandResultSuspension: Send + 'static {
111 fn order(&self) -> CommandSuspensionOrder {
112 CommandSuspensionOrder::Source
113 }
114
115 fn poll(&mut self) -> CommandResultSuspensionPoll;
116
117 fn cancel(&mut self) {}
118}
119
120pub(crate) enum CommandSuspensionPoll<S>
122where
123 S: ExecutionCommandSource,
124{
125 Pending,
126 Ready(Box<dyn EntryAction<S>>),
127}
128
129impl<S> CommandSuspensionPoll<S>
130where
131 S: ExecutionCommandSource,
132{
133 pub(crate) fn resume(action: impl EntryAction<S>) -> Self {
134 Self::Ready(Box::new(action))
135 }
136}
137
138pub(crate) trait CommandSuspension<S>: Send + 'static
140where
141 S: ExecutionCommandSource,
142{
143 fn order(&self) -> CommandSuspensionOrder {
144 CommandSuspensionOrder::Source
145 }
146
147 fn poll(&mut self) -> CommandSuspensionPoll<S>;
148
149 fn cancel(&mut self) {}
150}
151
152struct CommandQueueEntry<S>
153where
154 S: ExecutionCommandSource,
155{
156 frame: Frame,
157 action: Box<dyn EntryAction<S>>,
158}
159
160struct ActiveSuspension<S>
161where
162 S: ExecutionCommandSource,
163{
164 frame: Frame,
165 suspension: Box<dyn CommandSuspension<S>>,
166}
167
168struct SuspensionResumeAction<S>
169where
170 S: ExecutionCommandSource,
171{
172 action: Box<dyn EntryAction<S>>,
173}
174
175impl<S> EntryAction<S> for SuspensionResumeAction<S>
176where
177 S: ExecutionCommandSource,
178{
179 fn execute(self: Box<Self>, context: &mut CommandExecutionContext<S>, frame: Frame) {
180 self.action.execute(context, frame);
181 }
182
183 fn runs_after_command_limit(&self) -> bool {
184 true
185 }
186
187 fn cancel(&mut self) {
188 self.action.cancel();
189 }
190}
191
192pub(crate) struct CommandExecutionContext<S>
194where
195 S: ExecutionCommandSource,
196{
197 command_limit: usize,
198 fork_limit: usize,
199 queue_limit: usize,
200 command_quota: usize,
201 queue_overflow: bool,
202 command_queue: VecDeque<CommandQueueEntry<S>>,
203 new_top_commands: Vec<CommandQueueEntry<S>>,
204 suspension: Option<ActiveSuspension<S>>,
205 current_frame_depth: usize,
206}
207
208impl<S> CommandExecutionContext<S>
209where
210 S: ExecutionCommandSource,
211{
212 pub(crate) fn new(command_limit: usize, fork_limit: usize) -> Self {
213 let command_limit = command_limit.max(1);
214 Self {
215 command_limit,
216 fork_limit,
217 queue_limit: MAX_COMMAND_QUEUE_DEPTH,
218 command_quota: command_limit,
219 queue_overflow: false,
220 command_queue: VecDeque::new(),
221 new_top_commands: Vec::new(),
222 suspension: None,
223 current_frame_depth: 0,
224 }
225 }
226
227 #[cfg(test)]
228 pub(super) fn with_queue_limit(
229 command_limit: usize,
230 fork_limit: usize,
231 queue_limit: usize,
232 ) -> Self {
233 let mut context = Self::new(command_limit, fork_limit);
234 context.queue_limit = queue_limit;
235 context
236 }
237
238 pub(crate) fn queue_initial_command(
239 &mut self,
240 chain: SteelContextChain<S>,
241 source: S,
242 return_value_consumer: CommandResultCallback,
243 ) {
244 let source = Arc::new(source);
245 let frame = self.create_top_frame(return_value_consumer);
246 self.queue_entry(CommandQueueEntry {
247 frame,
248 action: Box::new(BuildContextsAction {
249 chain,
250 original_source: Arc::clone(&source),
251 sources: vec![source],
252 modifiers: ChainModifiers::default(),
253 }),
254 });
255 }
256
257 pub(crate) fn run(&mut self) -> ExecutionStop {
258 if self.suspension.is_some() {
259 return ExecutionStop::Suspended;
260 }
261 if self.queue_overflow {
262 log::error!(
263 "Command execution stopped due to command queue overflow (max {})",
264 self.queue_limit
265 );
266 return ExecutionStop::QueueOverflow;
267 }
268
269 self.push_new_commands();
270 let stop = loop {
271 if self.command_quota == 0
272 && !self
273 .command_queue
274 .front()
275 .is_some_and(|entry| entry.action.runs_after_command_limit())
276 {
277 log::info!(
278 "Command execution stopped due to limit (executed {} commands)",
279 self.command_limit
280 );
281 break ExecutionStop::CommandLimit;
282 }
283
284 let Some(entry) = self.command_queue.pop_front() else {
285 break ExecutionStop::Completed;
286 };
287 self.current_frame_depth = entry.frame.depth;
288 entry.action.execute(self, entry.frame);
289 if self.queue_overflow {
290 log::error!(
291 "Command execution stopped due to command queue overflow (max {})",
292 self.queue_limit
293 );
294 break ExecutionStop::QueueOverflow;
295 }
296 if self.suspension.is_some() {
297 break ExecutionStop::Suspended;
298 }
299 self.push_new_commands();
300 };
301 self.current_frame_depth = 0;
302 stop
303 }
304
305 pub(crate) fn poll_suspension(&mut self) -> ExecutionStop {
307 let Some(mut active) = self.suspension.take() else {
308 return self.run();
309 };
310
311 match active.suspension.poll() {
312 CommandSuspensionPoll::Pending => {
313 self.suspension = Some(active);
314 ExecutionStop::Suspended
315 }
316 CommandSuspensionPoll::Ready(action) => {
317 self.queue_next(active.frame, SuspensionResumeAction { action });
318 self.run()
319 }
320 }
321 }
322
323 pub(crate) fn suspension_order(&self) -> Option<CommandSuspensionOrder> {
324 self.suspension
325 .as_ref()
326 .map(|active| active.suspension.order())
327 }
328
329 pub(crate) fn cancel(&mut self) {
331 if let Some(mut active) = self.suspension.take() {
332 active.suspension.cancel();
333 }
334 self.cancel_command_queue();
335 self.cancel_new_top_commands();
336 self.queue_overflow = false;
337 self.current_frame_depth = 0;
338 }
339
340 pub(crate) const fn fork_limit(&self) -> usize {
341 self.fork_limit
342 }
343
344 pub(crate) const fn increment_cost(&mut self) {
345 self.command_quota = self.command_quota.saturating_sub(1);
346 }
347
348 const fn create_top_frame(&self, return_value_consumer: CommandResultCallback) -> Frame {
349 if self.current_frame_depth == 0 {
350 return Frame {
351 depth: 0,
352 return_value_consumer,
353 discard: FrameDiscard::All,
354 };
355 }
356
357 let depth = self.current_frame_depth + 1;
358 Frame {
359 depth,
360 return_value_consumer,
361 discard: FrameDiscard::AtOrAbove(depth),
362 }
363 }
364
365 fn queue_next(&mut self, frame: Frame, action: impl EntryAction<S>) {
366 self.queue_entry(CommandQueueEntry {
367 frame,
368 action: Box::new(action),
369 });
370 }
371
372 fn queue_boxed(&mut self, frame: Frame, action: Box<dyn EntryAction<S>>) {
373 self.queue_entry(CommandQueueEntry { frame, action });
374 }
375
376 fn queue_entry(&mut self, mut entry: CommandQueueEntry<S>) {
377 if self
378 .new_top_commands
379 .len()
380 .saturating_add(self.command_queue.len())
381 > self.queue_limit
382 {
383 self.queue_overflow = true;
384 self.cancel_new_top_commands();
385 self.cancel_command_queue();
386 }
387 if self.queue_overflow {
388 entry.action.cancel();
389 return;
390 }
391 self.new_top_commands.push(entry);
392 }
393
394 fn push_new_commands(&mut self) {
395 while let Some(command) = self.new_top_commands.pop() {
396 self.command_queue.push_front(command);
397 }
398 }
399
400 fn discard(&mut self, frame: &Frame) {
401 match frame.discard {
402 FrameDiscard::All => self.cancel_command_queue(),
403 FrameDiscard::AtOrAbove(depth) => {
404 while self
405 .command_queue
406 .front()
407 .is_some_and(|entry| entry.frame.depth >= depth)
408 {
409 if let Some(mut entry) = self.command_queue.pop_front() {
410 entry.action.cancel();
411 }
412 }
413 }
414 }
415 }
416
417 fn cancel_command_queue(&mut self) {
418 for mut entry in self.command_queue.drain(..) {
419 entry.action.cancel();
420 }
421 }
422
423 fn cancel_new_top_commands(&mut self) {
424 for mut entry in self.new_top_commands.drain(..) {
425 entry.action.cancel();
426 }
427 }
428}
429
430impl<S> Drop for CommandExecutionContext<S>
431where
432 S: ExecutionCommandSource,
433{
434 fn drop(&mut self) {
435 self.cancel();
436 }
437}
438
439pub(crate) struct ExecutionControl<'context, S>
441where
442 S: ExecutionCommandSource,
443{
444 context: &'context mut CommandExecutionContext<S>,
445 frame: Frame,
446}
447
448impl<'context, S> ExecutionControl<'context, S>
449where
450 S: ExecutionCommandSource,
451{
452 pub(crate) const fn new(
453 context: &'context mut CommandExecutionContext<S>,
454 frame: Frame,
455 ) -> Self {
456 Self { context, frame }
457 }
458
459 pub(crate) const fn current_frame(&self) -> &Frame {
460 &self.frame
461 }
462
463 pub(crate) fn queue_next(&mut self, action: impl EntryAction<S>) {
464 self.context.queue_next(self.frame.clone(), action);
465 }
466
467 pub(crate) fn suspend(&mut self, suspension: impl CommandSuspension<S>) {
469 self.queue_next(SuspendAction {
470 suspension: Box::new(suspension),
471 });
472 }
473
474 pub(crate) fn queue_contexts(
475 &mut self,
476 chain: SteelContextChain<S>,
477 original_source: Arc<S>,
478 sources: Vec<Arc<S>>,
479 modifiers: ChainModifiers,
480 ) {
481 self.context.queue_next(
482 self.frame.clone(),
483 BuildContextsAction {
484 chain,
485 original_source,
486 sources,
487 modifiers,
488 },
489 );
490 }
491
492 pub(crate) fn discard_frame(&mut self) {
493 self.context.discard(&self.frame);
494 }
495
496 pub(crate) fn queue_fallthrough(&mut self) {
497 self.queue_next(FallthroughAction);
498 }
499
500 pub(crate) fn return_success(&mut self, result: i32) {
501 self.frame.return_success(result);
502 self.context.discard(&self.frame);
503 }
504
505 pub(crate) fn return_failure(&mut self) {
506 self.frame.return_failure();
507 self.context.discard(&self.frame);
508 }
509}
510
511struct SuspendAction<S>
512where
513 S: ExecutionCommandSource,
514{
515 suspension: Box<dyn CommandSuspension<S>>,
516}
517
518impl<S> EntryAction<S> for SuspendAction<S>
519where
520 S: ExecutionCommandSource,
521{
522 fn execute(self: Box<Self>, context: &mut CommandExecutionContext<S>, frame: Frame) {
523 assert!(
524 context.suspension.is_none(),
525 "a command execution cannot activate two suspensions at once"
526 );
527 context.suspension = Some(ActiveSuspension {
528 frame,
529 suspension: self.suspension,
530 });
531 }
532
533 fn runs_after_command_limit(&self) -> bool {
534 true
535 }
536
537 fn cancel(&mut self) {
538 self.suspension.cancel();
539 }
540}
541
542struct BuildContextsAction<S>
543where
544 S: ExecutionCommandSource,
545{
546 chain: SteelContextChain<S>,
547 original_source: Arc<S>,
548 sources: Vec<Arc<S>>,
549 modifiers: ChainModifiers,
550}
551
552impl<S> EntryAction<S> for BuildContextsAction<S>
553where
554 S: ExecutionCommandSource,
555{
556 fn execute(self: Box<Self>, context: &mut CommandExecutionContext<S>, frame: Frame) {
557 let Self {
558 mut chain,
559 original_source,
560 mut sources,
561 mut modifiers,
562 } = *self;
563
564 while chain.stage() == ContextChainStage::Modify {
565 sources.retain(|source| source.execution_is_current());
566 if sources.is_empty() {
567 if modifiers.is_return() {
568 context.queue_next(frame, FallthroughAction);
569 }
570 return;
571 }
572 if chain.top_context().is_forked() {
573 modifiers = modifiers.with_forked();
574 }
575
576 match chain.top_context().modifier() {
577 Some(SteelModifier::Custom(modifier)) => {
578 let mut control = ExecutionControl::new(context, frame);
579 modifier.apply(original_source, sources, &chain, modifiers, &mut control);
580 return;
581 }
582 Some(SteelModifier::Standard(modifier)) => {
583 context.increment_cost();
584 let mut next_sources = Vec::new();
585 for source in sources {
586 let command_context = chain.top_context().copy_for(Arc::clone(&source));
587 let new_sources = match modifier(&command_context) {
588 Ok(sources) => sources,
589 Err(error) => {
590 if modifiers.is_forked() {
591 continue;
592 }
593 source.handle_error(&error, false);
594 return;
595 }
596 };
597 if next_sources.len().saturating_add(new_sources.len())
598 >= context.fork_limit()
599 {
600 let error = CommandSyntaxError::dynamic(format!(
601 "Command fork limit reached ({})",
602 context.fork_limit()
603 ));
604 original_source.handle_error(&error, modifiers.is_forked());
605 return;
606 }
607 next_sources.extend(new_sources.into_iter().map(Arc::new));
608 }
609 sources = next_sources;
610 }
611 None => {}
612 }
613
614 let Some(next_stage) = chain.next_stage() else {
615 unreachable!("a modifying command stage always has a following stage")
616 };
617 chain = next_stage;
618 }
619
620 sources.retain(|source| source.execution_is_current());
621 if sources.is_empty() {
622 if modifiers.is_return() {
623 context.queue_next(frame, FallthroughAction);
624 }
625 return;
626 }
627
628 let Some(executor) = chain.top_context().executor() else {
629 unreachable!("a context chain's final stage is always executable")
630 };
631 match executor {
632 SteelExecutor::Custom(executor) => {
633 for source in sources {
634 let mut control = ExecutionControl::new(context, frame.clone());
635 executor.run(source, &chain, modifiers, &mut control);
636 }
637 }
638 SteelExecutor::Standard(_) | SteelExecutor::Suspended(_) => {
639 if modifiers.is_return() {
640 let Some(source) = sources.into_iter().next() else {
641 unreachable!("empty source lists return before terminal scheduling")
642 };
643 let callback = CommandResultCallback::chain(
644 source.callback(),
645 frame.return_value_consumer.clone(),
646 );
647 let source = Arc::new(source.with_callback(callback));
648 schedule_executions(context, frame, chain, vec![source], modifiers);
649 } else {
650 schedule_executions(context, frame, chain, sources, modifiers);
651 }
652 }
653 }
654 }
655}
656
657fn schedule_executions<S>(
658 context: &mut CommandExecutionContext<S>,
659 frame: Frame,
660 chain: SteelContextChain<S>,
661 sources: Vec<Arc<S>>,
662 modifiers: ChainModifiers,
663) where
664 S: ExecutionCommandSource,
665{
666 match sources.len() {
667 0 => {}
668 1 | 2 => {
669 for source in sources {
670 context.queue_next(
671 frame.clone(),
672 ExecuteAction {
673 chain: chain.clone(),
674 source,
675 modifiers,
676 },
677 );
678 }
679 }
680 _ => context.queue_next(
681 frame,
682 ExecuteContinuation {
683 chain,
684 sources: sources.into(),
685 modifiers,
686 },
687 ),
688 }
689}
690
691struct ExecuteContinuation<S>
692where
693 S: ExecutionCommandSource,
694{
695 chain: SteelContextChain<S>,
696 sources: VecDeque<Arc<S>>,
697 modifiers: ChainModifiers,
698}
699
700impl<S> EntryAction<S> for ExecuteContinuation<S>
701where
702 S: ExecutionCommandSource,
703{
704 fn execute(mut self: Box<Self>, context: &mut CommandExecutionContext<S>, frame: Frame) {
705 let Some(source) = self.sources.pop_front() else {
706 return;
707 };
708 context.queue_next(
709 frame.clone(),
710 ExecuteAction {
711 chain: self.chain.clone(),
712 source,
713 modifiers: self.modifiers,
714 },
715 );
716 if !self.sources.is_empty() {
717 context.queue_boxed(frame, self);
718 }
719 }
720}
721
722struct ExecuteAction<S>
723where
724 S: ExecutionCommandSource,
725{
726 chain: SteelContextChain<S>,
727 source: Arc<S>,
728 modifiers: ChainModifiers,
729}
730
731impl<S> EntryAction<S> for ExecuteAction<S>
732where
733 S: ExecutionCommandSource,
734{
735 fn execute(self: Box<Self>, context: &mut CommandExecutionContext<S>, frame: Frame) {
736 let Self {
737 chain,
738 source,
739 modifiers,
740 } = *self;
741 if !source.execution_is_current() {
742 source.callback().on_result(false, 0);
743 return;
744 }
745 context.increment_cost();
746 let command_context = chain.top_context().copy_for(Arc::clone(&source));
747 let Some(executor) = command_context.executor() else {
748 unreachable!("a scheduled execute action always has a terminal executor")
749 };
750 match executor {
751 SteelExecutor::Standard(executor) => {
752 complete_command_result(source.as_ref(), modifiers, executor(&command_context));
753 }
754 SteelExecutor::Suspended(executor) => match executor(&command_context) {
755 Ok(suspension) => context.queue_next(
756 frame,
757 SuspendAction {
758 suspension: Box::new(CommandResultSuspensionAdapter {
759 suspension,
760 source,
761 modifiers,
762 }),
763 },
764 ),
765 Err(error) => complete_command_result(source.as_ref(), modifiers, Err(error)),
766 },
767 SteelExecutor::Custom(_) => {
768 unreachable!("custom executors run directly while building contexts")
769 }
770 }
771 }
772}
773
774struct CommandResultSuspensionAdapter<S>
775where
776 S: ExecutionCommandSource,
777{
778 suspension: Box<dyn CommandResultSuspension>,
779 source: Arc<S>,
780 modifiers: ChainModifiers,
781}
782
783impl<S> CommandSuspension<S> for CommandResultSuspensionAdapter<S>
784where
785 S: ExecutionCommandSource,
786{
787 fn order(&self) -> CommandSuspensionOrder {
788 self.suspension.order()
789 }
790
791 fn poll(&mut self) -> CommandSuspensionPoll<S> {
792 if !self.source.execution_is_current() {
793 self.suspension.cancel();
794 return CommandSuspensionPoll::resume(UnavailableCommandResultAction {
795 source: Arc::clone(&self.source),
796 });
797 }
798 match self.suspension.poll() {
799 CommandResultSuspensionPoll::Pending => CommandSuspensionPoll::Pending,
800 CommandResultSuspensionPoll::Ready(result) => {
801 CommandSuspensionPoll::resume(CompleteCommandResultAction {
802 source: Arc::clone(&self.source),
803 modifiers: self.modifiers,
804 result,
805 })
806 }
807 }
808 }
809
810 fn cancel(&mut self) {
811 self.suspension.cancel();
812 }
813}
814
815struct UnavailableCommandResultAction<S>
816where
817 S: ExecutionCommandSource,
818{
819 source: Arc<S>,
820}
821
822impl<S> EntryAction<S> for UnavailableCommandResultAction<S>
823where
824 S: ExecutionCommandSource,
825{
826 fn execute(self: Box<Self>, _context: &mut CommandExecutionContext<S>, _frame: Frame) {
827 self.source.callback().on_result(false, 0);
828 }
829}
830
831struct CompleteCommandResultAction<S>
832where
833 S: ExecutionCommandSource,
834{
835 source: Arc<S>,
836 modifiers: ChainModifiers,
837 result: Result<i32, CommandSyntaxError>,
838}
839
840impl<S> EntryAction<S> for CompleteCommandResultAction<S>
841where
842 S: ExecutionCommandSource,
843{
844 fn execute(self: Box<Self>, _context: &mut CommandExecutionContext<S>, _frame: Frame) {
845 complete_command_result(self.source.as_ref(), self.modifiers, self.result);
846 }
847}
848
849fn complete_command_result<S>(
850 source: &S,
851 modifiers: ChainModifiers,
852 result: Result<i32, CommandSyntaxError>,
853) where
854 S: ExecutionCommandSource,
855{
856 match result {
857 Ok(result) => source.callback().on_result(true, result),
858 Err(error) => {
859 source.callback().on_result(false, 0);
860 if !modifiers.is_forked() {
861 source.handle_error(&error, false);
862 }
863 }
864 }
865}
866
867struct FallthroughAction;
868
869impl<S> EntryAction<S> for FallthroughAction
870where
871 S: ExecutionCommandSource,
872{
873 fn execute(self: Box<Self>, context: &mut CommandExecutionContext<S>, frame: Frame) {
874 frame.return_failure();
875 context.discard(&frame);
876 }
877}