The Dense Component Entity System

by Florian Blasius, with contributions from the Rust Community
annotated and documented by Ralf Zerres and all contributors

This version of the text assumes you’re using Rust v1.60.0 or later. Cargo.toml should define edition="2018". That enables and uses Rust 2018 Edition idioms in all derived projects.

The 2020 Edition of this guide is the initial release. It will be released with the DCES version 0.4.0.

  • Appendix A Keywords, explains the new raw identifiers.
  • Appendix D Translations, is work in progress. We will release instances of this book in the target language once they are translated.

For online reading, a HTML rendered version is available at DCES guide. Alternatively you might want to have it handy for offline usage. Either you download a rendered pdf or ebookversion or go ahead and download the source. Then kick on mdbook (the definition of the target location is optional).

If development has stabalized, this guide will be offered in multiple languages. The source directory is already structured to serve localized sub-directories. Thus to render its contents you need an enhanced version of mdbook. Ruin0x11 is maintaining a git branch localization, a PR is commited upstream.

Go ahead and install that mdBook version like this:

TMPDIR=<your_temporary_directory>
mkdir -p $TMPDIR; cd $TMPDIR
git clone https://github.com/Ruin0x11/mdBook.git
cd mdBook
git checkout localization
cargo update
cargo install --path .

We do make use of process visualization, that will need mermaid. To download and compile it from source, please use the following commands:

cargo install mdbook-mermaid
mermaid install

Now, that all needed binaries are installed on your system you can render the guide like this:

cd  <the_DCES_guide__directory>
mdbook build --language en --dest-dir dces-guide/en --open

Introduction

Welcome to Dense Component Entity System, a guide to DCES. In the model of an archetype, or dense ECS the entities are stored in tables. Components are the columns and entities are the rows. Archetype implementations are fast to query and iterate.

This document provides a quick overview of the different features and concepts in DCES with short examples. This is a good resource if you’re just getting started or just want to get a better idea of what kind of features are available in DCES!

Warning: This guide is incomplete. Documenting everything and rewriting outdated parts take a while. See the issue tracker to check what’s missing or outdated. If there are any mistakes or ideas that haven’t been reported, feel free to open a new issue there.

Features

  • Register entities with components
  • Share components between entities
  • Register systems and read / write components of entities
  • Order systems execution by priority
  • Register container for entity organization (Vec, FxHashMap, Custom Container, …)
  • Register init and cleanup system

Who DCES is for

DCES does suite for programmers that like to take advantage of the Rust programming language to develop games or enhance UI’s frameworks. Since everything is build native in Rust, there is no need to transform data structures and types. As an example OrbTk will completely rely on this entity component system (ECS) variant.

There are a number of reasons why an ECS is gaining popularity especially amongst game developers:

ECS can typically support larger numbers of game objects
ECS code tends to be more reusable
ECS code is easier to extend with new features
ECS allows for a more dynamic coding style

Why not just …

You might ask, why not just use another production grade, maintained and feature complete ECS? And you are right. There is freedom. At least there are two crates, that are powerful and totally suitable alternative.

DCES primary goal has been to provide a fast, dependency free ECS with full integration into the Orbital Toolkit (OrbTK). We have thought about incorporating parallel execution into DCES. This is deferred for now, since there is always overhead in parallelization. You should carefully profile to see if there are benefits in the switch. If you have only few things to iterate over then sequential join is faster.

using Specs

Components and Systems in Specs may be computed in parallel. It uses as Dispatcher to achieve this goal. Details are documented inside the Tutorial.

using legion

The ECS demonstrated an archetypal memory layout and trait-less components.

using Shipyard

This is a sparse set based ECS. It stores each component in its own sparse set, where each entity id is the key to the component. Sparse set implementations allow for fast add/remove operations.

Source Code

The source files from which this guide is generated can be found on its homepage at DCES guide (en).

Getting Started

Let’s start your DCES journey! There’s a lot to learn, but every journey starts somewhere. In this chapter, we’ll discuss:

  • Installing DCES on Linux, Bsd, macOS or Windows.
  • Using cargo, Rust’s package manager and build system.

Installation

The first step is to install Rust. This is described in depth following Rust book Chapter 1

When creating an DCES application, we define the needed dependencies to the DCES crate in the Cargo.toml file of our project. The complile process will resolve the references and download the source as needed.

Command Line Notation

In this chapter and throughout the book, we’ll show some commands used in the terminal. Lines that you should enter in a terminal all start with $. You don’t need to type in the $ character; it indicates the start of each command. Lines that don’t start with $ typically show the output of the previous command. Additionally, PowerShell-specific examples will use > rather than $.

Troubleshooting

WIP: What are the most common culprits? Can we provide some general, basic solutions?

Local documentation

DCES offers the option to install its documentation locally, so you can read it offline.

Any time a type, a function, a method or a crate is reference by the toolkit and you’re not sure what it does or how to use it, have a look at its application programming interface API documentation to find out!

Install Rust on Linux or macOS

If you are using Linux or macOS open up an terminal and copy and paste the text below and hit the enter key on your keyboard:

curl https://sh.rustup.rs -sSf | sh

Install Rust on Windows

Download and run the Rust windows installer from https://www.rust-lang.org/tools/install.

Install Redoxer (Redox OS)

If you want build and run your Rust application on a KVM capable OS for Redox you can use redoxer.

To install Redoxer you have to first install the rust toolchain. After that open up an terminal and copy and paste the text below and hit the enter key on your keyboard:

cargo install redoxer

To compile and run your application on Redox OS you should check the Redox OS Book.

Editor and IDE integration

A wide range of editors and IDE’s are providing support for Rust code like

  • like syntax-highlighting
  • auto-completion
  • linting
  • lsp support

VS Code

There is a big community that rely on the visualstudio implementation to handle their code base. Following are the steps needed to expand your installation to support VS Code for Rust development:

  1. Download VS Code from.
  2. Install Rust Language Server plugin (the Rust Language Server).

Alternative Editors and IDEs

If you prefer other solution, you will find in depth help inside the context of this incomplete links:

The Ingredients

DCES provides a dense ECS rust centric architecture. The interaction with DCES is managed via the Entity Component Manager(ECM), a wrapper API.

In contrast to the Object Oriented Programming (OOP) paradigma, the DCES separates data from behavior. Behaviors are handled via systems. Systems provide the logic and implement the behavior on how to act on entities with given properties. Data are bound to components that are assigned to entities. Thus entities are unique identifiers, that may have multiple, dynamically changable components. The components itself are usually preceived as properties in the consuming rust codebase.

The Architecture view

To break up the architecture of an ECS it’s quite a help to first define the building blocks. Here we go:

  • Entity: Is an uniquely identifyed object. The Entity can contain zero or more components.

  • Component: You are able to save datatypes inside a component. We will call the saved value of a component it’s property. The property may be looked up via its component key.

  • Entity_Component_Manager: The ECM keeps track of an entity store (bound entities and an associated component store).

  • Resource: A resource is a data slot which lives in a World. The data are shared between systems. Resources can only be accessed according to Rust’s typical borrowing model (one writer xor multiple readers). You may structure the components using resources.

  • Store: For any given storage provider the DCES defines an instance. It is quite common to utilize different, specialized stores. The store handles multiple entities, componets or resources.

  • Systems: The behaviors are processed with functions. This behavior will be processed for all matching entities of a component query.

  • World: A world represents the root of the DECS tree. Since it is totally leagal to have multiple world instances, each one represents a distinguished entity storage provider. This instances are handled via the methods of the entity_component_manager (ECM).

The following ClassDiagramms visualizes the involved DCES elements. To improve visibility, we will break up the complete tree into detailed sub-trees.

classDiagram

World --o EntityComponentManager
World --o Resource
World --o SystemStore

EntityComponentManager --o EntityStore
EntityComponentManager --o ComponentStore

EntityComponentManager : entity_store[EntityStore]
EntityComponentManager : component_store[ComponentStore]
EntityComponentManager : component_store()
EntityComponentManager : component_store_mut()
EntityComponentManager : create_entity()
EntityComponentManager : entity_counter[u32]
EntityComponentManager : entity_store()
EntityComponentManager : entity_store_mut()
EntityComponentManager : new()
EntityComponentManager : register_entity()
EntityComponentManager : remove_entity()
EntityComponentManager : stores()
EntityComponentManager : stores_mut()

Resource: FxHashMap[TypeId, Box->dyn Any]
Resource : contains()
Resource : get()
Resource : get_mut()
Resource : insert()
Resource : is_empty()
Resource : len()
Resource : new()
Resource : try_get()
Resource : try_get_mut()

World : entity_component_manager[EntityComponetManager]
World : resources[FxHashmap]
World : system_counter[u32]
World : system_store[SystemStore]
World : first_run bool
World : create_entity()
World : create_system()
World : drop()
World : entity_component_manager()
World : from_entity_store()
World : insert_resource()
World : print_entity()
World : register_init_system()
World : resource_mut()
World : remove_entity()
World : run()

Workflow 1-1: Global DCES architecture view

Next we have a look at the pieces inside the Entity hierarchy.

classDiagram

Trait_EntityStore --o VecEntityStore
Trait_EntityStore --o EntityBuilder
VecEntityStore --o Entity
EntityBuilder --o Entity
EntityBuilder --o Component

EntityBuilder : EntityStore[Entity, ComponentStore, EntityStore]
EntityBuilder : build()
EntityBuilder : components()

VecEntityStore : Vec[Entity]
VecEntityStore : register_entity()
VecEntityStore : remove_entity()

Trait_EntityStore : Entity
Trait_EntityStore : register_entity()
Trait_EntityStore : remove_entity()

Entity : Entity[u32]

Component: E[Any]

Workflow 1-2: Detailed Entity architecture view

Next we present an isolated visualizaton of the Componets hierarchy.

classDiagram

Trait_Component --o ComponentStore
Trait_Component --o ComponentBuilder
Trait_Component --o ComponentBox
ComponentBox --o SharedComponentBox
ComponentBox --o Component
SharedComponentBox --o Component
ComponentStore --o Component
ComponentBuilder --o Component

Component : E[Any]

ComponentBuilder : ComponentBuilder[components, shared]
ComponentBuilder : build()
ComponentBuilder : new()
ComponentBuilder : with()
ComponentBuilder : with_shared()

ComponentBox : ComponentBox[Box->dyn Any, TypeId]
ComponentBox : consume()
ComponentBox : new()

ComponentStore : ComponentStore[Components, SharedComponents]
ComponentStore : append()
ComponentStore : entities_of_component()
ComponentStore : get()
ComponentStore : get_mut()
ComponentStore : is()
ComponentStore : is_empty()
ComponentStore : is_origin()
ComponentStore : len()
ComponentStore : print_entity()
ComponentStore : register()
ComponentStore : register_box()
ComponentStore : register_shared()
ComponentStore : register_shared_box()
ComponentStore : register_shared_box_by_source_key()
ComponentStore : register_shared_by_source_key()
ComponentStore : remove_entity()
ComponentStore : source()
ComponentStore : source_from_shared()
ComponentStore : target_key()

SharedComponentBox : SharedComponentBox[Entity, TypeId]
SharedComponentBox : consume()
SharedComponentBox : new()

Trait_Component : E[Any]

Workflow 1-3: Detailed Component architecture view

Followed by the presentation of an isolated visualizaton of the System hierarchy.

classDiagram

Trait_System --o SystemStore

SystemStore --o SystemStoreBuilder
SystemStore --o EntitySystem
SystemStoreBuilder --o EntitySystem

SystemStoreBuilder <-- InitSystem
SystemStoreBuilder <-- CleanupSystem


Trait_System : EntityStore[Any]
Trait_System : run()

EntitySystem: EntitySystem[Box->dyn System, priority]
EntitySystem: new()

SystemStore : EntityStore[entity_system->HashMap, init_system>Option, cleanup_system->Option, priorities->BTreeMap]
SystemStore : borrow_cleanup_system()
SystemStore : borrow_init_system()
SystemStore : new()
SystemStore : register_cleanup_system()
SystemStore : register_init_system()
SystemStore : register_priority()
SystemStore : register_system()
SystemStore : remove_system()

SystemStoreBuilder: SystemStoreBuilder[entity_system_id, system_store, priority]
SystemStoreBuilder: build()
SystemStoreBuilder: with_priority()

Workflow 1-4: Detailed System architecture view

DCES Example Applications

This section provides DCES example apps. We hope to cover interesting aspects.

Take them as a tutorial, all listings are created as a reference. They have in mind to serve as an introduction to a specific topic. As educational content, this apps are marked with in-lined comments and anchors. If we did well, you can concentrate on the parts we like to emphasize.

Inside the library, you will find the collection of example code in the sub-directory examples.

The annotated sources are placed in the subdirectory listings. A Cargo.toml will group all apps members as a workspace. Change inside the listings root and call:

cargo build

That will build them all in one run using debug mode. To run a specific app, e.g. call:

cargo run --bin dces_resource

DCES Examples - Basic

This subsection documents the example application dces_basic.

This example instantiates a single world. It creates three entities each with an assigned component that provides the property “name”, “depth” and “size”.

A size system will query the entity store and manipulate the width and height struct values of components that provide and match a key “size”. As a rudimentary use case, we do simply increment by one.

A print system will queries into the entity store “e_store”, collects given entities and prints out the “value” of any component that has a matching component with the property key “name”. Without any fancy magic, entities name, its width and its height are printed to stdout.

The project root

Change to your project root directory. If you didn’t already create the app in the first place, go ahead and type the following in your console:

$ cd ~/guide/examples

Create the source code

Next we will use cargo to create the app. All boilerplate tasks are handled using cargo’s inherited template handling.

$ cargo new dces_basic
$ cd dces_basic

The first command, cargo new, takes the name of the project (“dces_basic”) as the first argument. The second command changes to the new project’s directory.

Look at the generated Cargo.toml file:

[package]
name = "dces_example"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

Listing 1-1: Default metadata “dces_basic”

With cargo new, a default project structure is created. Maybe the author information is already exchanged if Cargo could obtain a definition from your environment. Cargo also generated source code for a “Hello, world!” program. Let’s Check out the corresponding src/main.rs file:

fn main() {
    println!("Hello DECS!");
}

Listing 1-2: Default source file “main.rs”

No need to compile that stage with cargo run, since we are going to exchange the project metadata, as well as the dces source code right away.

Update Cargo.toml

First reopen the Cargo.toml file and enter the Code in Listing 1-3 into Cargo.toml

[package]
name = "dces_basic"
version = "0.4.0"
authors = [
	"Florian Blasius <flovanpt@posteo.de>",
	"Ralf Zerres <ralf.zerres.de@gmail.com>",
]
description = "DCES - Basic example"
documentation = "https://doc.redox-os.org/dces-guide"
repository = "https://gitlab.redox-os.org/redox-os/dces-guide/"
readme = "README.md"
license = "MIT"
keywords = [
	"dces",
	"ecs",
]
edition = "2021"

[profile.dev]
opt-level = 1

[dependencies]
dces = { git = "https://gitlab.redox-os.org/redox-os/dces-rust.git", branch = "develop" }

[[bin]]
name = "dces_basic"
path = "src/main.rs"

Listing 1-3: Project metadata “dces_basic”

You may wonder, why the name property inside the Cargo.toml is formatted like dces_basic.

name = "dces_basic"

It is a good habit to follow rusts naming convention, that encourages you to use snake_case naming. While expanding the DCES example sources, we will keep the grouping prefix dces. That way we end up to call our first target binary dces_basic.

Update main.rs

All of the DCES specific code that is needed to build our first example is shown in Listing 1-4, that you encode into src/main.rs.

Save your changes to the file and go back to the terminal window. Enter the following commands to compile and run the file in debug mode:

$ cargo run --example basic

The following output should be printed inside your console window:

Button width: 6; height: 6
CheckBox width: 4; height: 4
RadioButton width: 5; height: 7

Recap and annotation

The anatomy of a DCES application

Let’s review in detail what just happened in your DCES-basic application. Here are the relevant pieces of the puzzle:

use dces::prelude::*;

#[derive(Default)]
struct Size {
    width: u32,
    height: u32,
}

#[derive(Default)]
struct Name(String);

#[derive(Default)]
struct Depth(u32);

pub struct SizeSystem;
impl System<EntityStore> for SizeSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get_mut::<Size>("size", *entity) {
                comp.width += 1;
                comp.height += 1;
            }
        }
    }
}

pub struct PrintSystem;
impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(name) = c_store.get::<Name>("name", *entity) {
                if let Ok(size) = c_store.get::<Size>("size", *entity) {
                    println!("{} width: {}; height: {}", name.0, size.width, size.height);
                }
            }
        }
    }
}

fn main() {
    let mut world = World::from_entity_store(EntityStore::default());

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("Button")))
                .with("depth", Depth(4))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 5,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("CheckBox")))
                .with("depth", Depth(1))
                .with(
                    "size",
                    Size {
                        width: 3,
                        height: 3,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("RadioButton")))
                .with("detph", Depth(2))
                .with(
                    "size",
                    Size {
                        width: 4,
                        height: 6,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(3))
                .with(
                    "size",
                    Size {
                        width: 10,
                        height: 4,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(0))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 8,
                    },
                )
                .build(),
        )
        .build();

    world.create_system(PrintSystem).with_priority(1).build();

    world.create_system(SizeSystem).with_priority(0).build();

    world.run();
}

The first line is introducing a use declaration. A use declaration is used to shorten the path required to refer to rust module items. The prelude is a convenient way to a list of things, that rust will automatically import to you program. Here, we bind the path dces::prelude. All default items defined in this path (referenced with ::) are now accessible in your source using their shorthand name. No need to type in their common prefix (dces::prelude::)

use dces::prelude::*;

#[derive(Default)]
struct Size {
    width: u32,
    height: u32,
}

#[derive(Default)]
struct Name(String);

#[derive(Default)]
struct Depth(u32);

pub struct SizeSystem;
impl System<EntityStore> for SizeSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get_mut::<Size>("size", *entity) {
                comp.width += 1;
                comp.height += 1;
            }
        }
    }
}

pub struct PrintSystem;
impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(name) = c_store.get::<Name>("name", *entity) {
                if let Ok(size) = c_store.get::<Size>("size", *entity) {
                    println!("{} width: {}; height: {}", name.0, size.width, size.height);
                }
            }
        }
    }
}

fn main() {
    let mut world = World::from_entity_store(EntityStore::default());

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("Button")))
                .with("depth", Depth(4))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 5,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("CheckBox")))
                .with("depth", Depth(1))
                .with(
                    "size",
                    Size {
                        width: 3,
                        height: 3,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("RadioButton")))
                .with("detph", Depth(2))
                .with(
                    "size",
                    Size {
                        width: 4,
                        height: 6,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(3))
                .with(
                    "size",
                    Size {
                        width: 10,
                        height: 4,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(0))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 8,
                    },
                )
                .build(),
        )
        .build();

    world.create_system(PrintSystem).with_priority(1).build();

    world.create_system(SizeSystem).with_priority(0).build();

    world.run();
}

The main function is special: it defines the first execution point inside a compiled Rust binary (leaving rust’s minimal runtime out of scope). In our example the function name main has no parameters and returns nothing. If there were parameters, they would go inside the parentheses, ().

Also, note that the function body is wrapped in curly brackets, {}. Rust requires these around all function bodies. It’s good style to place the opening curly bracket on the same line as the function declaration, adding one space in between.

An automatic formatter tool called rustfmt will help you to stick to a standard style across Rust projects. DCES is following this guidance. rustfmt will format your code in a particular style. Depending on the version of your rust toolchain, it is probably already installed on your computer! Check the online documentation for more details.

Lets focus on some other important details:

  • First, Rust style is to indent with four spaces, not a tab.
  • Second, we create five entities inside a world.
  • Third, we create two systems inside a world.
  • Forth, we start the run loop for our world.

As a first step, we do instantiate a mutable world structure. This structure will make use of an EnityStore.

use dces::prelude::*;

#[derive(Default)]
struct Size {
    width: u32,
    height: u32,
}

#[derive(Default)]
struct Name(String);

#[derive(Default)]
struct Depth(u32);

pub struct SizeSystem;
impl System<EntityStore> for SizeSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get_mut::<Size>("size", *entity) {
                comp.width += 1;
                comp.height += 1;
            }
        }
    }
}

pub struct PrintSystem;
impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(name) = c_store.get::<Name>("name", *entity) {
                if let Ok(size) = c_store.get::<Size>("size", *entity) {
                    println!("{} width: {}; height: {}", name.0, size.width, size.height);
                }
            }
        }
    }
}

fn main() {
    let mut world = World::from_entity_store(EntityStore::default());

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("Button")))
                .with("depth", Depth(4))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 5,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("CheckBox")))
                .with("depth", Depth(1))
                .with(
                    "size",
                    Size {
                        width: 3,
                        height: 3,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("RadioButton")))
                .with("detph", Depth(2))
                .with(
                    "size",
                    Size {
                        width: 4,
                        height: 6,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(3))
                .with(
                    "size",
                    Size {
                        width: 10,
                        height: 4,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(0))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 8,
                    },
                )
                .build(),
        )
        .build();

    world.create_system(PrintSystem).with_priority(1).build();

    world.create_system(SizeSystem).with_priority(0).build();

    world.run();
}

Next we create five new entities inside the our world. For each entity we assign components. The components are constructed consuming the method ComponentBuilder to define their values (uppercase string). The corresponding keys are name, depth, and size (in parentheses). A terminating block is calling the build method, that finally instantiates the code.

Following code block extracts the definition of the first entity:

use dces::prelude::*;

#[derive(Default)]
struct Size {
    width: u32,
    height: u32,
}

#[derive(Default)]
struct Name(String);

#[derive(Default)]
struct Depth(u32);

pub struct SizeSystem;
impl System<EntityStore> for SizeSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get_mut::<Size>("size", *entity) {
                comp.width += 1;
                comp.height += 1;
            }
        }
    }
}

pub struct PrintSystem;
impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(name) = c_store.get::<Name>("name", *entity) {
                if let Ok(size) = c_store.get::<Size>("size", *entity) {
                    println!("{} width: {}; height: {}", name.0, size.width, size.height);
                }
            }
        }
    }
}

fn main() {
    let mut world = World::from_entity_store(EntityStore::default());

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("Button")))
                .with("depth", Depth(4))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 5,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("CheckBox")))
                .with("depth", Depth(1))
                .with(
                    "size",
                    Size {
                        width: 3,
                        height: 3,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("RadioButton")))
                .with("detph", Depth(2))
                .with(
                    "size",
                    Size {
                        width: 4,
                        height: 6,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(3))
                .with(
                    "size",
                    Size {
                        width: 10,
                        height: 4,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(0))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 8,
                    },
                )
                .build(),
        )
        .build();

    world.create_system(PrintSystem).with_priority(1).build();

    world.create_system(SizeSystem).with_priority(0).build();

    world.run();
}

Please note, that “size” itself is a structure that handles the values width and height.

use dces::prelude::*;

#[derive(Default)]
struct Size {
    width: u32,
    height: u32,
}

#[derive(Default)]
struct Name(String);

#[derive(Default)]
struct Depth(u32);

pub struct SizeSystem;
impl System<EntityStore> for SizeSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get_mut::<Size>("size", *entity) {
                comp.width += 1;
                comp.height += 1;
            }
        }
    }
}

pub struct PrintSystem;
impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(name) = c_store.get::<Name>("name", *entity) {
                if let Ok(size) = c_store.get::<Size>("size", *entity) {
                    println!("{} width: {}; height: {}", name.0, size.width, size.height);
                }
            }
        }
    }
}

fn main() {
    let mut world = World::from_entity_store(EntityStore::default());

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("Button")))
                .with("depth", Depth(4))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 5,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("CheckBox")))
                .with("depth", Depth(1))
                .with(
                    "size",
                    Size {
                        width: 3,
                        height: 3,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("RadioButton")))
                .with("detph", Depth(2))
                .with(
                    "size",
                    Size {
                        width: 4,
                        height: 6,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(3))
                .with(
                    "size",
                    Size {
                        width: 10,
                        height: 4,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(0))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 8,
                    },
                )
                .build(),
        )
        .build();

    world.create_system(PrintSystem).with_priority(1).build();

    world.create_system(SizeSystem).with_priority(0).build();

    world.run();
}

We should mention, that our example-code defines all entities with the same number and names of component elements. This is not a requirement nor a design restriction. Which components are attached to which entities may differ significantly and is completely arbitrary.

Our binary will consume a Size- and a Print-System. Both of them do handle entity manipulation via the EntityComponentManager (ECM). The ECM is capable to borrow or mutate Enities and Components via its EntityStore. Thus we can assign distinct store structures for each of them (here: e_store, c_store).

use dces::prelude::*;

#[derive(Default)]
struct Size {
    width: u32,
    height: u32,
}

#[derive(Default)]
struct Name(String);

#[derive(Default)]
struct Depth(u32);

pub struct SizeSystem;
impl System<EntityStore> for SizeSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get_mut::<Size>("size", *entity) {
                comp.width += 1;
                comp.height += 1;
            }
        }
    }
}

pub struct PrintSystem;
impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(name) = c_store.get::<Name>("name", *entity) {
                if let Ok(size) = c_store.get::<Size>("size", *entity) {
                    println!("{} width: {}; height: {}", name.0, size.width, size.height);
                }
            }
        }
    }
}

fn main() {
    let mut world = World::from_entity_store(EntityStore::default());

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("Button")))
                .with("depth", Depth(4))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 5,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("CheckBox")))
                .with("depth", Depth(1))
                .with(
                    "size",
                    Size {
                        width: 3,
                        height: 3,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("RadioButton")))
                .with("detph", Depth(2))
                .with(
                    "size",
                    Size {
                        width: 4,
                        height: 6,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(3))
                .with(
                    "size",
                    Size {
                        width: 10,
                        height: 4,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(0))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 8,
                    },
                )
                .build(),
        )
        .build();

    world.create_system(PrintSystem).with_priority(1).build();

    world.create_system(SizeSystem).with_priority(0).build();

    world.run();
}

Next we simply loop over the store in question and use the get() method to consume the values of the given entities and components that match our query.

use dces::prelude::*;

#[derive(Default)]
struct Size {
    width: u32,
    height: u32,
}

#[derive(Default)]
struct Name(String);

#[derive(Default)]
struct Depth(u32);

pub struct SizeSystem;
impl System<EntityStore> for SizeSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get_mut::<Size>("size", *entity) {
                comp.width += 1;
                comp.height += 1;
            }
        }
    }
}

pub struct PrintSystem;
impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(name) = c_store.get::<Name>("name", *entity) {
                if let Ok(size) = c_store.get::<Size>("size", *entity) {
                    println!("{} width: {}; height: {}", name.0, size.width, size.height);
                }
            }
        }
    }
}

fn main() {
    let mut world = World::from_entity_store(EntityStore::default());

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("Button")))
                .with("depth", Depth(4))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 5,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("CheckBox")))
                .with("depth", Depth(1))
                .with(
                    "size",
                    Size {
                        width: 3,
                        height: 3,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("RadioButton")))
                .with("detph", Depth(2))
                .with(
                    "size",
                    Size {
                        width: 4,
                        height: 6,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(3))
                .with(
                    "size",
                    Size {
                        width: 10,
                        height: 4,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(0))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 8,
                    },
                )
                .build(),
        )
        .build();

    world.create_system(PrintSystem).with_priority(1).build();

    world.create_system(SizeSystem).with_priority(0).build();

    world.run();
}

Complete example source

Find attached the complete source code for our dces_basic example.

use dces::prelude::*;

#[derive(Default)]
struct Size {
    width: u32,
    height: u32,
}

#[derive(Default)]
struct Name(String);

#[derive(Default)]
struct Depth(u32);

pub struct SizeSystem;
impl System<EntityStore> for SizeSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get_mut::<Size>("size", *entity) {
                comp.width += 1;
                comp.height += 1;
            }
        }
    }
}

pub struct PrintSystem;
impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores_mut();

        for entity in &e_store.inner {
            if let Ok(name) = c_store.get::<Name>("name", *entity) {
                if let Ok(size) = c_store.get::<Size>("size", *entity) {
                    println!("{} width: {}; height: {}", name.0, size.width, size.height);
                }
            }
        }
    }
}

fn main() {
    let mut world = World::from_entity_store(EntityStore::default());

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("Button")))
                .with("depth", Depth(4))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 5,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("CheckBox")))
                .with("depth", Depth(1))
                .with(
                    "size",
                    Size {
                        width: 3,
                        height: 3,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("RadioButton")))
                .with("detph", Depth(2))
                .with(
                    "size",
                    Size {
                        width: 4,
                        height: 6,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(3))
                .with(
                    "size",
                    Size {
                        width: 10,
                        height: 4,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("depth", Depth(0))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 8,
                    },
                )
                .build(),
        )
        .build();

    world.create_system(PrintSystem).with_priority(1).build();

    world.create_system(SizeSystem).with_priority(0).build();

    world.run();
}

Listing 1-4: dces_basic - Create a World, its entities and systems.

Compiling and Running Are Separate Steps

Before you are able to run a DCES application, you must compile its source code. A typical DCES project will generate the executable binary code using cargo and place the result in the target subfolder of the project.

Profiles may be used to configure compiler options such as optimization levels and debug settings. By default the dev or test profiles are used. If the --release flag is given, then the release or bench profiles are used.

$ cargo build --release --bin dces_basic.rs
$ ../target/release/dces_basic

On Windows, you need to use backslash as a path delimiter:

> cargo build --release --bin dces_basic.rs
> ..\target\release\dces_basic.exe

DCES Examples - Minimal

This subsection documents the example application dces_minimal.

In a minimalistic use-case, this example creates a single world. It creates one entity and assignes one component that provides the property “name”.

A print system will queries into the entity store “e_store”, collects the given entity and prints out the “value” of any component that has a matching component with the property key “name”. Without any fancy magic, they are printed to stdout.

Update Cargo.toml

First have a look at the corresponding Cargo.toml file as shown in Listing 1-1.

# ANCHOR: All
[package]
# ANCHOR: Name
name = "dces_minimal"
# ANCHOR_END: Name
version = "0.4.0"
authors = [
	"Florian Blasius <flovanpt@posteo.de>",
	"Ralf Zerres <ralf.zerres.de@gmail.com>",
]
description = "DCES - Minimal example"
documentation = "https://doc.redox-os.org/dces-guide"
repository = "https://gitlab.redox-os.org/redox-os/dces-guide"
readme = "README.md"
license = "MIT"
keywords = [
	"dces",
	"ecs",
]
edition = "2021"

[profile.dev]
opt-level = 1

[dependencies]
dces = { git = "https://gitlab.redox-os.org/redox-os/dces-rust.git", branch = "develop" }

[[bin]]
# ANCHOR: Name
name = "dces_minimal"
# ANCHOR_END: Name
path = "src/main.rs"
# ANCHOR_END: All

Listing 1-1: Projects metadata “dces_minimal”

Program source code

All of the DCES specific code that is needed to build the dces_minimal example is shown in [Listing 1-2][dces_minimal, that is encode in src/main.rs.

Enter the following commands inside the terminal window to compile and run in debug mode:

$ cargo run --example miminal

The following output should be printed inside your console window:

DCES

Recap and annotation

The anatomy of the dces_minimal application

Let’s review the relevant parts of the dces_minimal application. A more in depth view of a typical DCES application is documented inside the annotated dces_basic source.

Following code block extracts the implementation of the PrintSystem:

impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores();

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get::<Name>("name", *entity) {
                println!("{}", comp.value);
            }
            // ANCHOR_END Print_ComponentValue
        }
    }
}

We loop over the given entity store “e_store” and get any component with a key matching "name". If the value us non-null, it will be printed to stdout.

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get::<Name>("name", *entity) {
                println!("{}", comp.value);
            }
            // ANCHOR_END Print_ComponentValue
        }

As a result, the string DCES! will make it as the console output.

Complete example source

Find attached the complete source code for our dces_minimal example.

use dces::prelude::*;

#[derive(Default)]
struct Name {
    value: String,
}

struct PrintSystem;

impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores();

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get::<Name>("name", *entity) {
                println!("{}", comp.value);
            }
            // ANCHOR_END Print_ComponentValue
        }
    }
}

fn main() {
    let mut world = World::from_entity_store(EntityStore::default());

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with(
                    "name",
                    Name {
                        value: String::from("DCES"),
                    },
                )
                .build(),
        )
        .build();

    world.create_system(PrintSystem).build();
    world.run();
}

Listing 1-2: dces_minimal - Create a World, an entities, and a print systems.

Compiling and Running Are Separate Steps

The cargo call to compile dces_minimal will place the resulting binary in the target subfolder of the project.

$ cargo build --release --bin dces_minimal.rs
$ ../target/release/dces_minimal

On Windows, you need to use backslash as a path delimiter:

> cargo build --release --bin dces_minimal.rs
> ..\target\release\dces_minimal.exe

DCES Examples - Resource

This subsection documents the example application dces_resource.

Again, we create a single world instance. Next one entity is build and we assignes one component that provides the property “name”. A mutable resource consuming type “HelloDCES” is assigned as well. Inside the print system we take care to print out any property value that matches the component key “name”. In addition the “say_hello()” method of resource “HelloDCES” is executed, that simply returns the String “Hello DCES!”.

Update Cargo.toml

First have a look at the corresponding Cargo.toml file as shown in Listing 1-1.

# ANCHOR: All
[package]
# ANCHOR: Name
name = "dces_resource"
# ANCHOR_END: Name
version = "0.4.0"
authors = [
	"Florian Blasius <flovanpt@posteo.de>",
	"Ralf Zerres <ralf.zerres.de@gmail.com>",
]
description = "DCES - Resource example"
documentation = "https://doc.redox-os.org/dces-guide"
repository = "https://gitlab.redox-os.org/redox-os/dces-guide/"
readme = "README.md"
license = "MIT"
keywords = [
	"orbital",
	"widget",
	"ui",
]
edition = "2021"

[profile.dev]
opt-level = 1

[dependencies]
dces = { git = "https://gitlab.redox-os.org/redox-os/dces-rust.git", branch = "develop" }

[[bin]]
# ANCHOR: Name
name = "dces_resource"
# ANCHOR_END: Name
path = "src/main.rs"
# ANCHOR_END: All

Listing 1-1: Projects metadata “dces_resources”

Program source code

All of the DCES specific code that is needed to build the dces_resources example is shown in Listing 1-2, that is encode in src/main.rs.

Enter the following commands inside the terminal window to compile and run in debug mode:

$ cargo run --example resource

The following output should be printed inside your console window:

DCES
Hello DCES!

Recap and annotation

The anatomy of the dces_resource application

Let’s review the relevant parts of the dces_resource application. A more in depth view of a typical DCES application is documented inside the annotated dces_basic source.

Following code block extracts the implementation of the PrintSystem:

impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, res: &mut Resources) {
        let (e_store, c_store) = ecm.stores();

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get::<Name>("name", *entity) {
                println!("{}", comp.value);
            }
        }

        println!("{}", res.get::<HelloDCES>().say_hello());
    }
}

The value of component key "name" will be processed as already documented. The new part is inside the handling of the Resource-Structure via the PrintSystem.

struct HelloDCES;

The code implements a the HelloDCES type which provides the say_hello() method.

impl HelloDCES {
    pub fn say_hello(&self) -> &str {
        return "Hello DCES!";
    }
}

With the resource get() method, we chain the call to its say_hello() method. It returns a String that is passed over to the println!() macro.

        println!("{}", res.get::<HelloDCES>().say_hello());

As a result, the string Hello DCES! will make it as the console output.

Complete example source

Find attached the complete source code for our dces_resource example.

use dces::prelude::*;

#[derive(Default)]
struct Name {
    value: String,
}

struct HelloDCES;

impl HelloDCES {
    pub fn say_hello(&self) -> &str {
        return "Hello DCES!";
    }
}

struct PrintSystem;

impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, res: &mut Resources) {
        let (e_store, c_store) = ecm.stores();

        for entity in &e_store.inner {
            if let Ok(comp) = c_store.get::<Name>("name", *entity) {
                println!("{}", comp.value);
            }
        }

        println!("{}", res.get::<HelloDCES>().say_hello());
    }
}

fn main() {
    let mut world = World::from_entity_store(EntityStore::default());

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with(
                    "name",
                    Name {
                        value: String::from("DCES"),
                    },
                )
                .build(),
        )
        .build();

    world.resources_mut().insert(HelloDCES);

    world.create_system(PrintSystem).build();

    world.run();
}

Listing 1-2: dces_resource - Create a World, its entities, resources and a print systems.

Compiling and Running Are Separate Steps

The compiled dces_resource with cargo will be placed the resulting binary in the target subfolder of the project.

$ cargo build --release --bin dces_resource.rs
$ ../target/release/dces_resource

On Windows, you need to use backslash as a path delimiter:

> cargo build --release --bin dces_resource.rs
> ..\target\release\dces_resource.exe

DCES Examples - Shared

This subsection documents the example application dces_shared.

In a minimalistic use-case, this example creates a single world. It creates two entities. Each entity gets assinged components that provide the properties “name”, “depth” and “size”. When using the ComponentBuilder for the second entity (component “name” == “CheckBox”) we make use of a method “with_shared()”, that will reference to an already given struct “size”.

A size system will query the entity store and manipulate the width and height struct values of components that provide and match a key “size”. As a rudimentary use case, we do simply increment them by one.

A print system will queries into the entity store “e_store”, collects given entities and prints out the “value” of any component that has a matching property key “name”. Without any fancy magic, entities name, its width and its height are printed to stdout. and assignes three component that provides the property “name”, “depth” and “size”.

Update Cargo.toml

First have a look at the corresponding Cargo.toml file as shown in Listing 1-1.

# ANCHOR: All
[package]
# ANCHOR: Name
name = "dces_shared"
# ANCHOR_END: Name
version = "0.4.0"
authors = [
	"Florian Blasius <flovanpt@posteo.de>",
	"Ralf Zerres <ralf.zerres.de@gmail.com>",
]
description = "DCES - Shared example"
documentation = "https://doc.redox-os.org/dces-guide"
repository = "https://gitlab.redox-os.org/redox-os/dces-guide/"
readme = "README.md"
license = "MIT"
keywords = [
	"dces",
	"ecs",
]
edition = "2021"

[profile.dev]
opt-level = 1

[dependencies]
dces = { git = "https://gitlab.redox-os.org/redox-os/dces-rust.git", branch = "develop" }

[[bin]]
# ANCHOR: Name
name = "dces_shared"
# ANCHOR_END: Name
path = "src/main.rs"
# ANCHOR_END: All

Listing 1-1: Projects metadata “dces_shared”

Program source code

All of the DCES specific code that is needed to build the dces_shared example is shown in Listing 1-2, that is encode in src/main.rs.

Enter the following commands inside the terminal window to compile and run in debug mode:

$ cargo run --example shared

The following output should be printed inside your console window:

entity: 0; name: Button; width: 6; height: 6
entity: 1; name: CheckBox; width: 6; height: 6

Recap and annotation

The anatomy of the dces_shared application

Let’s review the relevant parts of the dces_shared application. A more in depth view of a typical DCES application is documented inside the annotated dces_basic source.

Following code block extracts the implementation of the PrintSystem:

impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores();

        for entity in &e_store.inner {
            if let Ok(name) = c_store.get::<Name>("name", *entity) {
                if let Ok(size) = c_store.get::<Size>("size", *entity) {
                    println!(
                        "entity: {}; name: {}; width: {}; height: {}",
                        entity.0, name.0, size.width, size.height
                    );
                }
            }
        }
    }
}

The values of the component keys "name", "width" and "height" will be processed as already documented.

The new part is inside the handling of the Size-Structure to a component. Inside the main function, when we do attach the component “size” with ComponentBuilder inside the second entity, we make use of the with_shared method. This method is able to borrow the already assigned structure “size”. Therefore it will reference the given values.

                .with_shared::<Size>("size", source)

Complete example source

Find attached the complete source code for our dces_shared example.

use dces::prelude::*;

#[derive(Default)]
struct Size {
    width: u32,
    height: u32,
}

#[derive(Default)]
struct Name(String);

#[derive(Default)]
struct Depth(u32);

pub struct SizeSystem {
    source: Entity,
}

impl System<EntityStore> for SizeSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        if let Ok(comp) = ecm
            .component_store_mut()
            .get_mut::<Size>("size", self.source)
        {
            comp.width += 1;
            comp.height += 1;
        }
    }
}

struct PrintSystem;

impl System<EntityStore> for PrintSystem {
    fn run(&self, ecm: &mut EntityComponentManager<EntityStore>, _: &mut Resources) {
        let (e_store, c_store) = ecm.stores();

        for entity in &e_store.inner {
            if let Ok(name) = c_store.get::<Name>("name", *entity) {
                if let Ok(size) = c_store.get::<Size>("size", *entity) {
                    println!(
                        "entity: {}; name: {}; width: {}; height: {}",
                        entity.0, name.0, size.width, size.height
                    );
                }
            }
        }
    }
}

fn main() {
    let mut world = World::from_entity_store(EntityStore::default());

    let source = world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("Button")))
                .with("depth", Depth(4))
                .with(
                    "size",
                    Size {
                        width: 5,
                        height: 5,
                    },
                )
                .build(),
        )
        .build();

    world
        .create_entity()
        .components(
            ComponentBuilder::new()
                .with("name", Name(String::from("CheckBox")))
                .with("depth", Depth(1))
                .with_shared::<Size>("size", source)
                .build(),
        )
        .build();

    world.create_system(PrintSystem).with_priority(1).build();

    world
        .create_system(SizeSystem { source })
        .with_priority(0)
        .build();

    world.run();
}

Listing 1-2: dces_shared - Create a World, its entities and a shared component.

Compiling and Running Are Separate Steps

The compiled dces_shared with cargo will be placed the resulting binary in the target subfolder of the project.

$ cargo build --release --bin dces_shared.rs
$ ../target/release/dces_shared

On Windows, you need to use backslash as a path delimiter:

> cargo build --release --bin dces_shared.rs
> ..\target\release\dces_shared.exe

DCES Appendix

This is WIP

DCES Appendix - Keywords

This is WIP

DCES Appendix - Resources

This is WIP

Appendix D: Guide translations

For resources in languages other than English. This is work in progress; see the Translations label to help or let us know about a new translation!