Skip to main content

steel_core/command/execution/
runtime.rs

1#![cfg_attr(
2    not(test),
3    expect(
4        dead_code,
5        reason = "custom runtime variants are reserved for future keyed command integrations"
6    )
7)]
8
9use std::sync::Arc;
10
11use super::{
12    BiomeOrTag, BlockInput, BlockPredicate, ChainModifiers, CommandResultSuspension, CommandSource,
13    Coordinates, ExecutionCommandSource, ExecutionControl, GameProfileArgument, IntRange,
14    ItemPredicate, PermissionGroupName, ScoreHolderArgument, ScoreHolderWildcard,
15    SteelArgumentType, StructureOrTagKey, WorldArgument,
16    argument::{
17        ComponentValue, CoordinateAxes, DomainValue, EnchantmentValue, EntityTypeValue,
18        GameModeValue, IdentifierValue, ItemStackValue, NbtPathValue, ObjectiveValue,
19        SteelArgumentValue, TimeValue, TimelineValue, WorldClockValue,
20    },
21    selector::EntitySelector,
22};
23use crate::command::brigadier::{
24    ArgumentSuggestionContext, CommandContext, CommandNodeBuilder, CommandRedirectTarget,
25    CommandRuntime, CommandSyntaxError, ContextChain,
26};
27use crate::command::execution::argument::{AdvancementValue, DamageTypeValue};
28use crate::command::incorrectly_typed_argument;
29use crate::{
30    chunk::heightmap::HeightmapType,
31    entity::{EntityAnchor, SharedEntity},
32    permission::{PermissionMetadataExpression, PermissionRuleExpression},
33    player::Player,
34    scoreboard::ScoreHolder,
35};
36use steel_registry::advancement::registry::AdvancementRef;
37use steel_registry::damage_type::DamageTypeRef;
38use steel_registry::data_components::vanilla_components::SwingAnimationType;
39use steel_registry::{
40    enchantment::EnchantmentRef, entity_type::EntityTypeRef, item_stack::ItemStack,
41    timeline::TimelineRef, world_clock::WorldClockRef,
42};
43use steel_utils::{DowncastType, Identifier, nbt::NbtPath, translations, types::GameType};
44use text_components::TextComponent;
45
46/// Runtime model interpreted by Steel's tick-owned command scheduler.
47pub(crate) struct SteelCommandRuntime;
48
49pub(crate) type SteelCommandContext<S> = CommandContext<S, SteelCommandRuntime>;
50pub(crate) type SteelContextChain<S> = ContextChain<S, SteelCommandRuntime>;
51pub(crate) type SteelSuggestionContext<'context, S> =
52    ArgumentSuggestionContext<'context, S, SteelArgumentValue>;
53
54type StandardExecutor<S> =
55    dyn Fn(&SteelCommandContext<S>) -> Result<i32, CommandSyntaxError> + Send + Sync;
56type SuspendedExecutor<S> = dyn Fn(&SteelCommandContext<S>) -> Result<Box<dyn CommandResultSuspension>, CommandSyntaxError>
57    + Send
58    + Sync;
59type StandardModifier<S> =
60    dyn Fn(&SteelCommandContext<S>) -> Result<Vec<S>, CommandSyntaxError> + Send + Sync;
61
62/// A terminal executor stored in a Steel command graph.
63pub(crate) enum SteelExecutor<S>
64where
65    S: ExecutionCommandSource,
66{
67    Standard(Box<StandardExecutor<S>>),
68    Suspended(Box<SuspendedExecutor<S>>),
69    Custom(Arc<dyn CustomCommandExecutor<S>>),
70}
71
72/// A redirect modifier stored in a Steel command graph.
73pub(crate) enum SteelModifier<S>
74where
75    S: ExecutionCommandSource,
76{
77    Standard(Box<StandardModifier<S>>),
78    Custom(Arc<dyn CustomModifierExecutor<S>>),
79}
80
81/// Special terminal behavior that controls command frames or queues more work.
82pub(crate) trait CustomCommandExecutor<S>: Send + Sync
83where
84    S: ExecutionCommandSource,
85{
86    fn run(
87        &self,
88        source: Arc<S>,
89        chain: &SteelContextChain<S>,
90        modifiers: ChainModifiers,
91        control: &mut ExecutionControl<'_, S>,
92    );
93}
94
95/// Special redirect behavior that controls command frames or queues more work.
96pub(crate) trait CustomModifierExecutor<S>: Send + Sync
97where
98    S: ExecutionCommandSource,
99{
100    fn apply(
101        &self,
102        original_source: Arc<S>,
103        sources: Vec<Arc<S>>,
104        chain: &SteelContextChain<S>,
105        modifiers: ChainModifiers,
106        control: &mut ExecutionControl<'_, S>,
107    );
108}
109
110impl<S> CommandRuntime<S> for SteelCommandRuntime
111where
112    S: ExecutionCommandSource,
113{
114    type Argument = SteelArgumentType;
115    type ArgumentValue = SteelArgumentValue;
116    type Executor = SteelExecutor<S>;
117    type Modifier = SteelModifier<S>;
118}
119
120/// Creates a literal backed by Steel's runtime model.
121pub(crate) fn literal<S>(name: impl Into<Box<str>>) -> CommandNodeBuilder<S, SteelCommandRuntime>
122where
123    S: ExecutionCommandSource,
124{
125    CommandNodeBuilder::literal(name)
126}
127
128/// Creates an argument backed by Steel's runtime model.
129pub(crate) fn argument<S>(
130    name: impl Into<Box<str>>,
131    argument_type: impl Into<SteelArgumentType>,
132) -> CommandNodeBuilder<S, SteelCommandRuntime>
133where
134    S: ExecutionCommandSource,
135{
136    CommandNodeBuilder::argument(name, argument_type.into())
137}
138
139impl<S> CommandNodeBuilder<S, SteelCommandRuntime>
140where
141    S: ExecutionCommandSource,
142{
143    /// Attaches an ordinary synchronous executor.
144    #[must_use]
145    pub(crate) fn executes(
146        self,
147        executor: impl Fn(&SteelCommandContext<S>) -> Result<i32, CommandSyntaxError>
148        + Send
149        + Sync
150        + 'static,
151    ) -> Self {
152        self.executes_with_executor(Arc::new(SteelExecutor::Standard(Box::new(executor))))
153    }
154
155    /// Attaches an ordinary executor whose command result is produced across ticks.
156    #[must_use]
157    pub(crate) fn executes_suspended<T>(
158        self,
159        executor: impl Fn(&SteelCommandContext<S>) -> Result<T, CommandSyntaxError>
160        + Send
161        + Sync
162        + 'static,
163    ) -> Self
164    where
165        T: CommandResultSuspension,
166    {
167        let executor = move |context: &SteelCommandContext<S>| {
168            executor(context)
169                .map(|suspension| Box::new(suspension) as Box<dyn CommandResultSuspension>)
170        };
171        self.executes_with_executor(Arc::new(SteelExecutor::Suspended(Box::new(executor))))
172    }
173
174    /// Attaches an internal executor with frame and queue control.
175    #[must_use]
176    pub(crate) fn executes_custom(self, executor: impl CustomCommandExecutor<S> + 'static) -> Self {
177        self.executes_with_executor(Arc::new(SteelExecutor::Custom(Arc::new(executor))))
178    }
179
180    /// Redirects parsing and transforms the source once before continuing.
181    #[must_use]
182    pub(crate) fn redirects_with(
183        self,
184        target: impl Into<CommandRedirectTarget>,
185        modifier: impl Fn(&SteelCommandContext<S>) -> Result<S, CommandSyntaxError>
186        + Send
187        + Sync
188        + 'static,
189    ) -> Self {
190        let modifier = SteelModifier::Standard(Box::new(move |context| {
191            modifier(context).map(|source| vec![source])
192        }));
193        self.redirects_with_modifier(target, Arc::new(modifier), false)
194    }
195
196    /// Redirects parsing and expands one source into zero or more sources.
197    #[must_use]
198    pub(crate) fn forks(
199        self,
200        target: impl Into<CommandRedirectTarget>,
201        modifier: impl Fn(&SteelCommandContext<S>) -> Result<Vec<S>, CommandSyntaxError>
202        + Send
203        + Sync
204        + 'static,
205    ) -> Self {
206        self.redirects_with_modifier(
207            target,
208            Arc::new(SteelModifier::Standard(Box::new(modifier))),
209            true,
210        )
211    }
212
213    /// Redirects with an internal modifier that controls frames or queued work.
214    #[must_use]
215    pub(crate) fn redirects_custom(
216        self,
217        target: impl Into<CommandRedirectTarget>,
218        modifier: impl CustomModifierExecutor<S> + 'static,
219        forks: bool,
220    ) -> Self {
221        self.redirects_with_modifier(
222            target,
223            Arc::new(SteelModifier::Custom(Arc::new(modifier))),
224            forks,
225        )
226    }
227}
228
229impl<S> SteelCommandContext<S>
230where
231    S: ExecutionCommandSource,
232{
233    fn typed_argument<T: DowncastType>(&self, name: &str) -> Result<&T, CommandSyntaxError> {
234        self.argument(name)?
235            .downcast_ref::<T>()
236            .ok_or_else(|| incorrectly_typed_argument(name))
237    }
238
239    /// Returns a parsed Minecraft time argument in ticks.
240    pub(crate) fn time(&self, name: &str) -> Result<i32, CommandSyntaxError> {
241        self.typed_argument::<TimeValue>(name).map(|value| value.0)
242    }
243
244    pub(crate) fn swing_animation(
245        &self,
246        name: &str,
247    ) -> Result<SwingAnimationType, CommandSyntaxError> {
248        self.typed_argument::<super::swing_animation::SwingAnimationValue>(name)
249            .map(|value| value.0)
250    }
251
252    /// Returns a parsed coordinate expression without resolving it early.
253    pub(crate) fn coordinates(&self, name: &str) -> Result<Coordinates, CommandSyntaxError> {
254        self.typed_argument::<Coordinates>(name).copied()
255    }
256
257    /// Returns a parsed entity position anchor.
258    pub(crate) fn entity_anchor(&self, name: &str) -> Result<EntityAnchor, CommandSyntaxError> {
259        self.typed_argument::<EntityAnchor>(name).copied()
260    }
261
262    pub(crate) fn swizzle(&self, name: &str) -> Result<CoordinateAxes, CommandSyntaxError> {
263        self.typed_argument::<CoordinateAxes>(name).copied()
264    }
265
266    pub(crate) fn heightmap(&self, name: &str) -> Result<HeightmapType, CommandSyntaxError> {
267        self.typed_argument::<HeightmapType>(name).copied()
268    }
269
270    pub(crate) fn score_holder_argument(
271        &self,
272        name: &str,
273    ) -> Result<&ScoreHolderArgument, CommandSyntaxError> {
274        self.typed_argument(name)
275    }
276
277    pub(crate) fn objective_name(&self, name: &str) -> Result<&str, CommandSyntaxError> {
278        self.typed_argument::<ObjectiveValue>(name)
279            .map(|value| value.0.as_ref())
280    }
281
282    pub(crate) fn int_range(&self, name: &str) -> Result<IntRange, CommandSyntaxError> {
283        self.typed_argument::<IntRange>(name).copied()
284    }
285
286    pub(crate) fn biome_or_tag(&self, name: &str) -> Result<&BiomeOrTag, CommandSyntaxError> {
287        self.typed_argument(name)
288    }
289
290    pub(crate) fn structure_or_tag_key(
291        &self,
292        name: &str,
293    ) -> Result<&StructureOrTagKey, CommandSyntaxError> {
294        self.typed_argument(name)
295    }
296
297    pub(crate) fn block_predicate(
298        &self,
299        name: &str,
300    ) -> Result<&BlockPredicate, CommandSyntaxError> {
301        self.typed_argument(name)
302    }
303
304    pub(crate) fn block_input(&self, name: &str) -> Result<&BlockInput, CommandSyntaxError> {
305        self.typed_argument(name)
306    }
307
308    /// Returns a configured Steel domain name.
309    pub(crate) fn domain(&self, name: &str) -> Result<&str, CommandSyntaxError> {
310        self.typed_argument::<DomainValue>(name)
311            .map(|value| value.0.as_ref())
312    }
313
314    pub(crate) fn world_argument(&self, name: &str) -> Result<&WorldArgument, CommandSyntaxError> {
315        self.typed_argument(name)
316    }
317
318    /// Returns a parsed vanilla game mode.
319    pub(crate) fn game_mode(&self, name: &str) -> Result<GameType, CommandSyntaxError> {
320        self.typed_argument::<GameModeValue>(name)
321            .map(|value| value.0)
322    }
323
324    pub(crate) fn entity_type(&self, name: &str) -> Result<EntityTypeRef, CommandSyntaxError> {
325        self.typed_argument::<EntityTypeValue>(name)
326            .map(|value| value.0)
327    }
328
329    pub(crate) fn enchantment(&self, name: &str) -> Result<EnchantmentRef, CommandSyntaxError> {
330        self.typed_argument::<EnchantmentValue>(name)
331            .map(|value| value.0)
332    }
333
334    pub(crate) fn damage_type(&self, name: &str) -> Result<DamageTypeRef, CommandSyntaxError> {
335        self.typed_argument::<DamageTypeValue>(name)
336            .map(|value| value.0)
337    }
338
339    pub(crate) fn item_stack(&self, name: &str) -> Result<&ItemStack, CommandSyntaxError> {
340        self.typed_argument::<ItemStackValue>(name)
341            .map(|value| &value.0)
342    }
343
344    pub(crate) fn item_predicate(&self, name: &str) -> Result<&ItemPredicate, CommandSyntaxError> {
345        self.typed_argument(name)
346    }
347
348    pub(crate) fn text_component(&self, name: &str) -> Result<&TextComponent, CommandSyntaxError> {
349        self.typed_argument::<ComponentValue>(name)
350            .map(|value| &value.0)
351    }
352
353    pub(crate) fn nbt_path(&self, name: &str) -> Result<&NbtPath, CommandSyntaxError> {
354        self.typed_argument::<NbtPathValue>(name)
355            .map(|value| &value.0)
356    }
357
358    pub(crate) fn identifier(&self, name: &str) -> Result<&Identifier, CommandSyntaxError> {
359        self.typed_argument::<IdentifierValue>(name)
360            .map(|value| &value.0)
361    }
362
363    pub(crate) fn world_clock(&self, name: &str) -> Result<WorldClockRef, CommandSyntaxError> {
364        self.typed_argument::<WorldClockValue>(name)
365            .map(|value| value.0)
366    }
367
368    pub(crate) fn timeline(&self, name: &str) -> Result<TimelineRef, CommandSyntaxError> {
369        self.typed_argument::<TimelineValue>(name)
370            .map(|value| value.0)
371    }
372
373    pub(crate) fn advancement(&self, name: &str) -> Result<AdvancementRef, CommandSyntaxError> {
374        self.typed_argument::<AdvancementValue>(name)
375            .map(|value| value.0)
376    }
377
378    pub(crate) fn entity_selector(
379        &self,
380        name: &str,
381    ) -> Result<&EntitySelector, CommandSyntaxError> {
382        self.typed_argument(name)
383    }
384
385    pub(crate) fn game_profile_argument(
386        &self,
387        name: &str,
388    ) -> Result<&GameProfileArgument, CommandSyntaxError> {
389        self.typed_argument(name)
390    }
391
392    pub(crate) fn permission_rule_expression(
393        &self,
394        name: &str,
395    ) -> Result<&PermissionRuleExpression, CommandSyntaxError> {
396        self.typed_argument(name)
397    }
398
399    pub(crate) fn permission_metadata_expression(
400        &self,
401        name: &str,
402    ) -> Result<&PermissionMetadataExpression, CommandSyntaxError> {
403        self.typed_argument(name)
404    }
405
406    pub(crate) fn permission_group(
407        &self,
408        name: &str,
409    ) -> Result<&PermissionGroupName, CommandSyntaxError> {
410        self.typed_argument(name)
411    }
412}
413
414impl<S> SteelSuggestionContext<'_, S> {
415    fn typed_argument<T: DowncastType>(&self, name: &str) -> Result<&T, CommandSyntaxError> {
416        self.argument(name)?
417            .downcast_ref::<T>()
418            .ok_or_else(|| incorrectly_typed_argument(name))
419    }
420
421    /// Returns a parsed advancement argument.
422    pub(crate) fn advancement(&self, name: &str) -> Result<AdvancementRef, CommandSyntaxError> {
423        self.typed_argument::<AdvancementValue>(name)
424            .map(|value| value.0)
425    }
426}
427
428impl SteelCommandContext<CommandSource> {
429    pub(crate) fn score_holders(
430        &self,
431        name: &str,
432        wildcard: ScoreHolderWildcard,
433    ) -> Result<Vec<ScoreHolder>, CommandSyntaxError> {
434        let holders = self
435            .score_holder_argument(name)?
436            .resolve(self.source(), wildcard)?;
437        if holders.is_empty() {
438            Err(CommandSyntaxError::dynamic(TextComponent::from(
439                &translations::ARGUMENT_SCORE_HOLDER_EMPTY,
440            )))
441        } else {
442            Ok(holders)
443        }
444    }
445
446    pub(crate) fn score_holder(&self, name: &str) -> Result<ScoreHolder, CommandSyntaxError> {
447        let mut holders = self.score_holders(name, ScoreHolderWildcard::Empty)?;
448        Ok(holders.remove(0))
449    }
450
451    pub(crate) fn optional_entities(
452        &self,
453        name: &str,
454    ) -> Result<Vec<SharedEntity>, CommandSyntaxError> {
455        self.entity_selector(name)?.find_entities(self.source())
456    }
457
458    pub(crate) fn entities(&self, name: &str) -> Result<Vec<SharedEntity>, CommandSyntaxError> {
459        let entities = self.optional_entities(name)?;
460        if entities.is_empty() {
461            Err(CommandSyntaxError::dynamic(TextComponent::from(
462                &translations::ARGUMENT_ENTITY_NOTFOUND_ENTITY,
463            )))
464        } else {
465            Ok(entities)
466        }
467    }
468
469    pub(crate) fn entity(&self, name: &str) -> Result<SharedEntity, CommandSyntaxError> {
470        let mut entities = self.entities(name)?;
471        if entities.len() != 1 {
472            return Err(CommandSyntaxError::dynamic(TextComponent::from(
473                &translations::ARGUMENT_ENTITY_TOOMANY,
474            )));
475        }
476        Ok(entities.remove(0))
477    }
478
479    pub(crate) fn optional_players(
480        &self,
481        name: &str,
482    ) -> Result<Vec<Arc<Player>>, CommandSyntaxError> {
483        self.entity_selector(name)?.find_players(self.source())
484    }
485
486    pub(crate) fn players(&self, name: &str) -> Result<Vec<Arc<Player>>, CommandSyntaxError> {
487        let players = self.optional_players(name)?;
488        if players.is_empty() {
489            Err(CommandSyntaxError::dynamic(TextComponent::from(
490                &translations::ARGUMENT_ENTITY_NOTFOUND_PLAYER,
491            )))
492        } else {
493            Ok(players)
494        }
495    }
496
497    pub(crate) fn player(&self, name: &str) -> Result<Arc<Player>, CommandSyntaxError> {
498        let mut players = self.players(name)?;
499        if players.len() != 1 {
500            return Err(CommandSyntaxError::dynamic(TextComponent::from(
501                &translations::ARGUMENT_PLAYER_TOOMANY,
502            )));
503        }
504        Ok(players.remove(0))
505    }
506}