diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index a2e266911..8c1e2eedc 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -9597,7 +9597,8 @@ static void RequireClientTargetBootAuthority(string root) var mainMenuSurfaceBuilderPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.state", "Runtime", "AetheriaRuntimeMainMenuSurfaceBuilder.cs"); var bootstrapPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveRuntimeBootstrap.cs"); var presenterPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveSurfacePresenter.cs"); - var unityEveRuntimeHooksPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityEveRuntimeHooks.cs"); + var eveRuntimeClientCachePath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveRuntimeUnityClientCache.cs"); + var eveRuntimeHookInstallerPath = Path.Combine(root, "Packages", "org.gamecult.aetheria.eve-runtime", "Runtime", "AetheriaEveRuntimeUnityHookInstaller.cs"); var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); var gameplayBootShellPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityGameplayBootShell.cs"); var mainMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "MainMenu.cs"); @@ -9615,7 +9616,8 @@ static void RequireClientTargetBootAuthority(string root) mainMenuSurfaceBuilderPath, bootstrapPath, presenterPath, - unityEveRuntimeHooksPath, + eveRuntimeClientCachePath, + eveRuntimeHookInstallerPath, actionGameManagerPath, gameplayBootShellPath, mainMenuPath @@ -9643,7 +9645,8 @@ static void RequireClientTargetBootAuthority(string root) var mainMenuSurfaceBuilder = File.ReadAllText(mainMenuSurfaceBuilderPath); var bootstrap = File.ReadAllText(bootstrapPath); var presenter = File.ReadAllText(presenterPath); - var unityEveRuntimeHooks = File.ReadAllText(unityEveRuntimeHooksPath); + var eveRuntimeClientCache = File.ReadAllText(eveRuntimeClientCachePath); + var eveRuntimeHookInstaller = File.ReadAllText(eveRuntimeHookInstallerPath); var actionGameManager = File.ReadAllText(actionGameManagerPath); var gameplayBootShell = File.ReadAllText(gameplayBootShellPath); var mainMenu = File.ReadAllText(mainMenuPath); @@ -9992,11 +9995,55 @@ static void RequireClientTargetBootAuthority(string root) string.Join(", ", missingPresenterSymbols)); } - if (!unityEveRuntimeHooks.Contains("AetheriaRuntimeStateBoot.Inspect(", StringComparison.Ordinal) || - !unityEveRuntimeHooks.Contains("AetheriaUnityRuntimePaths.GameDataDirectory", StringComparison.Ordinal)) + var requiredEveRuntimeClientCacheSymbols = new[] + { + "public static class AetheriaEveRuntimeUnityClientCache", + "new DirectoryInfo(Application.dataPath).Parent.CreateSubdirectory(\"GameData\")", + "AetheriaRuntimeStateBoot.Inspect(GameDataDirectory).StateFilePath", + "AetheriaClient", + ".OpenAsync(", + "startServer: false", + "pullOnOpen: true", + "CreateEveSurfaceCultMeshStateRefResolver()", + "public static void Dispose()" + }; + var missingEveRuntimeClientCacheSymbols = requiredEveRuntimeClientCacheSymbols + .Where(symbol => !eveRuntimeClientCache.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (missingEveRuntimeClientCacheSymbols.Length > 0) { throw new InvalidOperationException( - "Aetheria Eve runtime hooks no longer own Unity client-target boot resolution."); + "Aetheria Eve runtime package no longer owns its generic Unity client cache: " + + string.Join(", ", missingEveRuntimeClientCacheSymbols)); + } + + var requiredEveRuntimeHookInstallerSymbols = new[] + { + "public static class AetheriaEveRuntimeUnityHookInstaller", + "RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)", + "public static void Install()", + "AetheriaEveRuntimeUnityHooks.ResolveStateBoot", + "AetheriaRuntimeStateBoot.Inspect(", + "AetheriaEveRuntimeUnityClientCache.GameDataDirectory", + "AetheriaEveRuntimeUnityHooks.RuntimeState", + "AetheriaEveRuntimeUnityHooks.Control", + "AetheriaEveRuntimeUnityHooks.Ui", + "AetheriaEveRuntimeUnityHooks.StateRefResolver" + }; + var missingEveRuntimeHookInstallerSymbols = requiredEveRuntimeHookInstallerSymbols + .Where(symbol => !eveRuntimeHookInstaller.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + if (missingEveRuntimeHookInstallerSymbols.Length > 0) + { + throw new InvalidOperationException( + "Aetheria Eve runtime package no longer owns Unity client-target hook installation: " + + string.Join(", ", missingEveRuntimeHookInstallerSymbols)); + } + + if (File.Exists(Path.Combine(root, "Assets", "Scripts", "Gameplay", "AetheriaUnityEveRuntimeHooks.cs"))) + { + throw new InvalidOperationException( + "Aetheria Eve runtime hooks are still installed by the legacy Unity gameplay assembly; the provider package must own this boundary."); } if (bootstrap.Contains("StatePathEnvironmentVariable", StringComparison.Ordinal) || diff --git a/Assets/Scripts/Gameplay/AetheriaUnityEveRuntimeHooks.cs b/Assets/Scripts/Gameplay/AetheriaUnityEveRuntimeHooks.cs deleted file mode 100644 index 88f20d525..000000000 --- a/Assets/Scripts/Gameplay/AetheriaUnityEveRuntimeHooks.cs +++ /dev/null @@ -1,23 +0,0 @@ -using GameCult.Aetheria.EveRuntime; -using GameCult.Aetheria.State.Verse; -using UnityEngine; - -public static class AetheriaUnityEveRuntimeHooks -{ - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] - private static void Install() - { - AetheriaEveRuntimeUnityHooks.ResolveStateBoot = - overridePath => AetheriaRuntimeStateBoot.Inspect( - AetheriaUnityRuntimePaths.GameDataDirectory, - overridePath ?? ""); - AetheriaEveRuntimeUnityHooks.RuntimeState = - (stateBoot, runtimeId) => AetheriaUnityRuntimeClientProvider.RuntimeState(stateBoot, runtimeId); - AetheriaEveRuntimeUnityHooks.Control = - (stateBoot, runtimeId) => AetheriaUnityRuntimeClientProvider.Control(stateBoot, runtimeId); - AetheriaEveRuntimeUnityHooks.Ui = - (stateBoot, runtimeId) => AetheriaUnityRuntimeClientProvider.Ui(stateBoot, runtimeId); - AetheriaEveRuntimeUnityHooks.StateRefResolver = - (stateBoot, runtimeId) => AetheriaUnityRuntimeClientProvider.EveSurfaceCultMeshStateRefResolver(stateBoot, runtimeId); - } -} diff --git a/Assets/Scripts/Gameplay/AetheriaUnityEveRuntimeHooks.cs.meta b/Assets/Scripts/Gameplay/AetheriaUnityEveRuntimeHooks.cs.meta deleted file mode 100644 index edad1dbcc..000000000 --- a/Assets/Scripts/Gameplay/AetheriaUnityEveRuntimeHooks.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 72069f7b0885490fb690dc771e422aac -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs index 335d7a67d..510502041 100644 --- a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs +++ b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs @@ -2777,29 +2777,6 @@ public class DaemonRuntimeDocumentTests 0.02); PublishLatestFrameThroughVerseClient(statePath, frame); - using var client = AetheriaClient - .OpenAsync(statePath, "unity-scene-receipt-test", startServer: false, pullOnOpen: true) - .GetAwaiter() - .GetResult(); - var stateBoot = new AetheriaRuntimeStateBootReport( - "", - "", - "state-path-override", - "Aetheria daemon test", - "aetheria.local", - "unity-scene-receipt-test", - "", - statePath, - "", - stateFileExists: true, - supportsLocalStateFileRead: true, - "", - Array.Empty(), - Array.Empty(), - "", - "", - "", - ""); var previousResolveStateBoot = AetheriaEveRuntimeUnityHooks.ResolveStateBoot; var previousRuntimeState = AetheriaEveRuntimeUnityHooks.RuntimeState; var previousControl = AetheriaEveRuntimeUnityHooks.Control; @@ -2808,12 +2785,7 @@ public class DaemonRuntimeDocumentTests try { - AetheriaEveRuntimeUnityHooks.ResolveStateBoot = _ => stateBoot; - AetheriaEveRuntimeUnityHooks.RuntimeState = (_, __) => client.State; - AetheriaEveRuntimeUnityHooks.Control = (_, __) => client.Control; - AetheriaEveRuntimeUnityHooks.Ui = (_, __) => client.Ui; - AetheriaEveRuntimeUnityHooks.StateRefResolver = (_, __) => - client.State.CreateEveSurfaceCultMeshStateRefResolver(); + AetheriaEveRuntimeUnityHookInstaller.Install(); using var bridge = new AetheriaEveUnitySceneProviderBridge( statePath, @@ -2850,6 +2822,7 @@ public class DaemonRuntimeDocumentTests } finally { + AetheriaEveRuntimeUnityClientCache.Dispose(); AetheriaEveRuntimeUnityHooks.ResolveStateBoot = previousResolveStateBoot; AetheriaEveRuntimeUnityHooks.RuntimeState = previousRuntimeState; AetheriaEveRuntimeUnityHooks.Control = previousControl; diff --git a/GameCult.Aetheria.EveRuntime.csproj b/GameCult.Aetheria.EveRuntime.csproj index 25bcfa01c..e0eac137c 100644 --- a/GameCult.Aetheria.EveRuntime.csproj +++ b/GameCult.Aetheria.EveRuntime.csproj @@ -49,10 +49,12 @@ + + C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Data\Managed\UnityEngine\UnityEngine.dll diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityClientCache.cs b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityClientCache.cs new file mode 100644 index 000000000..2272ef87d --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityClientCache.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.IO; +using GameCult.Aetheria.State.Verse; +using GameCult.Mesh; +using UnityEngine; + +#nullable enable + +namespace GameCult.Aetheria.EveRuntime +{ + public static class AetheriaEveRuntimeUnityClientCache + { + private static readonly Dictionary RuntimeClients = + new Dictionary(StringComparer.Ordinal); + private static DirectoryInfo? s_gameDataDirectory; + private static string? s_runtimeStateFilePath; + + public static DirectoryInfo GameDataDirectory => + s_gameDataDirectory ??= new DirectoryInfo(Application.dataPath).Parent.CreateSubdirectory("GameData"); + + public static string RuntimeStateFilePath => + s_runtimeStateFilePath ??= AetheriaRuntimeStateBoot.Inspect(GameDataDirectory).StateFilePath; + + public static AetheriaClientState RuntimeState( + AetheriaRuntimeStateBootReport stateBoot, + string runtimeId = "") + { + return RuntimeClient(stateBoot, runtimeId).State; + } + + public static AetheriaControl Control( + AetheriaRuntimeStateBootReport stateBoot, + string runtimeId = "") + { + return RuntimeClient(stateBoot, runtimeId).Control; + } + + public static AetheriaUi Ui( + AetheriaRuntimeStateBootReport stateBoot, + string runtimeId = "") + { + return RuntimeClient(stateBoot, runtimeId).Ui; + } + + public static CultMeshStateRefResolver EveSurfaceCultMeshStateRefResolver( + AetheriaRuntimeStateBootReport stateBoot, + string runtimeId = "") + { + return RuntimeState(stateBoot, runtimeId).CreateEveSurfaceCultMeshStateRefResolver(); + } + + public static void ClearRuntimeStateFilePathCache() + { + s_runtimeStateFilePath = null; + } + + public static void Dispose() + { + foreach (var client in RuntimeClients.Values) + client.Dispose(); + + RuntimeClients.Clear(); + s_runtimeStateFilePath = null; + } + + private static AetheriaClient RuntimeClient( + AetheriaRuntimeStateBootReport stateBoot, + string runtimeId = "") + { + return ResolveClient( + stateBoot.StateFilePath, + string.IsNullOrWhiteSpace(runtimeId) ? stateBoot.RuntimeId : runtimeId); + } + + private static AetheriaClient ResolveClient(string stateFilePath, string runtimeId) + { + var effectiveStateFilePath = stateFilePath ?? ""; + var effectiveRuntimeId = string.IsNullOrWhiteSpace(runtimeId) + ? AetheriaRuntimeStateBoundary.DefaultClientRuntimeId + : runtimeId; + var cacheKey = effectiveStateFilePath + "\n" + effectiveRuntimeId; + if (RuntimeClients.TryGetValue(cacheKey, out var runtimeClient)) + return runtimeClient; + + runtimeClient = AetheriaClient + .OpenAsync( + effectiveStateFilePath, + effectiveRuntimeId, + "local", + startServer: false, + pullOnOpen: true) + .GetAwaiter() + .GetResult(); + RuntimeClients[cacheKey] = runtimeClient; + return runtimeClient; + } + } +} diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityClientCache.cs.meta b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityClientCache.cs.meta new file mode 100644 index 000000000..fa28ca608 --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityClientCache.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2a2c3bf5e2954d1f8aeab60ee92f7f0a diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityHookInstaller.cs b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityHookInstaller.cs new file mode 100644 index 000000000..dd2eb416f --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityHookInstaller.cs @@ -0,0 +1,28 @@ +using GameCult.Aetheria.State.Verse; +using UnityEngine; + +#nullable enable + +namespace GameCult.Aetheria.EveRuntime +{ + public static class AetheriaEveRuntimeUnityHookInstaller + { + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + public static void Install() + { + AetheriaEveRuntimeUnityHooks.ResolveStateBoot = + overridePath => AetheriaRuntimeStateBoot.Inspect( + AetheriaEveRuntimeUnityClientCache.GameDataDirectory, + overridePath ?? ""); + AetheriaEveRuntimeUnityHooks.RuntimeState = + (stateBoot, runtimeId) => AetheriaEveRuntimeUnityClientCache.RuntimeState(stateBoot, runtimeId); + AetheriaEveRuntimeUnityHooks.Control = + (stateBoot, runtimeId) => AetheriaEveRuntimeUnityClientCache.Control(stateBoot, runtimeId); + AetheriaEveRuntimeUnityHooks.Ui = + (stateBoot, runtimeId) => AetheriaEveRuntimeUnityClientCache.Ui(stateBoot, runtimeId); + AetheriaEveRuntimeUnityHooks.StateRefResolver = + (stateBoot, runtimeId) => + AetheriaEveRuntimeUnityClientCache.EveSurfaceCultMeshStateRefResolver(stateBoot, runtimeId); + } + } +} diff --git a/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityHookInstaller.cs.meta b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityHookInstaller.cs.meta new file mode 100644 index 000000000..a5cda3433 --- /dev/null +++ b/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveRuntimeUnityHookInstaller.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d60d5ddc6b3240438128f85f90464b42