Arch 1.0.7
See the version list below for details.
dotnet add package Arch --version 1.0.7
NuGet\Install-Package Arch -Version 1.0.7
<PackageReference Include="Arch" Version="1.0.7" />
paket add Arch --version 1.0.7
#r "nuget: Arch, 1.0.7"
// Install Arch as a Cake Addin #addin nuget:?package=Arch&version=1.0.7 // Install Arch as a Cake Tool #tool nuget:?package=Arch&version=1.0.7
Arch
A C# based Archetype Entity Component System (ECS).
Each Archetype stores their entities within 16KB sized chunks perfectly fitting into L1 Caches for maximum iteration performance.
This technique has two main advantages, first of all it provides an great entity allocation speed and second it lowers the cache misses to the best possible minimum.
Its incredible fast, especially for well architectured component structures.
Supports .NetStandard 2.1, .Net Core 6 and 7.
Since .NetStandard is supported, you may also use it with Unity.
Download the package and get started today !
dotnet add PROJECT package Arch --version 1.0.7
Code Sample
public class Game {
public struct Position { public float x, y; }
public struct Velocity { public float dx, dy; }
// The entity structure and or filter/query
public static Type[] archetype = { typeof(Position), typeof(Velocity) };
public static void Main(string[] args) {
var world = World.Create();
var query = new QueryDescription{ All = archetype }; // Query all entities with Position AND Velocity components
// Create entities
for (var index = 0; index < 1000; index++) {
var entity = world.Create(archetype);
entity.Set(new Position{ x = 0, y = 0});
entity.Set(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;
});
}
}
Content
Quickstart
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 for 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 is completly encapsulated from other worlds.
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.
public readonly struct Entity : IEquatable<Entity> {
public readonly int EntityId; // Its id/key in the world
public readonly byte WorldId; // The world the entity lives in
public readonly ushort Version; // Its version, how often the entity or its id was recycled
....
}
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 archetype = new []{ typeof(Position), typeof(Velocity), ... };
var entity = world.Create(archetype);
world.Destroy(in entity);
To ease writing code, you can acess the entity directly to modify its data or to check its metadata.
Lets take a look at the most important methods.
entity.IsAlive(); // True if the entity is still existing in its world
entity.Has<Position>(); // True if the entity has a position component
entity.Set(new Position( x = 10 )); // Replaces the position component and updates it data
entity.Get<Position>(); // Returns a reference to the entity position, can directly acess and update position attributes
entity.GetComponentTypes(); // Returns an array of its component types. Should be treated as readonly
entity.GetComponents(); // Returns an array of all its components, allocates memory.
With those utility methods you are able to implement your game logic.
A small example looks like this...
var archetype = new []{ typeof(Position), typeof(Velocity) };
var entity = world.Create(archetype);
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
Querying and Filtering
To performs operations and to define your game logic, queries are used to iterate over entities.
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.
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 !
world.Query(in query, entity => {}); // Passes the fitting entity
world.Query(in query, (ref T1 t1, T2 t2, ...) => {}) // Passes the defined components of the fitting entity, up to 10 components
world.Query(in query, (in Entity en, ref T1 t1, ref T2 t2, ...) => {}) // Passed the fitting entity and its defined components, up to 10 components
var filteredEntities = new List<Entity>();
var filteredArchetypes = new List<Archetype>();
var filteredChunks = new List<Chunk>();
world.GetEntities(query, filteredEntities); // Fills all fitting entities into the passed list
world.GetArchetypes(query, filteredArchetypes); // Fills all fitting archetypes into the list
world.GetChunks(query, filteredChunks); // Fills all fitting chunks into the list
Archetype's and Chunk's are internal structures of the world and store entities with the same component types. You will mostly never use them directly, therefore more on them later.
Bulk adding
Arch supports bulk adding of entities, this is incredible fast since it allows us to allocate enough space for a certain set of entities in one go. This reservation happens on top of the already existing entities in an archetype. You only need to reserve space once and than it will be filled later or sooner.
var archetype = new []{ typeof(Position), typeof(Velocity) };
// Create 1k entities
for(var index = 0; index < 1000; index++)
world.Create(archetype)
world.Reserve(archetype, 1000000); // Reserves space for additional 1mil entities
for(var index = 0; index < 1000000; index++) // Create additional 1 mil entities
world.Create(archetype)
// In total there now 1mil and 1k entities in that certain archetype.
Internal Structure and Memory layout
Arch is an archetype ecs. An archetype ecs groups entities with the same set of components in tightly packed arrays for the fastest possible iteration performance. This has no direct effect on its API useage or the way you develop your game. But understanding the internal structure can help you to improve your game performance even more. However it has an big impact on the internal structures being used and is the secret to its incredible performance.
Archetype
An archetype manages all entities with a common set of components. Like the world is used to manage ALL entities, the archetype is only used to manage a specific set of entities... all entities with the same component structure. The world stores those archetypes and acesses them to iterate, create, update and remove entities.
// Creates one entity, one archetype where all entities with Position and Velocity will be stored
var archetype = new []{ typeof(Position), typeof(Velocity) };
var entity = world.Create(archetype);
// Creates another entity, another archetype where all entities with Position, Velocity AND Rotation will be stored
var secondArchetype = new []{ typeof(Position), typeof(Velocity), typeof(Rotation) } ;
var secondEntity = world.Create(secondArchetype);
You may probably now ask : "Why do this create two seperate archtypes ? Both entities share position and velocity, so they could be stored together."
... Shared subsets of component do NOT matter... all what matters is the exact structure of an entity. Why is that ? Because this way we can utilize the cache during iterations, however explaining this would probably break the scope of this documentation.
Chunks
Chunks are were the entities and their components are stored. They utilize dense packed contiguous arrays ( SoA ) to store and acess entities with the same group of components. Its internal arrays are always 16KB in total, this is intended since 16kb fits perfectly into the L1 CPU Cache which gives us insane iteration speeds.
The internal structure simplified looks like this...
Chunk
[
[Entity, Entity, Entity],
[Position, Position, Position],
[Velocity, Velocity, Velocity]
...
]
This way they are fast to (de)allocate which also reduces memory useage to a minimum. Each archetype contains multiple chunks and will create and destroy chunks based on worlds need.
Archetype and Chunk usage
Arch gives you acess to the internal structures aswell. You will mostly do not need this feature, however it can be usefull to leverage the performance, writing custom queries or add new features.
var archetypes = world.Archetypes; // Returns direct acess to all archetypes of the world
var chunks = archetypes[0].Chunks // Returns direct acess to all chunks of a archetype
world.GetArchetypes(in queryDescription, myArchetypeList); // Fills all archetypes fitting the query description into the passed list
world.GetChunks(in queryDescription, myChunkList); // Fills all chunks fitting the query description into the passed list
Acessing or using those methods, you will have the power to write and optimize queries yourself. If you want you can even implement new features using them.
Lets take a look at how the world.Query
methods are implemented using those.
// Inside the world
public void Query<T0>(in QueryDescription description, ForEach<T0> forEach)
{
// Cache query
if (!QueryCache.TryGetValue(description, out var query))
{
query = new Query(description);
QueryCache[description] = query;
}
var size = Archetypes.Count;
for (var index = 0; index < size; index++)
{
var archetype = Archetypes[index];
var archetypeSize = archetype.Size;
var bitset = archetype.BitSet;
if (!query.Valid(bitset)) continue; // Process archetype & chunks if it fits the query description
// Loop over all its chunks in an unsafe manner
ref var chunkFirstElement = ref archetype.Chunks[0];
for (var chunkIndex = 0; chunkIndex < archetypeSize; chunkIndex++)
{
ref readonly var chunk = ref Unsafe.Add(ref chunkFirstElement, chunkIndex);
var chunkSize = chunk.Size;
var t0Array = chunk.GetArray<T0>(); // Acess component array
ref var t0FirstElement = ref t0Array[0];
// Loop over all components in an unsafe manner
for (var entityIndex = 0; entityIndex < chunkSize; entityIndex++)
{
ref var t0Component = ref Unsafe.Add(ref t0FirstElement, entityIndex);
forEach(ref t0Component);
}
}
}
}
With the power of acessing Archetype
and Chunk
directly from the world, you can easily write such high performance queries yourself. Great ! Isnt it ? ð
Since this is pretty dangerous you should only do this when you are already familiar with archetypes and chunks. Those features should be handled as readonly as long as you do not really know what you are doing. However entities should ONLY be created and removed using the world directly.
Lets look at an small example of how you could utilize those features...
var archetype = new[]{ typeof(Position); }
var queryDesc = new QueryDescription{ All = archetype };
var allChunks = new List<Chunk>();
world.Query(in queryDesc, allChunks); // Get all chunks for entities with a position component
foreach(var chunk in allChunks){
// Acess the position component array and loop over each position
var positions = chunk.GetArray<Position>();
for (var entityIndex = 0; entityIndex < chunk.Size; entityIndex++){
ref var pos = ref positions[entityIndex];
Console.WriteLine($"{pos.x}/{pos.y}");
pos.x++;
pos.y++;
}
}
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 only tests it Archetype/Chunk iteration performance.
Two different iteration techniques, 2 Components ( Transform & Rotation ) modification and Entity + 2 Components modification.
public struct Transform{ float x; float y; float z; }
public struct Rotation{ float x; float y; float z; float w; }
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 Rotation.
NET.7
Method | amount | Mean | Error | StdDev | CacheMisses/Op |
---|---|---|---|---|---|
IterationNormalTwoComponents | 10000 | 27.94 us | 0.146 us | 0.129 us | 41 |
IterationUnsafeAddTwoComponents | 10000 | 25.46 us | 0.112 us | 0.105 us | 18 |
IterationNormalEntityTwoComponents | 10000 | 40.44 us | 0.191 us | 0.179 us | 35 |
IterationUnsafeAddEntityTwoComponents | 10000 | 37.95 us | 0.164 us | 0.146 us | 41 |
IterationNormalTwoComponents | 100000 | 275.20 us | 2.149 us | 2.010 us | 540 |
IterationUnsafeAddTwoComponents | 100000 | 250.88 us | 1.380 us | 1.223 us | 549 |
IterationNormalEntityTwoComponents | 100000 | 397.54 us | 1.935 us | 1.810 us | 842 |
IterationUnsafeAddEntityTwoComponents | 100000 | 373.28 us | 1.713 us | 1.519 us | 695 |
IterationNormalTwoComponents | 1000000 | 2,810.57 us | 16.850 us | 15.762 us | 24,009 |
IterationUnsafeAddTwoComponents | 1000000 | 2,573.61 us | 13.632 us | 12.752 us | 24,724 |
IterationNormalEntityTwoComponents | 1000000 | 4,050.63 us | 36.415 us | 34.063 us | 28,637 |
IterationUnsafeAddEntityTwoComponents | 1000000 | 3,804.67 us | 29.850 us | 23.305 us | 28,960 |
IterationNormalTwoComponents | 10000000 | 32,790.69 us | 176.802 us | 165.381 us | 257,843 |
IterationUnsafeAddTwoComponents | 10000000 | 30,275.69 us | 261.629 us | 244.728 us | 271,411 |
IterationNormalEntityTwoComponents | 10000000 | 45,073.30 us | 365.498 us | 341.887 us | 323,789 |
IterationUnsafeAddEntityTwoComponents | 10000000 | 43,000.07 us | 205.964 us | 192.659 us | 304,333 |
Different Iteration and Acess Techniques
We have been testing different strategies and techniques to iterate over the archetype itself and all its chunks for providing the best overall performance.
Suprisingly all of them are great but especially the Unsafe.Add Iterations were quite faster. Thats why we picked the techniques of IterationUnsafeAddTwoComponents
and IterationUnsafeAddTwoComponentsUnsafeArray
and decided that IterationUnsafeAddTwoComponents
is the best overall since it comes along the least CacheMisses. We will run this benchmark regulary in the future to adjust the ECS performance based on new .NET improvements.
Method | amount | Mean | Error | StdDev | Median | CacheMisses/Op |
---|---|---|---|---|---|---|
IterationNormalTwoComponents | 10000 | 27.92 us | 0.121 us | 0.107 us | 27.96 us | 30 |
IterationUnsafeAddTwoComponents | 10000 | 25.33 us | 0.090 us | 0.080 us | 25.36 us | 13 |
IterationNormalTwoComponentsSpan | 10000 | 27.96 us | 0.080 us | 0.071 us | 27.93 us | 22 |
IterationUnsafeAddTwoComponentsSpan | 10000 | 25.50 us | 0.104 us | 0.087 us | 25.51 us | 22 |
IterationNormalTwoComponentsUnsafeArray | 10000 | 27.98 us | 0.179 us | 0.168 us | 27.99 us | 23 |
IterationUnsafeAddTwoComponentsUnsafeArray | 10000 | 25.32 us | 0.123 us | 0.102 us | 25.33 us | 27 |
IterationNormalTwoComponentsUnsafeSpan | 10000 | 27.92 us | 0.158 us | 0.148 us | 27.95 us | 26 |
IterationUnsafeAddTwoComponentsUnsafeSpan | 10000 | 25.36 us | 0.126 us | 0.118 us | 25.34 us | 29 |
IterationUnsafeAddTwoComponentsCompleteUnsafe | 10000 | 25.35 us | 0.082 us | 0.077 us | 25.34 us | 37 |
IterationNormalTwoComponents | 100000 | 277.82 us | 3.237 us | 2.870 us | 276.88 us | 943 |
IterationUnsafeAddTwoComponents | 100000 | 249.18 us | 0.717 us | 0.670 us | 249.02 us | 353 |
IterationNormalTwoComponentsSpan | 100000 | 276.43 us | 2.455 us | 2.050 us | 276.19 us | 544 |
IterationUnsafeAddTwoComponentsSpan | 100000 | 249.42 us | 0.552 us | 0.489 us | 249.54 us | 364 |
IterationNormalTwoComponentsUnsafeArray | 100000 | 273.32 us | 0.634 us | 0.529 us | 273.17 us | 311 |
IterationUnsafeAddTwoComponentsUnsafeArray | 100000 | 248.47 us | 0.684 us | 0.534 us | 248.32 us | 609 |
IterationNormalTwoComponentsUnsafeSpan | 100000 | 284.59 us | 5.645 us | 12.856 us | 277.44 us | 799 |
IterationUnsafeAddTwoComponentsUnsafeSpan | 100000 | 249.78 us | 0.889 us | 0.788 us | 249.90 us | 452 |
IterationUnsafeAddTwoComponentsCompleteUnsafe | 100000 | 249.76 us | 1.407 us | 1.175 us | 249.68 us | 367 |
IterationNormalTwoComponents | 1000000 | 2,803.56 us | 12.563 us | 11.137 us | 2,798.01 us | 22,476 |
IterationUnsafeAddTwoComponents | 1000000 | 2,560.23 us | 8.799 us | 8.230 us | 2,559.64 us | 24,377 |
IterationNormalTwoComponentsSpan | 1000000 | 2,817.89 us | 10.937 us | 10.231 us | 2,813.88 us | 24,749 |
IterationUnsafeAddTwoComponentsSpan | 1000000 | 2,568.29 us | 18.498 us | 17.303 us | 2,567.17 us | 23,454 |
IterationNormalTwoComponentsUnsafeArray | 1000000 | 2,799.62 us | 14.872 us | 13.912 us | 2,802.79 us | 21,873 |
IterationUnsafeAddTwoComponentsUnsafeArray | 1000000 | 2,550.32 us | 13.997 us | 13.093 us | 2,555.53 us | 22,839 |
IterationNormalTwoComponentsUnsafeSpan | 1000000 | 2,819.96 us | 31.745 us | 28.141 us | 2,818.11 us | 22,059 |
IterationUnsafeAddTwoComponentsUnsafeSpan | 1000000 | 2,551.21 us | 6.009 us | 5.018 us | 2,549.80 us | 20,806 |
IterationUnsafeAddTwoComponentsCompleteUnsafe | 1000000 | 2,555.63 us | 19.448 us | 18.191 us | 2,553.00 us | 22,238 |
IterationNormalTwoComponents | 10000000 | 32,851.15 us | 169.640 us | 158.682 us | 32,919.60 us | 263,697 |
IterationUnsafeAddTwoComponents | 10000000 | 30,272.00 us | 126.307 us | 111.968 us | 30,294.22 us | 258,406 |
IterationNormalTwoComponentsSpan | 10000000 | 32,899.92 us | 78.192 us | 65.294 us | 32,884.78 us | 266,462 |
IterationUnsafeAddTwoComponentsSpan | 10000000 | 30,363.23 us | 114.287 us | 106.904 us | 30,324.14 us | 263,339 |
IterationNormalTwoComponentsUnsafeArray | 10000000 | 32,703.78 us | 50.003 us | 41.755 us | 32,691.33 us | 251,597 |
IterationUnsafeAddTwoComponentsUnsafeArray | 10000000 | 30,148.39 us | 115.400 us | 102.299 us | 30,125.48 us | 246,588 |
IterationNormalTwoComponentsUnsafeSpan | 10000000 | 32,790.12 us | 95.293 us | 84.475 us | 32,804.19 us | 259,823 |
IterationUnsafeAddTwoComponentsUnsafeSpan | 10000000 | 30,214.06 us | 151.878 us | 142.067 us | 30,254.44 us | 246,144 |
IterationUnsafeAddTwoComponentsCompleteUnsafe | 10000000 | 30,420.64 us | 307.392 us | 272.495 us | 30,429.62 us | 253,005 |
Legend
Legends
- amount : Value of the 'amount' parameter
- Mean : Arithmetic mean of all measurements
- Error : Half of 99.9% confidence interval
- StdDev : Standard deviation of all measurements
- Median : Value separating the higher half of all measurements (50th percentile)
- Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
- 1 Ξs : 1 Microsecond (0.000001 sec)
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.Buffers (>= 4.5.1)
- System.Runtime.CompilerServices.Unsafe (>= 5.0.0)
-
net6.0
- Collections.Pooled (>= 2.0.0-preview.27)
- CommunityToolkit.HighPerformance (>= 7.1.2)
- System.Buffers (>= 4.5.1)
- System.Runtime.CompilerServices.Unsafe (>= 5.0.0)
-
net7.0
- Collections.Pooled (>= 2.0.0-preview.27)
- CommunityToolkit.HighPerformance (>= 7.1.2)
- System.Buffers (>= 4.5.1)
- System.Runtime.CompilerServices.Unsafe (>= 5.0.0)
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 (4)
Showing the top 4 popular GitHub repositories that depend on Arch:
Repository | Stars |
---|---|
JasonBock/Rocks
A mocking library based on the Compiler APIs (Roslyn + Mocks)
|
|
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,166 | 8/15/2024 |
1.3.2-alpha | 77 | 8/14/2024 |
1.3.1-alpha | 82 | 8/13/2024 |
1.3.0-alpha | 226 | 8/12/2024 |
1.2.8.2-alpha | 548 | 7/22/2024 |
1.2.8.1-alpha | 1,443 | 4/19/2024 |
1.2.8 | 7,853 | 3/13/2024 |
1.2.7.1-alpha | 587 | 11/20/2023 |
1.2.7 | 3,206 | 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,425 | 8/25/2023 |
1.2.6.5-alpha | 302 | 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 | 527 | 5/21/2023 |
1.2.5.2-alpha | 154 | 5/16/2023 |
1.2.5.1-alpha | 259 | 5/11/2023 |
1.2.5 | 840 | 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 | 132 | 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 | 480 | 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 | 125 | 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 |
Added documentation. Dont worry, the last version for the next few days. Time to relax a bit.