amethyst / amethyst/rfcs

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

Đang mở
#13 7 bình luận 0 reaction 0 người được giao Xem trên GitHub
Ngôn ngữ chính
Không có dữ liệu ngôn ngữ
Star
32
Fork
10
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Mô tả

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)

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.