增加一致性判断编辑器
This commit is contained in:
parent
934493a32a
commit
a9e469c2bc
@ -11,7 +11,9 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using Logic.CrashSight;
|
||||
using MemoryPack;
|
||||
using RuntimeData;
|
||||
using Steamworks;
|
||||
using TH1_Logic.Core;
|
||||
using TH1_Logic.Net;
|
||||
using TH1_Logic.Steam;
|
||||
using UnityEditor;
|
||||
@ -988,4 +990,133 @@ namespace Logic.Editor
|
||||
return copy[index];
|
||||
}
|
||||
}
|
||||
|
||||
public class MapDataNetworkDebugEditorWindow : EditorWindow
|
||||
{
|
||||
private Vector2 _scrollPosition;
|
||||
private string _lastStatus = "等待发送";
|
||||
|
||||
[MenuItem("Tools/Steam MapData一致性诊断")]
|
||||
private static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<MapDataNetworkDebugEditorWindow>();
|
||||
window.titleContent = new GUIContent("MapData诊断");
|
||||
window.minSize = new Vector2(520, 300);
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
var lobby = LobbyManager.Instance.Lobby;
|
||||
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
|
||||
DrawStatus(lobby);
|
||||
EditorGUILayout.Space(10);
|
||||
DrawButtons(lobby);
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void DrawStatus(ILobby lobby)
|
||||
{
|
||||
EditorGUILayout.LabelField("Steam MapData 一致性诊断", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox("只发送当前 MapData 做对比,不会触发 ForceUpdate,也不会覆盖接收方地图。接收方在 Console 查看 [MapDataDebug] 日志。", MessageType.Info);
|
||||
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
|
||||
{
|
||||
EditorGUILayout.LabelField($"PlayMode: {Application.isPlaying}");
|
||||
if (lobby == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("Lobby: null");
|
||||
EditorGUILayout.LabelField($"最后操作: {_lastStatus}");
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField($"Lobby 初始化: {lobby.IsInitialized()}");
|
||||
EditorGUILayout.LabelField($"房间中: {lobby.IsInLobby()}");
|
||||
EditorGUILayout.LabelField($"是否房主: {lobby.IsLobbyOwner()}");
|
||||
EditorGUILayout.LabelField($"自己: {lobby.GetSelfMemberId()}");
|
||||
EditorGUILayout.LabelField($"房主: {lobby.GetLobbyOwnerId()}");
|
||||
EditorGUILayout.LabelField($"成员数: {lobby.GetMemberCount()}/{lobby.GetMemberLimit()}");
|
||||
|
||||
var mapData = Main.MapData;
|
||||
if (mapData == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("MapData: null");
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField($"MapHash: {SafeHash(mapData)}");
|
||||
EditorGUILayout.LabelField($"ActionCount: {mapData.Net?.Actions?.Count ?? 0}");
|
||||
EditorGUILayout.LabelField($"NetMode: {mapData.Net?.Mode}");
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField($"最后操作: {_lastStatus}", EditorStyles.wordWrappedLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawButtons(ILobby lobby)
|
||||
{
|
||||
var canUse = Application.isPlaying
|
||||
&& lobby != null
|
||||
&& lobby.IsInitialized()
|
||||
&& lobby.IsInLobby()
|
||||
&& Main.MapData != null;
|
||||
var isOwner = lobby != null && lobby.IsLobbyOwner();
|
||||
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(!canUse || isOwner))
|
||||
{
|
||||
if (GUILayout.Button("成员发送当前 MapData 给房主", GUILayout.Height(44)))
|
||||
SendToHost();
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(!canUse || !isOwner))
|
||||
{
|
||||
if (GUILayout.Button("房主广播当前 MapData 给所有成员", GUILayout.Height(44)))
|
||||
BroadcastFromHost();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SendToHost()
|
||||
{
|
||||
try
|
||||
{
|
||||
var ok = GameNetSender.Instance.SendMapDataDebugToHost("manual editor member->host");
|
||||
_lastStatus = ok ? $"已发送给房主 {DateTime.Now:HH:mm:ss}" : $"发送给房主失败 {DateTime.Now:HH:mm:ss}";
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_lastStatus = $"发送给房主异常: {e.Message}";
|
||||
LogSystem.LogError($"[MapDataDebug] SendToHost failed: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
private void BroadcastFromHost()
|
||||
{
|
||||
try
|
||||
{
|
||||
var ok = GameNetSender.Instance.BroadcastMapDataDebug("manual editor host->members");
|
||||
_lastStatus = ok ? $"已广播给成员 {DateTime.Now:HH:mm:ss}" : $"广播给成员失败 {DateTime.Now:HH:mm:ss}";
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_lastStatus = $"广播给成员异常: {e.Message}";
|
||||
LogSystem.LogError($"[MapDataDebug] BroadcastFromHost failed: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string SafeHash(MapData mapData)
|
||||
{
|
||||
if (mapData?.Net == null) return "null";
|
||||
try
|
||||
{
|
||||
return NetData.GetMapDataHash(mapData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return $"hash_error:{e.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
using Logic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Logic.Action;
|
||||
using Logic.AI;
|
||||
using Logic.CrashSight;
|
||||
using RuntimeData;
|
||||
using TH1_Core.Events;
|
||||
@ -41,6 +46,7 @@ namespace TH1_Logic.Steam
|
||||
if (message.MessageType == P2PMsgType.ChatMessage) OnReceivedChatMessage((ChatMessage)message);
|
||||
if (message.MessageType == P2PMsgType.InviteMessage) OnReceivedInviteMessage((InviteMessage)message);
|
||||
if (message.MessageType == P2PMsgType.LobbyReport) OnReceivedLobbyReport((LobbyReportMessage)message);
|
||||
if (message.MessageType == P2PMsgType.MapDataDebug) OnReceivedMapDataDebug((MapDataDebugMessage)message);
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
@ -374,6 +380,140 @@ namespace TH1_Logic.Steam
|
||||
// Main.MapData = message.MapData;
|
||||
}
|
||||
|
||||
private void OnReceivedMapDataDebug(MapDataDebugMessage message)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
LogSystem.LogError($"消息解析失败: OnReceivedMapDataDebug");
|
||||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.NetworkMessageParseFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
var localMap = Main.MapData;
|
||||
var remoteMap = message.MapData;
|
||||
var lobby = LobbyManager.Instance.Lobby;
|
||||
var selfMemberId = lobby?.GetSelfMemberId() ?? 0;
|
||||
var selfRole = lobby != null && lobby.IsLobbyOwner() ? "房主" : "成员";
|
||||
var localHash = SafeGetMapDataHash(localMap);
|
||||
var remoteHash = string.IsNullOrEmpty(message.MapHash) ? SafeGetMapDataHash(remoteMap) : message.MapHash;
|
||||
var localActionCount = localMap?.Net?.Actions?.Count ?? 0;
|
||||
var remoteActionCount = remoteMap?.Net?.Actions?.Count ?? message.ActionIndex;
|
||||
|
||||
if (localMap == null || remoteMap == null)
|
||||
{
|
||||
LogSystem.LogError(
|
||||
$"[MapDataDebug] 无法比较: localMap null={localMap == null}, remoteMap null={remoteMap == null}, " +
|
||||
$"fromMember={message.SenderMemberId}, selfMember={selfMemberId}");
|
||||
return;
|
||||
}
|
||||
|
||||
var differences = BuildMapDataDebugDifferences(localMap, remoteMap);
|
||||
var sb = new StringBuilder(8192);
|
||||
sb.AppendLine($"[MapDataDebug] {selfRole}收到 MapData 诊断包");
|
||||
sb.AppendLine($"fromMember={message.SenderMemberId}, fromPlayer={message.SenderPlayerId}, selfMember={selfMemberId}");
|
||||
sb.AppendLine($"note={message.Note}");
|
||||
sb.AppendLine($"localHash={localHash}, remoteHash={remoteHash}");
|
||||
sb.AppendLine($"localActions={localActionCount}, remoteActions={remoteActionCount}");
|
||||
if (!IsValidIncomingMultiMap(remoteMap))
|
||||
sb.AppendLine("remoteMap=无效多人地图数据");
|
||||
|
||||
if (differences.Count == 0)
|
||||
{
|
||||
sb.AppendLine("result=一致");
|
||||
LogSystem.LogInfo(sb.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
sb.AppendLine($"result=不一致, diffCount={differences.Count}");
|
||||
AppendFirstActionMismatch(sb, localMap, remoteMap);
|
||||
var logCount = Math.Min(differences.Count, 200);
|
||||
for (var i = 0; i < logCount; i++)
|
||||
sb.AppendLine($"diff[{i}] {differences[i]}");
|
||||
if (differences.Count > logCount)
|
||||
sb.AppendLine($"diff 过多,仅打印前 {logCount} 条,剩余 {differences.Count - logCount} 条");
|
||||
LogSystem.LogError(sb.ToString());
|
||||
}
|
||||
|
||||
private static List<string> BuildMapDataDebugDifferences(MapData localMap, MapData remoteMap)
|
||||
{
|
||||
var differences = new List<string>();
|
||||
try
|
||||
{
|
||||
MapData.CompareComponent(localMap.MapConfig, remoteMap.MapConfig, "MapConfig", differences);
|
||||
MapData.CompareComponent(localMap.GridMap, remoteMap.GridMap, "GridMap", differences);
|
||||
MapData.CompareComponent(localMap.PlayerMap, remoteMap.PlayerMap, "PlayerMap", differences);
|
||||
MapData.CompareComponent(localMap.PlayerMap?.PlayerDataList, remoteMap.PlayerMap?.PlayerDataList,
|
||||
"PlayerMap.PlayerDataList", differences);
|
||||
MapData.CompareComponent(localMap.CityMap, remoteMap.CityMap, "CityMap", differences);
|
||||
MapData.CompareComponent(localMap.UnitMap, remoteMap.UnitMap, "UnitMap", differences);
|
||||
MapData.CompareComponent(localMap.Net, remoteMap.Net, "Net", differences);
|
||||
MapData.CompareComponent(localMap.CityToPlayerDict, remoteMap.CityToPlayerDict, "CityToPlayerDict", differences);
|
||||
MapData.CompareComponent(localMap.UnitToCityDict, remoteMap.UnitToCityDict, "UnitToCityDict", differences);
|
||||
MapData.CompareComponent(localMap.UnitToGridDict, remoteMap.UnitToGridDict, "UnitToGridDict", differences);
|
||||
MapData.CompareComponent(localMap.CityToGridDict, remoteMap.CityToGridDict, "CityToGridDict", differences);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
differences.Add($"component comparison failed: {e.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
differences.AddRange(MapData.FindDifferences(localMap, remoteMap));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
differences.Add($"FindDifferences failed: {e.Message}");
|
||||
}
|
||||
|
||||
return differences.Where(diff => !string.IsNullOrEmpty(diff)).Distinct().ToList();
|
||||
}
|
||||
|
||||
private static void AppendFirstActionMismatch(StringBuilder sb, MapData localMap, MapData remoteMap)
|
||||
{
|
||||
var localActions = localMap.Net?.Actions;
|
||||
var remoteActions = remoteMap.Net?.Actions;
|
||||
if (localActions == null || remoteActions == null) return;
|
||||
|
||||
var minCount = Math.Min(localActions.Count, remoteActions.Count);
|
||||
for (var i = 0; i < minCount; i++)
|
||||
{
|
||||
var localAction = localActions[i];
|
||||
var remoteAction = remoteActions[i];
|
||||
if (localAction == null && remoteAction == null) continue;
|
||||
if (localAction != null && localAction.IsEqual(remoteAction)) continue;
|
||||
|
||||
sb.AppendLine($"firstActionMismatchIndex={i}");
|
||||
sb.AppendLine($"localAction={GetActionDebugLine(localAction)}");
|
||||
sb.AppendLine($"remoteAction={GetActionDebugLine(remoteAction)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (localActions.Count == remoteActions.Count) return;
|
||||
sb.AppendLine($"firstActionMismatchIndex={minCount}");
|
||||
sb.AppendLine($"localActionCount={localActions.Count}, remoteActionCount={remoteActions.Count}");
|
||||
}
|
||||
|
||||
private static string GetActionDebugLine(ActionNetData action)
|
||||
{
|
||||
if (action == null) return "null";
|
||||
var actionText = action.ActionId == null ? "null" : action.ActionId.GetStringLog().Replace("\n", " | ");
|
||||
return $"version={action.Version}, mapHash={action.MapHash}, action={actionText}";
|
||||
}
|
||||
|
||||
private static string SafeGetMapDataHash(MapData mapData)
|
||||
{
|
||||
if (mapData?.Net == null) return "null";
|
||||
try
|
||||
{
|
||||
return NetData.GetMapDataHash(mapData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return $"hash_error:{e.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidIncomingMultiMap(MapData mapData)
|
||||
{
|
||||
return mapData?.Net != null
|
||||
|
||||
@ -178,6 +178,45 @@ namespace TH1_Logic.Steam
|
||||
BroadcastMessage(data);
|
||||
}
|
||||
|
||||
// MapData 一致性诊断 (成员 => 房主)
|
||||
public bool SendMapDataDebugToHost(string note = null)
|
||||
{
|
||||
if (!TryGetValidMultiMapForBroadcast("SendMapDataDebugToHost", out var mapData)) return false;
|
||||
var data = BuildMapDataDebugMessage(mapData, note);
|
||||
return SendMessage(data);
|
||||
}
|
||||
|
||||
// MapData 一致性诊断 (房主 => 所有成员)
|
||||
public bool BroadcastMapDataDebug(string note = null)
|
||||
{
|
||||
if (!TryGetValidMultiMapForBroadcast("BroadcastMapDataDebug", out var mapData)) return false;
|
||||
var data = BuildMapDataDebugMessage(mapData, note);
|
||||
return BroadcastMessage(data);
|
||||
}
|
||||
|
||||
// MapData 一致性诊断 (任意成员 => 指定成员)
|
||||
public bool SendMapDataDebugToPlayer(ulong memberId, string note = null)
|
||||
{
|
||||
if (memberId == 0) return false;
|
||||
if (!TryGetValidMultiMapForBroadcast("SendMapDataDebugToPlayer", out var mapData)) return false;
|
||||
var data = BuildMapDataDebugMessage(mapData, note);
|
||||
return SendMessageToPlayer(memberId, data);
|
||||
}
|
||||
|
||||
private MapDataDebugMessage BuildMapDataDebugMessage(MapData mapData, string note)
|
||||
{
|
||||
return new MapDataDebugMessage
|
||||
{
|
||||
MapData = mapData,
|
||||
SenderMemberId = LobbyManager.Instance.Lobby.GetSelfMemberId(),
|
||||
SenderPlayerId = mapData.PlayerMap?.SelfPlayerId ?? 0,
|
||||
ActionIndex = mapData.Net?.Actions?.Count ?? 0,
|
||||
MapHash = NetData.GetMapDataHash(mapData),
|
||||
Note = note ?? string.Empty,
|
||||
CreatedUtcTicks = System.DateTime.UtcNow.Ticks
|
||||
};
|
||||
}
|
||||
|
||||
private bool TryGetValidMultiMapForBroadcast(string context, out MapData mapData)
|
||||
{
|
||||
mapData = Main.MapData;
|
||||
|
||||
@ -61,6 +61,8 @@ namespace TH1_Logic.Steam
|
||||
NetworkStress = 17,
|
||||
// 房间举报
|
||||
LobbyReport = 18,
|
||||
// MapData 一致性诊断
|
||||
MapDataDebug = 19,
|
||||
}
|
||||
|
||||
public enum NetworkStressMessageKind : byte
|
||||
@ -92,6 +94,7 @@ namespace TH1_Logic.Steam
|
||||
[MemoryPackUnion(16, typeof(InviteMessage))]
|
||||
[MemoryPackUnion(17, typeof(NetworkStressMessage))]
|
||||
[MemoryPackUnion(18, typeof(LobbyReportMessage))]
|
||||
[MemoryPackUnion(19, typeof(MapDataDebugMessage))]
|
||||
public abstract partial class BaseMessage
|
||||
{
|
||||
public abstract P2PMsgType MessageType { get; }
|
||||
@ -157,6 +160,20 @@ namespace TH1_Logic.Steam
|
||||
public override P2PMsgType MessageType => P2PMsgType.ForceUpdate;
|
||||
public MapData MapData { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class MapDataDebugMessage : BaseMessage
|
||||
{
|
||||
public override P2PMsgType MessageType => P2PMsgType.MapDataDebug;
|
||||
public MapData MapData { get; set; }
|
||||
public ulong SenderMemberId { get; set; }
|
||||
public uint SenderPlayerId { get; set; }
|
||||
public int ActionIndex { get; set; }
|
||||
public string MapHash { get; set; }
|
||||
public string Note { get; set; }
|
||||
public long CreatedUtcTicks { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[MemoryPackable]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user