Issue 999, this is gonna be epic!
Summary
Ability to control an Amethyst application using commands issued through stdin, with human-friendly terminal interaction.
Motivation
Inspecting and manipulating the state1 of an application at run time is a crucial part of development, with at least the following use cases:
- Determining that the application is behaving as expected.
- Experimenting with new features.
- Triggering certain cases.
- Investigating / troubleshooting unexpected behaviour.
- Automatically driving the application for integration tests.
A command terminal will greatly reduce the effort to carry out the aforementioned tasks.
1 state here means the runtime values, not amethyst::State
Prior Art
Expand -- copied from #995 (warning: code heavy)
okay, so this post is code heavy, but it's how I've done commands in my game (youtube). It shouldn't force people to use the state machine, since event types are "plug in if you need it".
Crate: stdio_view (probably analogous to amethyst_commands)
- Reads
stdin strings, uses shell_words to parse into separate tokens.
- Parses the first token into an
AppEventVariant to determine which AppEvent the tokens correspond to. On success, it sends a tuple: (AppEventVariant, Vec<String>) (the tokens) to an EventChannel<(AppEventVariant, Vec<String>)>.
Changes if put into Amethyst:
StdinSystem would be generic over top level types E and EVariant, which would take in AppEvent and AppEventVariant.
Crate: application_event
-
Contains AppEvent and AppEventVariant.
-
AppEvent is an enum over all custom event types, AppEventVariant is derived from AppEvent, without the fields.
Example:
use character_selection_model::CharacterSelectionEvent;
use map_selection_model::MapSelectionEvent;
#[derive(Clone, Debug, Display, EnumDiscriminants, From, PartialEq)]
#[strum_discriminants(
name(AppEventVariant),
derive(Display, EnumIter, EnumString),
strum(serialize_all = "snake_case")
)]
pub enum AppEvent {
/// `character_selection` events.
CharacterSelection(CharacterSelectionEvent),
/// `map_selection` events.
MapSelection(MapSelectionEvent),
}
This would be an application specific crate, so it wouldn't go into Amethyst. If I want to have State event control, this will include an additional variant State(StateEvent) from use amethyst_state::StateEvent;, where StateEvent carries the information of what to do (e.g. Pop or Switch).
Crate: stdio_spi
-
StdinMapper is a trait with the following associated types:
use structopt::StructOpt;
use Result;
/// Maps tokens from stdin to a state specific event.
pub trait StdinMapper {
/// Resource needed by the mapper to construct the state specific event.
///
/// Ideally we can have this be the `SystemData` of an ECS system. However, we cannot add
/// a `Resources: for<'res> SystemData<'res>` trait bound as generic associated types (GATs)
/// are not yet implemented. See:
///
/// * <https://users.rust-lang.org/t/17444>
/// * <https://github.com/rust-lang/rust/issues/44265>
type Resource;
/// State specific event type that this maps tokens to.
type Event: Send + Sync + 'static;
/// Data structure representing the arguments.
type Args: StructOpt;
/// Returns the state specific event constructed from stdin tokens.
///
/// # Parameters
///
/// * `tokens`: Tokens received from stdin.
fn map(resource: &Self::Resource, args: Self::Args) -> Result<Self::Event>;
}
Args is a T: StructOpt which we can convert the String tokens from before we pass it to the map function. Resource is there because the constructed AppEvent can contain fields that are constructed based on an ECS resource.
-
This crate also provides a generic MapperSystem that reads from EventChannel<(AppEventVariant, Vec<String>)> from the stdio_view crate. If the variant matches the AppEventVariant this system is responsible for, it passes all of the tokens to a T: StdinMapper that understands how to turn them into an AppEvent, given the Resource.
/// Type to fetch the application event channel.
type MapperSystemData<'s, SysData> = (
Read<'s, EventChannel<VariantAndTokens>>,
Write<'s, EventChannel<AppEvent>>,
SysData,
);
impl<'s, M> System<'s> for MapperSystem<M>
where
M: StdinMapper + TypeName,
M::Resource: Default + Send + Sync + 'static,
AppEvent: From<M::Event>,
{
type SystemData = MapperSystemData<'s, Read<'s, M::Resource>>;
fn run(&mut self, (variant_channel, mut app_event_channel, resources): Self::SystemData) {
// ...
let args = M::Args::from_iter_safe(tokens.iter())?;
M::map(&resources, args)
// ... collect each event
app_event_channel.drain_vec_write(&mut events);
}
}
Crate: character_selection_stdio (or any other crate that supports stdin -> AppEvent)
Can use it as inspiration to drive the design, or I'm happy to push my code up for the reusable parts (stdio_spi should be usable as is, stdio_view probably needs a re-write).
Detailed Design
TODO: discuss
Alternatives
The amethyst-editor will let you do some of the above tasks (inspecting, manipulating entities and components). It doesn't cater for:
- Server side inspection (e.g. SSH to an application running on a headless server)
- Automated tests
- Easy repeatability (source controlled actions)
Issue 999, this is gonna be epic!
Summary
Ability to control an Amethyst application using commands issued through stdin, with human-friendly terminal interaction.
Motivation
Inspecting and manipulating the state1 of an application at run time is a crucial part of development, with at least the following use cases:
A command terminal will greatly reduce the effort to carry out the aforementioned tasks.
1 state here means the runtime values, not
amethyst::StatePrior Art
Expand -- copied from #995 (warning: code heavy)
okay, so this post is code heavy, but it's how I've done commands in my game (youtube). It shouldn't force people to use the state machine, since event types are "plug in if you need it".
Crate:
stdio_view(probably analogous toamethyst_commands)stdinstrings, usesshell_wordsto parse into separate tokens.AppEventVariantto determine whichAppEventthe tokens correspond to. On success, it sends a tuple:(AppEventVariant, Vec<String>)(the tokens) to anEventChannel<(AppEventVariant, Vec<String>)>.Changes if put into Amethyst:
StdinSystemwould be generic over top level typesEandEVariant, which would take inAppEventandAppEventVariant.Crate:
application_eventContains
AppEventandAppEventVariant.AppEventis an enum over all custom event types,AppEventVariantis derived fromAppEvent, without the fields.Example:
This would be an application specific crate, so it wouldn't go into Amethyst. If I want to have
Stateevent control, this will include an additional variantState(StateEvent)fromuse amethyst_state::StateEvent;, whereStateEventcarries the information of what to do (e.g.PoporSwitch).Crate:
stdio_spiStdinMapperis a trait with the following associated types:Argsis aT: StructOptwhich we can convert theStringtokens from before we pass it to themapfunction.Resourceis there because the constructedAppEventcan contain fields that are constructed based on an ECS resource.This crate also provides a generic
MapperSystemthat reads fromEventChannel<(AppEventVariant, Vec<String>)>from thestdio_viewcrate. If the variant matches theAppEventVariantthis system is responsible for, it passes all of the tokens to aT: StdinMapperthat understands how to turn them into anAppEvent, given theResource.Crate:
character_selection_stdio(or any other crate that supports stdin -> AppEvent)Implements the
stdio_spi.The
Argstype:The
StdinMappertype:The bundle, which adds a
MapperSystem<MapSelectionEventStdinMapper>:Can use it as inspiration to drive the design, or I'm happy to push my code up for the reusable parts (
stdio_spishould be usable as is,stdio_viewprobably needs a re-write).Detailed Design
TODO: discuss
Alternatives
The
amethyst-editorwill let you do some of the above tasks (inspecting, manipulating entities and components). It doesn't cater for: