Giter Site home page Giter Site logo

editor-core's Introduction

amethyst-editor-sync

Build Status Join us on Discord MIT/Apache

A crate that allows an Amethyst game to communicate with an editor over IPC. This is being developed in conjunction with a WASM (Yew & web_view) editor, but is intended to be general enough to facilitate development of other editor front-ends.

Setup and Usage

Here's an example of how to setup an Amethyst game to communicate with the editor:

extern crate amethyst;
extern crate amethyst_editor_sync;
extern crate serde;
extern crate tap;

use amethyst::prelude::*;
use amethyst::ecs::prelude::*;
use amethyst_editor_sync::*;
use serde::*;
use tap::*;

fn main() -> Result<(), amethyst::Error> {
    // Create a `SyncEditorBundle` which will register all necessary systems to serialize and send
    // data to the editor.
    let editor_bundle = SyncEditorBundle::default()
        // Register the default types from the engine.
        .tap(SyncEditorBundle::sync_default_types)
        // Register the components and resources specified above.
        .tap(|bundle| sync_components!(bundle, Foo))
        .tap(|bundle| sync_resources!(bundle, BarResource));

    let _game_data = GameDataBuilder::default()
        .with_bundle(editor_bundle)?;
    Ok(())
}

// Make sure you enable serialization for your custom components and resources!
#[derive(Serialize, Deserialize)]
struct Foo {
    bar: usize,
    baz: String,
}

impl Component for Foo {
    type Storage = DenseVecStorage<Self>;
}

#[derive(Serialize, Deserialize)]
struct BarResource {
    bar: usize,
}

Once your game is setup using amethyst-editor-sync, it will automatically connect to any running editor instance on your machine. You can use the WASM editor for visualization once this is setup.

Motivation and Philosophy

The goal of this project is to provide a functional 80% solution to the problem of sending arbitrary state data from an Amethyst game to an editor/visualizer tool. It doesn't attempt to perform any ~~magic~~ in order to detect user-defined components, instead requiring that the developer explicitly list all components that they want to see in the editor. This introduces fairly heavy boilerplate when setting up editor support, but means that we have a functional solution that works today. The goal is that this project will act as a placeholder to get us going until we can put together a less boilerplate-heavy solution for registering user-defined components.

This project is also built around a multi-process architecture, where the game runs independently of the editor and the two communicate via IPC. This is a deliberate design choice in order to increase the robustness of the editor: If the game crashes, it cannot crash or corrupt the editor.

Contributing

You'll need a stable version of Rust installed, which can be done via rustup. Install the latest stable toolchain (if you don't already have a stable toolchain installed) and then clone the repository. You can run the pong example for testing by running cargo run --example pong. If you need to test functionality against an editor, you can use the WASM editor.

For any feature requests, please open an issue in the GitHub issue tracker. Pull requests are also greatly appreciated โค๏ธ

All contributors are expected to follow the Rust Code of Conduct.

Status

This project is very early in development and should be treated as experimental.

Currently it supports communicating with an editor application over UDP. The entire world state is serialized as a JSON string and broadcast to the editor every frame. It currently supports sending arbitrary components and resources to an editor, and can do so for any component or resource that implements Serialize. Users must manually setup syncing for every component and resource type that they wish to visualize in the editor, though.

The goal is to support communication over IPC in order to minimize overhead, and to use a binary protocol to speed up serialization and improve throughput. The current architecture allows for serialization of each component/resource type to happen in parallel, so we should try to keep that property going forward.

editor-core's People

Contributors

erlend-sh avatar flappybug avatar mh84 avatar mvesterli avatar randompoison avatar velfi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

editor-core's Issues

Add new components to entities at runtime

This is currently blocked by #27.

Add a way for editors to request that a new component be added to an existing entity at runtime. The editor should specify the ID for the component type (i.e. the user-provided name for the type) and the entity it should be attached to. The game should then create the corresponding component in the game world.

Since the editor currently knows nothing about the type definitions for components, it won't know how to create the data for a new component when adding one. Instead, we'll leave it to the game process to use Default or similar user-provided logic to create the new component directly within the game.

To do this, add a new variant to IncomingMessage called AddComponent that specifies the ID for the component type and the entity the component should be added to. We'll also likely have to add Default as a bound for WriteComponentSet.

Note that it may prove insufficient to use Default for some component types, since they may require data from resources or other components when being initialized. If this proves to be the case, we can discuss adding a new trait such as ComponentDefault that allows users to access world state when creating default values for a component.

Identify read-only components to the editor

This ticket is blocked until #26 and #27 are complete.

As defined in #26 and #27, we support syncing Components/Resources that only implement Serialize, but in a read-only fashion that can't accept edits from the editor. In order to allow editors to provide a better user-experience for read-only components, we should provide information to the editor about which types have edit support vs which ones are read-only.

Ideally, it would be best to provide this sort of metadata once when the connection is established, however we don't currently have any sense of a "connection" between the game and editor. As such, there's no reasonable way to ensure that the game will know to send the information to the editor when the editor first connects. For simplicity, then, we should send this information with every state update that the game sends out.

We should update the SerializedComponent and SerializedResource structs to include some indication of whether or not the types supports editing. A boolean read_only field would be sufficient, but it might be nicer to have an EditMode enum that has variants for ReadOnly and ReadWrite. The approach to take is up to whomever takes on this task.

Support editing components

This issue references work that is in ticket #26. It isn't fully blocked by that ticket, but it might be easier to do once #26 is finished.

Following the examples set for #25 and #26 we should also add support for editing components. The following things need to be done to enable such support:

  • Add a new variant to IncomingMessage called ComponentUpdate that contains the ID for the component type, the entity the component is attached to (as a SerializableEntity), and the data for the component (as a serde_json::Value).
  • Rename SyncComponentSystem to ReadComponentSystem and move it into it's own read_component module.
  • Make a new System WriteComponentSystem mirroring WriteResourceSystem and put it in a new write_component module.
  • Rename ComponentSet to ReadComponentSet.
  • Add a new trait WriteComponentSet in the type_set module mirroring the WriteResourceSet trait (which should be created for #26).
  • Rename SyncEditorSystem::deserializer_map to resource_deserializer_map and update SyncEditorBundle accordingly.
  • Add a member component_deserializer_map to SyncEditorSystem that mirrors resource_deserializer_map. It should take the Component ID (i.e. the name used when registering the component) and map it to a Sender<(Entity, serde_json::Value)>. The corresponding WriteComponentSystem should have the Receiver end of the channel.
  • Update SyncComponentBundle::sync_component and sync_components to require that the registered types be Serialize + DeserializeOwned (or ReadComponentSet + WriteComponentSet in the case of sync_components.
  • Add methods read_component and read_components to SyncComponentsBundle that only require the types to be Serialize/ReadComponentSet.

Note that these steps assume that #26 has been completed. If this is being done before #26, you can still follow all the steps here, however you may need read #26 to understand the details for how to support read-only components (i.e. needing both a ReadComponentSet and a WriteComponentSet trait).

I'm happy to answer any questions or offer mentorship for anyone who wants to take on this task ๐Ÿ™‚

Automatic registration of engine types

The engine provides a lot of component and resource types and it quickly becomes a pain to manually remember every one that you need to register when using the editor with your game. It would be better if this crate provided a way to automatically register all the engine-provided types.

For convenience this functionality should be enabled by default, but there should be a way to disable it in case there is any use case where the user wouldn't want to register all the engine types.

Here's a list of types we should start with:

  • AmbientColor
  • Light
  • Named
  • Shape
  • RotationControl
  • Camera
  • Transform
  • GlobalTransform

Remove patch for amethyst dependency

Currently our Cargo.toml includes a patch section for amethyst that pulls in the development version of Amethyst, rather than using the stable 0.8 release. I believe I did this originally because I copied in the pong example from the development branch, but I don't think the crate itself actually needs any functionality that's only on the development branch currently. In order to prepare for a 0.1 release of the library, we should redo the pong example to be compatible with the stable release of Amethyst, and thereby make sure that the crate as a whole is compatible with the official Amethyst release.

If it turns out that there is anything in the main crate that depends on unreleased features in Amethyst, please bring that up here so that we can discuss solutions as necessary ๐Ÿ™‚

Improved UDP socket binding

Currently we require that the application logic manually create and bind a UDP socket and add it to the world as a resource. This gives the user control over what port the socket is bound to, but ultimately exposes UDP as an implementation detail that we don't want to finalize on.

Instead, we should have the EditorSyncSystem create its own UDP socket, and add a way to configure which port it binds to. By default it should let the system decide what port it binds to (by binding to 0.0.0.0:0), but allow the user to specify a different port/address to bind to. It might also be good to allow the user to override the default port via an environment variable.

Create and destroy entities at runtime

Add support for allowing the editor to request that a new entity be added to the game world.

  • Add a new variant to IncomingMessage called CreateEntities for requesting that an entity be created. The variant should carry a field specifying how many entities should be created.
  • Add a new field to outgoing messages "created_entities" that is a list of any entities that were just created at the request of the editor.
  • The list of new entities should be sent as soon as the entities are created, ideally on the same frame.
  • The list of new entities should only be sent once.

Similarly, to support destroying entities at runtime we'll need to add a variant to IncomingMessage called DestroyEntities that takes a list of entities to be destroyed. The list of destroyed entities does not need to be sent back to the editor, though.

Test README examples with rust-skeptic

It's pretty easy to forget to update the code examples in README.md when the API changes, which means the first usage example new users see might not even be correct. In order to catch such issues as quickly as possible, we should setup rust-skeptic. Doing so will ensure that code examples in the README will get tested as part of cargo test (and therefor as part of CI builds) just like any other doc test.

Panic when resource is missing

Originally reported by @Jojolepro in randomPoison/amethyst-editor#24. The issue is that we use ReadExpect in SyncResourceSystem so that we don't need to enforce the Default bound needed by Read. A better solution that @Jojolepro pointed out is to use Option<Read>, which will give us control over how we handle missing resources.

Setup Travis CI

It would be good to have automated testing for commits and pull requests so that we can have some basic verification for contributions. Setting up Travis should be fairly quick to do, and we shouldn't need to do much in the way of testing on multiple platforms since we should be avoiding platform-specific logic in this crate.

Logging is broken

One of the things that wasn't finished in #40 when I merged it was updating the setup for the logger. In SyncEditorBundle::new, a sender/receiver pair is created for sending log output, but the receiver half is immediately dropped. As a result, the first time a message is logged after hooking up the editor logger, we immediately get an error and a panic. I need to finish implementing the logger setup for the new bundle/systems so that we can send log output to the editor.

Use UUIDs to identify types

Right now we use the user-provided names for Resources/Components to identify them when deserializing update data sent from the editor. While this is an okay solution in the short term, we'll need a more stable way of identifying types in order to support prefab editing in the future. To do this, we can use serde-dyn, which provides the trait TypeUuid.

TODO: Note that we'll have to define our own TypeUuid trait because of the orphan rule.

TODO: Note that we should put this behind a feature until we're ready to make it mandatory.

Pass logs through to Amethyst logger

Currently EditorLogger only passes log output to the editor, but doesn't log anything to the local console. It would be good to also be able to pass log output through the regular Amethyst logger in order to be able to still see log output when the editor isn't running. This is especially important currently since no editor front end handles displaying log output well.

Amethyst exposes the Logger type as the concrete logger implementation, as well as the LoggerConfig for configuring the logger. EditorLogger should be updated to take a LoggerConfig as an optional parameter, and should forward any log output to an internal Amethyst Logger if configured to do so. This can likely take the form of an additional method EditorLogger::forward_logs(config: LoggerConfig).

Register read-only resources

#25 added support for editing Resources, but in doing so added the requirement that registered Resource types be DeserializeOwned. While most Resources types will likely be able to #[derive(Deserialize)], it would be good to still support displaying Resources that only implement Serialize.

To do so, we should add a new method SyncEditorBundle::read_resource for registering a read-only Resource, as well as a corresponding SyncEditorBundle::read_resources method for registering a set of read-only resources generated with type_set!.

To do this, we'll likely have to split the ResourceSet trait into two traits ReadResourceSet and WriteResourceSet. ReadResourceSet would only need to enforce the Serialize bound, and WriteResourceSet would only need to enforce the DeserializeOwned bound.

Setup bundle for easier, more configurable initialization

#3 made setup for editor support easier by allowing users to register their component and resource types with EditorSyncSystem directly, removing the need manually register a separate system for each component/resource type. Unfortunately this change removed the ability to run serialization in parallel for each data type being serialized, so it would be good if we could take an alternate approach that allowed us to still register a separate system for each type while keeping the ergonomic improvements that #3 added.

Instead of registering the types directly with EditorSyncSystem, we should instead setup a bundle builder. Users would build the bundle similar to how they currently register their types with EditorSyncSystem, and then the bundle can register all the systems with GameDataBuilder. This approach would still be ergonomic for the user, but would allow us to regain the ability to serialize the game state in parallel.

Notes

One ergonomic drawback is that users will also need to specify a system name for each type that they register. The signature for GameDataBuilder::with requires a &str be used as the system name, which means we can't have the bundle generate a unique name for each system at runtime (which would be the preferred solution). As a first pass we can have users manually specify a name for each system, but later we should make a PR to amethyst to use a Cow<'a, str> for system names so that it can support runtime-generated String values for names.

Panic on Linux when no editor running

Original bug report from @Jojolepro:

Using amethyst-editor-sync in a game when the editor is closed leads to a crash.
thread '' panicked at 'Failed to send message: Os { code: 111, kind: ConnectionRefused, message: "Connection refused" }

I should note that I don't see this issue on Windows, and it's a very weird issue considering we're using UDP, which doesn't actually have a concept of connections ๐Ÿค” This might be because we're using UdpSocket::connect.

Note that while this might be easy to solve for UDP, we also want to switch to TCP in order to fix #12, so we'd end up running into the same issue with TCP anyway.

trait bound not satisfied

I'm trying this tool:

// devtool setup
    let components = type_set![];
    let resources = type_set![];
    let editor_bundle = SyncEditorBundle::new()
        .sync_default_types()
        .sync_components(&components)
        .sync_resources(&resources);

let game_data = GameDataBuilder::default()
        .with(PrefabLoaderSystem::<MyPrefabData>::default(), "", &[])
        .with_bundle(AnimationBundle::<AnimationId, Transform>::new(
            "head_animation_control_system",
            "head_sampler_interpolation_system",
        ))?.with_bundle(TransformBundle::new().with_dep(&["head_sampler_interpolation_system"]))?
        .with_bundle(FPSCounterBundle::default())?
        .with_bundle(InputBundle::<String, String>::new())?
        .with_bundle(UiBundle::<String, String>::new())?
        .with(Processor::<Source>::new(), "source_processor", &[])
        .with(UiEventHandlerSystem::new(), "ui_event_handler", &[])
        .with_basic_renderer(display_config_path, DrawShaded::<PosNormTex>::new(), true)?


        // here from example โ†“
        .with_bundle(editor_bundle)?;
    let mut game = Application::new(resources, EnterScene::default(), game_data)?;
    game.run();
    Ok(())

But got:

error[E0277]: the trait bound `amethyst_editor_sync::SyncEditorBundle<impl amethyst_editor_sync::ReadComponentSet, impl amethyst_editor_sync::ReadComponentSet+amethyst_editor_sync::WriteComponentSet, impl amethyst_editor_sync::ReadResourceSet, impl amethyst_editor_sync::ReadResourceSet+amethyst_editor_sync::WriteResourceSet>: amethyst::amethyst_core::SystemBundle<'_, '_>` is not satisfied
   --> src/main.rs:216:10
    |
216 |         .with_bundle(editor_bundle)?;
    |          ^^^^^^^^^^^ the trait `amethyst::amethyst_core::SystemBundle<'_, '_>` is not implemented for `amethyst_editor_sync::SyncEditorBundle<impl amethyst_editor_sync::ReadComponentSet, impl amethyst_editor_sync::ReadComponentSet+amethyst_editor_sync::WriteComponentSet, impl amethyst_editor_sync::ReadResourceSet, impl amethyst_editor_sync::ReadResourceSet+amethyst_editor_sync::WriteResourceSet>`

error[E0277]: the trait bound `amethyst_editor_sync::TypeSet<()>: std::convert::AsRef<std::path::Path>` is not satisfied
   --> src/main.rs:217:20
    |
217 |     let mut game = Application::new(resources, EnterScene::default(), game_data)?;
    |                    ^^^^^^^^^^^^^^^^ the trait `std::convert::AsRef<std::path::Path>` is not implemented for `amethyst_editor_sync::TypeSet<()>`
    |
    = note: required by `<amethyst::CoreApplication<'a, T, E, R>>::new`

error: aborting due to 2 previous errors

I'm in rustc 1.30.1 (1433507eb 2018-11-07), amethyst = "0.9.0" and amethyst-editor-sync = "0.3.0"

Send log output to editor

It would be useful if users could also send log files to their editor, that way the editor can provide a more interactive view for searching and filtering log output.

This should be setup as a logger implementation for the log crate. It should export log lines in an easily parsed format (e.g. JSON structured), and should send logs using the same connection that the editor uses for other data. It should not attempt to filter log output from within the game process, instead allowing all filtering and processing of log data to happen within the editor process.

Update to Amethyst 0.10

People trying to use this crate with the latest version of Amethyst (currently 0.10) are getting compiler errors due to version mismatches between the version of Amethyst that they're using and the version that amethyst-editor-sync is depending on. We need to update to use the latest version and publish a new version of amethyst-editor-sync.

Lower update rate for sending serialized state

Currently we serialize and send the entire game state to the editor every singe frame. This potentially puts an unnecessary load on the editor, which has to process and re-render the entire game state 60 times per second. In practice we only need to update the editor maybe 5-10 times per second in order to avoid any visual lag for the end user. As such, reducing the rate at which we send the data to the editor could potentially fix some performance issues, specifically randomPoison/amethyst-editor#26.

Note that while most of the game data should only be sent infrequently, we still need to send log data every frame.

Tracking issue for serializing more engine types

Originally brought up by @Jojolepro in randomPoison/amethyst-editor#29.

The following components provided by Amethyst do not implement Serialize:

  • UiTransform
  • MeshData
  • UiText
  • Removal<T>
  • UiButton
  • FlyControlTag
  • Parent

And the following resources provided by Amethyst do not implement Serialize:

  • HideCursor

I'm making a PR to Amethyst adding missing Serialize impls to any types that only contain simple data. The following types are more complex and will need special handling:

  • MeshData contains potentially large amounts of data, and therefor should not be serialized and sent every frame for performance purposes. I'm also not clear what exactly MeshData is used for? I'm more familiar with using MeshHandle for attaching meshes to entities. Is MeshData used for dynamically-generated meshes? We'll have to discuss it in more detail to determine how it should be handled, both in terms of serialization and how we should display it in the editor.
  • UiText contains a good chunk of plain data that could be shown directly in the editor, but it also contains some private data that can't be (or at least shouldn't be) serialized (specifically the cached font handle, cached glyphs, and cached brush ID). There are potentially a few ways to solve this:
    • Only implement Serialize and have it only serialize the data we care about. This is okay for now, but we do eventually want to be able to modify this data in the editor, which will require a Deserialize impl.
    • Split the cached data into a separate UiTextCache component.
    • Wait for #7 to be implemented so that we can use intermediate serialization to split off the data we don't want to serialize.
  • Parent contains an Entity, which means it cannot be serialized directly. It is going to need #7 to be implemented in order to properly support serialization of Entity values.

Macro for registering types for syncing

Originally requested by @Jojolepro in randomPoison/amethyst-editor#28.

A macro for registering components/resources for synchronization would be helpful in cutting down boilerplate when registering a large number of types. Such a macro should define a way to register both components and resources, and should only require that the type be specified for each. It should automatically generate the name by stringifying the type name used for registration (the stringify macro can be used for this). It should generate code that just calls sync_component and sync_resource, returning a new SyncEditorBundle.

A strawman syntax could be:

let editor_bundle = sync_editor! {
    components:
        Foo,
        Bar,
        Baz,

    resources:
        Abba,
        Baab,
        Flub,
};

Panic when syncing many entities

If the synchronization message is greater than supported by the socket, it fails to send and therefore panics.
On windows 10 this limit is 64kB, which is ~2500 empty entities and significantly less when components are involved.

This code causes the error on my machine.

extern crate amethyst;
extern crate amethyst_editor_sync;

use amethyst::prelude::*;
use amethyst_editor_sync::*;

struct TestState;

impl<'a, 'b> SimpleState<'a, 'b> for TestState {
    fn on_start(&mut self, data: StateData<GameData>) {
        for _ in 0..2_500 {
            data.world.create_entity().build();
        }
    }
}

fn main() -> amethyst::Result<()> {
    let editor_sync_bundle = SyncEditorBundle::new();
    let game_data = GameDataBuilder::default()
        .with_bundle(editor_sync_bundle)?;
    let mut game = Application::build(".", TestState)?
        .build(game_data)?;
    game.run();
    Ok(())
}

Split out specs support

The core functionality of this crate, synchronizing component and resource data with an editor, isn't actually specific to Amethyst. Most of the functionality is still useful to projects that use specs directly, with only SyncEditorBundle using an Amethyst-specific API in order to make setup more user-friendly. It would be possible to expose an core API that only relied on specs so that a broader portion of the community could take advantage of this functionality.

That said, there is Amethyst-specific functionality that we would like to add in the future (e.g. default registration of Amethyst components and resources, showing the state machine stack, showing state event history, etc.). As such, it would likely make sense to split this project into two crates: One that provides specs-compatible functionality, and then continue to build the amethyst-editor-sync crate on top of the specs-only crate.

Intermediate types in serialization

In order to properly support serialization for types that contain an Entity, we need a way to create an intermediate type that converts entities and other non-stable data into a serialization-safe form. The idea is that any type that can be directly serialized has itself as the intermediate type, but types that need preprocessing before being serialized can specify a different type to use for serialization, and can be converted to and from that intermediate representation. This expands on the ideas present in saveload, but should provide a more flexible approach that doesn't require all types to be known at compile time.

Background and Motivation

The initial motivation for this idea is that Entity is not Serialize. Not allowing entities to be serialized directly is a deliberate design decision by the specs developers, and one that I think is entirely reasonable. Entities are meant to be ephemeral, and neither specs nor Amethyst make any guarantees about consistency in the actual entity IDs generated at runtime; That is to say, the same logic object in your game world may have a different entity ID each time you run your game. For cases like networking and prefab saving/loading, it's not practical to try to serialize the entity ID directly, since the entity ID won't necessarily be the same between client and server (in the case of networking).

For the purposes of a read-only editor, it's actually fine for us to serialize the entity directly. Since the editor only needs to know the entity values in order to display components by entity, and it will never try to reuse an entity ID across sessions, there's nothing that can break as a result of using entity IDs directly. In fact, for an editor, it's desirable to be able to see the actual entity IDs being used within an active session of a game for debugging purposes.

However, even with a read-only editor we quickly run into limitations resulting from Entity not being Serialize. The biggest of such issues is that we can't serialize components that have an Entity as a member (and therefore we can't view such components in the editor). As a short-term workaround for this we provide the SerializableEntity type, but that solution is very specific to this crate and only serves to obfuscate the larger problem of finding stable IDs to use in place of entity IDs for serialization.

Current Solution: saveload

The currently recommended solution is is the saveload module that specs provides. saveload provides functionality specifying stable "marker" values to be used instead of entity IDs when serializing a group of components. This core concept of allowing the user (or some external system) to provide stable marker values that are specific to the current context works well, however the specific implementation for saveload has some drawbacks that make it unsuitable in my estimation:

  • Only addresses Entity values for component grouping but still doesn't support replacing Entity values that are used within a component. This means that you still can't use saveload to serialize a component that has an Entity as one of its members.
  • All component types must be known at compile time because the implementation is provided over a tuple of component types, e.g. you must specify something like (Transform, Light, MyFoo, MyBar) and only those specified component types will be serialized.
  • Component data is grouped around entities rather than all components of a given type being serialized into a flat array. This setup is useful for things that are meant to be human-facing since grouping an entity's components together is easier to read, but it's an inherently inefficient solution for serializing and storing large amounts of component data.

The proposed solution builds on the approach taken by saveload, but attempts to solve some of the ergonomic and functional issues that it has.

Proposed Solution

Rather than directly using the Serialize impl on a component, we should introduce an intermediate trait SerializeIntermediate which is able to produce a serialization-safe intermediate value:

pub trait SerializeIntermediate {
    type Intermediate: Serialize;

    fn to(&self) -> Self::Intermediate;
    fn from(from: Self::Intermediate) -> Self;
}

Any type that can't be directly serialized (e.g. because it has an Entity member) could instead specify an alternate type that can be used, and the internal serialization mechanism would first convert each instance to the intermediate representation before actually serializing the data.

This solution can work for deserialization as well: As long as the component knows how to convert from the intermediate representation back into the concrete type, then we can handle deserialization by deserializing into the intermediate representation first, then converting back to the main type.

Example: Parent Component

The Parent component is a commonly-used component that can't be serialized (and therefore can't be viewed in the editor) today. Using SerializeIntermediate, we can instead convert to a second type that uses a stable marker to describe the hierarchy:

pub struct ParentIntermediate {
    pub entity: EntityMarker,
}

impl SerializeIntermediate for Parent {
    type Intermediate = ParentIntermediate;

    fn to(&self) -> Self::Intermediate {
        let marker = ...; // Lookup marker corresponding to entity.
        ParentIntermediate {
            entity: marker,
        }
    }

    fn from(from: Self::Intermediate) -> Self {
        let entity = ...; // Lookup entity corresponding to the marker.
        Parent { entity }
    }
}

Note that this example does not demonstrate a proper mechanism for how marker values would be generated or mapped to/from entities. See the next section for discussion on how this can be handled.

Generating Intermediate Values

The question remains of how to handle generating marker values and map them to/from entity IDs. I don't think this is something that should be handled by this crate or the serialization library, since different use cases call for different approaches (i.e. prefabs use the index within the file to determine the entity, game networking may require the server to generate unique IDs and send them to the client, the editor may want to use entity IDs directly, etc.).

Instead, I think it would be enough to allow the SerializeIntermediate trait to provide a SystemData type that can be passed in as a parameter to the conversion functions. This would allow the implementation for each type to perform any case-specific conversion that it cares to do. I suspect that this approach is not going to handle all the necessary use cases, but it simple enough to get us started.

The adjusted SerializeIntermediate would be as follows:

pub trait SerializeIntermediate {
    type Intermediate: Serialize;
    type Data: SystemData;

    fn to(&self, data: Self::Data) -> Self::Intermediate;
    fn from(from: Self::Intermediate, data: Self::Data) -> Self;
}

Remove components at runtime

This issue is currently blocked by #27.

Allow the editor to request that an existing component be removed from an entity at runtime.

To do this, add a variant to IncomingMessage called RemoveComponent that specifies the ID of the component type (i.e. the user-provided name of the component) and the entity from which the component should be removed. It probably makes the most sense to add the logic for actually removing the component to WriteComponentSystem, since it already requests write access to the appropriate component storage.

I'm happy to answer any questions or provide mentorship for anyone who wants to take on this task ๐Ÿ™‚

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.