refactor(runtime): own canonical entity and object lifetime

Introduce one presentation-free RuntimeEntityObjectLifetime for the exact entity directory and ClientObjectTable. Make GameWindow, graphical projections, retained UI, interaction, session routing, create/delete integration, and reset borrow that owner while preserving synchronous retail ordering, dormant retention, and retry semantics.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-26 05:54:46 +02:00
parent d9bf4c4960
commit 5ef8b5371d
40 changed files with 712 additions and 170 deletions

View file

@ -13,8 +13,19 @@ namespace AcDream.App.Tests.World;
public sealed class RuntimeEntityOwnershipTests
{
[Fact]
public void LiveEntityRuntime_ComposesCanonicalRuntimeDirectory()
public void LiveEntityRuntime_BorrowsCanonicalRuntimeDirectory()
{
var lifetime = new RuntimeEntityObjectLifetime();
var runtime = new LiveEntityRuntime(
new GpuWorldState(),
new DelegateLiveEntityResourceLifecycle(
static _ => { },
static _ => { }),
lifetime);
FieldInfo owner = typeof(LiveEntityRuntime).GetField(
"_entityObjects",
BindingFlags.Instance | BindingFlags.NonPublic)
?? throw new InvalidOperationException("Missing Runtime lifetime root.");
FieldInfo directory = typeof(LiveEntityRuntime).GetField(
"_directory",
BindingFlags.Instance | BindingFlags.NonPublic)
@ -25,9 +36,62 @@ public sealed class RuntimeEntityOwnershipTests
?? throw new InvalidOperationException("Missing App projection sidecar store.");
Assert.Equal(typeof(RuntimeEntityDirectory), directory.FieldType);
Assert.Same(lifetime, owner.GetValue(runtime));
Assert.Same(lifetime.Entities, directory.GetValue(runtime));
Assert.Equal(typeof(LiveEntityProjectionStore), sidecars.FieldType);
}
[Fact]
public void ProductionComposition_HasOneRuntimeEntityObjectConstructionRoot()
{
string root = FindRepositoryRoot();
string appRoot = Path.Combine(root, "src", "AcDream.App");
string runtimeRoot = Path.Combine(root, "src", "AcDream.Runtime");
string[] appSources = Directory.GetFiles(
appRoot,
"*.cs",
SearchOption.AllDirectories);
string[] runtimeSources = Directory.GetFiles(
runtimeRoot,
"*.cs",
SearchOption.AllDirectories);
string[] appObjectAllocators = appSources
.Where(path => !path.Contains(
$"{Path.DirectorySeparatorChar}Studio{Path.DirectorySeparatorChar}",
StringComparison.OrdinalIgnoreCase))
.Where(path => File.ReadAllText(path).Contains(
"new ClientObjectTable",
StringComparison.Ordinal))
.ToArray();
string[] appDirectoryAllocators = appSources
.Where(path => File.ReadAllText(path).Contains(
"new RuntimeEntityDirectory",
StringComparison.Ordinal))
.ToArray();
string[] runtimeObjectAllocators = runtimeSources
.Where(path => File.ReadAllText(path).Contains(
"new ClientObjectTable",
StringComparison.Ordinal))
.Select(path => Path.GetFileName(path)!)
.ToArray();
string[] runtimeDirectoryAllocators = runtimeSources
.Where(path => File.ReadAllText(path).Contains(
"new RuntimeEntityDirectory",
StringComparison.Ordinal))
.Select(path => Path.GetFileName(path)!)
.ToArray();
Assert.Empty(appObjectAllocators);
Assert.Empty(appDirectoryAllocators);
Assert.Equal(
["RuntimeEntityObjectLifetime.cs"],
runtimeObjectAllocators);
Assert.Equal(
["RuntimeEntityObjectLifetime.cs"],
runtimeDirectoryAllocators);
}
[Fact]
public void AppProjectionOwners_UseExactRuntimeKeysAndNoGuidDictionary()
{
@ -212,4 +276,18 @@ public sealed class RuntimeEntityOwnershipTests
field.FieldType.IsGenericType
&& field.FieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>)
&& field.FieldType.GetGenericArguments()[0] == keyType;
private static string FindRepositoryRoot()
{
DirectoryInfo? directory = new(AppContext.BaseDirectory);
while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
return directory.FullName;
directory = directory.Parent;
}
throw new DirectoryNotFoundException(
$"Could not find AcDream.slnx above {AppContext.BaseDirectory}.");
}
}