Arch 1.0.16
See the version list below for details.
dotnet add package Arch --version 1.0.16
NuGet\Install-Package Arch -Version 1.0.16
<PackageReference Include="Arch" Version="1.0.16" />
paket add Arch --version 1.0.16
#r "nuget: Arch, 1.0.16"
// Install Arch as a Cake Addin #addin nuget:?package=Arch&version=1.0.16 // Install Arch as a Cake Tool #tool nuget:?package=Arch&version=1.0.16
Arch
A highperformance C# based Archetype & Chunks Entity Component System (ECS) for game development and data oriented programming.
- 🚀 FAST > Best cache efficiency, iteration and allocation speed. Plays in the same league as C++/Rust ECS Libs !
- 🚀🚀 FASTER > Arch is on average quite faster than other ECS implemented in C#. Check out this Benchmark !
- 🤏 BARE MINIMUM > Not bloated, its small and only provides the essentials for you !
- ☕️ SIMPLE > Promotes a clean, minimal and self-explanatory API that is simple by design. Check out the Wiki !
- 💪 MAINTAINED > Its actively being worked on, maintained and supported !
- 🚢 SUPPORT > Supports .NetStandard 2.1, .Net Core 6 and 7 and therefore you may use it with Unity or Godot !
Download the package and get started today !
dotnet add PROJECT package Arch --version 1.0.16
Code Sample
Enough spoken, lets take a look at some code. Arch is bare minimum, easy to use and efficient. Lets say we want to create some game entities and make them move based on their velocity, sounds complicated ?
Its not ! Arch does everything for you, you only need to define the entities and the logic.
public class Game {
public struct Position { public float x, y; }
public struct Velocity { public float dx, dy; }
public static void Main(string[] args) {
var world = World.Create();
var query = new QueryDescription{ All = { typeof(Position), typeof(Velocity) } }; // Query all entities with Position AND Velocity components
// Create entities
for (var index = 0; index < 1000; index++)
var entity = world.Create(new Position{ x = 0, y = 0}, new Velocity{ dx = 1, dy = 1});
// Query and modify entities
world.Query(in query, (ref Position pos, ref Velocity vel) => {
pos.x += vel.dx;
pos.y += vel.dy;
});
}
}
Contents
Quickstart
I bet you dont wanna read tons of documentations, theory and other boring stuff right ?
Lets just ignore all that deep knowledge and jump in directly to get something done.
For more detailed API and features, check out the wiki !
ECS
Entity Component System (ECS) is a software architectural pattern mostly used for the representation of game world objects or data oriented design in general. An ECS comprises entities composed from components of data, with systems or queries which operate on entities' components.
ECS follows the principle of composition over inheritance, meaning that every entity is defined not by a type hierarchy, but by the components that are associated with it.
World
The world acts as a management class for all its entities, it contains methods to create, destroy and query them and handles all the internal mechanics.
Therefore it is the most important class, you will use the world heavily.
Multiple worlds can be used in parallel, each instance and its entities are completly encapsulated from other worlds. Currently worlds and their content can not interact with each other, however this feature is already planned.
Worlds are created and destroyed like this...
var world = World.Create();
World.Destroy(world);
There can be up to 255 worlds in total.
Entity
A entity represents your game entity.
It is a simple struct with some metadata acting as a key to acess and manage its components.
Entities are being created by a world and will "live" in the world in which they were created.
When an entity is being created, you need to specify the components it will have. Components are basically the additional data or structure the entity will have. This is called "Archetype".
var otherEntity = world.Create<Transform, Collider, PowerUp>(... optional);
or
var archetype = new []{ typeof(Position), typeof(Velocity), ... };
var entity = world.Create(archetype);
world.Destroy(in entity);
Entity creation/deletion should not happen during a Query ! CommandBuffers can be used for this ! 😃
Component
Components are data assigned to your entity. With them you define how an entity looks and behaves, they basically define the gamelogic with pure data.
Its recommended to use struct components since they offer better speed.
To ease writing code, you can acess the entity directly to modify its components or to check its metadata.
A small example could look like this...
var entity = world.Create<Position, Velocity>();
ref var position = ref entity.Get<Position>(); // Get reference to the position
position.x++; // Update x
position.y++; // Update y
if(entity.Has<Position>()) // Make sure that entity has a position ( Optional )
entity.Set(new Position{ x = 10, y = 10 }; // Replaces the old position
entity.Remove<Velocity>(); // Removes an velocity component and moves it to a new archetype.
entity.Add<Velocity>(new Velocity{ x = 1, y = 1); // Adds an velocity component and moves the entity back to the previous archetype.
Structural entity changes should not happen during a Query or Iteration ! CommandBuffers can be used for this ! 😃
System aka. Query
Queries aka. Systems are used to iterate over a set of entities to apply logic and behaviour based on their components.
This is performed by using the world ( remember, it manages your created entities ) and by defining a description of which entities we want to iterate over.
// Define a description of which entities you want to query
var query = new QueryDescription {
All = new []{ typeof(Position), typeof(Velocity) }, // Should have all specified components
Any = new []{ typeof(Player), typeof(Projectile) }, // Should have any of those
None = new []{ typeof(AI) } // Should have none of those
};
// Execute the query
world.Query(in query, entity => { /* Do something */ });
// Execute the query and modify components in the same step, up to 10 generic components at the same time.
world.Query(in query, (ref Position pos, ref Velocity vel) => {
pos.x += vel.dx;
pos.y += vel.dy;
});
In the example above we want to move our entities based on their Position
and Velocity
components.
To perform this operation we need to iterate over all entities having both a Position
and Velocity
component (All
). We also want that our entity either is a Player
or a Projectile
(Any
). However, we do not want to iterate and perform that calculation on entities which are controlled by an AI
(None
).
The world.Query
method than smartly searches for entities having both a Position
and Velocity
, either a Player
or Projectile
and no AI
component and executes the defined logic for all of those fitting entities.
Besides All
, Any
and None
, QueryDescription
can also target a exclusive set of components via Exclusive
. If thats set, it will ignore All
, Any
and None
and only target entities with a exactly defined set of components. Its also important to know that there are multiple different overloads to perform such a query.
The less you query in terms of components and the size of components... the faster the query is !
Outlook
This is all you need to know, with this little knowledge you are already able to bring your worlds to life.
However, if you want to take a closer look at Arch's features and performance techniques, check out the Wiki !
Theres more to explore, for example...
- Bulk Entity Adding
- Highperformance Queries
- Archetypes
- Chunks
- Parallel / Multithreaded Queries
- Enumerators
- CommandBuffers
- More api
Performance
Well... its fast, like REALLY fast.
However the iteration speed depends, the less you query, the faster it is.
This rule targets the amount of queried components aswell as their size.
Based on https://github.com/Doraku/Ecs.CSharp.Benchmark - Benchmark, it is among the fastest ecs frameworks in terms of allocation and iteration.
Benchmark
The current Benchmark tested a bunch of different iterations and acess techniques. However the most interesting one is the QueryBenchmark
.
It tests world.Query
against world.HPQuery
and a world.Query(in desc, (in Entity) => { entity.Get<T>... }
variant.
public struct Transform{ float x; float y; float z; }
public struct Velocity { float x; float y; }
The used structs are actually quite big, the smaller the components, the faster the query. However i wanted to create a realistic approach and therefore used a combination of Transform and Velocity.
Method | Amount | Mean | Error | StdDev | Allocated |
---|---|---|---|---|---|
Query | 10000 | 20.648 us | 16.4985 us | 0.9043 us | - |
EntityQuery | 10000 | 17.791 us | 1.1502 us | 0.0630 us | - |
StructQuery | 10000 | 7.517 us | 5.6294 us | 0.3086 us | - |
StructEntityQuery | 10000 | 7.851 us | 0.9644 us | 0.0529 us | - |
PureEntityQuery | 10000 | 513.765 us | 297.3252 us | 16.2974 us | - |
Query | 100000 | 199.354 us | 0.8275 us | 0.0454 us | - |
EntityQuery | 100000 | 200.223 us | 23.0012 us | 1.2608 us | - |
StructQuery | 100000 | 75.616 us | 46.9128 us | 2.5714 us | - |
StructEntityQuery | 100000 | 79.390 us | 8.5673 us | 0.4696 us | - |
PureEntityQuery | 100000 | 5,032.846 us | 2,116.4383 us | 116.0091 us | - |
Query | 1000000 | 2,257.803 us | 52.4331 us | 2.8740 us | - |
EntityQuery | 1000000 | 2,661.399 us | 74.9702 us | 4.1094 us | - |
StructQuery | 1000000 | 1,556.662 us | 734.0822 us | 40.2375 us | - |
StructEntityQuery | 1000000 | 1,746.782 us | 627.7881 us | 34.4112 us | - |
PureEntityQuery | 1000000 | 50,894.500 us | 1,944.0534 us | 106.5601 us | - |
Contributing
I will accept contributions, especially bugfixes, performance improvements and new features. New features however should not harm its performance, if they do they should be wrapped within predecessor variables for enabling/disabling them.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.1 is compatible. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- Collections.Pooled (>= 2.0.0-preview.27)
- CommunityToolkit.HighPerformance (>= 7.1.2)
- System.Runtime.CompilerServices.Unsafe (>= 5.0.0)
- ZeroAllocJobScheduler (>= 1.0.2)
-
net6.0
- Collections.Pooled (>= 2.0.0-preview.27)
- CommunityToolkit.HighPerformance (>= 7.1.2)
- System.Runtime.CompilerServices.Unsafe (>= 5.0.0)
- ZeroAllocJobScheduler (>= 1.0.2)
-
net7.0
- Collections.Pooled (>= 2.0.0-preview.27)
- CommunityToolkit.HighPerformance (>= 7.1.2)
- System.Runtime.CompilerServices.Unsafe (>= 5.0.0)
- ZeroAllocJobScheduler (>= 1.0.2)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on Arch:
Package | Downloads |
---|---|
Arch.Persistence
A Persistence-Framework for Arch. |
|
Arch.Relationships
Simple Entity-Relationships for Arch. |
|
Undine.Arch
Package Description |
|
Doprez.Stride.Arch
A package of helper classes for using Arch ECS in Stride3D |
GitHub repositories (3)
Showing the top 3 popular GitHub repositories that depend on Arch:
Repository | Stars |
---|---|
genaray/Arch.Extended
Extensions for Arch with some useful features like Systems, Source Generator and Utils.
|
|
AnnulusGames/Arch.Unity
Arch ECS integration for Unity.
|
|
Doraku/Ecs.CSharp.Benchmark
Benchmarks of some C# ECS frameworks.
|
Version | Downloads | Last updated |
---|---|---|
1.3.3-alpha | 1,158 | 8/15/2024 |
1.3.2-alpha | 77 | 8/14/2024 |
1.3.1-alpha | 82 | 8/13/2024 |
1.3.0-alpha | 209 | 8/12/2024 |
1.2.8.2-alpha | 546 | 7/22/2024 |
1.2.8.1-alpha | 1,433 | 4/19/2024 |
1.2.8 | 7,783 | 3/13/2024 |
1.2.7.1-alpha | 587 | 11/20/2023 |
1.2.7 | 3,198 | 10/15/2023 |
1.2.6.8-alpha | 387 | 9/17/2023 |
1.2.6.7-alpha | 175 | 8/27/2023 |
1.2.6.6-alpha | 3,395 | 8/25/2023 |
1.2.6.5-alpha | 298 | 8/19/2023 |
1.2.6.4-alpha | 156 | 8/14/2023 |
1.2.6.3-alpha | 87 | 8/14/2023 |
1.2.6 | 688 | 8/13/2023 |
1.2.5.3-alpha | 526 | 5/21/2023 |
1.2.5.2-alpha | 154 | 5/16/2023 |
1.2.5.1-alpha | 254 | 5/11/2023 |
1.2.5 | 826 | 4/26/2023 |
1.2.4.2-beta | 107 | 4/16/2023 |
1.2.4 | 421 | 4/15/2023 |
1.2.3 | 223 | 3/19/2023 |
1.2.0 | 206 | 3/5/2023 |
1.1.9 | 205 | 1/29/2023 |
1.1.8 | 128 | 1/29/2023 |
1.1.7 | 130 | 1/29/2023 |
1.1.6 | 158 | 1/19/2023 |
1.1.5 | 128 | 1/18/2023 |
1.1.4 | 137 | 1/16/2023 |
1.1.3 | 130 | 1/16/2023 |
1.1.2 | 126 | 1/16/2023 |
1.1.1 | 479 | 12/30/2022 |
1.1.0 | 147 | 12/20/2022 |
1.0.17 | 126 | 12/9/2022 |
1.0.16 | 244 | 12/4/2022 |
1.0.15 | 122 | 11/27/2022 |
1.0.14 | 116 | 11/23/2022 |
1.0.13 | 118 | 11/22/2022 |
1.0.12 | 115 | 11/20/2022 |
1.0.11 | 119 | 11/15/2022 |
1.0.10 | 111 | 11/15/2022 |
1.0.9 | 123 | 11/13/2022 |
1.0.8 | 146 | 11/6/2022 |
1.0.7 | 125 | 11/4/2022 |
1.0.6 | 113 | 11/4/2022 |
1.0.5 | 121 | 11/2/2022 |
1.0.4 | 107 | 11/1/2022 |
1.0.3 | 113 | 11/1/2022 |
1.0.2 | 109 | 11/1/2022 |
1.0.1 | 130 | 11/1/2022 |
1.0.0 | 162 | 11/1/2022 |
Introduced Exclusive Queries and CommandBuffers :)