Feel free to join the next Helmholtz Hacky Hour #26 on Wednesday, April 21, 2021 from 2PM to 3PM!

Commit 821354d2 authored by Martin Lange's avatar Martin Lange

tried to separate ECS notes from artemis-specific notes

parent 7ddb32a8
Pipeline #12195 passed with stage
in 12 seconds
......@@ -37,7 +37,7 @@ Model development is an iterative process. For example, submodels are frequently
A design approach that facilitates this style of work is the Entity鈥揅omponent鈥揝ystem (ECS) pattern, by decoupling the state of the model from the logic. While it is rooted in game design, it has proven to be useful for IBMs.
In ECS terms, *entities* are an abstract handle to refer to what we usually consider entities in the IBM world. For example in a predator-prey-model, each predator and each prey is represented by an entity. In a livestock model, each individual animal, each herd, and each market is represented by an entity.
In ECS terms, *entities* are an abstract handles to refer to what we usually consider entities in the IBM world. For example in a predator-prey-model, each predator and each prey could be represented by an entity. In a livestock model, each individual animal, each herd, and each market could be represented by an entity.
Each entity has one or more *components* which contain the entity's state variables. What can happen with an entity in the model results from the composition of its components. To resume the livestock example, all three types of entities could have a location component, but only animals would have an age. The herds could e.g. have a component holding their trade partners.
......@@ -58,14 +58,12 @@ The purpose of this model is to demonstrate the use of the ECS approach for indi
## Entities, state variables and scales
> In an ECS, all entities are generic, but characterized by the components they possess. Components contain an entity's state variables.
> In an ECS, the entities are indistinct, but characterized by the components they possess. Components contain an entity's state variables.
**Components**
In this grassing model, the only entities are grassers. All grassers possess the components `Position`, and `Energy`. An optional component `Movement` determines the current activity of each grasser.
> Components must extend `com.artemis.Component`.
Component `Position` contains continuous coordinates in a two-dimensional world.
```java
......@@ -73,6 +71,8 @@ Component `Position` contains continuous coordinates in a two-dimensional world.
package grassing.comp;
public class Position extends com.artemis.Component {
// Components must extend class Component
public float x;
public float y;
......@@ -102,7 +102,7 @@ public class Energy extends com.artemis.Component {
Component `Movement` contains the direction the entity is moving, in radians.
Entities have components `Movement` only when searching, but not when grassing. The presence or absence of the component thus defines an entity's behaviour.
Entities have components `Movement` only when searching, but not when grassing. The presence or absence of the component defines an entity's behaviour.
```java
/// file:src/main/java/grassing/comp/Movement.java
......@@ -120,7 +120,7 @@ public class Movement extends com.artemis.Component {
**Resources**
> Resources are objects that exist only once in the model, contrary to entities, and are potentially available for all submodels. When using the Artemis-odb, any instance of a Java class can become a resource. However, only one instance of each class is possible.
> Resources are objects that exist only once in the model, contrary to entities, and are potentially accessible for all submodels. This is kind of an optimization, it would also be possible to use one-of-a-kind entities instead.
Grassers live in a two-dimensional world. The world is represented by a grid of grass that grassers can consume.
......@@ -130,7 +130,8 @@ package grassing.res;
import grassing.util.FloatGrid;
public class Grass {
public class Grass {
// Any instance of a Java class can be a resource, but only one instance of each class is possible
final public FloatGrid grass;
......@@ -168,8 +169,6 @@ Spatial and temporal scales of the model are arbitrary. The duration of a model
Processes in the model are grass growth, grasser metabolism, grasser reproduction and the two grasser behaviours grassing and searching. Processes are executed in that order.
> During initialization, systems are added to the world in a certain order, which defines the order of their execution. As systems interact indirectly via components, it is important to understand how the order of execution shapes the resulting dynamics.
>
> In the ECS, processes (or submodels, or systems) are added during world construction via `with(...)`. Resources are added via `register(...)`.
```java
/// file:src/main/java/grassing/Main.java
......@@ -242,7 +241,7 @@ private static void createEntities(World world, Random rng) {
Submodels are described in their order of execution.
> What the ODD calls submodels is called systems in the ECS world. A system must extend `com.artemis.BaseSystem` or one of it's many sub-classes that serve different purposes.
> What the ODD calls submodels is called systems in the ECS world.
### Grass growth
......@@ -254,9 +253,9 @@ p_{t+1} = p_t + r p_t \frac{k - p_t}{k}
where $`p_t`$ is the amount in the current step and $`p_{t+1}`$ in the next step. $`r`$ is the growth rate per step and $`k`$ is the capacity. Capacity is always 1.0.
> Since the grassing system does not use any entities, we simply extend `BaseSystem`. However, it requires access to the `Grass` grid, which is "injected" by the ECS through the line `@Wire Grass grass;`.
> The grassing system does not use any entities, and only modifies the `Grass` grid, which is a resource here in ECS terms.
>
> The method `processSystem` is automatically called at every model step.
> A system implements what happens in each time step, but scheduling of those steps is done by the ECS, outside the systems.
```java
/// file:src/main/java/grassing/sys/LogisticGrassGrowth.java
......@@ -266,9 +265,11 @@ import com.artemis.BaseSystem;
import com.artemis.annotations.Wire;
import grassing.res.Grass;
public class LogisticGrassGrowth extends BaseSystem {
public class LogisticGrassGrowth extends BaseSystem {
// A system must extend BaseSystem or one of its many sub-classes for different purposes
// Since this system does not use any entities, we simply extend `BaseSystem`
@Wire Grass grass;
@Wire Grass grass; // The resource is automatically "injected" by the ECS through @Wire
private final float growthRate;
private final float capacity;
......@@ -279,7 +280,7 @@ public class LogisticGrassGrowth extends BaseSystem {
}
@Override
protected void processSystem() {
protected void processSystem() { // This method is called at every time step
for(int i=0; i<grass.grass.length; i++) {
float v = grass.grass.get(i);
grass.grass.set(i, v + growthRate * v * (capacity - v) / capacity);
......@@ -294,9 +295,7 @@ The grasser metabolism works on all entities that have the component `Energy`.
In each iteration, grassers lose `consumption` energy. When it's energy drops to zero, a grasser dies.
> Here, we extend `IteratingSystem`, which carries out the work of iterating over the relevant entities. Method `process(int id)` is automatically called at every model step, for every entity (`id`) matching the system's required component(s). Here, we specified a single required component via `@All(Energy.class)`.
>
> To access the component `Energy` of any entity, we use `ComponentMapper<Energy>` in the line `Energy e = mEnergy.get(id);`. The mapper is injected by Artemis automatically, so we don't need to construct it.
> Here, we are interested only in the `Energy` component which is modified in-place, thereby influencing the model evolution.
```java
/// file:src/main/java/grassing/sys/Metabolism.java
......@@ -307,9 +306,12 @@ import com.artemis.annotations.All;
import com.artemis.systems.IteratingSystem;
import grassing.comp.Energy;
@All(Energy.class)
@All(Energy.class) // Tells the system that is operates on entities that have all the listed components
public class Metabolism extends IteratingSystem {
// We extend IteratingSystem, which carries out the work of iterating over the relevant entities
// This is used in process(...) to access component Energy by entity id
// The mapper is injected by Artemis automatically, so we don't need to construct it
ComponentMapper<Energy> energy;
private final float consumption;
......@@ -319,8 +321,8 @@ public class Metabolism extends IteratingSystem {
}
@Override
protected void process(int id) {
Energy e = energy.get(id);
protected void process(int id) { // This is automatically called at every model step, for every entity (id) matching the system's required component(s)
Energy e = energy.get(id); // Get the component for the current entity
e.value -= consumption;
if(e.value <= 0) {
world.delete(id);
......@@ -337,9 +339,7 @@ In each iteration, each grasser reproduces with a fixed probability.
When reproducing, energy is split in half between the grasser and it's offspring. The offspring starts with the grassing behaviour (i.e. without `Movement`).
> We extend `IteratingSystem` again. This time we wrote `@All({Position.class, Energy.class})`, which means that we are interested only in entities that have all listed components.
>
> This system shows how to create entities in a running model.
> Here, we are interested only in entities that have components `Position` and `Energy`. Only component `Energy` is modified, while `Position` is required to determine the grasser's grid cell.
```java
/// file:src/main/java/grassing/sys/Reproduction.java
......@@ -352,7 +352,7 @@ import com.artemis.systems.IteratingSystem;
import grassing.comp.*;
import grassing.res.Randomness;
@All({Position.class, Energy.class})
@All({Position.class, Energy.class}) // Again, the combination of components we are interested in
public class Reproduction extends IteratingSystem {
ComponentMapper<Position> position;
......@@ -385,13 +385,13 @@ public class Reproduction extends IteratingSystem {
### Grassing
The grassing behaviour works on all entities that have the components `Position`, `Energy` but no `Movement`. Thus, it ignores grassers that are currently searching (and thus have `Movement`).
The grassing behaviour works on all entities that have the components `Position`, `Energy` but not `Movement`. Thus, it ignores grassers that are currently searching (and thus have `Movement`).
If the grasser's energy is 1.0 or above, it does nothing. Otherwise, the grasser decides if there is enough grass in it's grid cell. If this is the case, the grasser consumes grass and converts it to energy. Otherwise, the grasser switches to the searching behaviour.
> The behaviour is switched by adding component `Movement`.
>
> We extend `IteratingSystem` again, and use `@All(...)` as before, but with an additional `@Exclude(...)`. This time, we want only entities that have no `Movement`, which is optional according to our model logic. Thus, grassers that are searching instead of grassing will not be processed by this system. In the `else` branch at the end of the code, we add a component `Movement`, which means that the respective entity will not be processed by this system in the next step, but by the `SearchBehaviour`.
> Here, we want only entities that have components `Position` and `Energy`, but not `Movement`, which is optional according to our model logic. Thus, grassers that are searching instead of grassing will not be processed by this system. When the `Movement` component is added, this implies that the respective entity will not be processed by this system in the next step, but by the `SearchBehaviour`.
```java
/// file:src/main/java/grassing/sys/GrassingBehaviour.java
......@@ -405,8 +405,8 @@ import com.artemis.systems.IteratingSystem;
import grassing.comp.*;
import grassing.res.*;
@All({Position.class, Energy.class})
@Exclude(Movement.class)
@All({Position.class, Energy.class}) // Components of interest
@Exclude(Movement.class) // Here, we specify that we are only interested in entities that do not have component Movement
public class GrassingBehaviour extends IteratingSystem {
ComponentMapper<Position> position;
......@@ -436,6 +436,7 @@ public class GrassingBehaviour extends IteratingSystem {
e.value += consumption;
grass.grass.set((int) pos.x, (int) pos.y, g - consumption);
} else {
// Add component Movement and switch to search behaviour
world.edit(id)
.add(new Movement(random.rng.nextFloat() * 2 * (float) Math.PI));
}
......@@ -489,6 +490,7 @@ public class SearchBehaviour extends IteratingSystem {
float g = grass.grass.get((int) pos.x, (int) pos.y);
if( g >= minGrass ) {
// Remove component Movement to switch to grassing behaviour
movement.remove(id);
} else {
randomWalk(id);
......
......@@ -115,6 +115,8 @@ public class Graphics extends BaseEntitySystem {
## Utilities
The grid of `float` values for the grass.
```java
/// file:src/main/java/grassing/util/FloatGrid.java
package grassing.util;
......@@ -159,6 +161,8 @@ public class FloatGrid {
}
```
Utility functions to handle angles.
```java
/// file:src/main/java/grassing/util/MathUtil.java
package grassing.util;
......@@ -174,6 +178,8 @@ public abstract class MathUtil {
}
```
Vector for heading calculated from angle.
```java
/// file:src/main/java/grassing/util/Vector.java
package grassing.util;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment