You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
/// <item>Abstract types and open generic definitions are skipped.</item>
812
812
/// <item>All types are registered with default configuration (no custom GUID, no custom serialization).</item>
813
813
/// <item>A single type implementing multiple interfaces will be registered for each applicable one.</item>
814
-
/// <item>If no assemblies are specified, only the calling assembly is scanned (not all loaded assemblies).</item>
814
+
/// <item>The fluent wrapper <c>World<TWorld>.Types().RegisterAll()</c> (without arguments) defaults to <c>typeof(TWorld).Assembly</c>. This method itself does not default — pass assemblies explicitly.</item>
815
815
/// </list>
816
816
/// </para>
817
817
/// <para>
@@ -826,7 +826,9 @@ public static class AutoRegistration {
826
826
/// </summary>
827
827
/// <typeparam name="TWorld">The world type to register types into.</typeparam>
828
828
/// <param name="assemblies">
829
-
/// Assemblies to scan. If <c>null</c> or empty, scans the calling assembly only.
829
+
/// Assemblies to scan. Must contain at least one assembly — this is the low-level entry point and does not
830
+
/// fall back to any default. For the ergonomic default (scan <c>typeof(TWorld).Assembly</c>), use the fluent
@@ -1508,10 +1508,21 @@ public readonly struct TypeRegistrar {
1508
1508
/// <item><see cref="IEntityType"/> — registered as entity type with Id from a static <c>byte Id</c> field. <see cref="Default"/> is skipped (already registered).</item>
1509
1509
/// </list>
1510
1510
/// <para>
1511
-
/// If no assemblies are specified, scans the calling assembly only.
1511
+
/// The scanned assembly is resolved as <c>typeof(TWorld).Assembly</c>. This is a pure reflection lookup
1512
+
/// and does not rely on stack walking, so it works correctly on all runtimes, including
1513
+
/// <b>Unity IL2CPP, Unity WebGL, and NativeAOT</b> (where <c>Assembly.GetCallingAssembly</c> returns
1514
+
/// unreliable results).
1515
+
/// </para>
1516
+
/// <para>
1517
+
/// If <typeparamref name="TWorld"/> lives in a different assembly than your ECS types (e.g. a shared
1518
+
/// "core" assembly), use the overload <see cref="RegisterAll(Assembly, Assembly[])"/> and pass the
1519
+
/// assemblies explicitly.
1520
+
/// </para>
1521
+
/// <para>
1512
1522
/// The StaticEcs framework assembly itself is always excluded from scanning.
1513
1523
/// Abstract types and open generic type definitions are skipped.
1514
-
/// All types are registered with default configuration (default GUID, default serialization settings).
1524
+
/// All types are registered with default configuration (default GUID, default serialization settings);
1525
+
/// for custom configuration use the explicit <see cref="Component{T}"/>, <see cref="Event{T}"/>, etc.
1515
1526
/// </para>
1516
1527
/// <para>
1517
1528
/// Must be called during the <see cref="WorldStatus.Created"/> phase
@@ -1520,14 +1531,39 @@ public readonly struct TypeRegistrar {
1520
1531
/// will be registered for each applicable interface.
1521
1532
/// </para>
1522
1533
/// </summary>
1523
-
/// <param name="assemblies">Assemblies to scan for ECS type implementations. If empty, scans the calling assembly.</param>
1524
1534
/// <returns>This registrar for chaining.</returns>
Copy file name to clipboardExpand all lines: docs/en/aiagentguide.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -39,13 +39,13 @@ public abstract class GameSys : W.Systems<GameSystems> { }
39
39
40
40
### World Lifecycle (strict order)
41
41
1.`W.Create(WorldConfig.Default())` — creates the world
42
-
2.`W.Types().RegisterAll()` or manual registration `.Component<T>().Tag<T>().Event<T>()` — register ALL types (required!)
42
+
2.`W.Types().RegisterAll()` or manual registration `.Component<T>().Tag<T>().Event<T>()` — register ALL types (required!). `RegisterAll()` without arguments scans `typeof(TWorld).Assembly` (safe on IL2CPP/WebGL/NativeAOT). For types split across assemblies use `RegisterAll(typeof(TWorld).Assembly, typeof(Other).Assembly)`.
43
43
3.`W.Initialize()` — after this, entity operations are available
44
44
4. Work: create entities, run systems, iterate queries
45
45
5.`W.Destroy()` — cleanup
46
46
47
47
### Critical Rules
48
-
- ALWAYS register component/tag/event/link types between Create() and Initialize(). Use `W.Types().RegisterAll()` to auto-register all types from the assembly, or register manually. Unregistered types cause runtime errors.
48
+
- ALWAYS register component/tag/event/link types between Create() and Initialize(). Use `W.Types().RegisterAll()` to auto-register all types from the assembly that declares your `TWorld` marker (works on Unity IL2CPP / WebGL / NativeAOT because it uses `typeof(TWorld).Assembly`, not `GetCallingAssembly`), or register manually. For multi-assembly projects pass each assembly explicitly: `W.Types().RegisterAll(typeof(TWorld).Assembly, typeof(OtherAssemblyMarker).Assembly)`. Unregistered types cause runtime errors.
49
49
- Entity is a 4-byte uint handle — NOT a persistent reference. NEVER store Entity in fields/collections across frames. Use EntityGID for persistent references.
50
50
-`Add<T>()` without value is idempotent (if exists → returns ref, no hooks). `Set(value)` ALWAYS overwrites with OnDelete→OnAdd hook cycle.
51
51
-`Ref<T>()` returns a ref to the component. Assumes component exists — check with `Has<T>()` first if uncertain.
Copy file name to clipboardExpand all lines: docs/en/features/entity.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -53,7 +53,7 @@ W.Types()
53
53
W.Types().RegisterAll();
54
54
```
55
55
56
-
`RegisterAll()` discovers all types implementing `IEntityType` in the specified assemblies (defaults to the calling assembly) and registers them automatically. The identifier is obtained from the `Id()` method.
56
+
`RegisterAll()` discovers all types implementing `IEntityType` in the specified assemblies (defaults to `typeof(TWorld).Assembly` — no stack walking, safe on Unity IL2CPP, Unity WebGL and NativeAOT) and registers them automatically. The identifier is obtained from the `Id()` method.
Copy file name to clipboardExpand all lines: docs/en/features/world.md
+42-9Lines changed: 42 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -137,26 +137,57 @@ ___
137
137
138
138
#### Auto-registration of types:
139
139
Instead of manually registering each type, you can use automatic assembly scanning.
140
-
`RegisterAll()` discovers all structs implementing ECS interfaces and registers them automatically:
140
+
`RegisterAll()` discovers all structs implementing ECS interfaces in one or more assemblies and registers each one via the corresponding `Register*` API.
141
141
142
142
```csharp
143
143
W.Create(WorldConfig.Default());
144
144
145
-
// Auto-register all types from the calling assembly
145
+
// Parameterless form — scans the assembly that declares the IWorldType struct `WT`
146
+
// (resolved as typeof(WT).Assembly). No stack walking.
146
147
W.Types().RegisterAll();
147
148
148
-
// Or specify particular assemblies
149
+
// Explicit form — scans the given assemblies only. The first assembly is required
// Can be combined with manual registration (fluent chain)
152
154
W.Types()
153
155
.RegisterAll()
154
156
.Component<SpecialComponent>();
155
157
156
158
W.Initialize();
157
159
```
158
160
159
-
Detected interfaces:
161
+
**How the scanned assembly is resolved**
162
+
163
+
| Overload | Scanned assemblies |
164
+
|----------|-------------------|
165
+
|`RegisterAll()`|`typeof(TWorld).Assembly` — the assembly that declares your `IWorldType` struct (in the examples, `WT` — not the alias class `W : World<WT>`, but the struct itself) |
166
+
|`RegisterAll(Assembly first, params Assembly[] rest)`| Exactly the assemblies you pass — `TWorld`'s assembly is **not** added implicitly |
167
+
168
+
The parameterless form deliberately uses `typeof(TWorld).Assembly` and never calls `Assembly.GetCallingAssembly()`. This means it works correctly on **all runtimes**, including:
169
+
170
+
- .NET Framework / .NET Core / .NET 5+
171
+
- Mono and Unity Mono
172
+
-**Unity IL2CPP**
173
+
-**Unity WebGL**
174
+
-**NativeAOT**
175
+
176
+
On IL2CPP/WebGL/NativeAOT, `Assembly.GetCallingAssembly()` returns unreliable results because stack walking is stripped or restricted — that is why the implementation derives the assembly from a generic type argument instead. As long as your `IWorldType` struct (`WT`) lives in the same assembly as your ECS types, the parameterless form is all you need.
177
+
178
+
**Multi-assembly scenario**
179
+
180
+
If your `IWorldType` struct and your ECS types live in different assemblies (for example, `WT` is defined in a shared "core" assembly and your components live in a game assembly), use the explicit overload and list every assembly that contains ECS types:
181
+
182
+
```csharp
183
+
W.Types().RegisterAll(
184
+
typeof(WT).Assembly, // core assembly with the IWorldType struct
185
+
typeof(Position).Assembly, // gameplay assembly with components
186
+
typeof(AiPlugin).Assembly// another plugin assembly
187
+
);
188
+
```
189
+
190
+
**Detected interfaces**
160
191
161
192
| Interface | Registration |
162
193
|-----------|-------------|
@@ -169,15 +200,17 @@ Detected interfaces:
169
200
|`IEntityType`|`Types().EntityType<T>()`|
170
201
171
202
{: .note }
172
-
- If no assemblies are specified, only the calling assembly is scanned (not all loaded assemblies)
173
-
- The StaticEcs framework assembly itself is always excluded from scanning
203
+
- The StaticEcs framework assembly itself is always excluded from scanning.
204
+
- Abstract types and open generic type definitions are skipped.
205
+
- A struct implementing multiple interfaces (e.g. both `IComponent` and `IMultiComponent`) is registered for each applicable interface.
206
+
- The `Default` entity type is skipped because it is already registered by the world.
174
207
-`RegisterAll()` searches for a static field or property of the matching config type inside each struct and uses it if found. Otherwise, default configuration is used. Lookup rules:
175
208
-`IComponent` — looks for `ComponentTypeConfig<T>` (prefers name `Config`)
176
209
-`IEvent` — looks for `EventTypeConfig<T>` (prefers name `Config`)
177
210
-`ITag` — looks for `TagTypeConfig<T>` (prefers name `Config`)
178
211
-`IEntityType` — looks for `byte` (prefers name `Id`)
179
-
- Both fields and properties are supported
180
-
-A struct implementing multiple interfaces (e.g. both `IComponent` and `IMultiComponent`) will be registered for each one
212
+
- Both fields and properties are supported.
213
+
-Must be called during the `Created` phase — after `W.Create()` and before `W.Initialize()`.
0 commit comments