Giter Site home page Giter Site logo

legion's Introduction

Amethyst Game Engine

Build Status Crates.io docs page MIT/Apache Join us on Discord Community forum Reddit Code coverage Lines of Code

Inactively Maintained!

Inactively Maintained

The Amethyst Game Engine has halted its development. Read this post about it: https://amethyst.rs/posts/amethyst--starting-fresh

For 0.15.3 and older, the following Rust version has to be used for compiles to work.

rustup override set 1.47

What is Amethyst?

Amethyst is a data-driven and data-oriented game engine aiming to be fast and as configurable as possible.

Principles

These principles are what makes Amethyst unique and competitive in the world of game engines:

  • Massively parallel architecture.
  • Powered by a correct Entity Component System model.
  • Rapid prototyping with RON files for prefabs and an abstract scripting API.
  • Strong focus on encouraging reusability and clean interfaces.

Why Amethyst?

Extreme Multithreading

Amethyst is based over a very powerful parallel ECS called Specs. This allows games built with Amethyst to maximize the available processing power to run as smoothly and as quickly as possible, without the headaches of multi-threaded programming.

Clean

By design, the Amethyst engine encourages you to write clean and reusable code for your behaviours and data structures. This allows engine users to easily share useful components, thus reducing development time and cost.

Using the ECS architecture, the code of games can be cleanly divided between data and behaviour, making it easy to understand what is going on, even if the game is running on a massive 64-core processor.

Community

  • Discord - Announcements, help, useful information, general discussion.

Features

Please visit the features page for a list of features Amethyst provides.

Navigation

Usage

While the engine can be hard to use at times, we made a lot of documentation that will teach you everything you need to use Amethyst comfortably.

If you don't understand a part of the documentation, please let us know. Join us on Discord or open an issue; we are always happy to help!

Getting started

Before you begin

This repository uses Git LFS for some files used in examples. If you intend to run the examples, make sure you have LFS installed in your system before you clone. You can download it and read the installation instructions at Git LFS home page.

Examples

To compile any of the examples run:

$ cargo run -p name_of_example

All available examples are listed under the examples directory.

For a full-blown "Hello World" tutorial check out the Getting Started chapter in the book.

Showcase games

Our official showcase games demonstrate larger, continuously developed game projects made with Amethyst:

For more examples see Games Made With Amethyst topic on the community forum for some good sources of inspiration.

Dependencies

If you are compiling on Linux, make sure to install the dependencies below.

Arch Linux

pacman -Syu grep gcc pkgconf openssl alsa-lib cmake make python3 freetype2 awk libxcb

Debian/Ubuntu

apt install gcc pkg-config openssl libasound2-dev cmake build-essential python3 libfreetype6-dev libexpat1-dev libxcb-composite0-dev libssl-dev libx11-dev libfontconfig1-dev

Fedora

dnf install pkgconfig gcc openssl-devel alsa-lib-devel cmake make gcc-c++ freetype-devel expat-devel libxcb-devel libX11-devel

openSUSE

zypper install gcc pkg-config libopenssl-devel alsa-devel cmake gcc-c++ python3 freetype2-devel libexpat-devel libxcb-devel

Nix/NixOS

In your project's root folder, create a file shell.nix with the following contents:

let
  mozilla = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz);
  nixpkgs = import <nixpkgs> { overlays = [ mozilla ]; };
in

  with nixpkgs;

  mkShell {
    buildInputs = [
      alsaLib
      cmake
      freetype
      latest.rustChannels.stable.rust
      expat
      openssl
      pkgconfig
      python3
      vulkan-validation-layers
      xlibs.libX11
    ];

    APPEND_LIBRARY_PATH = lib.makeLibraryPath [
      vulkan-loader
      xlibs.libXcursor
      xlibs.libXi
      xlibs.libXrandr
    ];

    shellHook = ''
      export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$APPEND_LIBRARY_PATH"
    '';
  }

Other

See your distribution-specific installation process for the equivalent dependencies.

Please note that you need to have a functional graphics driver installed. If you get a panic about the renderer unable to create the context when trying to run an example, a faulty driver installation could be the issue.

Building Documentation

You can build the book locally with:

cargo install mdbook
mdbook build book

If you're actively editing the book, it's easiest to run:

mdbook serve book

and navigate to http://localhost:3000. The text itself can be found in book/html/index.html. For more information, please see the mdBook project.

To generate the API documentation locally, do:

$ cargo doc

The API reference can be found in target/doc/amethyst/index.html.

Questions/Help

Amethyst supports only the latest stable release of Rust. Use the nightly and beta channels with this project at your own risk.

If you have a question, please check out the FAQ before asking. Chances are, the solution to your problem is already present there. If you still need help, feel free to ask on our Discord server.

Other places you might want to check out are r/rust_gamedev and the #rust-gamedev IRC.

Contributing

Note: Any interaction with the Amethyst project is subject to our Code of Conduct.

Amethyst is a community-based project that welcomes contributions from anyone. If you're interested in helping out, please read the contribution guidelines before getting started.

We have a good first issue category that groups all issues or feature requests that can be made without having an extensive knowledge of Rust or Amethyst. Working on those issues is a good, if not the best, way to learn.

If you think you are not ready to code yet, you can still contribute by reviewing code written by other members of the community. Code reviews ensure that code merged into Amethyst is of the highest quality as possible. Pull requests that are available for reviews can be found here.

If for some reason we don't have any open PRs in need of a review nor any good first issues (that would be a good thing), feel free to consult our issue tracker.

Backers

Thank you to all our backers! ๐Ÿ™ Become a backer

Sponsors

Amethyst is supported by:

License

Amethyst is free and open source software distributed under the terms of both the MIT License and the Apache License 2.0.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

legion's People

Contributors

aclysma avatar aeubanks avatar alvelarsson avatar alyjak avatar amethyst-bors avatar athilenius avatar autumnontape avatar bestouff avatar bofh69 avatar caelunshun avatar dakom avatar ezpuzz avatar fluffycreature avatar frizi avatar grzi avatar guvante avatar immemorconsultrixcontrarie avatar jaynus avatar kabergstrom avatar magicrb avatar martin-t avatar mjhostet avatar nominolo avatar ralith avatar repi avatar rua avatar timonpost avatar tomgillen avatar tyfkda avatar veykril 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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 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

legion's Issues

Unsoundness in ResourceSet

This program results in undefined behavior:

use legion::prelude::*;

fn fetcher() -> legion::resource::PreparedRead<u32> {
    let mut resources = Resources::default();
    resources.insert(0u32);

    <Read<u32>>::fetch(&resources)
}

fn main() {
    println!("0x{:x}", *fetcher());
}

Query random access APIs

Systems cannot safely access a World, as it provides unvalidated access to component types that the system scheduler is unaware the system may access.

We could move to a model where all accesses to entities in a world always occurs via a Query. In such case, we should expand the query API to allow the user to fetch components and tags via Entity ID, and have the query validate that the type requested is declared as part of the query's View.

the trait `legion::EntitySource` is not implemented for Type

I encountered this error when trying to use legion 0.1.1

error[E0277]: the trait bound `alers::math::transform::Transform: legion::EntitySource` is not satisfied
  --> src/app\game.rs:45:7
   |
45 |       (Transform::new())
   |       ^^^^^^^^^^^^^^^^^^ the trait `legion::EntitySource` is not implemented for `alers::math::transform::Transform`

error: aborting due to previous error

Here are the definitions for my structs

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Transform {
  pub position: Vector3<f32>,
  pub lcl_rotation : Quaternion<f32>,
  pub scale : Vector3<f32>,
  matrix : Option<Matrix4<f32>>,
}

Here is the offending code

 pub fn load(&mut self, context : &mut Context) {
    // Load meshes
    let mut mesh = resource::fbx_convert::to_static_meshes(
      resource::fbx::load("resources/test/geom/triangle.fbx").unwrap()).first().unwrap();

    // Load shaders
    let mut lambert = resource::shader::ShaderFile::new(
      fs::read_to_string("shaders/lambert.vs").unwrap(),
      fs::read_to_string("shaders/lambert.fs").unwrap()
    );

    context.static_mesh(&mesh);
    context.shader(&lambert);

    // Create entities
    self.world.insert(
      (mesh.uid(), lambert.uid()),
      (Transform::new())
    );
  }

Did I miss out on anything ?

Creating Entities with n components

Please correct me if wrong, but world.insert() only allows the creation of entities with 2-4 components? Are there plans to allow for creating entities with n components?

Unsound fn ComponentStorage::move_entity

(@mooman219#0454 pointed this out to me on the community rust discord)

In a release build, this will access out-of-bounds - the fn should likely be marked unsafe, or the debug_assert upgraded to a regular assert: https://github.com/TomGillen/legion/blob/98a1cc44bf46a6df55d10b180ccf7ec73233a9ea/src/storage.rs#L924-L930

I'm not sure if there's a way to get your hands on a ComponentStorage using the public/safe interface of legion, although the type is publicly exported as legion::storage::ComponentStorage. Might be worth constraining some of these types to pub(crate) if they're inaccessable from the public API...

Invalid value passed to `tag`set for `find_chunk_with_delta` slow path

@TomGillen Can you correct in me assuming:

https://github.com/TomGillen/legion/blob/0f67adc237af35799df173f31a2c238b3d8010a2/src/storage.rs#L443

This assertion is hit when moving chunks with tags.

I believe its because this line is incorrect:
https://github.com/TomGillen/legion/blob/0f67adc237af35799df173f31a2c238b3d8010a2/src/world.rs#L337

It should be

let mut tags = source_archetype.tags().tag_set(source_location.set());

@TomGillen Can you confirm this is correct? If so, I will commit it to my CommandBuffer PR

Components are marked as changed when accessed mutable but when not changed.

When I do:

// Define our entity data types
#[derive(Clone, Copy, Debug, PartialEq)]
struct Position {
    x: f32,
    y: f32,
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct Velocity {
    dx: f32,
    dy: f32,
}

pub fn main() {
    // Create a world to store our entities
    let universe = Universe::new();
    let mut real_world = universe.create_world();

    // Create entities with `Position` and `Velocity` data
    real_world.insert(
       (),
        (0..999).map(|_| (Position { x: 0.0, y: 0.0 }, Velocity { dx: 0.1, dy: 0.1 }))
    );

    // Create a query which finds all `Position` and `Velocity` components
    let query = <(Write<Position>, Read<Velocity>)>::query();
     //Iterate through all entities that match the query in the world
    for (mut pos, vel) in query.iter(&mut real_world) {
        // because we loop mutable all position components are marked changed. While I haven't changed anything.
    }

    let query = <(Read<Position>)>::query().filter(changed::<Position>());
    for pos in query.iter(&mut real_world) {
        println!("changed: {} {}", pos.x, pos.y);
    }
}

Components are marked as changed even if I don't change them. This has to do with the fact that the mutable query increases the version of a component. If the version has changed then it is considered changed. This seems to me to be undesirable behavior or should at least be mentioned in the documentation as a warning. Is this intended, is this problem known, and would it be possible to somehow fix this? It is a difficult issue, specs does basicly the same.

Custom component storage

Currently, all components of each type are stored within each chunk as a single slice, accessed by what is essentially a custom vtable. This is implemented this way primarily to support a future c-api, where legion may be asked to handle external data types that are not statically known to the rust compiler.

Storage logic for each component could be delegated to a custom storage type, specified via an associated type on the Component trait. A default implementation may be similar to the existing code, but with a static type (which would allow for some further compiler optimisations in some situations), and the existing untyped storage logic could be provided for external types. Custom storages could be specified by overriding this type via specialization (or we could use a feature-flag to disable the blanket component implementation).

These storages would each be responsible for storing all of the components of a single type for a single chunk. They would be required to provide all logic for component insertion and removal, and for moving components between chunks during defragmentation and entity component addition/removal. They would be required to provide iterators over all components stored, and to provide indexers to components by the index of an entity within the chunk. Chunks returned via the query API will provide direct (strongly typed) access to each component's storage.

The motivation for this is to allow component types to decompose arrays of themselves into a structure of arrays. This would allow a storage to expose each sub-element slice when accessed via the chunk API, allowing for easier use of SIMD instructions. Such storages would construct and de-construct their higher-level component representations when the storage is accessed.

e.g. a Position component, made out of x, y and z floating point elements, would provide a storage implementation which internally stores all x, y and z elements in their own arrays. The storage would yield Position types when accessed through the usual APIs, but the user could access the storage directly via chunk.get_component_storage::<Position>().get_x(), and use 3rd party SIMD libraries to process many entities in the chunk at once. We could perhaps provide a procedural macro to implement this decomposed storage automatically.

It could also potentially be used by a component to implement a more memory-compact representation of Option<T> components that are expected to be sparsely populated.

hierarchy example?

Just wondering if you happen to have a hierarchy example to look at, e.g. for implementing a scene graph. Specifically:

  • walking from parents to children entities, and mutating their components (e.g. to set transforms)
  • getting a parent from a child (e.g. until finding the mesh that contains a primitive)
  • removing / adding nodes

Thanks!

bench build fails

Building the benchmark part does not work, even on nightly.

cargo 1.42.0-nightly (ad3dbe10e 2020-01-13)

Here's the output I am getting:

   Compiling legion v0.2.1 (C:\Users\[redacted]\Documents\GitHub\legion)
error[E0277]: the trait bound `itertools::groupbylazy::Group<'_, u8, std::slice::Iter<'_, Variants>, [closure@benches\parallel_query.rs:54:45: 54:59]>: std::iter::ExactSizeIterator` is not satisfied
  --> benches\parallel_query.rs:58:17
   |
58 | /                 group.map(|x| {
59 | |                     if let Variants::AB(a, b) = x {
60 | |                         (*a, *b)
61 | |                     } else {
62 | |                         panic!();
63 | |                     }
64 | |                 }),
   | |__________________^ the trait `std::iter::ExactSizeIterator` is not implemented for `itertools::groupbylazy::Group<'_, u8, std::slice::Iter<'_, Variants>, [closure@benches\parallel_query.rs:54:45: 54:59]>`
   |
   = note: required because of the requirements on the impl of `std::iter::ExactSizeIterator` for `std::iter::Map<itertools::groupbylazy::Group<'_, u8, std::slice::Iter<'_, Variants>, [closure@benches\parallel_query.rs:54:45: 54:59]>, [closure@benches\parallel_query.rs:58:27: 64:18]>`
   = note: required because of the requirements on the impl of `legion::world::ComponentSource` for `legion::world::ComponentTupleSet<(A, B), std::iter::Map<itertools::groupbylazy::Group<'_, u8, std::slice::Iter<'_, Variants>, [closure@benches\parallel_query.rs:54:45: 54:59]>, [closure@benches\parallel_query.rs:58:27: 64:18]>>`
   = note: required because of the requirements on the impl of `legion::world::IntoComponentSource` for `std::iter::Map<itertools::groupbylazy::Group<'_, u8, std::slice::Iter<'_, Variants>, [closure@benches\parallel_query.rs:54:45: 54:59]>, [closure@benches\parallel_query.rs:58:27: 64:18]>`

error[E0277]: the trait bound `itertools::groupbylazy::Group<'_, u8, std::slice::Iter<'_, Variants>, [closure@benches\parallel_query.rs:54:45: 54:59]>: std::iter::ExactSizeIterator` is not satisfied
  --> benches\parallel_query.rs:68:17
   |
68 | /                 group.map(|x| {
69 | |                     if let Variants::AC(a, c) = x {
70 | |                         (*a, *c)
71 | |                     } else {
72 | |                         panic!();
73 | |                     }
74 | |                 }),
   | |__________________^ the trait `std::iter::ExactSizeIterator` is not implemented for `itertools::groupbylazy::Group<'_, u8, std::slice::Iter<'_, Variants>, [closure@benches\parallel_query.rs:54:45: 54:59]>`
   |
   = note: required because of the requirements on the impl of `std::iter::ExactSizeIterator` for `std::iter::Map<itertools::groupbylazy::Group<'_, u8, std::slice::Iter<'_, Variants>, [closure@benches\parallel_query.rs:54:45: 54:59]>, [closure@benches\parallel_query.rs:68:27: 74:18]>`
   = note: required because of the requirements on the impl of `legion::world::ComponentSource` for `legion::world::ComponentTupleSet<(A, C), std::iter::Map<itertools::groupbylazy::Group<'_, u8, std::slice::Iter<'_, Variants>, [closure@benches\parallel_query.rs:54:45: 54:59]>, [closure@benches\parallel_query.rs:68:27: 74:18]>>`
   = note: required because of the requirements on the impl of `legion::world::IntoComponentSource` for `std::iter::Map<itertools::groupbylazy::Group<'_, u8, std::slice::Iter<'_, Variants>, [closure@benches\parallel_query.rs:54:45: 54:59]>, [closure@benches\parallel_query.rs:68:27: 74:18]>`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.
error: could not compile `legion`.
warning: build failed, waiting for other jobs to finish...
error[E0308]: mismatched types
  --> benches\benchmarks.rs:57:29
   |
57 |             Box::new(|e, w| w.add_component(e, A(0.0))),
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found enum `std::result::Result`
   |
   = note: expected unit type `()`
                   found enum `std::result::Result<(), &'static str>`

error[E0308]: mismatched types
  --> benches\benchmarks.rs:58:29
   |
58 |             Box::new(|e, w| w.add_component(e, B(0.0))),
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found enum `std::result::Result`
   |
   = note: expected unit type `()`
                   found enum `std::result::Result<(), &'static str>`

error[E0308]: mismatched types
  --> benches\benchmarks.rs:59:29
   |
59 |             Box::new(|e, w| w.add_component(e, C(0.0))),
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found enum `std::result::Result`
   |
   = note: expected unit type `()`
                   found enum `std::result::Result<(), &'static str>`

error[E0308]: mismatched types
  --> benches\benchmarks.rs:61:29
   |
61 |             Box::new(|e, w| w.add_component(e, D(0.0))),
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found enum `std::result::Result`
   |
   = note: expected unit type `()`
                   found enum `std::result::Result<(), &'static str>`

error[E0308]: mismatched types
  --> benches\benchmarks.rs:63:29
   |
63 |             Box::new(|e, w| w.add_component(e, E(0.0))),
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found enum `std::result::Result`
   |
   = note: expected unit type `()`
                   found enum `std::result::Result<(), &'static str>`

error[E0308]: mismatched types
  --> benches\benchmarks.rs:65:29
   |
65 |             Box::new(|e, w| w.add_component(e, F(0.0))),
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found enum `std::result::Result`
   |
   = note: expected unit type `()`
                   found enum `std::result::Result<(), &'static str>`

error[E0061]: this function takes 2 parameters but 1 parameter was supplied
   --> benches\benchmarks.rs:165:19
    |
165 |             world.insert(
    |                   ^^^^^^ expected 2 parameters

error[E0618]: expected function, found `(Tag,)`
   --> benches\benchmarks.rs:166:17
    |
166 |                   (Tag(i as f32),)
    |  _________________-^^^^^^^^^^^^^^^
167 | |                 (0..10000).map(|_| (Position(0.), Rotation(0.))),
    | |__________________________- call expression requires function

Example "hello_world.rs" doesn't build

I just discovered this library and wanted to give it a shot by developing a simple game. I wanted to try the examples to see how it works but it doesn't build.

I am building with legion 0.2.1. I should note I removed the following line

let _ = tracing_subscriber::fmt::try_init();

as it seemed to come from another lib. Below is the cargo build output:

error[E0599]: no method named `iter_mut` found for type `legion::query::Query<(legion::query::Write<Pos>, legion::query::Read<Vel>), legion::filter::EntityFilterTuple<legion::filter::And<(legion::filter::ComponentFilter<Pos>, legion::filter::ComponentFilter<Vel>)>, legion::filter::And<(legion::filter::Passthrough, legion::filter::Passthrough)>, legion::filter::And<(legion::filter::Passthrough, legion::filter::Passthrough)>>>` in the current scope
  --> src/main.rs:46:33
   |
46 |     for (mut pos, vel) in query.iter_mut(&mut world) {
   |                                 ^^^^^^^^ method not found in `legion::query::Query<(legion::query::Write<Pos>, legion::query::Read<Vel>), legion::filter::EntityFilterTuple<legion::filter::And<(legion::filter::ComponentFilter<Pos>, legion::filter::ComponentFilter<Vel>)>, legion::filter::And<(legion::filter::Passthrough, legion::filter::Passthrough)>, legion::filter::And<(legion::filter::Passthrough, legion::filter::Passthrough)>>>`

error[E0599]: no method named `iter_mut` found for type `&mut legion::system::SystemQuery<(legion::query::Write<Pos>, legion::query::Read<Vel>), legion::filter::EntityFilterTuple<legion::filter::And<(legion::filter::ComponentFilter<Pos>, legion::filter::ComponentFilter<Vel>)>, legion::filter::And<(legion::filter::Passthrough, legion::filter::Passthrough)>, legion::filter::And<(legion::filter::Passthrough, legion::filter::Passthrough)>>>` in the current scope
  --> src/main.rs:60:41
   |
60 |             for (mut pos, vel) in query.iter_mut(&mut world) {
   |                                         ^^^^^^^^ method not found in `&mut legion::system::SystemQuery<(legion::query::Write<Pos>, legion::query::Read<Vel>), legion::filter::EntityFilterTuple<legion::filter::And<(legion::filter::ComponentFilter<Pos>, legion::filter::ComponentFilter<Vel>)>, legion::filter::And<(legion::filter::Passthrough, legion::filter::Passthrough)>, legion::filter::And<(legion::filter::Passthrough, legion::filter::Passthrough)>>>`

error[E0599]: no method named `iter_mut` found for type `legion::query::Query<(legion::query::Write<Pos>, legion::query::Read<Vel>), legion::filter::EntityFilterTuple<legion::filter::And<(legion::filter::ComponentFilter<Pos>, legion::filter::ComponentFilter<Vel>)>, legion::filter::And<(legion::filter::Passthrough, legion::filter::Passthrough)>, legion::filter::And<(legion::filter::Passthrough, legion::filter::Passthrough)>>>` in the current scope
  --> src/main.rs:93:37
   |
93 |         for (mut pos, vel) in query.iter_mut(world) {
   |                                     ^^^^^^^^ method not found in `legion::query::Query<(legion::query::Write<Pos>, legion::query::Read<Vel>), legion::filter::EntityFilterTuple<legion::filter::And<(legion::filter::ComponentFilter<Pos>, legion::filter::ComponentFilter<Vel>)>, legion::filter::And<(legion::filter::Passthrough, legion::filter::Passthrough)>, legion::filter::And<(legion::filter::Passthrough, legion::filter::Passthrough)>>>`

error: aborting due to 3 previous errors

README example needs `iter_mut`

The example in the readme does not compile:

// Iterate through all entities that match the query in the world
for (mut pos, vel) in query.iter(&mut world) {
    pos.x += vel.dx;
    pos.y += vel.dy;
}

Here is the compile error:

error[E0277]: the trait bound `legion_core::query::Write<Position>: legion_core::query::ReadOnly` is not satisfied
  --> src/main.rs:38:33
   |
38 |     for (mut pos, vel) in query.iter(&mut world) {
   |                                 ^^^^ the trait `legion_core::query::ReadOnly` is not implemented for `legion_core::query::Write<Position>`
   |
   = note: required because of the requirements on the impl of `legion_core::query::ReadOnly` for `(legion_core::query::Write<Position>, legion_core::query::Read<Velocity>)`

error: aborting due to previous error

I believe query.iter(...) needs to be changed to query.iter_mut(...)

Random access APIs should be marked as unsafe

Random access APIs, such as World.get_component cannot have their safety expressed statically in Rust's type system. Instead, they are runtime borrow checked.

However, this runtime borrow checking is only there as a diagnostic aid; it will provide more useful fail-fast behaviour and stack traces, rather than the state corruption that would otherwise have to be debugged.

The user must still carefully consider how these APIs are used and ensure that they are not breaking any borrowing rules. Therefore, these functions should still be marked as unsafe, to more clearly communicate this responsibility.

Panic in `World::defrag()` after adding entities

Relevant part of the stack trace:

thread 'main' panicked at 'attempt to subtract with overflow', /home/caelum/.cargo/git/checkouts/legion-7610998b50d8953d/15d2b5c/src/storage.rs:975:24
stack backtrace:
   0: backtrace::backtrace::libunwind::trace
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/libunwind.rs:88
   1: backtrace::backtrace::trace_unsynchronized
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/mod.rs:66
   2: std::sys_common::backtrace::_print_fmt
             at src/libstd/sys_common/backtrace.rs:84
   3: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
             at src/libstd/sys_common/backtrace.rs:61
   4: core::fmt::write
             at src/libcore/fmt/mod.rs:1025
   5: std::io::Write::write_fmt
             at src/libstd/io/mod.rs:1426
   6: std::sys_common::backtrace::_print
             at src/libstd/sys_common/backtrace.rs:65
   7: std::sys_common::backtrace::print
             at src/libstd/sys_common/backtrace.rs:50
   8: std::panicking::default_hook::{{closure}}
             at src/libstd/panicking.rs:193
   9: std::panicking::default_hook
             at src/libstd/panicking.rs:210
  10: std::panicking::rust_panic_with_hook
             at src/libstd/panicking.rs:471
  11: rust_begin_unwind
             at src/libstd/panicking.rs:375
  12: core::panicking::panic_fmt
             at src/libcore/panicking.rs:84
  13: core::panicking::panic
             at src/libcore/panicking.rs:51
  14: legion::storage::Chunkset::defrag
             at /home/caelum/.cargo/git/checkouts/legion-7610998b50d8953d/15d2b5c/src/storage.rs:975
  15: legion::storage::ArchetypeData::defrag
             at /home/caelum/.cargo/git/checkouts/legion-7610998b50d8953d/15d2b5c/src/storage.rs:793
  16: legion::world::World::defrag
             at /home/caelum/.cargo/git/checkouts/legion-7610998b50d8953d/15d2b5c/src/world.rs:635
  17: feather_server::run_loop
             at server/src/lib.rs:264

This appears to happen only after an entity is added to the world.

In case it's relevant, I'm using the latest master.

How to sequence events, one after another?

Looking for advice on this. How would you wait for an animation sequence and then trigger something, then trigger another thing, and so on?

For example, let's say the player wants to throw a fireball at an enemy. It should cast a spell with an initial player animation, then create the fireball entity, then the fireball should follow the target pointed at by the player initially, then it should explode and make damage to all the entities around the target.

A comment that got me thinking about this: "I really, really like to code all that stuff in one place in code that looks something roughly similar to that sentence above. But usually [with ECS systems in general] I end up with 5 systems, 5 components, everything decoupled and there is no way to see how the fireball will actually fly, without knowledge of all the systems in the game."

[Feature] FFI

Is there any current work for an FFI?

This is really not within my experience yet, but I thought I might start messing around a little bit with it and I wanted to make sure there wasn't any existing work on a C interface yet.

Experimental Branch Tracking Issue

Checklist for feature parity with master.

  • Entity ID allocation
  • Storage
    • Archetypes
    • Chunks
    • Components
    • Tags
  • Entity management
    • Create
    • Delete
    • Mutate
      • Component addition
      • Component removal
      • Tag addition
      • Tag removal
      • Tag value change
  • Filtering
  • Queries
    • Views
    • Iterators
  • World merging
    • Archetype/chunk unique IDs
    • Archetype/chunkset/chunk storage refactor

Tagged Iter question

Why does the fetch impl for Tagged return a repeating iter (the first tag value, for every component) instead of the iter of the slice it grabs?

https://github.com/jaynus/legion/blob/3a9d2a3607856f2492130fd5a0793e6309331ae0/src/query.rs#L307

std::iter::repeat(data).take(chunk.len())

as opposed to

.data_slice::<T>().iter()

And the appropriate type Iter.

If I pull data from tags <(Read<Transform>,Tagged<MyTag>)>::query() I would expect the tag value in each iteration for (transform, tag) in query.iter(&world) to represent that particular entity's tag.

Are tags chunked together based on tag value? If true, and I were doing an iter_chunks I would want this functionality, but iterating individual components with differing tag values gives me undesirable results.

I made the change locally to see if it worked correctly, and it seems to. But I wonder if I'm ignoring something I'm not knowledgeable of. Ultimately I can use a component in place of the tag I was using, but I'm loving working with legion thus far, and want to understand.

Thanks!

Entity eventing

There is currently no easy way to determine when an entity is created, deleted, or its composition is changed. This makes building out-of-bound data structures which store data about entities that a system is interested in difficult. Typically, ECS systems provide entity added/deleted events, with the ID of the entity provided as event payload.

I mentioned my thoughts on what legion needs for eventing in #11:

The problem with global added/deleted events is that the entity IDs alone aren't enough to determine if the entity is/was of interest to the system. What it really needs is information about the component composition of the entity, too. This is further complicated by entity mutations, which could move an entity in/out of interest without creating or destroying it.

Entity deletion events can safely be used by a system to clean up it's state, although every system with such state will have to listen to all such events and determine for themselves if said entity is one they are managing.

Entity mutation (component/tag add/remove) events are very difficult to work with. It is not going to be very clear when a given change will cause an entity to no longer be of interest (especially if the system is using complex queries with component_a | component_b conditions) and be of no use in determining if the entity has become of interest.

Entity added events are almost useless, outside of some logging or metrics events.

What I would like to do is support an eventing API that allows the user to register to recieve entity events for all entities that match an EntityFilter. Such event channels would attach themselves to all chunks that match the query, and would fire added/removed events when an entity is moved into or out of said chunk.

Immutable filters and queries

Most of the filters that are attached to a query are immutable and deterministic, and only read immutable data from the world (such as archetypes, which are never modified once created). In fact, the only filter that breaks this rule is the ComponentChangedFilter - this filter is the sole reason why filters (and queries) require &mut self to run.

If we could pull this state out of ComponentChangedFilter, we could make queries fully immutable.

This would make #17 much easier to implement.

Add/remove components on an entity

Is it possible to add/remove components on an existing entity?

I understand that it's probably a (relatively) slow operation and should be discouraged, but it's especially useful on initial load.

For example - walking through a 3d scene and creating ECS out of it, we might know at first that each node has a transform, and only later on during load (but before starting the game) we'd add on mesh, material, etc.

Tag value changes when adding a component

I have 2 entities with similar tags (Layer(0), Layer(1)) when I add a component to both of them the second entity's tag changes its value to Layer(0).

    #[derive(Debug, Clone, Copy, PartialEq)]
    struct Layer(pub u32);

    #[derive(Debug, Clone, Copy, PartialEq)]
    struct Collision(pub bool);

    #[test]
    fn test_tag_change() {
        let mut world = World::new();
        let entity0 = *world.insert((Layer(0),), vec![()]).first().unwrap();
        let entity1 = *world.insert((Layer(1),), vec![()]).first().unwrap();

        {
            let tag0 = world.get_tag::<Layer>(entity0).unwrap();
            let tag1 = world.get_tag::<Layer>(entity1).unwrap();

            assert_eq!(*tag0, Layer(0), "before component: tag0 failed"); // OK
            assert_eq!(*tag1, Layer(1), "before component: tag1 failed"); // OK
        }

        world.add_component(entity0, Collision(false));
        world.add_component(entity1, Collision(false));

        {
            let tag0 = world.get_tag::<Layer>(entity0).unwrap();
            let tag1 = world.get_tag::<Layer>(entity1).unwrap();

            assert_eq!(*tag0, Layer(0), "after component: tag0 failed"); // OK
            assert_eq!(*tag1, Layer(1), "after component: tag1 failed"); // failed
        }
    }

Query caching

Most of the filters within a query are deterministic and will always yield the same result for each archetype/chunkset/chunk. The only exception to this is the ComponentChangedFilter.

If we could cache the matches for a query between executions, we could skip most of the work and need only resume the query from the last archetype/chunkset/chunk it had seen, needing only to inspect new elements. Caching for all three levels in a data structure that is faster to read than the full query itself may be tricky, but caching only archetype matches should be trivial and offer significant potential performance gains.

This could tie well into system execution. Systems currently need to pre-execute queries to determine which archetypes the system will access, as part of its scheduling logic. If systems could cache their archetype matches between executions, then that would both allow for incremental querying (inspecting only new archetypes each frame), and eliminate an additional query execution from each system. It would also allow systems to cheaply determine if their queries match any entities, which may allow us to automatically disable inactive systems (although this would have to be opt-in for each system).

wrong entity gets deleted after merging two worlds?

minimal example:

use legion::prelude::*;

fn main() {
    let u = Universe::new();
    let mut v = u.create_world();
    let mut w = u.create_world();
    let components_a = (String::from("Bill"),);
    let components_b = (String::from("Ted"),);
    let e = w.insert((), vec![components_a])[0];
    let f = v.insert((), vec![components_b])[0];
    w.merge(v);
    w.delete(f);
    for name in <Read<String>>::query().iter(&mut w) {
        println!("{}", *name);
    }
}

It should print Bill I think, but instead prints Ted.

Make legion sent `EntityChanged` event when an component is changed.

I work on an entity synchronization library (for amethyst) and look into ways to detect changes here I need to track each change that happens to a component. As we spoke about this in discord when we query we only get to know that something is changed and what the newest version of that component is. I need all the changes because this is needed for the simulation of the game world on other clients. And for server stuff.

Would it be possible to drag along the event channel to the DerefMut for RefMapMut? Then when dereferenced and a mutate is going to be made, the current T will be pushed onto the channel with the event Event::EntityChanged(T).

There are already Remove and Add events, however, change events need to be requested via the query API. I understand the motivation behind this, though it would be awesome if I can directly see the exact changes that were made.

I am not entirely sure if this scenario can be achieved other than by capturing a mutable call to a component. For this, you need to own the component. Since the legion owns the component and knows exactly when a component is changed, it can note the changes and tell the user what and when something has changed. Therefore legion is only able to do this in the most optimal way without hacky workaround.

Any ideas, suggestions, proposals, insights?

Tag values not mutating

The following test fails (on the systems branch):

#[derive(Debug, Clone, PartialEq, Eq)]
struct SomeTag(i32);

#[test]
fn tag_test() {
  let mut world = Universe::new().create_world();
  let entity = *world.insert((), vec![(42,)]).first().unwrap();

  // Set the tag to 1
  world.add_tag(entity, SomeTag(1));

  // All is well in the world.
  assert_eq!(*world.get_tag::<SomeTag>(entity).unwrap(), SomeTag(1));

  // Set the same tag to 2.
  // Even if you do this first: world.remove_tag::<SomeTag>(entity);
  world.add_tag(entity, SomeTag(2));

  // Panic:
  // ... assertion failed: `(left == right)`
  //   left: `SomeTag(1)`,
  //  right: `SomeTag(2)`',
  assert_eq!(*world.get_tag::<SomeTag>(entity).unwrap(), SomeTag(2));

  // The world is broken. Brutal.
}

I didn't test master because hopefully systems merges soon. Also tagging @jaynus in hopes of a pointer on what might be happening.

Add/remove multiple components/tags at once

Currently, World provides add_tag() and add_component() which add tags and components to an entity. However, these functions are somewhat expensive due to the moving of entities between archetypes, and calling them once for each component which should be added is undesirable.

Functions add_tags() and add_components(), or similar, should be added to allow for optimizing the creation of multiple components or tags.

Legion v0.2.0 will not compile

Hi I just updated to your new release, yay! But it won't compile:

   Compiling legion v0.2.0
error[E0412]: cannot find type `PhantomData` in this scope
   --> ....\.cargo\registry\src\github.com-1ecc6299db9ec823\legion-0.2.0\src\borrow.rs:185:12
    |
185 |     state: PhantomData<&'a ()>,
    |            ^^^^^^^^^^^ not found in this scope
help: possible candidates are found in other modules, you can import them into scope
    |
4   | use core::marker::PhantomData;
    |
4   | use std::marker::PhantomData;

Per-component borrow checking

Currently, runtime borrow checking is performed in order to allow the user to access two different components at the same time (which rust's static borrow checking would not allow), but disallow the same component from being incorrecttly borrowed multiple times.

The runtime state of a component's borrow is stored as an atomic integer along with each component's slice in each chunk. This is efficient and allows us to include borrow checking in release builds (although this is currently not the case) without significant performance penalty.

However, it is overly strict. It will not allow two components of the same type to be accessed at the same time when they each belong to different entities - even though this should be safe. This makes random component access very difficult in many situations.

We could relax these borrowing restrictions by tracking borrow state for every component, rather than each component slice. However, it is unlikely that this would provide acceptable performance, so we almost certainly would have to disable runtime borrow checking in release builds (perhaps with an opt-in).

Creating entities without components

In several of the GTK applications that I write, I need to know the entity ID in advance of assigning components to the entity, so that the entity ID may be passed into GTK signals belonging to the widgets, which are later added as components. Currently using slotmap to achieve this at the moment:

// Componentless entity
let entity = entities.insert();

...

revealer.connect_clicked(enclose!((sender) move |_| {
    let _ = sender.send(UiEvent::Audit(AuditEvent::ToggleSku(entity)));
}):

button.connect_clicked(enclose!((sender) move |_| {
    let _ = sender.send(UiEvent::Audit(AuditEvent::ToggleSku(entity)));
}):

...

// Adding components after we've initialized their behaviors in advance
revealers.insert(entity, revealer);
buttons.insert(entity, button);

How to query for "entities on screen," or "in range entities"?

Looking for advice here. Have others dealt with the issue of introspecting whats going on on the screen, or querying for "in range entities"?

For example, how would I get a list of the entities that are visible on the screen right now, with all components, and show them in one nice imgui window (think of a mini-map)?

A similar problem is how to iterate over all enemies within range of the player's spell. Would you cache the distance-to-player somehow? Or is there another way to query for entities in range of another entity?

For an entity get all components that can be cast into a trait

Because legion is made in such a way that a c api is possible, it might also be possible to get all components and cast them to a specific trait. I would use this feature for serde serialization and deserialization, world saving specifically. Could this be done? If yes I'm more than happy to help!

Deleting all entities from a world

Is there a way to easily delete all entities from a world? I'm looking at the query docs but I'm unsure how to get all entity ID's without regard for attached components to feed the ID's into world.delete(..). Any help would be appreciated.

Additionally maybe there is room here for a helper method on world to delete all entities that could be more efficient than deleting one by one?

Bug in `View::read_types()` implementation for view tuples

The following program:

use legion::query::{Read, View, Write};
use legion::storage::ComponentTypeId;

fn main() {
    dbg!(ComponentTypeId::of::<u32>(), ComponentTypeId::of::<u64>());
    dbg!(<(Read<u32>, Write<u64>)>::read_types());
}

outputs:

[src/main.rs:5] ComponentTypeId::of::<u32>() = ComponentTypeId(
    TypeId {
        t: 12849923012446332737,
    },
    0,
)
[src/main.rs:5] ComponentTypeId::of::<u64>() = ComponentTypeId(
    TypeId {
        t: 11388137604015455702,
    },
    0,
)
[src/main.rs:6] <(Read<u32>, Write<u64>)>::read_types() = [
    ComponentTypeId(
        TypeId {
            t: 14930314211906227715,
        },
        0,
    ),
    ComponentTypeId(
        TypeId {
            t: 5470155405045357034,
        },
        0,
    ),
]

As can be seen, the type IDs returned by View::read_types() do not match those of ComponentTypeId::of(). This is caused by https://github.com/TomGillen/legion/blob/5e368018e4d963db348abaa5d761c161fa2aafca/src/query.rs#L431 where the function returns the type IDs of the components wrapped in Read or Write rather than that of the components themselves. On the contrary, Read<T>::read_types() returns the type ID of T rather than Read<T>, which I believe is the correct behavior.

Add duplicate type check to fn insert

fn insert currently doesn't actually check for duplicate components being inserted for a given entity.

In debug_assertions, this causes a already borrowed error. in Release, this causes UB.

we should add a check for this case in the insert iterators to confirm we arn't causing this.

Request: Remove Copy bound from Components

I came across this project and tried to use it in my engine, however, components seem to need the copy bound, which is unfortunate. Is there any good reason why this is necessary? Wouldn't clone suffice?

Successive add_tag() don't work

(looks like a sort-of-dupe of #12)
This code doesn't work:

use legion::prelude::*;

// Define our entity data types
#[derive(Clone, Copy, Debug, PartialEq)]
struct Position {
    x: f32,
    y: f32,
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct Velocity {
    dx: f32,
    dy: f32,
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct Model(usize);

#[derive(Clone, Copy, Debug, PartialEq)]
struct Static;

fn main() {
    // Create a world to store our entities
    let universe = Universe::new();
    let mut world = universe.create_world();

    // Create entities with `Position` data and a tagged with `Model` data and as `Static`
    // Tags are shared across many entities, and enable further batch processing and filtering use cases
    let e = world.insert(
        (Model(5), Static),
        (0..999).map(|_| (Position { x: 0.0, y: 0.0 },)),
    );
    let e = e[0];
    world.add_tag(e, Model(3));
    world.add_tag(e, Static); // if you remove this it works
    println!("Model = {:?}", world.get_tag::<Model>(e));
}

This code prints Model = Some(Model(5)). If you remove the last add_tag() then the code works and prints Model = Some(Model(3)).

Adding a component trigger `changed` filter for other components

Here is a simple example:

use legion::prelude::*;

#[derive(Debug)]
struct Foo(pub i32);

#[derive(Debug)]
struct Bar(pub i32);

fn main() {
    let universe = Universe::new();
    let mut world = universe.create_world();

    let foo_system = SystemBuilder::new("test")
        .with_query(<Read<Foo>>::query().filter(changed::<Foo>()))
        .build(|_, world, _, query| {
            for foo in query.iter(world) {
                println!("{:?} changed", foo);
            }
        });

    let mut schedule = Schedule::builder()
        .add_system(foo_system)
        .build();
    
    let entity = world.insert((), vec![(Foo(1),)])[0];

    println!("pass #1");
    schedule.execute(&mut world);

    // Adding Bar component will mark entity as if Foo has changed
    world.add_component::<Bar>(entity, Bar(0));

    println!("pass #2");
    schedule.execute(&mut world);

    println!("pass #3");
    schedule.execute(&mut world);
}

output

pass #1
Ref { borrow: Shared { state: 2 }, value: Foo(1) } changed
pass #2
Ref { borrow: Shared { state: 2 }, value: Foo(1) } changed
pass #3

It's not clear to me if it is the intended behavior, but typing .filter(changed::<Foo>()) strongly suggest it will pull only entities with modified Foo components.

Tested against 0.2.1 and master.

Adding a third, unused component to a query doubles performance

I'm writing a universal gravitation simulator and I noticed that adding a third, unused component to a query doubles performance (or more accurately, removing the component halves performance). I was thinking that maybe legion queries differently depending on how many elements there are but I haven't read the code.
There aren't any entities that have the first two components that don't have the third, and the speedup occurs for any component as far as I can tell.

Here's the relevant snippet:

pub fn apply_gravity(world: &World) {
    //for some reason adding a third component to the query doubles performance 
    let gravity_query = <(Read<Position>, Write<Kinematics>, Read<Radius>)>::query();
    let inner_query = <(Read<Position>, Read<Mass>, Read<Radius>)>::query();

    gravity_query.par_for_each(world, |(current_pos, kinematics, _)| {
        kinematics.accel.x = 0.0;
        kinematics.accel.y = 0.0;

        inner_query
            .iter(world)
            .for_each(|(other_pos, other_mass, _)| {
                let dist_vec = other_pos.0 - current_pos.0;
                let dist_mag_sqr = dist_vec.norm_squared();
                let dist_mag = dist_mag_sqr.powf(0.5);

                if current_pos != other_pos {
                    let dist_comp = dist_vec / dist_mag;

                    let grav_accel_mag = other_mass.0 / dist_mag_sqr * G;
                    let grav_accel: Vector = dist_comp * grav_accel_mag;

                    kinematics.accel += grav_accel
                }
            });
    });
}

wasm compatible?

Is Legion able to be targeted to wasm? I think only no-std is allowed I think.

Multiple views for disjoint components in a System

Hi, I'm trying to integrate legion with nphysics. Right now what I have involves writing wrappers around legion's Query/SubWorld so nphysics can query for a specific Body, or run a function on every Body in the world.

    let physics_system = SystemBuilder::new("physics")
        .with_query(nphysics_legion::create_legion_body_set_write_query())
        .with_query(nphysics_legion::create_legion_body_set_read_query())
        .with_query(nphysics_legion::create_legion_collider_set_write_query())
        .with_query(nphysics_legion::create_legion_collider_set_read_query())
        .write_resource::<PhysicsResource>()
        .build(move |_, world, resource, queries| {
            let mut bodies = nphysics_legion::SpecsBodySet::new(world, &queries.0, &queries.1);
            let mut colliders =
                nphysics_legion::SpecsColliderSet::new(world, &queries.2, &queries.3);
            resource.step(&mut bodies, &mut colliders);
        });

The problem with this is that nphysics expects the bodies and colliders (and other components) to be separate. The reason for this is given in dimforge/nphysics#233 (comment), which I think is reasonable. But this causes lifetime issues since I have two wrappers both wanting the same &mut SubWorld.

Does it make sense for legion to have some sort of way to express disjoint "SubWorlds" in a query? Maybe somehow make it possible for .build() to have multiple SubWorlds?

System Definition Alternatives

We currently provide the SystemBuilder for constructing systems. This API is needed because it is very difficult to manually express the type of a legion system, as it statically encodes an arbitrary number of queries and resource accesses, and queries statically encode into their type all of their data accesses (tag/component types and mutable/immutable) and filter expressions (which can be arbitrary boolean expressions).

However, in many cases the full power of SystemBuilder is not needed; most systems only use one query, and do not request any complex filter logic on that query. There may be scope for providing a higher level simplified API for system construction which would still be sufficient to cover the most common use cases.

System Traits

The first option may be to provide system traits which can be implemented on a unique struct for each system, similar to how specs/amethyst defines its systems:

// basic wrapper for `SystemBuilder`
struct MySystem;
impl IntoSystem for MySystem {
    fn into_system(self) -> Box<dyn Schedulable> {
        SystemBuilder::new("MySystem")
            .with_query(<(Read<Velocity>, Write<Position>)>::query())
            .read_resource::<Time>()
            .build(|_, world, query, time| {
                for (vel, mut pos) in query.iter(&mut world) {
                    *pos = *pos + *vel * time.delta_seconds();
                }
            })
    }
}

// simplified wrapper
struct MySystem;
impl ForEachSystem for MySystem {
    type Resources = Read<Time>;
    type Query = (Read<Velocity>, Write<Position>);

    fn for_each(entity: Entity, (vel: &Velocity, pos: &mut Position), time: &Time) {
        *pos = *pos + *vel + time.delta_seconds();
    }
}

There are a few limitations on ForEachSystem:

  • There can be only one query.
  • The query can't have any additional filters.
  • There is no world access. It could be possible, but only the unsafe APIs would be usable.
  • You can't run code outside of the loop body.

A ParForEachSystem alternative could be provided which uses par_for_each internally, but it would require the additional restriction that Resources must be ReadOnly.

These system structs can be converted into a System via .into_system().

Macro

Alternatively, we could use a procedural macro to define systems. I do not have experience writing procedural macros, so this is mostly speculative:

#[system(UpdatePositions)]
fn for_each(pos: &mut Position, vel: &Velocity, #[resource] time: &Time) {
    *pos = *pos + *vel * time.delta_seconds();
}

This would create an UpdatePositions system struct and implement IntoSystem. It has many of the restrictions of the ForEachSystem trait, except:

  • We can use argument position attributes to define #[tag], #[resource] and some filters such as #[on_changed].
  • The implementation would use either for_each or par_for_each, depending on whether or not all resources are accessed immutably, although the syntax does not make the performance impact obvious.
  • Requesting the Entity and CommandBuffer would be optional.
  • Access to PreparedWorld could be allowed, provided we check safety.

The above macro would generate something like:

struct UpdatePositions;

impl UpdatePositions {
    fn for_each(pos: &mut Position, vel: &Velocity, time: &Time) {
        *pos = *pos + *vel * time.delta_seconds();
    }
}

impl IntoSystem for UpdatePositions {
    fn into_system(self) -> Box<dyn Schedulable> {
        SystemBuilder::new(std::any::type_name::<Self>())
            .read_resource::<Time>()
            .with_query(<(Write<Position>, Read<Velocity>)>::query())
            .build(|_, world, query, time| {
                query.for_each(world, |(mut pos, vel)| {
                    Self::for_each(pos, vel, time);
                });
            })
    }
}

Question: Support for multiple processes?

I am writing a game where each servers manage (one or multiple) different (game-geographically adjacent) regions of a world, and a client connects to multiple regions of the world. How well does legion support this use case?

Suppose the client is located at (0,0), and I have four servers managing the regions [0,100]x[0,100], [-100, 0]x[0,100], [-100,0]x[-100,0] and [0,100]x[-100,0] respectively. The client would have to render (and partially simulate) the events happening on all nearby regions, so it has to connect to all 4 servers at once and handle the world events as if they are in the same world.

How would legion work with my use case? In particular,

  • Different processes would have different Universes. But I want to transfer entities between servers. How would legion work with this, e.g. could it preserve the entity ID? This matters because it would increase the risk of data anomaly if the client has to destroy-recreate entities all the time just because they moved between servers; if the client could identify entities in a consistent manner, the risk of bugs is much lower.
  • How much of the facilities from legion could I use when processes from different mcahines cannot share the same event queue, etc.?
  • Or did I miss something? Should I simply not use legion?

Make `Entity` (de)serializable

Hi,

For a networking game I'd like to use Entity as an identification for players (a player client would send to the server an Entity along other data).
If this is sound, would it be possible to make Entity (de)serializable to be able to send it in network packets ?

System Scheduler Design Discussion

Systems are functions. They each represent a unit of work responsible for processing resources or entity data. Each system may declare access to generic resources and/or a single Query for access to entity data. Each system also has an EntityCommandBuffer, into which it can queue structural changes to a world, such as entity insertions or deletions. These commands are deferred until later in the frame.

Systems are scheduled for execution with the scheduler. It is up to the scheduler to decide how and when to submit systems to a thread pool for execution. I propose a two phase scheduler:

Phase 1: Stage Scheduling

A single frame is defined as a sequence of Stages, for example: pre-update, update, post-update, physics, pre-render, render.

The scheduler maintains a queue of stages. The stage at the top of this queue is the active stage. A stage transition occurs when we pop a stage off the queue (exiting that stage), and enter the next stage.

Every system must specify a start: Stage and complete: Stage. These may or may not be the same stage, but complete must not be queued before start. A system is considered eligible for scheduling once its start stage has been entered (and any time thereafter). The recorded EntityCommandBuffer of a system will be comitted as the complete stage is exited.

A stage transition can occur once all systems that complete on the current stage have finished executing. During the transition, all command buffers for the stage are comitted. However, which archetypes are to be modified during command buffer execution cannot be known ahead of time (due to entity deletion and mutation). Command buffer execution, therefore, involves determining which archetypes are to be mutated before executing each command (and it cannot be computed any earlier as even earlier commands in the buffer can modify the result), and then determining if any other systems that have been marked eligible for scheduling access those archetypes. If so, the scheduler must yield and await the completion of those systems before continuing with the transition.

If the above conditions are met, we can safely execute command buffer actions even while other systems are still running (assuming all systems clone the archetype pointers for all archetypes they are to access ahead of time - during their start stage transition). As we determine which systems may be scheduled (and calculate which archetypes they shall access) as we enter a stage, and then apply change as we exit a stage, whether or not a system will see structural changes should be deterministic regardless of exactly when the system is submitted to a thread pool or when it actually executes.

Stages also form "soft" sync points, waiting for only the minimum set of systems to complete before a stage transition, while letting other systems that access data not modified by the transition to continue to execute through into the next stage (unless the system declares that it shall complete within the current stage).

Phase 2: System Scheduling

As the frame progresses through the sequence of stages, systems are "unlocked" by being marked eligible for scheduling. However, systems may have additional scheduling requirements which means we cannot immediately hand them over to the thread pool for execution. These requirements include explicit ordering via runs-before and runs-after constraints with other systems, and safety constraints that determine which pairings of systems can be executed concurrently based upon resource access (read/write of both generic resources and legion component accesses).

The second phase of the scheduler is responsible for determining which systems are dependant upon other systems. It submits all systems with no outstanding dependencies to the thread pool. When a system completes execution, the scheduler checks if this completed system was the last outstanding dependency of any other system and, if so, submits those.

A single system may sub-divide itself into multiple jobs when it is submitted to the thread pool. For example, a query system may be able to process all chunks independently and therefore submit one job per chunk. This decision is made by the system at the time it is submitted. The scheduler considers a system complete once all jobs have completed.

Undefined behavior in World::move_entity

Here in World::move_entity, two mutable references are taken to the contents of the storage field at once. A comment says this:

Safety Note:
It is only safe for us to have 2 &mut references to storage here because
we know we are only going to be modifying two chunks that are at different
indexes.

Unfortunately, this isn't correct. Having two &mut references to the same data alive at once is always undefined behavior, even if they're each only used to access separate fields.

I think all that should be necessary to fix this is to replace one reference with a pointer. I can make a PR if you want.

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.