Scaffold the .sln and M0 project structure
Buildable skeleton matching docs/kernel-contract.md: - src/Engine.Kernel — the frozen kernel. Interfaces and data types only (World, Component, GameObject, Transform, ISchedule, IServiceRegistry, IEventBus, ILogger, IPlugin/IPluginContext, plugin.json and project.json manifest models). No PluginHost/Scheduler/World implementation yet — that's M0's actual work, not scaffolding. - src/Engine.Host — the CLI runtime entry point, placeholder for now. - plugins/sandbox.echo — a minimal two-assembly plugin (Contracts in Default ALC, implementation in collectible ALC) that exists only to exercise the reload loop end to end once PluginHost exists. - tests/Engine.Kernel.Tests, tests/Engine.ConformanceHarness — wired up with one Skip-marked placeholder test each, naming what M0 needs to make them real (including the 200-cycle ALC leak test from §4). The harness references the sandbox plugin with ReferenceOutputAssembly="false" so it loads it dynamically by path instead of linking its types into its own Default ALC. - samples/EmptyProject — a starter project.json. - Directory.Build.props centralizes shared settings across every project, including AllowUnsafeBlocks=false to enforce the §7 rule at the build level rather than by convention. Solution builds clean and `dotnet test` runs both placeholders as Skipped (not failing) — confirms the wiring, not the engine. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
<Project>
|
||||
|
||||
<!-- Shared by every project in the solution. Project-specific settings
|
||||
(OutputType, IsPackable, etc.) stay in each .csproj. -->
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>13.0</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<!-- docs/kernel-contract.md §7: no unsafe in the v1 hot path. Enforced
|
||||
by the build, not just by convention, everywhere in the solution. -->
|
||||
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,121 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Kernel", "src\Engine.Kernel\Engine.Kernel.csproj", "{40297197-30BF-4180-88C9-916E66859E0F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Host", "src\Engine.Host\Engine.Host.csproj", "{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{07D57EEB-2F50-60C4-C011-FE4FA775C9A8}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sandbox.echo", "sandbox.echo", "{5277871D-FD82-4E0F-9994-47CB654DDE62}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sandbox.Echo.Contracts", "plugins\sandbox.echo\Sandbox.Echo.Contracts\Sandbox.Echo.Contracts.csproj", "{6CDA3EB0-CC08-48E6-B946-183D31840661}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sandbox.Echo", "plugins\sandbox.echo\Sandbox.Echo\Sandbox.Echo.csproj", "{B0F967C4-B801-4A53-829E-689C2728553A}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Kernel.Tests", "tests\Engine.Kernel.Tests\Engine.Kernel.Tests.csproj", "{D7C61E35-8805-4C28-B3D7-084D8FBC6820}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.ConformanceHarness", "tests\Engine.ConformanceHarness\Engine.ConformanceHarness.csproj", "{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{40297197-30BF-4180-88C9-916E66859E0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{40297197-30BF-4180-88C9-916E66859E0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{40297197-30BF-4180-88C9-916E66859E0F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{40297197-30BF-4180-88C9-916E66859E0F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{40297197-30BF-4180-88C9-916E66859E0F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{40297197-30BF-4180-88C9-916E66859E0F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{40297197-30BF-4180-88C9-916E66859E0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{40297197-30BF-4180-88C9-916E66859E0F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{40297197-30BF-4180-88C9-916E66859E0F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{40297197-30BF-4180-88C9-916E66859E0F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{40297197-30BF-4180-88C9-916E66859E0F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{40297197-30BF-4180-88C9-916E66859E0F}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661}.Release|x86.Build.0 = Release|Any CPU
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820}.Release|x86.Build.0 = Release|Any CPU
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{40297197-30BF-4180-88C9-916E66859E0F} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||
{A12B1299-FAF6-4BC9-A54A-D23F7A6DB6DF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||
{5277871D-FD82-4E0F-9994-47CB654DDE62} = {07D57EEB-2F50-60C4-C011-FE4FA775C9A8}
|
||||
{6CDA3EB0-CC08-48E6-B946-183D31840661} = {5277871D-FD82-4E0F-9994-47CB654DDE62}
|
||||
{B0F967C4-B801-4A53-829E-689C2728553A} = {5277871D-FD82-4E0F-9994-47CB654DDE62}
|
||||
{D7C61E35-8805-4C28-B3D7-084D8FBC6820} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||
{C9725988-61CD-4EFC-A3DD-F7F42B3C4B2D} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,13 @@
|
||||
using Engine.Kernel.World;
|
||||
|
||||
namespace Sandbox.Echo.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal component for exercising the reload loop described in
|
||||
/// docs/kernel-contract.md §7 — not a real engine feature, just something
|
||||
/// for a system to touch before any real plugin exists.
|
||||
/// </summary>
|
||||
public sealed class Ping : Component
|
||||
{
|
||||
public int Count;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Engine.Kernel\Engine.Kernel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,33 @@
|
||||
using Engine.Kernel.Plugins;
|
||||
using Engine.Kernel.Scheduling;
|
||||
using Engine.Kernel.World;
|
||||
using Sandbox.Echo.Contracts;
|
||||
|
||||
namespace Sandbox.Echo;
|
||||
|
||||
/// <summary>
|
||||
/// M0's test fixture. Exists only to exercise the reload loop end to end —
|
||||
/// edit, rebuild, ALC reload, headless run, dump — before any real
|
||||
/// subsystem exists to test it against. See M0 in docs/kernel-contract.md §8.
|
||||
/// </summary>
|
||||
public sealed class EchoPlugin : IPlugin
|
||||
{
|
||||
public void Configure(IPluginContext ctx)
|
||||
{
|
||||
ctx.Schedule.Add(Stage.Update, Tick)
|
||||
.Writes<Ping>();
|
||||
|
||||
ctx.Log.Info("sandbox.echo configured");
|
||||
}
|
||||
|
||||
public void Shutdown(IPluginContext ctx)
|
||||
{
|
||||
ctx.Schedule.RemoveAllFrom("sandbox.echo");
|
||||
}
|
||||
|
||||
static void Tick(IWorld world)
|
||||
{
|
||||
// TODO(M0): bump every Ping.Count by one. Placeholder until the
|
||||
// Scheduler actually exists to invoke this.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Engine.Kernel\Engine.Kernel.csproj" />
|
||||
<ProjectReference Include="..\Sandbox.Echo.Contracts\Sandbox.Echo.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"id": "sandbox.echo",
|
||||
"version": "0.0.1",
|
||||
"contracts": "Sandbox.Echo.Contracts.dll",
|
||||
"assembly": "Sandbox.Echo.dll",
|
||||
"dependsOn": {},
|
||||
"reloadable": true
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"engineVersion": "^0.1",
|
||||
"plugins": [
|
||||
{ "id": "sandbox.echo" }
|
||||
],
|
||||
"pluginPaths": []
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Engine.Kernel\Engine.Kernel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,11 @@
|
||||
// The runtime host: resolves a project's plugins and runs the engine loop.
|
||||
// Its shape is sketched in docs/kernel-contract.md §7 —
|
||||
//
|
||||
// engine run --headless --frames 60 --scene ... --dump ... --assert ...
|
||||
// engine diag why-pinned <plugin-id>
|
||||
//
|
||||
// Neither the argument parsing nor the loop it drives exists yet; both
|
||||
// depend on PluginHost and Scheduler, which are M0's actual work. This is
|
||||
// a placeholder so the solution builds and runs end to end.
|
||||
|
||||
Console.WriteLine("Lingua Engine host — not yet implemented. See docs/kernel-contract.md, M0.");
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Engine.Kernel.Diagnostics;
|
||||
|
||||
public interface ILogger
|
||||
{
|
||||
void Info(string message);
|
||||
void Warn(string message);
|
||||
void Error(string message);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Engine.Kernel.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Open question (see the footer of docs/kernel-contract.md): whether this
|
||||
/// is needed at launch at all, or whether event-components in World cover
|
||||
/// its role. Left as a bare marker so IPluginContext compiles — no API
|
||||
/// decided yet.
|
||||
/// </summary>
|
||||
public interface IEventBus
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Engine.Kernel.Plugins;
|
||||
|
||||
/// <summary>
|
||||
/// A plugin holds no game state — none. State lives in World; the plugin
|
||||
/// is code that operates on it. See docs/kernel-contract.md §3.
|
||||
/// </summary>
|
||||
public interface IPlugin
|
||||
{
|
||||
/// <summary>Registration: services, systems, component types.</summary>
|
||||
void Configure(IPluginContext ctx);
|
||||
|
||||
/// <summary>
|
||||
/// Full undo of Configure. Whether this method is honest determines
|
||||
/// whether the ALC unloads at all — see §4.
|
||||
/// </summary>
|
||||
void Shutdown(IPluginContext ctx);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Engine.Kernel.Diagnostics;
|
||||
using Engine.Kernel.Events;
|
||||
using Engine.Kernel.Scheduling;
|
||||
using Engine.Kernel.Services;
|
||||
using Engine.Kernel.World;
|
||||
|
||||
namespace Engine.Kernel.Plugins;
|
||||
|
||||
public interface IPluginContext
|
||||
{
|
||||
IWorld World { get; } // data
|
||||
IServiceRegistry Services { get; } // Provide<T> / Require<T>
|
||||
ISchedule Schedule { get; } // systems and ordering
|
||||
IEventBus Events { get; }
|
||||
ILogger Log { get; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Engine.Kernel.Plugins;
|
||||
|
||||
/// <summary>
|
||||
/// Deserialized shape of a plugin's <c>plugin.json</c>. Read *before*
|
||||
/// anything loads, so the Plugin Host can build the dependency graph ahead
|
||||
/// of any ALC loading — see docs/kernel-contract.md §3.
|
||||
/// </summary>
|
||||
public sealed class PluginManifest
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string Version { get; init; }
|
||||
|
||||
/// <summary>Assembly loaded into the Default ALC. Never unloads. See §4.</summary>
|
||||
public required string Contracts { get; init; }
|
||||
|
||||
/// <summary>Assembly loaded into a collectible ALC. Reloadable. See §4.</summary>
|
||||
public required string Assembly { get; init; }
|
||||
|
||||
public Dictionary<string, string> DependsOn { get; init; } = new();
|
||||
|
||||
public bool Reloadable { get; init; } = true;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Engine.Kernel.Plugins;
|
||||
|
||||
/// <summary>
|
||||
/// Deserialized shape of a project's <c>project.json</c> — which plugins a
|
||||
/// specific game loads, at which versions, and where to find its own. See
|
||||
/// "Per-project configuration" in docs/kernel-contract.md §2.
|
||||
/// </summary>
|
||||
public sealed class ProjectManifest
|
||||
{
|
||||
public required string EngineVersion { get; init; }
|
||||
|
||||
public List<PluginReference> Plugins { get; init; } = new();
|
||||
|
||||
/// <summary>Search paths for plugins local to this project, not shipped
|
||||
/// with the engine.</summary>
|
||||
public List<string> PluginPaths { get; init; } = new();
|
||||
}
|
||||
|
||||
public sealed class PluginReference
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
|
||||
/// <summary>Null means "whatever the engine ships by default."</summary>
|
||||
public string? Version { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Engine.Kernel.Scheduling;
|
||||
|
||||
/// <summary>
|
||||
/// Frame stages and system ordering. See docs/kernel-contract.md §2.
|
||||
/// </summary>
|
||||
public interface ISchedule
|
||||
{
|
||||
ISystemBuilder Add(Stage stage, Delegate system);
|
||||
|
||||
/// <summary>Called from a plugin's Shutdown() — must remove everything
|
||||
/// Configure() added, or the ALC it lives in will never unload. See §4.</summary>
|
||||
void RemoveAllFrom(string pluginId);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Engine.Kernel.World;
|
||||
|
||||
namespace Engine.Kernel.Scheduling;
|
||||
|
||||
/// <summary>
|
||||
/// Fluent builder returned by <see cref="ISchedule.Add"/>. Declared
|
||||
/// Reads/Writes are what lets the scheduler run systems with disjoint
|
||||
/// access in parallel and, in debug builds, enforce that a system only
|
||||
/// touches what it declared. See docs/kernel-contract.md §2 and §7.
|
||||
/// </summary>
|
||||
public interface ISystemBuilder
|
||||
{
|
||||
ISystemBuilder After(string systemId);
|
||||
ISystemBuilder Reads<T>() where T : Component;
|
||||
ISystemBuilder Writes<T>() where T : Component;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Engine.Kernel.Scheduling;
|
||||
|
||||
/// <summary>
|
||||
/// Open question (see the footer of docs/kernel-contract.md): whether the
|
||||
/// set of stages is fixed or plugin-extensible. This is only enough to
|
||||
/// make the §3 example compile — not a decision.
|
||||
/// </summary>
|
||||
public enum Stage
|
||||
{
|
||||
Update,
|
||||
Render,
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Engine.Kernel.Services;
|
||||
|
||||
/// <summary>
|
||||
/// The control-plane channel between plugins: commands and resources, not
|
||||
/// per-entity data. See the "two channels" table in docs/kernel-contract.md
|
||||
/// §2 — and the rule right below it about what never belongs here.
|
||||
/// </summary>
|
||||
public interface IServiceRegistry
|
||||
{
|
||||
void Provide<T>(T instance) where T : class;
|
||||
T Require<T>() where T : class;
|
||||
void Revoke<T>() where T : class;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Engine.Kernel.World;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all component data. Plain fields, no methods, no
|
||||
/// lifecycle hooks — behavior lives in systems, never on the component
|
||||
/// itself. See docs/kernel-contract.md §1 and §7.
|
||||
/// </summary>
|
||||
public abstract class Component
|
||||
{
|
||||
// Intentionally empty. A subclass adds fields only.
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Engine.Kernel.World;
|
||||
|
||||
/// <summary>
|
||||
/// The entity type: identity, hierarchy, and a list of components. See
|
||||
/// docs/kernel-contract.md §2.
|
||||
/// </summary>
|
||||
public sealed class GameObject
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
|
||||
public Transform Transform;
|
||||
|
||||
public GameObject? Parent { get; internal set; }
|
||||
|
||||
// TODO(M0): backing storage for children, tags, and the component list
|
||||
// + type index that makes World.Query<T>() O(matches), not O(all).
|
||||
|
||||
public IReadOnlyList<GameObject> Children => throw new NotImplementedException();
|
||||
|
||||
public T? GetComponent<T>() where T : Component
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public T AddComponent<T>() where T : Component, new()
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public void RemoveComponent<T>() where T : Component
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Engine.Kernel.World;
|
||||
|
||||
/// <summary>
|
||||
/// Kernel-owned storage for every <see cref="GameObject"/> in a scene.
|
||||
/// Shape sketched from its usage throughout docs/kernel-contract.md — not a
|
||||
/// final API; M0's job is to actually implement this.
|
||||
/// </summary>
|
||||
public interface IWorld
|
||||
{
|
||||
GameObject CreateGameObject(string name);
|
||||
|
||||
void Destroy(GameObject go);
|
||||
|
||||
/// <summary>Type-indexed lookup — O(matches), not O(all). See §2.</summary>
|
||||
IEnumerable<GameObject> Query<T>() where T : Component;
|
||||
|
||||
// TODO(§5): Snapshot()/Restore() for Play mode — a deep clone of the
|
||||
// GameObject graph, taken on EnterPlay and discarded on ExitPlay.
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Engine.Kernel.World;
|
||||
|
||||
/// <summary>
|
||||
/// Embedded directly on <see cref="GameObject"/> rather than modeled as a
|
||||
/// <see cref="Component"/> subclass, because nearly every system touches it
|
||||
/// every frame — see the kernel scope table in docs/kernel-contract.md §2.
|
||||
/// </summary>
|
||||
public struct Transform
|
||||
{
|
||||
public Vector3 LocalPosition;
|
||||
public Quaternion LocalRotation;
|
||||
public Vector3 LocalScale;
|
||||
|
||||
// TODO(M0): derive from the GameObject hierarchy once World can walk it.
|
||||
public readonly Matrix4x4 WorldMatrix => throw new NotImplementedException();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Engine.ConformanceHarness;
|
||||
|
||||
/// <summary>
|
||||
/// The test docs/kernel-contract.md §4 calls the one thing that keeps this
|
||||
/// architecture from slowly degrading: load and unload a plugin 200 times,
|
||||
/// and after every cycle verify the ALC actually collected. Runs against
|
||||
/// Sandbox.Echo — see the ReferenceOutputAssembly="false" note in this
|
||||
/// project's .csproj for why that reference doesn't link its types in.
|
||||
/// </summary>
|
||||
public class AlcUnloadTests
|
||||
{
|
||||
// TODO(M0): once PluginHost exists —
|
||||
// for (int i = 0; i < 200; i++) {
|
||||
// var handle = host.Load("sandbox.echo");
|
||||
// var weakAlc = host.Unload(handle);
|
||||
// GC.Collect(); GC.WaitForPendingFinalizers();
|
||||
// Assert.False(weakAlc.IsAlive, $"ALC survived cycle {i}");
|
||||
// }
|
||||
// and assert working-set memory hasn't grown beyond noise.
|
||||
[Fact(Skip = "PluginHost has no implementation yet — see M0 in docs/kernel-contract.md §8.")]
|
||||
public void Plugin_Survives_200_Load_Unload_Cycles()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Engine.Kernel\Engine.Kernel.csproj" />
|
||||
|
||||
<!-- Build-order only: the harness must load this plugin the same way
|
||||
PluginHost does at runtime, into its own collectible ALC by file
|
||||
path. A normal ProjectReference would link its types straight into
|
||||
the harness's Default ALC, which defeats the point of a leak test. -->
|
||||
<ProjectReference Include="..\..\plugins\sandbox.echo\Sandbox.Echo\Sandbox.Echo.csproj"
|
||||
ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Engine.Kernel\Engine.Kernel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Engine.Kernel.Tests;
|
||||
|
||||
public class WorldTests
|
||||
{
|
||||
// TODO(M0): once IWorld has a concrete implementation —
|
||||
// - GameObject hierarchy (parent/children) behaves correctly
|
||||
// - Query<T>() returns exactly the GameObjects holding a T, and is
|
||||
// O(matches) — see the World row in docs/kernel-contract.md §2
|
||||
// - AddComponent/RemoveComponent keep the type index consistent
|
||||
[Fact(Skip = "World has no implementation yet — see M0 in docs/kernel-contract.md §8.")]
|
||||
public void Placeholder()
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user