feat: Upgrade server with HyNB v2.4 modular architecture, minigames, admin GM web panel, companion summons, and car radio
This commit is contained in:
1 parent
74e2963a12
commit
3e5adf1dc5
1571 files changed
+336468
-6348
No files matched your search
@@ -27,6 +27,20 @@ public static class RuntimeLogs
|
||||
private static string? _consoleArchive;
|
||||
private static string? _packetLatest;
|
||||
private static string? _packetArchive;
|
||||
|
||||
// ⛑️⛑️ 长驻 writer —— 见 AppendText 的注释。原来每一行日志都要
|
||||
// 「CreateDirectory + new FileStream(Append) + new StreamWriter + 写 + 释放」×2 份文件,
|
||||
// 而 FileMode.Append 每次打开都要重新定位到 EOF ⇒ 单行的代价随日志文件变大而变大。
|
||||
// 实测:SyncPlayerInfo 那 200 KB 的 hexdump 有 12 541 行,在 09-30 16:50(日志还小)
|
||||
// 要 2.8 s,到 10-01 22:08(日志 20 MB)要 6.8~14.1 s —— 而这段时间是**同步**跑在
|
||||
// LoginGame handler 里的(RpcContext.NotifyAsync<T> 写完包立刻 RuntimeLogs.Decoded),
|
||||
// 收包循环串行 ⇒ 一帧都不读 ⇒ 客户端 TimeScaleAnomaly 判定掉线 ⇒ 重连 ⇒ 又打一份
|
||||
// 200 KB 的 hexdump ⇒ 日志更大 ⇒ 更慢。正反馈,永不收敛。
|
||||
private static StreamWriter? _consoleLatestWriter;
|
||||
private static StreamWriter? _consoleArchiveWriter;
|
||||
private static StreamWriter? _packetLatestWriter;
|
||||
private static StreamWriter? _packetArchiveWriter;
|
||||
|
||||
private static TextWriter? _originalOut;
|
||||
private static TextWriter? _originalError;
|
||||
private static bool _bootstrapConsoleHidden;
|
||||
@@ -53,17 +67,17 @@ public static class RuntimeLogs
|
||||
_includeDecoded = includeDecoded;
|
||||
_maxBodyBytes = Math.Max(0, maxBodyBytes);
|
||||
|
||||
_consoleLatest = Environment.GetEnvironmentVariable("Ananta_CONSOLE_LOG_LATEST")
|
||||
_consoleLatest = Environment.GetEnvironmentVariable("Ananta_CONSOLE_LOG_LATEST") ?? Environment.GetEnvironmentVariable("DRMK_CONSOLE_LOG_LATEST")
|
||||
?? Path.Combine(logDirectory, "console-latest.log");
|
||||
_consoleArchive = Environment.GetEnvironmentVariable("Ananta_CONSOLE_LOG_ARCHIVE")
|
||||
_consoleArchive = Environment.GetEnvironmentVariable("Ananta_CONSOLE_LOG_ARCHIVE") ?? Environment.GetEnvironmentVariable("DRMK_CONSOLE_LOG_ARCHIVE")
|
||||
?? Path.Combine(logDirectory, $"console-{runId}.log");
|
||||
_packetLatest = Environment.GetEnvironmentVariable("Ananta_PACKET_LOG_LATEST")
|
||||
_packetLatest = Environment.GetEnvironmentVariable("DRMK_PACKET_LOG_LATEST")
|
||||
?? Path.Combine(logDirectory, "packets-latest.log");
|
||||
_packetArchive = Environment.GetEnvironmentVariable("Ananta_PACKET_LOG_ARCHIVE")
|
||||
_packetArchive = Environment.GetEnvironmentVariable("DRMK_PACKET_LOG_ARCHIVE")
|
||||
?? Path.Combine(logDirectory, $"packets-{runId}.log");
|
||||
|
||||
// When launched without Run-All, create a fresh latest/archive pair here.
|
||||
if (Environment.GetEnvironmentVariable("Ananta_LOGS_PREPARED") != "1")
|
||||
if (Environment.GetEnvironmentVariable("DRMK_LOGS_PREPARED") != "1")
|
||||
{
|
||||
if (_consoleEnabled)
|
||||
{
|
||||
@@ -77,13 +91,23 @@ public static class RuntimeLogs
|
||||
}
|
||||
}
|
||||
|
||||
// 文件已经 PrepareFile 过(或由 Run-All 预先清空),这里一次性打开并常驻。
|
||||
// ⛑ latest 与 archive 指向同一个文件时只开一个 writer —— 两个 Append 流写同一个文件
|
||||
// 会各自维护文件指针、互相覆盖(旧代码是每次重写,所以没这个问题)。
|
||||
_consoleLatestWriter = _consoleEnabled ? OpenWriter(_consoleLatest) : null;
|
||||
_consoleArchiveWriter = _consoleEnabled && !SamePath(_consoleLatest, _consoleArchive)
|
||||
? OpenWriter(_consoleArchive) : null;
|
||||
_packetLatestWriter = _packetEnabled ? OpenWriter(_packetLatest) : null;
|
||||
_packetArchiveWriter = _packetEnabled && !SamePath(_packetLatest, _packetArchive)
|
||||
? OpenWriter(_packetArchive) : null;
|
||||
|
||||
_initialized = true;
|
||||
|
||||
if (_consoleEnabled)
|
||||
{
|
||||
_originalOut = Console.Out;
|
||||
_originalError = Console.Error;
|
||||
_bootstrapConsoleHidden = Environment.GetEnvironmentVariable("Ananta_HIDE_BOOT_LOG") == "1";
|
||||
_bootstrapConsoleHidden = Environment.GetEnvironmentVariable("DRMK_HIDE_BOOT_LOG") == "1";
|
||||
if (_bootstrapConsoleHidden)
|
||||
{
|
||||
// Run-All already cleared the launcher window. Keep startup diagnostics in the
|
||||
@@ -204,37 +228,53 @@ public static class RuntimeLogs
|
||||
private static void AppendConsole(string text)
|
||||
{
|
||||
if (!_initialized || !_consoleEnabled || string.IsNullOrEmpty(text)) return;
|
||||
AppendToPair(_consoleLatest, _consoleArchive, text);
|
||||
AppendToPair(_consoleLatestWriter, _consoleArchiveWriter, text);
|
||||
}
|
||||
|
||||
private static void AppendPacket(string line)
|
||||
=> AppendToPair(_packetLatest, _packetArchive, line + Environment.NewLine);
|
||||
=> AppendToPair(_packetLatestWriter, _packetArchiveWriter, line + Environment.NewLine);
|
||||
|
||||
private static void AppendToPair(string? latest, string? archive, string text)
|
||||
private static void AppendToPair(StreamWriter? latest, StreamWriter? archive, string text)
|
||||
{
|
||||
lock (FileLock)
|
||||
{
|
||||
// Logging is best-effort: a log file held/locked by another process (a viewer,
|
||||
// the Node proxy, an indexer) must never throw into the RPC pipeline and kill
|
||||
// a live session (that exact path caused a client "connection lost" on 2026-09-19).
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(latest)) AppendText(latest, text);
|
||||
if (!string.IsNullOrWhiteSpace(archive) && !string.Equals(latest, archive, StringComparison.OrdinalIgnoreCase))
|
||||
AppendText(archive, text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
latest?.Write(text);
|
||||
if (!ReferenceEquals(latest, archive)) archive?.Write(text);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendText(string path, string text)
|
||||
/// <summary>
|
||||
/// 一次性打开日志文件的长驻 writer。
|
||||
///
|
||||
/// ⛑️ 这里<b>不能</b>回到「每行 new FileStream(FileMode.Append)」的写法:Append 模式每次
|
||||
/// 打开都要重新定位到 EOF,单行代价随文件体积线性上涨,20 MB 的 packets 日志上实测
|
||||
/// ~0.5 ms/行 ×2 份文件 —— 一个 200 KB 包的 hexdump(12 541 行)就是 10 秒级同步阻塞,
|
||||
/// 而它发生在 RPC handler 里(见上面 writer 字段的注释)。
|
||||
/// </summary>
|
||||
private static StreamWriter? OpenWriter(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path)) return null;
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))!);
|
||||
using var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete);
|
||||
using var writer = new StreamWriter(stream, new UTF8Encoding(false));
|
||||
writer.Write(text);
|
||||
var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete);
|
||||
// AutoFlush=true:玩家/我自己是边跑边看 console-latest.log 的,不能为了省一次
|
||||
// syscall 把尾巴攒在缓冲区里。真正的开销在「打开/关闭」而不在 flush。
|
||||
return new StreamWriter(stream, new UTF8Encoding(false)) { AutoFlush = true };
|
||||
}
|
||||
|
||||
private static bool SamePath(string? a, string? b)
|
||||
=> !string.IsNullOrWhiteSpace(a) && !string.IsNullOrWhiteSpace(b)
|
||||
&& string.Equals(Path.GetFullPath(a), Path.GetFullPath(b), StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>把长驻 writer 全部 flush(进程正常退出时调用一次,可选)。</summary>
|
||||
public static void FlushAll()
|
||||
{
|
||||
lock (FileLock)
|
||||
{
|
||||
_consoleLatestWriter?.Flush();
|
||||
_consoleArchiveWriter?.Flush();
|
||||
_packetLatestWriter?.Flush();
|
||||
_packetArchiveWriter?.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrepareFile(string? path)
|
||||
|
||||
Reference in new issue
Block a user