amethyst / amethyst/rfcs

RFC: Std I/O driven application (aka `amethyst_commands`)

Abierto
#13 7 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
Sin datos de lenguaje
Estrellas
32
Forks
10
Métricas de merge de PR
Sin PR fusionados en 30 d

Descripción

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](https://youtu.be/jpk2MTeWz3w)). 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)` (the tokens) to an `EventChannel<(AppEventVariant, Vec)>`.

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:

```rust
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:

```rust
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:
///
/// *
/// *
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;
}
```

`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)>` 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`.

```rust
/// Type to fetch the application event channel.
type MapperSystemData<'s, SysData> = (
Read<'s, EventChannel>,
Write<'s, EventChannel>,
SysData,
);

impl<'s, M> System<'s> for MapperSystem
where
M: StdinMapper + TypeName,
M::Resource: Default + Send + Sync + 'static,
AppEvent: From,
{
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)

* Implements the `stdio_spi`.
* The `Args` type:

```rust
#[derive(Clone, Debug, PartialEq, StructOpt)]
pub enum MapSelectionEventArgs {
/// Select event.
#[structopt(name = "select")]
Select {
/// Slug of the map or random, e.g. "default/eruption", "random".
#[structopt(short = "s", long = "selection")]
selection: String,
},
}
```

* The `StdinMapper` type:

```rust
impl StdinMapper for MapSelectionEventStdinMapper {
type Resource = MapAssets; // Read resource from the `World`, I take a `MapHandle` from it
type Event = MapSelectionEvent; // Event to map to
type Args = MapSelectionEventArgs; // Strong typed arguments, rather than the String tokens

fn map(map_assets: &MapAssets, args: Self::Args) -> Result {
match args {
MapSelectionEventArgs::Select { selection } => {
Self::map_select_event(map_assets, &selection)
}
}
}
}
```

* The bundle, which adds a `MapperSystem`:

```rust
builder.add(
MapperSystem::::new(AppEventVariant::MapSelection),
&MapperSystem::::type_name(),
&[],
);
```

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)

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.