This commit is contained in:
wuyanchen 2025-12-26 14:10:52 +08:00
parent de844d7274
commit d0ff0dbfda
4 changed files with 133 additions and 139 deletions

View File

@ -13,7 +13,6 @@ namespace XNet.Business
private const int TARGET_FPS = 30; private const int TARGET_FPS = 30;
private const int FRAME_TIME_MS = 1000 / TARGET_FPS; private const int FRAME_TIME_MS = 1000 / TARGET_FPS;
private readonly List<string> _instanceIds = new List<string>(); private readonly List<string> _instanceIds = new List<string>();
public GameLoopService(NavMeshManager navMeshManager, SceneAgent sceneAgent, WsConnectionManager wsManager) public GameLoopService(NavMeshManager navMeshManager, SceneAgent sceneAgent, WsConnectionManager wsManager)
{ {
_navMeshManager = navMeshManager; _navMeshManager = navMeshManager;

View File

@ -15,10 +15,10 @@ namespace XNet.Business
{ {
public string InstanceId { get; } public string InstanceId { get; }
public DtCrowd Crowd { get; set; } public DtCrowd Crowd { get; set; }
public object SyncRoot { get; } = new object(); // 线程锁 //public object SyncRoot { get; } = new object(); // 线程锁
// 新增:存储该实例下所有 Agent 的索引(关键修复) // 新增:存储该实例下所有 Agent 的索引(关键修复)
public List<int> AgentIndices { get; } = new List<int>(); public ConcurrentDictionary<int, bool> AgentIndices { get; } = new ConcurrentDictionary<int, bool>();
// 构造函数 // 构造函数
public CrowdInstance(string instanceId, DtCrowd crowd) public CrowdInstance(string instanceId, DtCrowd crowd)
@ -30,22 +30,22 @@ namespace XNet.Business
// 辅助:添加 Agent 时自动记录索引 // 辅助:添加 Agent 时自动记录索引
public void AddAgentIndex(int agentIdx) public void AddAgentIndex(int agentIdx)
{ {
lock (SyncRoot) //lock (SyncRoot)
{ //{
if (!AgentIndices.Contains(agentIdx)) //if (!AgentIndices.ContainsKey(agentIdx))
{ //{
AgentIndices.Add(agentIdx); AgentIndices.TryAdd(agentIdx, true);
} //}
} //}
} }
// 辅助:移除 Agent 时清理索引 // 辅助:移除 Agent 时清理索引
public void RemoveAgentIndex(int agentIdx) public void RemoveAgentIndex(int agentIdx)
{ {
lock (SyncRoot) //lock (SyncRoot)
{ //{
AgentIndices.Remove(agentIdx); AgentIndices.TryRemove(agentIdx, out _);
} //}
} }
} }
@ -84,45 +84,32 @@ namespace XNet.Business
return syncList; return syncList;
} }
lock (ci.SyncRoot) //lock (ci.SyncRoot)
//{
// 遍历实例内所有Agent索引修复AgentIndices报错
foreach (var kv in ci.AgentIndices)
{ {
// 遍历实例内所有Agent索引修复AgentIndices报错 int agentIdx = kv.Key;
foreach (var agentIdx in ci.AgentIndices) var agent = ci.Crowd.GetAgent(agentIdx);
if (agent == null || agent.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
{ {
var agent = ci.Crowd.GetAgent(agentIdx); continue;
if (agent == null || agent.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID) }
{
continue;
}
// 获取当前状态 // 获取当前状态
Vector3 currPos = GetAgentPosition(instanceId, agentIdx); Vector3 currPos = GetAgentPosition(instanceId, agentIdx);
float currRot = GetAgentRotation(instanceId, agentIdx); float currRot = GetAgentRotation(instanceId, agentIdx);
// 对比上一帧状态 // 对比上一帧状态
if (lastStates.TryGetValue(agentIdx, out var lastState)) if (lastStates.TryGetValue(agentIdx, out var lastState))
{ {
// 判断是否超过阈值 // 判断是否超过阈值
bool posChanged = Vector3.Distance(currPos, lastState.Position) > POSITION_THRESHOLD; bool posChanged = Vector3.Distance(currPos, lastState.Position) > POSITION_THRESHOLD;
bool rotChanged = Math.Abs(currRot - lastState.Rotation) > ROTATION_THRESHOLD; bool rotChanged = Math.Abs(currRot - lastState.Rotation) > ROTATION_THRESHOLD;
if (posChanged || rotChanged) if (posChanged || rotChanged)
{
// 加入同步列表
syncList.Add(new AgentPositionSyncMsg
{
InstanceId = instanceId,
AgentIdx = agentIdx,
Position = new Vector3Msg { X = currPos.X, Y = currPos.Y, Z = currPos.Z },
Rotation = currRot
});
// 更新缓存
lastStates[agentIdx] = new AgentState { Position = currPos, Rotation = currRot };
}
}
else
{ {
// 首次同步,直接加入并缓存 // 加入同步列表
syncList.Add(new AgentPositionSyncMsg syncList.Add(new AgentPositionSyncMsg
{ {
InstanceId = instanceId, InstanceId = instanceId,
@ -130,17 +117,31 @@ namespace XNet.Business
Position = new Vector3Msg { X = currPos.X, Y = currPos.Y, Z = currPos.Z }, Position = new Vector3Msg { X = currPos.X, Y = currPos.Y, Z = currPos.Z },
Rotation = currRot Rotation = currRot
}); });
lastStates.TryAdd(agentIdx, new AgentState { Position = currPos, Rotation = currRot }); // 更新缓存
lastStates[agentIdx] = new AgentState { Position = currPos, Rotation = currRot };
} }
} }
else
{
// 首次同步,直接加入并缓存
syncList.Add(new AgentPositionSyncMsg
{
InstanceId = instanceId,
AgentIdx = agentIdx,
Position = new Vector3Msg { X = currPos.X, Y = currPos.Y, Z = currPos.Z },
Rotation = currRot
});
lastStates.TryAdd(agentIdx, new AgentState { Position = currPos, Rotation = currRot });
}
} }
//}
return syncList; return syncList;
} }
// ========================================== // ===============================================
// 修复2重写 GetAgentRotation通过速度计算朝向 // 修复2重写 GetAgentRotation通过速度计算朝向
// ========================================== // ===============================================
public float GetAgentRotation(string instanceId, int agentIdx) public float GetAgentRotation(string instanceId, int agentIdx)
{ {
if (!_crowdInstances.TryGetValue(instanceId, out var ci)) if (!_crowdInstances.TryGetValue(instanceId, out var ci))
@ -148,28 +149,28 @@ namespace XNet.Business
return 0f; return 0f;
} }
lock (ci.SyncRoot) //lock (ci.SyncRoot)
//{
var agent = ci.Crowd.GetAgent(agentIdx);
if (agent == null || agent.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
{ {
var agent = ci.Crowd.GetAgent(agentIdx); return 0f;
if (agent == null || agent.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
{
return 0f;
}
// 核心逻辑通过Agent的速度向量计算绕Y轴的旋转角度弧度
// 速度向量 (vx, vz) -> 朝向角度 = atan2(vz, vx)
float vx = agent.vel.X;
float vz = -agent.vel.Z; // 和坐标转换一致Z轴取反
float rotation = 0f;
// 只有速度大于阈值时才计算朝向(避免静止时角度抖动)
if (Math.Abs(vx) > 0.001f || Math.Abs(vz) > 0.001f)
{
rotation = (float)Math.Atan2(vz, vx); // 计算弧度(范围 -π ~ π)
}
return rotation;
} }
// 核心逻辑通过Agent的速度向量计算绕Y轴的旋转角度弧度
// 速度向量 (vx, vz) -> 朝向角度 = atan2(vz, vx)
float vx = agent.vel.X;
float vz = -agent.vel.Z; // 和坐标转换一致Z轴取反
float rotation = 0f;
// 只有速度大于阈值时才计算朝向(避免静止时角度抖动)
if (Math.Abs(vx) > 0.001f || Math.Abs(vz) > 0.001f)
{
rotation = (float)Math.Atan2(vz, vx); // 计算弧度(范围 -π ~ π)
}
return rotation;
//}
} }
@ -214,45 +215,45 @@ namespace XNet.Business
throw new ArgumentException($"实例 {instanceId} 不存在"); throw new ArgumentException($"实例 {instanceId} 不存在");
} }
lock (ci.SyncRoot) //lock (ci.SyncRoot)
{ //{
// 创建Agent原有逻辑 // 创建Agent原有逻辑
var agentParams = new DtCrowdAgentParams(); var agentParams = new DtCrowdAgentParams();
agentParams.radius = radius; agentParams.radius = radius;
agentParams.height = height; agentParams.height = height;
agentParams.maxAcceleration = 2.0f; agentParams.maxAcceleration = 2.0f;
agentParams.maxSpeed = 3.0f; agentParams.maxSpeed = 3.0f;
agentParams.collisionQueryRange = radius * 8.0f; agentParams.collisionQueryRange = radius * 8.0f;
agentParams.pathOptimizationRange = radius * 32.0f; agentParams.pathOptimizationRange = radius * 32.0f;
agentParams.separationWeight = 1.0f; agentParams.separationWeight = 1.0f;
var rcPos = new RcVec3f(position.X, position.Y, -position.Z); // 坐标转换(和之前一致) var rcPos = new RcVec3f(position.X, position.Y, -position.Z); // 坐标转换(和之前一致)
int agentIdx = ci.Crowd.AddAgent(rcPos, agentParams).idx; int agentIdx = ci.Crowd.AddAgent(rcPos, agentParams).idx;
// 关键记录Agent索引到实例的AgentIndices // 关键记录Agent索引到实例的AgentIndices
ci.AddAgentIndex(agentIdx); ci.AddAgentIndex(agentIdx);
// 初始化状态缓存 // 初始化状态缓存
_agentLastState.GetOrAdd(instanceId, _ => new ConcurrentDictionary<int, AgentState>()) _agentLastState.GetOrAdd(instanceId, _ => new ConcurrentDictionary<int, AgentState>())
.TryAdd(agentIdx, new AgentState { Position = position, Rotation = 0f }); .TryAdd(agentIdx, new AgentState { Position = position, Rotation = 0f });
return agentIdx; return agentIdx;
} //}
} }
public void RemoveAgent(string instanceId, int agentIdx) public void RemoveAgent(string instanceId, int agentIdx)
{ {
if (!_crowdInstances.TryGetValue(instanceId, out var ci)) return; if (!_crowdInstances.TryGetValue(instanceId, out var ci)) return;
lock (ci.SyncRoot) //lock (ci.SyncRoot)
//{
var agent = ci.Crowd.GetAgent(agentIdx);
// 【修复】判断 Agent 是否有效,需要检查 state
if (agent != null && agent.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
{ {
var agent = ci.Crowd.GetAgent(agentIdx); ci.Crowd.RemoveAgent(agent);
// 【修复】判断 Agent 是否有效,需要检查 state
if (agent != null && agent.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
{
ci.Crowd.RemoveAgent(agent);
}
} }
//}
} }
public bool AgentGoto(string instanceId, int agentIdx, Vector3 destination) public bool AgentGoto(string instanceId, int agentIdx, Vector3 destination)
@ -267,14 +268,14 @@ namespace XNet.Business
if (targetRef == 0) return false; if (targetRef == 0) return false;
lock (ci.SyncRoot) //lock (ci.SyncRoot)
//{
var agent = ci.Crowd.GetAgent(agentIdx);
if (agent != null && agent.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
{ {
var agent = ci.Crowd.GetAgent(agentIdx); return ci.Crowd.RequestMoveTarget(agent, targetRef, realTargetPos);
if (agent != null && agent.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
{
return ci.Crowd.RequestMoveTarget(agent, targetRef, realTargetPos);
}
} }
//}
return false; return false;
} }
@ -288,17 +289,17 @@ namespace XNet.Business
return Vector3.Zero; return Vector3.Zero;
} }
lock (ci.SyncRoot) //lock (ci.SyncRoot)
//{
var agent = ci.Crowd.GetAgent(agentIdx);
if (agent == null || agent.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
{ {
var agent = ci.Crowd.GetAgent(agentIdx); return Vector3.Zero;
if (agent == null || agent.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
{
return Vector3.Zero;
}
// 坐标转换和AddAgent时一致Z轴取反
return new Vector3(agent.npos.X, agent.npos.Y, -agent.npos.Z);
} }
// 坐标转换和AddAgent时一致Z轴取反
return new Vector3(agent.npos.X, agent.npos.Y, -agent.npos.Z);
//}
} }
public void UpdateAll(float deltaTime) public void UpdateAll(float deltaTime)
@ -308,10 +309,10 @@ namespace XNet.Business
MaxDegreeOfParallelism = Environment.ProcessorCount // 限制并行数=CPU核心数避免过载 MaxDegreeOfParallelism = Environment.ProcessorCount // 限制并行数=CPU核心数避免过载
}, ci => }, ci =>
{ {
lock (ci.SyncRoot) //lock (ci.SyncRoot)
{ //{
ci.Crowd.Update(deltaTime, null); ci.Crowd.Update(deltaTime, null);
} //}
}); });
} }
} }

View File

@ -12,7 +12,7 @@ namespace XNet.Business
{ {
// ========== 原有核心字段 ========== // ========== 原有核心字段 ==========
private readonly ConcurrentDictionary<string, WebSocket> _connections = new(); private readonly ConcurrentDictionary<string, WebSocket> _connections = new();
private readonly ConcurrentDictionary<string, HashSet<string>> _instanceSubscribers = new(); private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, bool>> _instanceSubscribers = new();
private readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; private readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
// ========== 新增:对象池配置 ========== // ========== 新增:对象池配置 ==========
@ -48,10 +48,7 @@ namespace XNet.Business
{ {
foreach (var instanceId in _instanceSubscribers.Keys) foreach (var instanceId in _instanceSubscribers.Keys)
{ {
lock (_instanceSubscribers[instanceId]) _instanceSubscribers[instanceId].TryRemove(connId, out _);
{
_instanceSubscribers[instanceId].Remove(connId);
}
} }
Console.WriteLine($"[WS .NET 10] 连接断开:{connId},当前连接数:{_connections.Count}"); Console.WriteLine($"[WS .NET 10] 连接断开:{connId},当前连接数:{_connections.Count}");
} }
@ -65,17 +62,15 @@ namespace XNet.Business
return false; return false;
} }
_instanceSubscribers.GetOrAdd(instanceId, _ => new HashSet<string>()); _instanceSubscribers.GetOrAdd(instanceId, _ => new ConcurrentDictionary<string, bool>());
lock (_instanceSubscribers[instanceId])
{ _instanceSubscribers[instanceId].TryAdd(connId, true);
_instanceSubscribers[instanceId].Add(connId);
}
return true; return true;
} }
public async Task SendAgentPositionBatchAsync(List<AgentPositionSyncMsg> syncMsgs) public async Task SendAgentPositionBatchAsync(List<AgentPositionSyncMsg> syncMsgs)
{ {
if(syncMsgs.Count == 0) return; if (syncMsgs.Count == 0) return;
foreach (var group in syncMsgs.GroupBy(m => m.InstanceId)) foreach (var group in syncMsgs.GroupBy(m => m.InstanceId))
{ {
@ -109,7 +104,7 @@ namespace XNet.Business
} }
else else
{ {
msgBytes = tempMs.ToArray(); msgBytes = tempMs.ToArray();// 超出池化数组长度,使用新分配的数组
needReturnPooledBytes = false; needReturnPooledBytes = false;
} }
} }
@ -130,21 +125,19 @@ namespace XNet.Business
var deadConnIds = _deadConnListPool.Get(); var deadConnIds = _deadConnListPool.Get();
try try
{ {
lock (subscriberConnIds) foreach (var connKv in subscriberConnIds)
{ {
foreach (var connId in subscriberConnIds) if (_connections.TryGetValue(connKv.Key, out var socket))
{ {
if (_connections.TryGetValue(connId, out var socket)) if (needReturnPooledBytes)
{ {
// 3. 发送时传递有效长度 // 3. 发送时传递有效长度
if (needReturnPooledBytes) _ = SendToSingleConnAsync(socket, msgBytes, msgBytesLength, connKv.Key, deadConnIds);
{ }
_ = SendToSingleConnAsync(socket, msgBytes, msgBytesLength, connId, deadConnIds); else
} {
else // 3. 发送整串字节数组
{ _ = SendToSingleConnAsync(socket, msgBytes, msgBytes.Length, connKv.Key, deadConnIds);
_ = SendToSingleConnAsync(socket, msgBytes, msgBytes.Length, connId, deadConnIds);
}
} }
} }
} }

View File

@ -4,6 +4,7 @@
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<OutputType>Library</OutputType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>