Skip to content

Commit dc4d6ff

Browse files
Merge pull request #27 from Felid-Force-Studios/2.1.4
2.1.4
2 parents e06694c + 6c29474 commit dc4d6ff

24 files changed

Lines changed: 347 additions & 81 deletions

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<a href="./README_RU.md"><img src="https://img.shields.io/badge/RU-Русский-blue?style=flat-square" alt="Русский"></a>
66
<a href="./README_ZH.md"><img src="https://img.shields.io/badge/ZH-中文-blue?style=flat-square" alt="中文"></a>
77
<br><br>
8-
<img src="https://img.shields.io/badge/version-2.1.3-blue?style=for-the-badge" alt="Version">
8+
<img src="https://img.shields.io/badge/version-2.1.4-blue?style=for-the-badge" alt="Version">
99
<a href="https://www.nuget.org/packages/FFS.StaticEcs/"><img src="https://img.shields.io/badge/NuGet-FFS.StaticEcs-004880?style=for-the-badge&logo=nuget" alt="NuGet"></a>
1010
<a href="https://felid-force-studios.github.io/StaticEcs/en/"><img src="https://img.shields.io/badge/Docs-documentation-blueviolet?style=for-the-badge" alt="Documentation"></a>
1111
<a href="https://gist.github.com/blackbone/6d254a684cf580441bf58690ad9485c3"><img src="https://img.shields.io/badge/Benchmarks-results-green?style=for-the-badge" alt="Benchmarks"></a>
@@ -180,7 +180,10 @@ public class Program {
180180
// Create the world
181181
W.Create();
182182

183-
// Auto-register all components, tags, events, etc. from the calling assembly
183+
// Auto-register all components, tags, events, links and entity types from the
184+
// assembly that declares the IWorldType struct `WT` (resolved as typeof(WT).Assembly).
185+
// Safe on all runtimes including Unity IL2CPP, Unity WebGL and NativeAOT — no stack walking.
186+
// For multi-assembly projects use: W.Types().RegisterAll(typeof(WT).Assembly, typeof(Other).Assembly);
184187
W.Types().RegisterAll();
185188

186189
// Initialize the world

README_RU.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<a href="./README_RU.md"><img src="https://img.shields.io/badge/RU-Русский-blue?style=flat-square" alt="Русский"></a>
66
<a href="./README_ZH.md"><img src="https://img.shields.io/badge/ZH-中文-blue?style=flat-square" alt="中文"></a>
77
<br><br>
8-
<img src="https://img.shields.io/badge/version-2.1.3-blue?style=for-the-badge" alt="Version">
8+
<img src="https://img.shields.io/badge/version-2.1.4-blue?style=for-the-badge" alt="Version">
99
<a href="https://www.nuget.org/packages/FFS.StaticEcs/"><img src="https://img.shields.io/badge/NuGet-FFS.StaticEcs-004880?style=for-the-badge&logo=nuget" alt="NuGet"></a>
1010
<a href="https://felid-force-studios.github.io/StaticEcs/ru/"><img src="https://img.shields.io/badge/Docs-документация-blueviolet?style=for-the-badge" alt="Документация"></a>
1111
<a href="https://gist.github.com/blackbone/6d254a684cf580441bf58690ad9485c3"><img src="https://img.shields.io/badge/Benchmarks-результаты-green?style=for-the-badge" alt="Benchmarks"></a>
@@ -180,7 +180,10 @@ public class Program {
180180
// Создаём мир
181181
W.Create();
182182

183-
// Авторегистрация всех компонентов, тегов, событий и т.д. из текущей сборки
183+
// Авторегистрация всех компонентов, тегов, событий, связей и типов сущностей
184+
// из сборки, в которой объявлена IWorldType-структура `WT` (берётся как typeof(WT).Assembly).
185+
// Безопасно на всех рантаймах, включая Unity IL2CPP, Unity WebGL и NativeAOT — без stack walking.
186+
// Для мульти-сборочных проектов: W.Types().RegisterAll(typeof(WT).Assembly, typeof(Other).Assembly);
184187
W.Types().RegisterAll();
185188

186189
// Инициализируем мир

README_ZH.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<a href="./README_RU.md"><img src="https://img.shields.io/badge/RU-Русский-blue?style=flat-square" alt="Русский"></a>
66
<a href="./README_ZH.md"><img src="https://img.shields.io/badge/ZH-中文-blue?style=flat-square" alt="中文"></a>
77
<br><br>
8-
<img src="https://img.shields.io/badge/version-2.1.3-blue?style=for-the-badge" alt="Version">
8+
<img src="https://img.shields.io/badge/version-2.1.4-blue?style=for-the-badge" alt="Version">
99
<a href="https://www.nuget.org/packages/FFS.StaticEcs/"><img src="https://img.shields.io/badge/NuGet-FFS.StaticEcs-004880?style=for-the-badge&logo=nuget" alt="NuGet"></a>
1010
<a href="https://felid-force-studios.github.io/StaticEcs/zh/"><img src="https://img.shields.io/badge/Docs-文档-blueviolet?style=for-the-badge" alt="文档"></a>
1111
<a href="https://gist.github.com/blackbone/6d254a684cf580441bf58690ad9485c3"><img src="https://img.shields.io/badge/Benchmarks-基准测试-green?style=for-the-badge" alt="基准测试"></a>
@@ -180,7 +180,10 @@ public class Program {
180180
// 创建世界
181181
W.Create();
182182

183-
// 自动注册当前程序集中的所有组件、标签、事件等
183+
// 自动注册声明 IWorldType 结构体 `WT` 的程序集中的所有组件、标签、事件、关联与实体类型
184+
// (程序集通过 typeof(WT).Assembly 解析)。
185+
// 在所有运行时都安全,包括 Unity IL2CPP、Unity WebGL 和 NativeAOT —— 不依赖调用栈回溯。
186+
// 多程序集项目请使用: W.Types().RegisterAll(typeof(WT).Assembly, typeof(Other).Assembly);
184187
W.Types().RegisterAll();
185188

186189
// 初始化世界

Src/Lib.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ internal static void Trace(string message) {
811811
/// <item>Abstract types and open generic definitions are skipped.</item>
812812
/// <item>All types are registered with default configuration (no custom GUID, no custom serialization).</item>
813813
/// <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&lt;TWorld&gt;.Types().RegisterAll()</c> (without arguments) defaults to <c>typeof(TWorld).Assembly</c>. This method itself does not default — pass assemblies explicitly.</item>
815815
/// </list>
816816
/// </para>
817817
/// <para>
@@ -826,7 +826,9 @@ public static class AutoRegistration {
826826
/// </summary>
827827
/// <typeparam name="TWorld">The world type to register types into.</typeparam>
828828
/// <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
831+
/// wrapper <c>World&lt;TWorld&gt;.Types().RegisterAll()</c> instead.
830832
/// The StaticEcs framework assembly is always excluded.
831833
/// </param>
832834
#if NET5_0_OR_GREATER

Src/World.API.cs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,7 +1496,7 @@ public static void NewEntities<TEntityType, T1, T2, T3, T4, T5, T6, T7, T8>(uint
14961496
public readonly struct TypeRegistrar {
14971497

14981498
/// <summary>
1499-
/// Auto-discovers and registers all ECS types found in the specified assemblies via reflection.
1499+
/// Auto-discovers and registers all ECS types found in the assembly that declares <typeparamref name="TWorld"/>.
15001500
/// Scans for structs implementing the following marker interfaces and registers each one:
15011501
/// <list type="bullet">
15021502
/// <item><see cref="IComponent"/> — registered as component (excludes internal framework components).</item>
@@ -1508,10 +1508,21 @@ public readonly struct TypeRegistrar {
15081508
/// <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>
15091509
/// </list>
15101510
/// <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>
15121522
/// The StaticEcs framework assembly itself is always excluded from scanning.
15131523
/// 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.
15151526
/// </para>
15161527
/// <para>
15171528
/// Must be called during the <see cref="WorldStatus.Created"/> phase
@@ -1520,14 +1531,39 @@ public readonly struct TypeRegistrar {
15201531
/// will be registered for each applicable interface.
15211532
/// </para>
15221533
/// </summary>
1523-
/// <param name="assemblies">Assemblies to scan for ECS type implementations. If empty, scans the calling assembly.</param>
15241534
/// <returns>This registrar for chaining.</returns>
1525-
[MethodImpl(NoInlining)]
1526-
public void RegisterAll(params Assembly[] assemblies) {
1527-
if (assemblies == null || assemblies.Length == 0) {
1528-
assemblies = new[] { Assembly.GetCallingAssembly() };
1535+
[MethodImpl(AggressiveInlining)]
1536+
public TypeRegistrar RegisterAll() {
1537+
AutoRegistration.RegisterAll<TWorld>(typeof(TWorld).Assembly);
1538+
return this;
1539+
}
1540+
1541+
/// <summary>
1542+
/// Auto-discovers and registers all ECS types found in the specified assemblies via reflection.
1543+
/// Use this overload when ECS types live in one or more assemblies different from the one that
1544+
/// declares <typeparamref name="TWorld"/>, e.g.:
1545+
/// <code>
1546+
/// World&lt;MyWorld&gt;.Types().RegisterAll(typeof(MyWorld).Assembly, typeof(SomeComponent).Assembly);
1547+
/// </code>
1548+
/// <para>
1549+
/// Discovery rules, excluded types, configuration defaults and lifecycle constraints are identical to
1550+
/// <see cref="RegisterAll()"/> — see that method for details.
1551+
/// </para>
1552+
/// </summary>
1553+
/// <param name="first">First assembly to scan (required — prevents accidental empty calls).</param>
1554+
/// <param name="rest">Additional assemblies to scan.</param>
1555+
/// <returns>This registrar for chaining.</returns>
1556+
[MethodImpl(AggressiveInlining)]
1557+
public TypeRegistrar RegisterAll(Assembly first, params Assembly[] rest) {
1558+
if (rest == null || rest.Length == 0) {
1559+
AutoRegistration.RegisterAll<TWorld>(first);
1560+
} else {
1561+
var assemblies = new Assembly[rest.Length + 1];
1562+
assemblies[0] = first;
1563+
Array.Copy(rest, 0, assemblies, 1, rest.Length);
1564+
AutoRegistration.RegisterAll<TWorld>(assemblies);
15291565
}
1530-
AutoRegistration.RegisterAll<TWorld>(assemblies);
1566+
return this;
15311567
}
15321568

15331569
/// <summary>

StaticEcs.csproj

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<NoWarn>$(NoWarn);IL2091</NoWarn>
1313
<RootNamespace>FFS.Libraries.StaticEcs</RootNamespace>
1414
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
15-
<Version>2.1.3</Version>
15+
<Version>2.1.4</Version>
1616
<Authors>Felid-Force-Studios</Authors>
1717
<Description>C# Hierarchical Inverted Bitmap ECS framework</Description>
1818
<Copyright>Felid-Force-Studios</Copyright>
@@ -48,18 +48,18 @@
4848
</PropertyGroup>
4949

5050
<ItemGroup>
51-
<Content Include="README.md" Pack="true" PackagePath=""/>
52-
<Content Include="README_RU.md"/>
53-
<Content Include="README_ZH.md"/>
54-
<Content Include="CHANGELOG_2_0_0.md"/>
55-
<Content Include="CHANGELOG_2_0_0_EN.md"/>
56-
<Content Include="CHANGELOG_2_0_0_ZH.md"/>
51+
<None Include="README.md" Pack="true" PackagePath=""/>
52+
<None Include="README_RU.md" Pack="true" PackagePath=""/>
53+
<None Include="README_ZH.md" Pack="true" PackagePath=""/>
5754
</ItemGroup>
5855
<ItemGroup>
5956
<None Remove="**/*.meta"/>
6057
<None Remove="FFS.StaticEcs.asmdef"/>
6158
<None Remove="package.json"/>
6259
<None Remove="LICENSE.meta"/>
60+
<None Remove="CHANGELOG_2_0_0.md"/>
61+
<None Remove="CHANGELOG_2_0_0_EN.md"/>
62+
<None Remove="CHANGELOG_2_0_0_ZH.md"/>
6363
</ItemGroup>
6464

6565
<ItemGroup Condition="'$(Configuration)' == 'Debug'">

docs/en/aiagentguide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ public abstract class GameSys : W.Systems<GameSystems> { }
3939

4040
### World Lifecycle (strict order)
4141
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)`.
4343
3. `W.Initialize()` — after this, entity operations are available
4444
4. Work: create entities, run systems, iterate queries
4545
5. `W.Destroy()` — cleanup
4646

4747
### 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.
4949
- Entity is a 4-byte uint handle — NOT a persistent reference. NEVER store Entity in fields/collections across frames. Use EntityGID for persistent references.
5050
- `Add<T>()` without value is idempotent (if exists → returns ref, no hooks). `Set(value)` ALWAYS overwrites with OnDelete→OnAdd hook cycle.
5151
- `Ref<T>()` returns a ref to the component. Assumes component exists — check with `Has<T>()` first if uncertain.

docs/en/features/entity.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ W.Types()
5353
W.Types().RegisterAll();
5454
```
5555

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.
5757

5858
### Lifecycle hooks (OnCreate / OnDestroy)
5959

docs/en/features/world.md

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,26 +137,57 @@ ___
137137

138138
#### Auto-registration of types:
139139
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.
141141

142142
```csharp
143143
W.Create(WorldConfig.Default());
144144

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.
146147
W.Types().RegisterAll();
147148

148-
// Or specify particular assemblies
149+
// Explicit form — scans the given assemblies only. The first assembly is required
150+
// so an empty call is syntactically impossible.
149151
W.Types().RegisterAll(typeof(MyGame).Assembly, typeof(MyPlugin).Assembly);
150152

151-
// Can be combined with manual registration
153+
// Can be combined with manual registration (fluent chain)
152154
W.Types()
153155
.RegisterAll()
154156
.Component<SpecialComponent>();
155157

156158
W.Initialize();
157159
```
158160

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**
160191

161192
| Interface | Registration |
162193
|-----------|-------------|
@@ -169,15 +200,17 @@ Detected interfaces:
169200
| `IEntityType` | `Types().EntityType<T>()` |
170201

171202
{: .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.
174207
- `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:
175208
- `IComponent` — looks for `ComponentTypeConfig<T>` (prefers name `Config`)
176209
- `IEvent` — looks for `EventTypeConfig<T>` (prefers name `Config`)
177210
- `ITag` — looks for `TagTypeConfig<T>` (prefers name `Config`)
178211
- `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()`.
181214

182215
___
183216

0 commit comments

Comments
 (0)