Move main menu surface access to CultMesh

This commit is contained in:
Meta Vens 2026-06-30 23:15:57 +01:00
commit eb0760c81f
5 changed files with 265 additions and 150 deletions

View file

@ -3,16 +3,12 @@ using System.Collections.Generic;
using GameCult.Aetheria.EveRuntime;
using GameCult.Aetheria.State.Verse;
using GameCult.Eve.Surface;
using GameCult.Mesh;
using TMPro;
using UniRx;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityEngine.UIElements;
using UiButton = UnityEngine.UIElements.Button;
using TextCoreFontAsset = UnityEngine.TextCore.Text.FontAsset;
using GlyphRenderMode = UnityEngine.TextCore.LowLevel.GlyphRenderMode;
public class MainMenu : MonoBehaviour
{
@ -35,8 +31,6 @@ public class MainMenu : MonoBehaviour
private bool _fading;
private float _fadeLerp;
private readonly List<MenuHoverButton> _hoverButtons = new List<MenuHoverButton>();
private static TextCoreFontAsset _titleFont;
private static TextCoreFontAsset _labelFont;
private readonly AetheriaEveUnitySurfaceChrome _menuSurfaceChrome = new AetheriaEveUnitySurfaceChrome
{
UseShell = false,
@ -63,19 +57,8 @@ public class MainMenu : MonoBehaviour
private void ShowMain(bool animateFromRight = true)
{
var stateBoot = CurrentStateBoot();
var sectorMap = ResolveSectorMap(stateBoot);
var verseHost = ResolveVerseHostSettings(stateBoot);
var playerSettings = ResolvePlayerSettings(stateBoot);
RenderMenuSurface(
AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot(
stateBoot,
sectorMap,
verseHost,
playerSettings,
CanOpenRuntimeInputScreen(),
InGame,
DateTime.UtcNow.ToString("O")),
ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.RootSurfaceId),
HandleMainSurfaceCommand,
animateFromRight);
}
@ -177,44 +160,6 @@ public class MainMenu : MonoBehaviour
}
}
private AetheriaRuntimePlayerSettingsDocument ResolvePlayerSettings(AetheriaRuntimeStateBootReport stateBoot)
{
if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists)
return null;
try
{
return AetheriaUnityRuntimeClientProvider
.RuntimeState(stateBoot, "unity-main-menu")
.PlayerSettings
.Latest();
}
catch (Exception ex)
{
Debug.LogError($"Failed to bind typed Aetheria player settings for the main menu: {ex}");
return null;
}
}
private AetheriaRuntimeVerseHostSettingsDocument ResolveVerseHostSettings(AetheriaRuntimeStateBootReport stateBoot)
{
if (!stateBoot.SupportsLocalStateFileRead || !stateBoot.StateFileExists)
return null;
try
{
return AetheriaUnityRuntimeClientProvider
.RuntimeState(stateBoot, "unity-main-menu")
.VerseHostSettings
.Latest();
}
catch (Exception ex)
{
Debug.LogError($"Failed to bind typed Aetheria Verse host settings for the main menu: {ex}");
return null;
}
}
private static bool IsMissingDaemonFrame(KeyNotFoundException ex)
{
return ex?.Message?.Contains("daemon:aetheria.frame.latest.v1", StringComparison.Ordinal) == true;
@ -231,21 +176,15 @@ public class MainMenu : MonoBehaviour
private void ShowSettings(bool animateFromRight = true)
{
RenderMenuSurface(
AetheriaRuntimeMainMenuSurfaceBuilder.BuildSettings(DateTime.UtcNow.ToString("O")),
ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.SettingsSurfaceId),
HandleSettingsSurfaceCommand,
animateFromRight);
}
private void ShowInputSettings(bool animateFromRight = true)
{
var stateBoot = CurrentStateBoot();
RenderMenuSurface(
AetheriaRuntimeMainMenuSurfaceBuilder.BuildInputSettings(
stateBoot,
ResolvePlayerSettings(stateBoot),
CanOpenRuntimeInputScreen(),
InGame,
DateTime.UtcNow.ToString("O")),
ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId),
HandleInputSettingsSurfaceCommand,
animateFromRight);
}
@ -253,26 +192,28 @@ public class MainMenu : MonoBehaviour
private void ShowPlayerSettingsSurface(bool animateFromRight = true)
{
RenderMenuSurface(
AetheriaRuntimeMainMenuSurfaceBuilder.BuildPlayerSettingsShell(
ResolvePlayerSettings(CurrentStateBoot()),
DateTime.UtcNow.ToString("O")),
ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.PlayerSettingsShellSurfaceId),
HandlePlayerSettingsSurfaceCommand,
animateFromRight);
}
private void ShowVerseSettingsSurface(bool animateFromRight = true)
{
var stateBoot = CurrentStateBoot();
RenderMenuSurface(
AetheriaRuntimeMainMenuSurfaceBuilder.BuildVerseSettingsShell(
AetheriaRuntimeClientTargetSurfaceBuilder.Build(
stateBoot,
ResolveVerseHostSettings(stateBoot),
DateTime.UtcNow.ToString("O"))),
ResolveMainMenuSurface(AetheriaRuntimeMainMenuCommands.VerseSettingsShellSurfaceId),
HandleVerseSettingsSurfaceCommand,
animateFromRight);
}
private AetheriaRuntimeSurfaceDocument ResolveMainMenuSurface(string surfaceId)
{
var stateBoot = CurrentStateBoot();
return AetheriaUnityRuntimeClientProvider
.RuntimeState(stateBoot, "unity-main-menu")
.MainMenuSurface(surfaceId, CanOpenRuntimeInputScreen(), InGame)
.Latest();
}
private void RenderMenuSurface(
AetheriaRuntimeSurfaceDocument document,
Action<EveSurfaceCommandRequest> commandHandler,
@ -375,7 +316,6 @@ public class MainMenu : MonoBehaviour
return;
StyleMenuTree(root, rootMainMenu);
ApplyDefaultTextFont(root);
var surface = rootMainMenu
? FindElement(root, $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.root")
@ -391,7 +331,6 @@ public class MainMenu : MonoBehaviour
var title = FindElement(root, $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.title") as Label;
if (title != null)
{
title.style.unityFontDefinition = new StyleFontDefinition(TitleFont);
title.style.fontSize = 86f;
title.style.color = new Color(0.82f, 0.95f, 1f, 0.96f);
title.style.unityFontStyleAndWeight = FontStyle.Normal;
@ -404,7 +343,6 @@ public class MainMenu : MonoBehaviour
var subtitle = FindElement(root, $"{AetheriaRuntimeMainMenuCommands.RootSurfaceId}.subtitle") as Label;
if (subtitle != null)
{
subtitle.style.unityFontDefinition = new StyleFontDefinition(TitleFont);
subtitle.style.fontSize = 39f;
subtitle.style.color = new Color(0.82f, 0.95f, 1f, 0.92f);
subtitle.style.unityFontStyleAndWeight = FontStyle.Normal;
@ -417,12 +355,6 @@ public class MainMenu : MonoBehaviour
RegisterHoverButtons(root);
}
private static void ApplyDefaultTextFont(VisualElement root)
{
root.Query<TextElement>().ForEach(element =>
element.style.unityFontDefinition = new StyleFontDefinition(LabelFont));
}
private void StyleMenuTree(VisualElement element, bool rootMainMenu)
{
if (element == null)
@ -508,7 +440,6 @@ public class MainMenu : MonoBehaviour
private static void StyleMenuLabel(Label label, bool rootMainMenu)
{
label.style.color = new Color(0.86f, 0.98f, 1f, 0.94f);
label.style.unityFontDefinition = new StyleFontDefinition(LabelFont);
label.style.unityFontStyleAndWeight = FontStyle.Normal;
label.style.unityTextAlign = TextAnchor.MiddleLeft;
label.style.whiteSpace = WhiteSpace.Normal;
@ -556,14 +487,12 @@ public class MainMenu : MonoBehaviour
field.style.marginTop = 0f;
field.style.marginBottom = rootMainMenu ? 4f : 8f;
field.style.color = new Color(0.86f, 0.98f, 1f, 0.96f);
field.style.unityFontDefinition = new StyleFontDefinition(LabelFont);
field.style.fontSize = rootMainMenu ? 18f : 15f;
field.pickingMode = PickingMode.Position;
field.labelElement.style.minWidth = rootMainMenu ? 100f : 190f;
field.labelElement.style.width = rootMainMenu ? 100f : 190f;
field.labelElement.style.color = new Color(0.78f, 0.92f, 0.98f, 0.84f);
field.labelElement.style.unityFontDefinition = new StyleFontDefinition(LabelFont);
}
private static void StyleMenuButton(UiButton button, bool rootMainMenu)
@ -574,7 +503,6 @@ public class MainMenu : MonoBehaviour
button.style.borderTopWidth = 0f;
button.style.borderBottomWidth = 0f;
button.style.color = new Color(0.86f, 0.98f, 1f, 0.96f);
button.style.unityFontDefinition = new StyleFontDefinition(LabelFont);
button.style.fontSize = rootMainMenu ? 24f : 16f;
button.style.unityFontStyleAndWeight = FontStyle.Normal;
button.style.unityTextAlign = TextAnchor.MiddleLeft;
@ -862,60 +790,6 @@ public class MainMenu : MonoBehaviour
}
}
private static TextCoreFontAsset TitleFont =>
_titleFont ??= ResolveFontAsset(
"Assets/Fonts/Ubuntu/Montserrat-Thin SDF.asset",
"Montserrat Thin",
"Montserrat");
private static TextCoreFontAsset LabelFont =>
_labelFont ??= ResolveFontAsset(
"Assets/Fonts/Ubuntu/Ubuntu-R SDF.asset",
"Ubuntu",
"Ubuntu Regular");
private static TextCoreFontAsset ResolveFontAsset(string assetPath, params string[] osFontNames)
{
#if UNITY_EDITOR
foreach (var asset in UnityEditor.AssetDatabase.LoadAllAssetsAtPath(assetPath))
{
if (asset is TextCoreFontAsset editorFontAsset)
return editorFontAsset;
}
#endif
foreach (var osFontName in osFontNames)
{
try
{
var fontAsset = TextCoreFontAsset.CreateFontAsset(
osFontName,
"Regular",
90,
9,
GlyphRenderMode.SDFAA);
if (fontAsset != null)
return fontAsset;
}
catch
{
// Try the next configured face name before falling back.
}
}
Debug.LogWarning($"Failed to resolve UI font asset '{assetPath}'. Falling back to Arial.");
var fallback = TextCoreFontAsset.CreateFontAsset(
"Arial",
"Regular",
90,
9,
GlyphRenderMode.SDFAA);
if (fallback != null)
return fallback;
throw new InvalidOperationException($"Failed to create fallback UI font asset for '{assetPath}'.");
}
private sealed class MenuHoverButton
{
public MenuHoverButton(UiButton button)

View file

@ -6,7 +6,9 @@ using GameCult.Eve.Surface;
using GameCult.Eve.UnityUIToolkit;
using GameCult.Mesh;
using UnityEngine;
using UnityEngine.TextCore.LowLevel;
using UnityEngine.UIElements;
using TextCoreFontAsset = UnityEngine.TextCore.Text.FontAsset;
namespace GameCult.Aetheria.EveRuntime
{
@ -42,6 +44,7 @@ namespace GameCult.Aetheria.EveRuntime
public static class AetheriaEveUnitySurfaceHost
{
private static PanelSettings _runtimePanelSettings;
private static readonly Dictionary<string, TextCoreFontAsset> FontCache = new Dictionary<string, TextCoreFontAsset>(StringComparer.Ordinal);
public static UIDocument Render(
Transform owner,
@ -73,15 +76,20 @@ namespace GameCult.Aetheria.EveRuntime
: null);
surface = AetheriaRuntimeSurfaceDocuments.ResolveStateRefs(surface, effectiveStateRefResolver);
var lowerer = new EveUiToolkitSurfaceLowerer(new EveUiToolkitSurfaceOptions(embeddedDocumentResolver));
VisualElement lowered;
if (chrome.UseShell)
{
var shell = CreateShell(chrome);
root.Add(shell);
shell.Add(lowerer.Lower(surface, commandHandler));
lowered = lowerer.Lower(surface, commandHandler);
ApplyTheme(lowered, surface);
shell.Add(lowered);
}
else
{
root.Add(lowerer.Lower(surface, commandHandler));
lowered = lowerer.Lower(surface, commandHandler);
ApplyTheme(lowered, surface);
root.Add(lowered);
}
Debug.Log(
@ -230,6 +238,75 @@ namespace GameCult.Aetheria.EveRuntime
return shell;
}
private static void ApplyTheme(VisualElement root, EveSurfaceDocument surface)
{
if (root == null || surface?.Surface?.Styles == null)
return;
var tokens = surface.Surface.Styles
.Where(token => !string.IsNullOrWhiteSpace(token?.Name))
.GroupBy(token => token.Name, StringComparer.Ordinal)
.ToDictionary(group => group.Key, group => group.Last().Value ?? "", StringComparer.Ordinal);
var bodyFont = ResolveFont(tokens, "body");
var titleFont = ResolveFont(tokens, "title") ?? bodyFont;
if (bodyFont != null)
{
root.Query<TextElement>().ForEach(element =>
element.style.unityFontDefinition = new StyleFontDefinition(bodyFont));
}
if (titleFont != null)
{
root.Query<TextElement>(className: "eve-kind-text-title").ForEach(element =>
element.style.unityFontDefinition = new StyleFontDefinition(titleFont));
}
}
private static TextCoreFontAsset ResolveFont(IReadOnlyDictionary<string, string> tokens, string role)
{
if (tokens == null || string.IsNullOrWhiteSpace(role))
return null;
if (!tokens.TryGetValue($"font.{role}.family", out var family) || string.IsNullOrWhiteSpace(family))
return null;
tokens.TryGetValue($"font.{role}.style", out var style);
style = string.IsNullOrWhiteSpace(style) ? "Regular" : style;
var key = $"{family}\n{style}";
if (FontCache.TryGetValue(key, out var cached))
return cached;
var font = TryCreateFontAsset(family, style)
?? TryCreateFontAsset(family, "Regular")
?? TryCreateFontAsset($"{family} {style}", "Regular");
if (font == null)
{
Debug.LogWarning($"Aetheria Eve UI could not resolve runtime font '{family}' style '{style}'.");
return null;
}
FontCache[key] = font;
return font;
}
private static TextCoreFontAsset TryCreateFontAsset(string family, string style)
{
try
{
return TextCoreFontAsset.CreateFontAsset(
family,
string.IsNullOrWhiteSpace(style) ? "Regular" : style,
90,
9,
GlyphRenderMode.SDFAA);
}
catch
{
return null;
}
}
private static bool ContainsStateRefs(EveSurfaceDocument surface)
{
return surface?.Surface?.Root != null && ContainsStateRefs(surface.Surface.Root);

View file

@ -25,6 +25,7 @@ namespace GameCult.Aetheria.State.Verse
private readonly Func<int, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _zoneDetailsSurface;
private readonly Func<AetheriaRuntimeInventoryPanelSurfaceRequest, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _inventoryPanelSurface;
private readonly Func<AetheriaRuntimeInventoryDropdownSurfaceRequest, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _inventoryDropdownSurface;
private readonly Func<string, bool, bool, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> _mainMenuSurface;
private readonly Func<int, CultMeshDocumentHandle<AetheriaRuntimeSelectedObjectDocument>> _selectedObject;
private readonly Func<int, CultMeshDocumentHandle<AetheriaRuntimeInventoryDocument>> _inventory;
private readonly Func<string, CultMeshDocumentHandle<AetheriaRuntimeStarbridgePlayerSeatDocument>> _starbridgePlayerSeat;
@ -63,6 +64,7 @@ namespace GameCult.Aetheria.State.Verse
Func<int, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> zoneDetailsSurface,
Func<AetheriaRuntimeInventoryPanelSurfaceRequest, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> inventoryPanelSurface,
Func<AetheriaRuntimeInventoryDropdownSurfaceRequest, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> inventoryDropdownSurface,
Func<string, bool, bool, CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument>> mainMenuSurface,
Func<int, CultMeshDocumentHandle<AetheriaRuntimeSelectedObjectDocument>> selectedObject,
Func<int, CultMeshDocumentHandle<AetheriaRuntimeInventoryDocument>> inventory,
CultMeshDocumentHandle<AetheriaRuntimeStarbridgeScenarioDocument> starbridgeScenario,
@ -99,6 +101,7 @@ namespace GameCult.Aetheria.State.Verse
_zoneDetailsSurface = zoneDetailsSurface ?? throw new ArgumentNullException(nameof(zoneDetailsSurface));
_inventoryPanelSurface = inventoryPanelSurface ?? throw new ArgumentNullException(nameof(inventoryPanelSurface));
_inventoryDropdownSurface = inventoryDropdownSurface ?? throw new ArgumentNullException(nameof(inventoryDropdownSurface));
_mainMenuSurface = mainMenuSurface ?? throw new ArgumentNullException(nameof(mainMenuSurface));
_selectedObject = selectedObject ?? throw new ArgumentNullException(nameof(selectedObject));
_inventory = inventory ?? throw new ArgumentNullException(nameof(inventory));
StarbridgeScenario = starbridgeScenario ?? throw new ArgumentNullException(nameof(starbridgeScenario));
@ -255,6 +258,17 @@ namespace GameCult.Aetheria.State.Verse
return _inventoryDropdownSurface(request ?? new AetheriaRuntimeInventoryDropdownSurfaceRequest());
}
public CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument> MainMenuSurface(
string surfaceId,
bool canOpenRuntimeInputScreen,
bool inGame)
{
if (string.IsNullOrWhiteSpace(surfaceId))
throw new ArgumentException("Main menu surface id must be non-empty.", nameof(surfaceId));
return _mainMenuSurface(surfaceId, canOpenRuntimeInputScreen, inGame);
}
public CultMeshDocumentHandle<AetheriaRuntimeSelectedObjectDocument> SelectedObject(int zoneIndex)
{
if (zoneIndex < 0)

View file

@ -27,6 +27,9 @@ namespace GameCult.Aetheria.State.Verse
public static class AetheriaRuntimeMainMenuSurfaceBuilder
{
public const string ProviderId = "aetheria";
public const string ProviderKind = "game.menu";
public static AetheriaRuntimeSurfaceDocument BuildRoot(
AetheriaRuntimeStateBootReport stateBoot,
AetheriaRuntimePlayerSettingsDocument playerSettings,
@ -327,18 +330,34 @@ namespace GameCult.Aetheria.State.Verse
params AetheriaRuntimeSurfaceComponent[] children)
{
return new AetheriaRuntimeSurfaceDocument(
providerId: "aetheria",
providerKind: "game.menu",
providerId: ProviderId,
providerKind: ProviderKind,
title: title,
version: version,
updatedAtUtc: updatedAtUtc,
surface: new AetheriaRuntimeSurfaceTree(
surfaceId,
Node($"{surfaceId}.root", "surface", Array.Empty<(string Key, string Value)>(), children),
Array.Empty<AetheriaRuntimeSurfaceStyleToken>()),
MainMenuStyleTokens()),
commands: commands);
}
private static IReadOnlyList<AetheriaRuntimeSurfaceStyleToken> MainMenuStyleTokens()
{
return new[]
{
new AetheriaRuntimeSurfaceStyleToken("font.title.family", "Montserrat"),
new AetheriaRuntimeSurfaceStyleToken("font.title.style", "Thin"),
new AetheriaRuntimeSurfaceStyleToken("font.title.weight", "100"),
new AetheriaRuntimeSurfaceStyleToken("font.body.family", "Ubuntu"),
new AetheriaRuntimeSurfaceStyleToken("font.body.style", "Regular"),
new AetheriaRuntimeSurfaceStyleToken("font.body.weight", "400"),
new AetheriaRuntimeSurfaceStyleToken(
"font.web.google",
"https://fonts.googleapis.com/css2?family=Montserrat:wght@100&family=Ubuntu:wght@400&display=swap")
};
}
private static AetheriaRuntimeSurfaceCommandTemplate Command(string command, string label)
{
return new AetheriaRuntimeSurfaceCommandTemplate(command, label, AetheriaRuntimeSurfaceCommandTemplate.CultMeshTransport);

View file

@ -391,6 +391,11 @@ namespace GameCult.Aetheria.State.Verse
() => Task.FromResult(BootstrapPlayerSettingsDocument()),
AetheriaRuntimePlayerSettingsDocument.SchemaId,
CatalogBootstrapSource("catalog:aetheria.player_settings"));
var verseHostSettingsDocument = BootstrapCatalogDocument(
"aetheria.settings.verse_host",
() => Task.FromResult(BootstrapVerseHostSettingsDocument()),
AetheriaRuntimeVerseHostSettingsDocument.SchemaId,
CatalogBootstrapSource("catalog:aetheria.verse_host_settings"));
var starbridgeScenarioDocument = Document<AetheriaRuntimeStarbridgeScenarioDocument>(
AetheriaRuntimeVerseRecordKeys.StarbridgeScenarioLatest);
var starbridgeSessionDocument = Document<AetheriaRuntimeStarbridgeSessionDocument>(
@ -427,11 +432,7 @@ namespace GameCult.Aetheria.State.Verse
catalogDocument,
loadoutTemplatesDocument,
playerSettingsDocument,
BootstrapCatalogDocument(
"aetheria.settings.verse_host",
() => Task.FromResult(BootstrapVerseHostSettingsDocument()),
AetheriaRuntimeVerseHostSettingsDocument.SchemaId,
CatalogBootstrapSource("catalog:aetheria.verse_host_settings")),
verseHostSettingsDocument,
ManagedFrameDocument(
"aetheria.current.zone",
frame => Task.FromResult(AetheriaRuntimeRtsDocuments.CurrentZone(frame)),
@ -501,6 +502,10 @@ namespace GameCult.Aetheria.State.Verse
AetheriaRuntimeInventoryDropdownSurfaceBuilder.SurfaceId,
CultMesh.ProjectionSource("catalog:aetheria.runtime"),
CultMesh.ProjectionSource("loadout-templates:aetheria.runtime")),
(surfaceId, canOpenRuntimeInputScreen, inGame) => ManagedMainMenuSurfaceDocument(
surfaceId,
canOpenRuntimeInputScreen,
inGame),
entityIndex => ManagedFrameDocument(
IndexedDocumentId("aetheria.object.selected", entityIndex),
frame => Task.FromResult(AetheriaRuntimeRtsDocuments.SelectedObject(frame, entityIndex)),
@ -608,6 +613,39 @@ namespace GameCult.Aetheria.State.Verse
routeHint: new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "Aetheria managed catalog state"));
}
CultMeshDocumentHandle<AetheriaRuntimeSurfaceDocument> ManagedMainMenuSurfaceDocument(
string surfaceId,
bool canOpenRuntimeInputScreen,
bool inGame)
{
surfaceId = string.IsNullOrWhiteSpace(surfaceId)
? AetheriaRuntimeMainMenuCommands.RootSurfaceId
: surfaceId;
var documentId = string.Join(
".",
"aetheria.main_menu.surface",
AetheriaRuntimeVerseRecordKeys.StableToken(surfaceId),
canOpenRuntimeInputScreen ? "input-open" : "input-closed",
inGame ? "in-game" : "title");
var descriptor = CultDocumentRegistry.Shared.GetRequired<AetheriaRuntimeSurfaceDocument>();
var sources = new[]
{
CultMesh.ProjectionSource(documentId, descriptor.SchemaId, "managed Aetheria main menu surface"),
CultMesh.ProjectionSource("catalog:aetheria.player_settings"),
CultMesh.ProjectionSource("catalog:aetheria.verse_host_settings"),
CultMesh.ProjectionSource(AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest.ToString())
};
var verse = CultMesh.Verse("aetheria.local", RuntimeId);
return CultMesh.Document(
documentId,
verse,
_ => Task.FromResult(BuildMainMenuSurface(surfaceId, canOpenRuntimeInputScreen, inGame)),
_ => frameChanges
.Select(_ => BuildMainMenuSurface(surfaceId, canOpenRuntimeInputScreen, inGame)),
sources: sources,
routeHint: new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "Aetheria managed main menu surface"));
}
Task<AetheriaRuntimeStationRefitDocument> StationRefitAsync(
AetheriaRuntimeDaemonFrameDocument frame)
{
@ -714,6 +752,99 @@ namespace GameCult.Aetheria.State.Verse
DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture)));
}
AetheriaRuntimeSurfaceDocument BuildMainMenuSurface(
string surfaceId,
bool canOpenRuntimeInputScreen,
bool inGame)
{
var updatedAtUtc = DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture);
var stateBoot = AetheriaRuntimeStateBoot.Inspect(
new DirectoryInfo(Path.GetDirectoryName(StatePath) ?? "."),
StatePath);
if (string.Equals(surfaceId, AetheriaRuntimeMainMenuCommands.RootSurfaceId, StringComparison.Ordinal))
{
return AetheriaRuntimeMainMenuSurfaceBuilder.BuildRoot(
stateBoot,
TryCurrentSectorMap(),
TryCurrentVerseHostSettings(),
TryCurrentPlayerSettings(),
canOpenRuntimeInputScreen,
inGame,
updatedAtUtc);
}
if (string.Equals(surfaceId, AetheriaRuntimeMainMenuCommands.SettingsSurfaceId, StringComparison.Ordinal))
return AetheriaRuntimeMainMenuSurfaceBuilder.BuildSettings(updatedAtUtc);
if (string.Equals(surfaceId, AetheriaRuntimeMainMenuCommands.InputSettingsSurfaceId, StringComparison.Ordinal))
{
return AetheriaRuntimeMainMenuSurfaceBuilder.BuildInputSettings(
stateBoot,
TryCurrentPlayerSettings(),
canOpenRuntimeInputScreen,
inGame,
updatedAtUtc);
}
if (string.Equals(surfaceId, AetheriaRuntimeMainMenuCommands.PlayerSettingsShellSurfaceId, StringComparison.Ordinal))
{
return AetheriaRuntimeMainMenuSurfaceBuilder.BuildPlayerSettingsShell(
TryCurrentPlayerSettings(),
updatedAtUtc);
}
if (string.Equals(surfaceId, AetheriaRuntimeMainMenuCommands.VerseSettingsShellSurfaceId, StringComparison.Ordinal))
{
return AetheriaRuntimeMainMenuSurfaceBuilder.BuildVerseSettingsShell(
AetheriaRuntimeClientTargetSurfaceBuilder.Build(
stateBoot,
TryCurrentVerseHostSettings(),
updatedAtUtc));
}
throw new ArgumentException($"Unknown Aetheria main menu surface id '{surfaceId}'.", nameof(surfaceId));
}
AetheriaRuntimePlayerSettingsDocument? TryCurrentPlayerSettings()
{
try
{
managedPlayerSettings ??= playerSettingsDocument.Reactive();
_managedPlayerSettings = managedPlayerSettings;
return managedPlayerSettings.Current;
}
catch (Exception)
{
return null;
}
}
AetheriaRuntimeVerseHostSettingsDocument? TryCurrentVerseHostSettings()
{
try
{
return verseHostSettingsDocument.Latest();
}
catch (Exception)
{
return null;
}
}
AetheriaRuntimeSectorMapDocument? TryCurrentSectorMap()
{
try
{
var frame = latestFrameDocument.Latest();
return frame == null ? null : AetheriaRuntimeRtsDocuments.SectorMap(frame);
}
catch (Exception)
{
return null;
}
}
static string IndexedDocumentId(string prefix, int index)
{
return $"{prefix}.{index.ToString(CultureInfo.InvariantCulture)}";