#nullable enable using System; using System.Linq; using Cysharp.Threading.Tasks; using ShrinkCommand; using ShrinkCommand.Integration.App; using ShrinkContext.AppAdapter; using ShrinkNetwork; using ShrinkNetwork.Integration.App; using UnityEngine; namespace ShrinkApp.Starter.Basic { public sealed class ShrinkAppBasicCommandSource : IShrinkCommandSource { private readonly Action? _sink; private readonly bool _logToConsole; public ShrinkAppBasicCommandSource(Action? sink = null, bool logToConsole = true) { _sink = sink; _logToConsole = logToConsole; } public string SourceId => "starter.basic.console"; public string DisplayName => "StarterBasicConsole"; public bool IsConsole => true; public bool HasPermission(string permission) => true; public UniTask WriteLineAsync(string message, System.Threading.CancellationToken cancellationToken = default) { _sink?.Invoke(message); if (_logToConsole) Debug.Log("[ShrinkApp.Basic.Command] " + message); return UniTask.CompletedTask; } } [ShrinkCommandSubscriber] public static class ShrinkAppBasicDemoCommands { [ShrinkCommand("starter status", Description = "显示 Starter.Basic 当前状态")] private static string StarterStatus() { var appRunning = ShrinkApp.IsRunning; var loadedSlot = ShrinkDataSaver.ShrinkSave.LoadedSlot; return $"appRunning={appRunning}, loadedSlot={loadedSlot}"; } } public static class ShrinkAppBasicDemoRuntime { private const string SettingsResourceName = "ShrinkAppBasicStarterSettings"; private static ShrinkLoopbackTransport? _loopbackClientTransport; private static ShrinkLoopbackTransport? _loopbackServerTransport; private static long _activeLoopbackSessionId = -1; public static ShrinkAppBasicStarterSettings GetOrCreateSettings() { var asset = Resources.Load(SettingsResourceName); if (asset != null) return asset; var fallback = ScriptableObject.CreateInstance(); fallback.defaultCommandText = "starter status"; fallback.defaultLoopbackSessionId = 1; fallback.logCommandOutputToConsole = true; return fallback; } public static string BuildStatusSummary() { var commandReady = ShrinkApp.Context?.Services.TryGet(out _) == true; var networkReady = ShrinkApp.Context?.Services.TryGet(out _) == true; var dataSaverReady = ShrinkApp.Context?.Services.TryGet(out _) == true; var appRunning = ShrinkApp.IsRunning || ShrinkAppLoaderBootstrapper.Instance?.Host.IsRunning == true; return $"app={appRunning}, save={dataSaverReady}, cmd={commandReady}, net={networkReady}, loopbackSession={_activeLoopbackSessionId}"; } public static string BuildDetailedStatus() { var settings = GetOrCreateSettings(); var recentSlot = ShrinkDataSaver.ShrinkSave.GetRecentSlotIndex(); var commandService = ShrinkApp.Context?.Services.TryGet(out var command) == true ? command : null; var networkService = ShrinkApp.Context?.Services.TryGet(out var network) == true ? network : null; var diagnostics = networkService?.GetDiagnosticsSnapshot(); var moduleCount = ShrinkAppLoaderBootstrapper.Instance?.Host.ActiveModuleIds.Count ?? ShrinkApp.Host?.StartedModules.Count ?? 0; var serviceCount = ShrinkApp.Context?.Services.GetRegisteredServiceTypeNames().Count ?? 0; return $"defaultCommand={settings.defaultCommandText}\n" + $"loopbackAutoBind={settings.autoBindLoopbackTransport}, sessionId={settings.defaultLoopbackSessionId}\n" + $"recentSlot={recentSlot}, loadedSlot={ShrinkDataSaver.ShrinkSave.LoadedSlot}\n" + $"modules={moduleCount}, services={serviceCount}, commandCount={commandService?.Commands.Count ?? 0}, currentSessions={diagnostics?.CurrentSessions ?? 0}\n" + $"{BuildStatusSummary()}"; } public static string BuildModuleSummary() { var loaderHost = ShrinkAppLoaderBootstrapper.Instance?.Host; if (loaderHost != null && loaderHost.ActiveModuleIds.Count > 0) return string.Join("\n", loaderHost.ActiveModuleIds); if (ShrinkApp.Host?.StartedModules == null || ShrinkApp.Host.StartedModules.Count == 0) return "No started modules."; return string.Join("\n", ShrinkApp.Host.StartedModules); } public static string BuildServiceSummary() { if (ShrinkApp.Context == null) return "No registered services."; return string.Join("\n", ShrinkApp.Context.Services.GetRegisteredServiceTypeNames()); } public static string BuildSaveMetaSummary(ShrinkDataSaver.SaveMeta[]? metas) { if (metas == null || metas.Length == 0) return "No save slots found."; return string.Join("\n", metas .OrderBy(meta => meta.SlotIndex) .Select(meta => $"slot={meta.SlotIndex}, name={meta.SlotName}, playtime={meta.PlaytimeSeconds:F0}s, modified={meta.LastModifiedTime:g}")); } public static string BuildCommandListSummary() { if (ShrinkApp.Context?.Services.TryGet(out var commandService) != true || commandService == null) return "Command service not ready."; return string.Join("\n", commandService.Commands .OrderBy(command => command.Path, StringComparer.OrdinalIgnoreCase) .Take(10) .Select(command => command.Path)); } public static string BuildNetworkSummary() { if (ShrinkApp.Context?.Services.TryGet(out var networkService) != true || networkService == null) return "Network service not ready."; var diagnostics = networkService.GetDiagnosticsSnapshot(); var sessions = networkService.GetSessionSummaries(); return $"sessions={diagnostics.CurrentSessions}, connected={diagnostics.SessionsConnected}, disconnected={diagnostics.SessionsDisconnected}\n" + $"packetsSent={diagnostics.PacketsSent}, packetsReceived={diagnostics.PacketsReceived}\n" + $"rpcStarted={diagnostics.RpcStarted}, rpcCompleted={diagnostics.RpcCompleted}\n" + string.Join("\n", sessions); } public static async UniTask ExecuteCommandAsync(string? commandText, Action? outputSink = null) { var command = ShrinkApp.Services.GetRequired(); var settings = GetOrCreateSettings(); var source = new ShrinkAppBasicCommandSource(outputSink, settings.logCommandOutputToConsole); var effectiveCommand = string.IsNullOrWhiteSpace(commandText) ? settings.defaultCommandText : commandText.Trim(); var result = await command.ExecuteAsync(source, effectiveCommand); if (!string.IsNullOrWhiteSpace(result.Message)) outputSink?.Invoke(result.Message); return result.Message; } public static string EnsureLoopbackReady() { var network = ShrinkApp.Services.GetRequired().Service; var settings = GetOrCreateSettings(); if (_loopbackClientTransport == null || _loopbackServerTransport == null) { _loopbackClientTransport = new ShrinkLoopbackTransport(); _loopbackServerTransport = new ShrinkLoopbackTransport(); _loopbackClientTransport.LinkPeer(_loopbackServerTransport); } network.BindTransport(_loopbackClientTransport); if (_activeLoopbackSessionId != settings.defaultLoopbackSessionId) { if (_activeLoopbackSessionId >= 0) _loopbackClientTransport.CloseSession(_activeLoopbackSessionId); _activeLoopbackSessionId = settings.defaultLoopbackSessionId; _loopbackClientTransport.OpenSession(_activeLoopbackSessionId); } return $"loopbackBound={network.Sessions.Count}, session={_activeLoopbackSessionId}"; } public static string AutoBindLoopbackIfEnabled() { var settings = GetOrCreateSettings(); if (!settings.autoBindLoopbackTransport) return string.Empty; return EnsureLoopbackReady(); } public static string DisconnectLoopback() { if (_loopbackClientTransport == null || _activeLoopbackSessionId < 0) return "loopbackNotBound"; _loopbackClientTransport.CloseSession(_activeLoopbackSessionId); _activeLoopbackSessionId = -1; return "loopbackDisconnected"; } } }