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