Skip to content

Commit 33e6a77

Browse files
authored
Merge pull request #10 from Juiix/feature/static-component-registry
feat(core): added static component registry, performance improvements…
2 parents 687ee9a + 9b50ec6 commit 33e6a77

205 files changed

Lines changed: 14877 additions & 18293 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

DOCS.md

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,11 @@
2222

2323
EntitiesDb is built around a classic data-oriented ECS design:
2424

25-
### **Entities**
26-
27-
Lightweight integer IDs. They have no data themselves — components define the data.
28-
29-
### **Components**
30-
31-
Plain C# types (`struct`, `class`, `record`, etc). Stored densely in chunked arrays.
32-
33-
### **Archetypes**
34-
35-
Unique sets of components. Entities with the same component signature are grouped into the same chunk layout.
36-
37-
### **Queries**
38-
39-
Pull in chunks of entities matching component filters — the basis for all system updates.
40-
41-
### **Source-Generated Iterators**
42-
43-
`ForEach` methods generate strongly-typed, allocation-free iteration code.
25+
* **Entities**: Lightweight integer IDs. They have no data themselves — components define the data.
26+
* **Components**: Plain C# types (`struct`, `class`, `record`, etc). Stored densely in chunked arrays.
27+
* **Archetypes**: Unique sets of components. Entities with the same component signature are grouped into the same chunk layout.
28+
* **Queries**: Pull in chunks of entities matching component filters — the basis for all system updates.
29+
* **Source-Generated Iterators**: `ForEach` methods generate strongly-typed, allocation-free iteration code.
4430

4531
---
4632

@@ -58,17 +44,17 @@ var options = new EntityDatabaseOptions(
5844
var db = new EntityDatabase(options);
5945
```
6046

61-
Each database is completely isolated, allowing multiple worlds.
62-
6347
---
6448

6549
# Entities
6650

6751
```csharp
52+
// create entity
6853
var entity = db.Create(new Position(10, 10), new Health(100, 100));
69-
7054
db.Exists(entity); // true
71-
db.Destroy(entity.Id); // destroy
55+
56+
// destroy entity
57+
db.Destroy(entity); // destroy
7258
db.Exists(entity); // false
7359
```
7460

@@ -79,26 +65,27 @@ db.Exists(entity); // false
7965

8066
# Components
8167

82-
Components are simple data types:
68+
Components are simple data types that can be `struct` or `class`:
8369

8470
```csharp
8571
public record struct Position(float X, float Y);
8672
public record struct Velocity(float Dx, float Dy);
8773
```
74+
> *`class` components should be avoided when fast enumeration is a priority of the component*
8875
8976
### Add / Read / Write / Remove
9077

9178
```csharp
92-
db.Add(entity.Id, new Velocity(10, 10));
79+
db.Add(entity, new Velocity(10, 10));
9380

94-
db.Has<Velocity>(entity.Id); // true
81+
db.Has<Velocity>(entity); // true
9582
96-
db.Set(entity.Id, new Velocity(200, 200)); // overwrite component
83+
db.Set(entity, new Velocity(200, 200)); // overwrite component
9784
98-
db.Remove<Velocity>(entity.Id);
85+
db.Remove<Velocity>(entity);
9986

10087
// Read-only
101-
readonly ref var position = ref db.Read<Position>(entity.Id);
88+
ref readonly var position = ref db.Read<Position>(entity);
10289

10390
// Write
10491
ref var pos = ref db.Write<Position>(entity.Id);
@@ -113,7 +100,7 @@ Buffers act like **inline component lists**, stored directly in the chunk.
113100
Only `unmanaged` types can be buffered.
114101

115102
```csharp
116-
[Buffer(8)]
103+
[Buffer(8)] // use the buffer attribute to mark a component as buffered and passing the inline buffer size
117104
public record struct InventoryItem(int Id, int Count);
118105
```
119106

@@ -126,7 +113,7 @@ var entity = db.Create(
126113
new[] {
127114
new InventoryItem(1, 1),
128115
new InventoryItem(2, 10)
129-
} // Buffer must be the last argument
116+
} // Buffers must be the last component arguments
130117
);
131118
```
132119

@@ -149,7 +136,7 @@ write.Add(new InventoryItem(3, 5));
149136

150137
## Tags
151138

152-
Tags are **zero-data components** used for classification:
139+
Tags are **zero-data components** used for classification and filtering:
153140

154141
```csharp
155142
[Tag] public struct PlayerTag { }
@@ -241,12 +228,18 @@ query.ForEachChunk((int len,
241228

242229
# Change Filter
243230

231+
Enable change filtering with the `TrackChanges` attribute:
232+
```csharp
233+
[TrackChanges]
234+
public record struct Position(float X, float Y);
235+
```
236+
244237
Only enumerate chunks whose components were written recently.
245238

246239
```csharp
247240
var query = db.QueryBuilder
248241
.WithAll<Position, SentPosition, PositionDelta>()
249-
.WithChangeFilter<Position>()
242+
.WithChangeFilter<Position>() // add change filter
250243
.Build();
251244

252245
query.ForEach((in Position pos, ref SentPosition sent, ref PositionDelta delta) =>

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,26 @@ EntitiesDb focuses on **raw performance**, **simple APIs**, and **zero external
1010

1111
* **🚀 Fast** — Chunk-based, archetype-organized storage with SIMD-friendly handles
1212
* **🧩 Simple** — Define any type as a component and query with expressive filters
13-
* **📦 Self-Contained** — Multiple isolated databases/worlds
1413
* **🧵 Parallel-Ready** — Built-in multithreaded query execution and reduction
1514
* **0️⃣ GC-Friendly** — Minimal allocations, predictable memory layout
1615
* **📚 Documentation** — API reference and guides in [DOCS.md](./DOCS.md)
1716

1817
---
1918

19+
# Features
20+
* Archetypes with cache-efficient chunking
21+
* Any type as a component
22+
* Component buffers for unmanaged types
23+
* High-performance component queries
24+
* Command buffers
25+
* Multithreaded queries
26+
* Manual archetype/chunk enumeration
27+
* Source generated delegate queries
28+
* Chunk change filters
29+
* SIMD-friendly
30+
31+
---
32+
2033
# Quick Start
2134

2235
```csharp

src/EntitiesDb.Benchmark/CreateWithOneComponent/EntitiesDb.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ public void EntitiesDb_Bulk()
2929
var entities = _entitiesDb.Entities;
3030
entities.Reserve<Component1>(EntityCount);
3131

32-
var bulk = entities.GetBulkCreate<Component1>();
3332
for (int i = 0; i < EntityCount; ++i)
3433
{
35-
entities.Create(in bulk, new Component1());
34+
entities.Create(new Component1());
3635
}
3736
}
3837
}

src/EntitiesDb.Benchmark/SystemWithOneComponent/EntitiesDb.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ public partial class SystemWithOneComponent
1010
private class EntitiesDbContext : EntitiesDbBaseContext
1111
{
1212
public Query Query;
13-
public Id<Component1> Id;
1413

1514
public EntitiesDbContext(int entityCount)
1615
{
@@ -22,7 +21,6 @@ public EntitiesDbContext(int entityCount)
2221
Query = Entities.QueryBuilder
2322
.WithAll<Component1>()
2423
.Build();
25-
Id = Entities.ComponentRegistry.GetId<Component1>();
2624
}
2725
}
2826

src/EntitiesDb.Benchmark/SystemWithTwoComponents/EntitiesDb.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ public partial class SystemWithTwoComponents
1010
private class EntitiesDbContext : EntitiesDbBaseContext
1111
{
1212
public Query Query;
13-
public Ids<Component1, Component2> Ids;
1413

1514
public EntitiesDbContext(int entityCount) : base(6)
1615
{
@@ -22,7 +21,6 @@ public EntitiesDbContext(int entityCount) : base(6)
2221
Query = Entities.QueryBuilder
2322
.WithAll<Component1, Component2>()
2423
.Build();
25-
Ids = Entities.ComponentRegistry.GetIds<Component1, Component2>();
2624
}
2725
}
2826

src/EntitiesDb.Benchmark/SystemWithTwoComponentsVariedComposition/EntitiesDb.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ public partial class SystemWithTwoComponentsVariedComposition
1010
private class EntitiesDbContext : EntitiesDbBaseContext
1111
{
1212
public Query Query;
13-
public Ids<Component1, Component2> Ids;
1413

1514
public EntitiesDbContext(int entityCount)
1615
{
@@ -28,7 +27,6 @@ public EntitiesDbContext(int entityCount)
2827
Query = Entities.QueryBuilder
2928
.WithAll<Component1>()
3029
.Build();
31-
Ids = Entities.ComponentRegistry.GetIds<Component1, Component2>();
3230
}
3331
}
3432

src/EntitiesDb.SourceGenerators/Model.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,28 +132,27 @@ private static ForEachRenderModel BuildModel(InvocationCapture cap)
132132
callArgs.Add(chunks ? "entityHandle" : "entityHandle[index]");
133133
}
134134

135-
var offsetsName = parallel ? "_offsets" : "offsets";
136135
for (int i = 0; i < comps.Count; i++)
137136
{
138137
var c = comps[i];
139-
var ofs = "T" + i.ToString();
140138
var baseVar = Utilities.Sanitize(c.Name) + "Handle";
139+
var displayType = Utilities.ToDisplay(c.ComponentType);
141140

142141
string h;
143142
switch (c.Kind)
144143
{
145144
case ParamKind.ComponentRef:
146145
case ParamKind.ComponentWriteHandle:
147-
h = "var " + baseVar + " = chunk.WriteHandle(" + offsetsName + "." + ofs + ");"; break;
146+
h = "var " + baseVar + " = chunk.WriteHandle<" + displayType + ">();"; break;
148147
case ParamKind.ComponentIn:
149148
case ParamKind.ComponentReadHandle:
150-
h = "var " + baseVar + " = chunk.ReadHandle(" + offsetsName + "." + ofs + ");"; break;
149+
h = "var " + baseVar + " = chunk.ReadHandle<" + displayType + ">();"; break;
151150
case ParamKind.WriteBuffer:
152151
case ParamKind.WriteBufferHandle:
153-
h = "var " + baseVar + " = chunk.WriteBufferHandle(" + offsetsName + "." + ofs + ");"; break;
152+
h = "var " + baseVar + " = chunk.WriteBufferHandle<" + displayType + ">();"; break;
154153
case ParamKind.ReadBuffer:
155154
case ParamKind.ReadBufferHandle:
156-
h = "var " + baseVar + " = chunk.ReadBufferHandle(" + offsetsName + "." + ofs + ");"; break;
155+
h = "var " + baseVar + " = chunk.ReadBufferHandle<" + displayType + ">();"; break;
157156
default:
158157
h = ""; break;
159158
}
@@ -176,9 +175,9 @@ private static ForEachRenderModel BuildModel(InvocationCapture cap)
176175
}
177176

178177
if (c.Kind is ParamKind.WriteBuffer or ParamKind.ReadBuffer or ParamKind.WriteBufferHandle or ParamKind.ReadBufferHandle)
179-
bufferedTypes.Add(Utilities.ToDisplay(c.ComponentType));
178+
bufferedTypes.Add(displayType);
180179
else
181-
notBufferedTypes.Add(Utilities.ToDisplay(c.ComponentType));
180+
notBufferedTypes.Add(displayType);
182181
}
183182

184183
if (cap.HasState)

src/EntitiesDb.SourceGenerators/Rendering.ForEach.cs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,35 +51,22 @@ public static string RenderForEach(ForEachRenderModel m)
5151
// ComponentMeta assertions
5252
if (m.BufferedTypes.Count > 0)
5353
{
54-
w.Append("global::EntitiesDb.ComponentMeta.AssertBuffered<")
54+
w.Append("_ = global::EntitiesDb.ComponentBufferWritable<")
5555
.Append(string.Join(", ", m.BufferedTypes.ToArray()))
56-
.AppendLine(">();");
56+
.AppendLine(">.Check;");
5757
}
5858
if (m.NotBufferedTypes.Count > 0)
5959
{
60-
w.Append("global::EntitiesDb.ComponentMeta.AssertNotBuffered<")
60+
w.Append("_ = global::EntitiesDb.ComponentSingleWritable<")
6161
.Append(string.Join(", ", m.NotBufferedTypes.ToArray()))
62-
.AppendLine(">();");
63-
}
64-
65-
// ids
66-
if (hasComponents)
67-
{
68-
w.Append("var ids = self.GetIds").Append(m.IdTypesJoined).AppendLine("();");
69-
w.AppendLine("var all = Signature.FromIds(in ids);");
62+
.AppendLine(">.Check;");
7063
}
7164

7265
// main loops
7366
w.AppendLine("foreach (var archetype in self.EnumerateArchetypes())");
7467
w.AppendLine("{");
7568
w.Indent();
7669

77-
if (hasComponents)
78-
{
79-
w.AppendLine("if (!archetype.Signature.HasAll(in all)) continue;");
80-
w.AppendLine("var offsets = archetype.GetOffsets(in ids);");
81-
}
82-
8370
w.AppendLine("foreach (ref readonly var chunk in archetype)");
8471
w.AppendLine("{");
8572
w.Indent();

src/EntitiesDb.SourceGenerators/Rendering.ForEachChunk.cs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,35 +50,22 @@ private static string RenderForEachChunk(ForEachRenderModel m)
5050
// ComponentMeta assertions
5151
if (m.BufferedTypes.Count > 0)
5252
{
53-
w.Append("global::EntitiesDb.ComponentMeta.AssertBuffered<")
53+
w.Append("_ = global::EntitiesDb.ComponentBufferWritable<")
5454
.Append(string.Join(", ", m.BufferedTypes.ToArray()))
55-
.AppendLine(">();");
55+
.AppendLine(">.Check;");
5656
}
5757
if (m.NotBufferedTypes.Count > 0)
5858
{
59-
w.Append("global::EntitiesDb.ComponentMeta.AssertNotBuffered<")
59+
w.Append("_ = global::EntitiesDb.ComponentSingleWritable<")
6060
.Append(string.Join(", ", m.NotBufferedTypes.ToArray()))
61-
.AppendLine(">();");
62-
}
63-
64-
// ids
65-
if (hasComponents)
66-
{
67-
w.Append("var ids = self.GetIds").Append(m.IdTypesJoined).AppendLine("();");
68-
w.AppendLine("var all = Signature.FromIds(in ids);");
61+
.AppendLine(">.Check;");
6962
}
7063

7164
// main loops
7265
w.AppendLine("foreach (var archetype in self.EnumerateArchetypes())");
7366
w.AppendLine("{");
7467
w.Indent();
7568

76-
if (hasComponents)
77-
{
78-
w.AppendLine("if (!archetype.Signature.HasAll(in all)) continue;");
79-
w.AppendLine("var offsets = archetype.GetOffsets(in ids);");
80-
}
81-
8269
w.AppendLine("foreach (ref readonly var chunk in archetype)");
8370
w.AppendLine("{");
8471
w.Indent();

0 commit comments

Comments
 (0)