fix: improve minigame reconnect flow

This commit is contained in:
ud18010
2026-07-29 19:30:50 +08:00
parent 6a5e534d00
commit 42c3874d82
21 changed files with 979 additions and 52 deletions
+160 -6
View File
@@ -22,9 +22,11 @@ public class XWorldUtil
private const string LOCAL_SERVER_EXE = "../../Server/build-gateway-local3/Debug/AIProjectServer.exe";
private const string LOCAL_SERVER_DLL_DIR = "../../Server/build-local-sqlite/vcpkg_installed/x64-windows/debug/bin";
private const string LOCAL_SERVER_LOG_DIR = "../../Server/logs";
private const string MINI_GAME_GATEWAY_RUNNER_PROJECT = "../../Server/Gateway.Runner/Gateway.Runner.csproj";
private const string MINI_GAME_GATEWAY_RUNNER_DLL = "../../Server/Gateway.Runner/bin/Debug/net10.0/XWorld.Server.Gateway.Runner.dll";
private const string MINI_GAME_SERVER_GAMES_ROOT = "../../deploy/minigame";
private const string MINI_GAME_GATEWAY_PID_FILE = "pc-minigame-gateway.pid";
private const string MINI_GAME_CDN_RUNNER_PROJECT = "../../Server/Cdn.Runner/Cdn.Runner.csproj";
private const string MINI_GAME_CDN_RUNNER_DLL = "../../Server/Cdn.Runner/bin/Debug/net10.0/XWorld.Server.Cdn.Runner.dll";
private const string MINI_GAME_CDN_ROOT = "../../CDN";
private const string MINI_GAME_CDN_PID_FILE = "pc-cdn.pid";
@@ -37,6 +39,7 @@ public class XWorldUtil
private const int LOCAL_INTERNAL_GAME_PORT = 7003;
private const int MINI_GAME_GATEWAY_PORT = 5005;
private const int MINI_GAME_TICK_MS = 100;
private const int MINI_GAME_RECONNECT_WINDOW_TICKS = 1800;
private const int MINI_GAME_MATCH_TIMEOUT_TICKS = 150;
private static readonly List<Process> LocalServerProcesses = new List<Process>();
@@ -323,6 +326,22 @@ public class XWorldUtil
{
StopExitedLocalServerProcesses();
SetLocalServerPrefs();
if (IsVisibleMiniGameGatewayConsoleRunning())
{
UnityEngine.Debug.Log("PC MiniGame Gateway console is already running; skipping build.");
return;
}
if (!ShouldBuildMiniGameLocalServer(IsMiniGameGatewayRunnerRunning()))
{
UnityEngine.Debug.Log("PC MiniGame Gateway is already running; skipping build.");
return;
}
if (!BuildMiniGameLocalServer())
return;
// 内网 CDN(HTTP 静态文件)独立于网关,先确保起来(端口已开则跳过)
StartMiniGameCdnRunnerProcess("XWorld PC CDN");
if (IsVisibleMiniGameGatewayConsoleRunning())
@@ -390,12 +409,26 @@ public class XWorldUtil
"OK");
}
[MenuItem("XWorld/Server/Build Local Server", false, 605)]
static public void BuildLocalServer()
{
if (BuildMiniGameLocalServer())
EditorUtility.DisplayDialog("XWorld Server", "Local server build finished.", "OK");
}
[MenuItem("XWorld/Server/Build CDN Runner", false, 606)]
static public void BuildCdnRunner()
{
if (BuildMiniGameRunnerProject("XWorld PC CDN", GetMiniGameCdnRunnerProjectPath(), GetMiniGameCdnRunnerPath()))
EditorUtility.DisplayDialog("XWorld Server", "CDN runner build finished.", "OK");
}
static public bool IsPcMiniGameGatewayReady()
{
return IsMiniGameGatewayRunnerRunning();
}
[MenuItem("XWorld/Server/Open Latest Log", false, 605)]
[MenuItem("XWorld/Server/Open Latest Log", false, 607)]
static public void OpenLatestLocalServerLog()
{
string logDir = Path.GetFullPath(Path.Combine(Application.dataPath, LOCAL_SERVER_LOG_DIR));
@@ -422,7 +455,7 @@ public class XWorldUtil
[MenuItem("XWorld/Server/Open Local Server (Separate)", true)]
static public bool CanOpenLocalServer()
{
return File.Exists(GetMiniGameGatewayRunnerPath());
return File.Exists(GetMiniGameGatewayRunnerProjectPath());
}
[MenuItem("XWorld/Server/Stop Local Server", true)]
@@ -484,6 +517,11 @@ public class XWorldUtil
return Path.GetFullPath(Path.Combine(Application.dataPath, MINI_GAME_GATEWAY_RUNNER_DLL));
}
static private string GetMiniGameGatewayRunnerProjectPath()
{
return Path.GetFullPath(Path.Combine(Application.dataPath, MINI_GAME_GATEWAY_RUNNER_PROJECT));
}
static private string GetMiniGameServerGamesRoot()
{
return Path.GetFullPath(Path.Combine(Application.dataPath, MINI_GAME_SERVER_GAMES_ROOT));
@@ -501,6 +539,11 @@ public class XWorldUtil
return Path.GetFullPath(Path.Combine(Application.dataPath, MINI_GAME_CDN_RUNNER_DLL));
}
static private string GetMiniGameCdnRunnerProjectPath()
{
return Path.GetFullPath(Path.Combine(Application.dataPath, MINI_GAME_CDN_RUNNER_PROJECT));
}
static private string GetMiniGameCdnRoot()
{
return Path.GetFullPath(Path.Combine(Application.dataPath, MINI_GAME_CDN_ROOT));
@@ -523,6 +566,101 @@ public class XWorldUtil
return Path.Combine(logDir, safeTitle + "-" + DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".log");
}
static private bool BuildMiniGameLocalServer()
{
if (!BuildMiniGameRunnerProject("XWorld PC minigame gateway", GetMiniGameGatewayRunnerProjectPath(), GetMiniGameGatewayRunnerPath()))
return false;
return true;
}
static private bool ShouldBuildMiniGameLocalServer(bool isGatewayRunning)
{
return !isGatewayRunning;
}
static private bool BuildMiniGameRunnerProject(string title, string projectPath, string runnerPath)
{
if (!File.Exists(projectPath))
{
EditorUtility.DisplayDialog("XWorld Server", "Server project not found:\n" + projectPath, "OK");
return false;
}
string logPath = GetLocalServerLogPath(title + " build");
UnityEngine.Debug.Log("Building " + title + " from " + projectPath + "\nLog: " + logPath);
EditorUtility.DisplayProgressBar("XWorld Server", "Building " + title, 0.5f);
try
{
StringBuilder output = new StringBuilder();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "dotnet";
startInfo.Arguments = "build " + QuoteProcessArg(projectPath) + " -c Debug --no-incremental -m:1";
startInfo.WorkingDirectory = GetProjectRoot();
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.CreateNoWindow = true;
using (Process process = new Process())
{
process.StartInfo = startInfo;
process.OutputDataReceived += (sender, args) =>
{
if (args.Data != null)
output.AppendLine(args.Data);
};
process.ErrorDataReceived += (sender, args) =>
{
if (args.Data != null)
output.AppendLine(args.Data);
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
process.WaitForExit(1000);
File.WriteAllText(logPath, output.ToString(), new UTF8Encoding(false));
if (process.ExitCode != 0)
{
EditorUtility.DisplayDialog("XWorld Server",
title + " build failed. Old DLL will not be started.\n\nLog:\n" + logPath,
"OK");
UnityEngine.Debug.LogError(title + " build failed with exit code " + process.ExitCode + ". Log: " + logPath);
return false;
}
}
}
catch (Exception ex)
{
File.WriteAllText(logPath, ex.ToString(), new UTF8Encoding(false));
EditorUtility.DisplayDialog("XWorld Server",
title + " build failed before launch. Old DLL will not be started.\n\n" + ex.Message + "\n\nLog:\n" + logPath,
"OK");
UnityEngine.Debug.LogError(title + " build failed: " + ex);
return false;
}
finally
{
EditorUtility.ClearProgressBar();
}
if (!IsDotnetRunnerLaunchable(runnerPath))
{
EditorUtility.DisplayDialog("XWorld Server",
title + " build finished, but runner output is incomplete:\n" + runnerPath +
"\n" + GetDotnetRunnerRuntimeConfigPath(runnerPath),
"OK");
return false;
}
UnityEngine.Debug.Log(title + " build finished. Runner: " + runnerPath);
return true;
}
static private void StartServerProcess(string arguments, string title)
{
string exePath = GetLocalServerExePath();
@@ -573,10 +711,12 @@ public class XWorldUtil
static private void StartMiniGameGatewayRunnerProcess(string title)
{
string runnerPath = GetMiniGameGatewayRunnerPath();
if (!File.Exists(runnerPath))
if (!IsDotnetRunnerLaunchable(runnerPath))
{
EditorUtility.DisplayDialog("XWorld Server",
"Gateway runner not found:\n" + runnerPath + "\n\nBuild Server/Gateway.Runner first.", "OK");
"Gateway runner output is incomplete:\n" + runnerPath +
"\n" + GetDotnetRunnerRuntimeConfigPath(runnerPath) +
"\n\nBuild Server/Gateway.Runner first.", "OK");
return;
}
@@ -612,6 +752,7 @@ public class XWorldUtil
"Write-Host ''\r\n" +
"& dotnet $runner --gamesRoot $gamesRoot --port " + MINI_GAME_GATEWAY_PORT +
" --tickMs " + MINI_GAME_TICK_MS +
" --reconnectWindowTicks " + MINI_GAME_RECONNECT_WINDOW_TICKS +
" --matchTimeoutTicks " + MINI_GAME_MATCH_TIMEOUT_TICKS +
" --lan --devToken " + DEV_DISCOVERY_TOKEN + // 开 DevDiscovery(UDP48923) 响应 + 广播 LAN IPCDN 地址由 --lan 兜底为 http://<lanip>:15081/
" *>&1 | Tee-Object -FilePath $logPath\r\n" +
@@ -645,9 +786,10 @@ public class XWorldUtil
static private void StartMiniGameCdnRunnerProcess(string title)
{
string runnerPath = GetMiniGameCdnRunnerPath();
if (!File.Exists(runnerPath))
if (!IsDotnetRunnerLaunchable(runnerPath))
{
UnityEngine.Debug.LogWarning("[CDN] 未找到 Cdn.Runner" + runnerPath + "\n请先构建 Server/Cdn.Runnerdotnet build Server/Server.sln)。");
UnityEngine.Debug.LogWarning("[CDN] Cdn.Runner output is incomplete; skipping CDN startup.\nDLL: " +
runnerPath + "\nRuntimeConfig: " + GetDotnetRunnerRuntimeConfigPath(runnerPath));
return;
}
@@ -722,6 +864,18 @@ public class XWorldUtil
return (text ?? string.Empty).Replace("'", "''");
}
static private string GetDotnetRunnerRuntimeConfigPath(string runnerPath)
{
string directory = Path.GetDirectoryName(runnerPath);
string fileName = Path.GetFileNameWithoutExtension(runnerPath) + ".runtimeconfig.json";
return Path.Combine(directory ?? string.Empty, fileName);
}
static private bool IsDotnetRunnerLaunchable(string runnerPath)
{
return File.Exists(runnerPath) && File.Exists(GetDotnetRunnerRuntimeConfigPath(runnerPath));
}
static private void StopExitedLocalServerProcesses()
{
LocalServerProcesses.RemoveAll(process =>