Skip to content

Latest commit

 

History

History
334 lines (250 loc) · 16.1 KB

README.md

File metadata and controls

334 lines (250 loc) · 16.1 KB

JSON Fliox   splash

nuget codecov CI-Engine docs stars

C# ECS - Friflo.Engine.ECS

Currently fastest 🔥 ECS implementation in C# / .NET - using Ecs.CSharp.Benchmark as reference.
See benchmark results - Mac Mini M2 - at the bottom of this page.
This ECS is an Archetype / AoS based Entity Component System. See: ECS ⋅ Wikipedia.

Feature highlights

  • Simple API - no boilerplate.
  • High-performance 🔥 and compact ECS with low memory footprint.
  • Zero allocations for entire API after buffers grown large enough.
  • Fully reactive - optional. Subscribe change events of all or specific entities.
  • JSON Serialization - optional.
  • SIMD Support - optional. Multi thread capable and remainder loop free.
  • Supports .NET Standard 2.1 .NET 5 .NET 6 .NET 7 .NET 8
    WASM / WebAssembly, Unity (Mono, AOT/IL2CPP, WebGL), Godot, MonoGame, ... and new Native AOT
  • Library uses only secure and managed code. No use of unsafe code. See Wiki ⋅ Library.
    App / Game can access component chunks with native or unsafe code using Span<>s.

Complete feature list at Wiki ⋅ Features.

Get package on nuget or use the dotnet CLI.

dotnet add package Friflo.Engine.ECS

Contents


Demos

MonoGame Demo is available as WASM / WebAssembly app. Try Demo in your browser.

MonoGame Project Unity Project Godot Project

Desktop Demo performance: Godot 202 FPS, Unity 100 FPS at 65536 entities.

All example Demos - Windows, macOS & Linux - available as projects for MonoGame, Unity and Godot.
See Demos · GitHub


Examples

This section contains two typical use cases when using an ECS.
More examples are in the GitHub Wiki.

Examples - General
Explain fundamental ECS types like Entity, Component, Tag, Command Buffer, ... and how to use them.

Examples - Optimization
Provide techniques how to improve ECS performance.

Hello World

The hello world examples demonstrates the creation of a world, some entities with components
and their movement using a simple ForEachEntity() call.

public struct Velocity : IComponent { public Vector3 value; }

public static void HelloWorld()
{
    var world = new EntityStore();
    for (int n = 0; n < 10; n++) {
        world.CreateEntity(new Position(n, 0, 0), new Velocity{ value = new Vector3(0, n, 0)});
    }
    var query = world.Query<Position, Velocity>();
    query.ForEachEntity((ref Position position, ref Velocity velocity, Entity entity) => {
        position.value += velocity.value;
    });
}

In case of moving (updating) thousands or millions of entities an optimized approach can be used.
See: Enumerate Query Chunks, Parallel Query Job and Query Vectorization - SIMD.
All query optimizations are using the same query but with different enumeration techniques.

Systems

Systems are new in Friflo.Engine.ECS v2
nuget

Systems in ECS are typically queries.
So you can still use the world.Query<Position, Velocity>() shown in the "Hello World" example.

Using Systems is optional but they have some significant advantages:

  • It enables chaining multiple decoupled QuerySystem classes.

  • A system can have state - fields or properties - which can be used as parameters in OnUpdate().
    The system state can be serialized to JSON.

  • Systems are added to a SystemGroup.
    Each group provide a CommandBuffer.

  • Systems can be enabled/disabled or removed.
    The order of systems in a group can be changed.

  • Systems have performance monitoring build-in to measure execution times and memory allocations.
    If enabled systems detected as bottleneck can be optimized.

  • Multiple worlds can be added to a single SystemRoot instance.
    root.Update() will execute every system on all worlds.

public static void HelloSystem()
{
    var world = new EntityStore();
    for (int n = 0; n < 10; n++) {
        world.CreateEntity(new Position(n, 0, 0), new Velocity(), new Scale3());
    }
    var root = new SystemRoot(world) {
        new MoveSystem(),
    //  new PulseSystem(),
    //  new ... multiple systems can be added. The execution order still remains clear.
    };
    root.Update(default);
}
        
class MoveSystem : QuerySystem<Position, Velocity>
{
    protected override void OnUpdate() {
        Query.ForEachEntity((ref Position position, ref Velocity velocity, Entity entity) => {
            position.value += velocity.value;
        });
    }
}

A valuable strength of an ECS is establishing a clear and decoupled code structure.
Adding the PulseSystem below to the SystemRoot above is trivial.

class PulseSystem : QuerySystem<Scale3>
{
    float frequency = 4f;
    
    protected override void OnUpdate() {
        Query.ForEachEntity((ref Scale3 scale, Entity entity) => {
            scale.value = Vector3.One * (1 + 0.2f * MathF.Sin(frequency * Tick.time));
        });
    }
}

System monitoring

System performance monitoring is disabled by default.
To enable monitoring call:

root.SetMonitorPerf(true);

The performance statistics available at SystemPerf.
To get performance statistics on console use:

root.Update(default);
Console.WriteLine(root.GetPerfLog());

The log result will look like:

stores: 1                           last ms       sum ms      updates     last mem      sum mem     entities
---------------------              --------     --------     --------     --------     --------     --------
Systems [2]                           0.079        3.074           10          128         1392
| ScaleSystem                         0.039        1.789           10           64          696        10000
| PositionSystem                      0.039        1.278           10           64          696        10000
last ms, sum ms     last/sum system execution time in ms
updates             number of executions
last mem, sum mem   last/sum allocated bytes
entities            number of entities matching a QuerySystem

Wiki

The GitHub Wiki provide you detailed information about the ECS and illustrate them by examples.

  • Examples - General
    Explain fundamental ECS types like Entity, Component, Tag, Command Buffer, ... and show you how to use them.
    Contains an example for Native AOT integration.

  • Examples - Optimization
    Provide you techniques how to improve ECS performance.

  • Extensions
    Projects extending Friflo.Engine.ECS with additional features.

  • Features
    Integration possibilities, a complete feature list and performance characteristics 🔥.

  • Library
    List supported platforms, properties of the assembly dll and build statistics.

  • Release Notes
    List of changes of every release available on nuget.


ECS Benchmarks

Two benchmarks - subset of GitHub ⋅ Ecs.CSharp.Benchmark + PR #38 running on a Mac Mini M2.

new 2024-05-29 - Updated benchmarks.
Improved create entities performance by 3x to 4x and minimized entity memory footprint from 48 to 16 bytes.
Published in nuget package 2.0.0-preview.3.

Made a subset as the other benchmarks are similar only with different parameters.

  1. Create 100.000 entities with three components
  2. Update 100.000 entities with two components

1. Create 100.000 entities with three components

Method Mean Gen0 Gen1 Gen2 Allocated
Arch 6,980.1 μs - - - 3948.51 KB
SveltoECS 28,165.0 μs - - - 4.97 KB
DefaultEcs 12,680.4 μs - - - 19517.01 KB
Fennecs 24,922.4 μs - - - 16713.45 KB
FlecsNet 12,114.1 μs - - - 3.81 KB
FrifloEngineEcs 🔥 405.3 μs - - - 3625.46 KB
HypEcs 22,376.5 μs 6000.0000 - - 68748.73 KB
LeopotamEcsLite 5,199.9 μs - - - 11248.47 KB
LeopotamEcs 8,758.8 μs 1000.0000 - - 15736.73 KB
MonoGameExtended 30,789.0 μs 1000.0000 - - 30154.38 KB
Morpeh_Direct 126,841.8 μs 9000.0000 5000.0000 2000.0000 83805.52 KB
Morpeh_Stash 67,127.7 μs 4000.0000 2000.0000 1000.0000 44720.38 KB
Myriad 15,824.5 μs - - - 7705.36 KB
RelEcs 58,002.5 μs 6000.0000 2000.0000 1000.0000 75702.71 KB
TinyEcs 20,190.4 μs 2000.0000 1000.0000 1000.0000 21317.2 KB

🔥 library of this project

2. Update 100.000 entities with two components

Benchmark parameter: Padding = 0

Notable fact
SIMD MonoThread running on a single core beats MultiThread running on 8 cores.
So other threads can still keep running without competing for CPU resources.

Method Mean Gen0 Allocated
Arch_MonoThread 62.09 μs - -
Arch_MonoThread_SourceGenerated 52.43 μs - -
Arch_MultiThread 49.57 μs - -
DefaultEcs_MonoThread 126.33 μs - -
DefaultEcs_MultiThread 128.18 μs - -
Fennecs_ForEach 56.30 μs - -
Fennecs_Job 69.65 μs - -
Fennecs_Raw 52.34 μs - -
FlecsNet_Each 103.26 μs - -
FlecsNet_Iter 64.23 μs - -
FrifloEngineEcs_MonoThread 🔥 57.62 μs - -
FrifloEngineEcs_MultiThread 🔥 17.17 μs - -
FrifloEngineEcs_SIMD_MonoThread 🔥 11.00 μs - -
HypEcs_MonoThread 57.57 μs - 112 B
HypEcs_MultiThread 61.94 μs 0.2441 2079 B
LeopotamEcsLite 150.11 μs - -
LeopotamEcs 134.98 μs - -
MonoGameExtended 467.59 μs - 161 B
Morpeh_Direct 1,590.35 μs - 3 B
Morpeh_Stash 1,023.88 μs - 3 B
Myriad_SingleThread 46.20 μs - -
Myriad_MultiThread 366.27 μs 28.8086 239938 B
Myriad_SingleThreadChunk 61.32 μs - -
Myriad_MultiThreadChunk 25.31 μs 0.3662 3085 B
Myriad_Enumerable 238.59 μs - -
Myriad_Delegate 73.47 μs - -
Myriad_SingleThreadChunk_SIMD 22.33 μs - -
RelEcs 251.30 μs - 169 B
SveltoECS 162.92 μs - -
TinyEcs_Each 37.09 μs - -
TinyEcs_EachJob 23.52 μs 0.1831 1552 B

🔥 library of this project


License

This project is licensed under LGPLv3.

Friflo.Engine.ECS
Copyright © 2024   Ullrich Praetz - https://github.com/friflo