refactor(EnemyManager): 性能优化与并发安全修复

- EnemyRegistry: 移除List冗余存储, Register/Remove从O(n)→O(1)
- 增加PruneInvalidEntries显式清理接口, 消除TryGet副作用
- Remove增加不存在告警, 防重复减成负数
- 增加CTS取消飞行中的异步生成, 关卡切换时取消+重建
- ClearEnemies先快照再遍历, 防Hide回调修改集合抛异常
- entityId去掉取模复用, 直接自增保证唯一
- Enemy EntityGroup调优: Capacity→10000, ReleaseInterval→30, ExpireTime→120
This commit is contained in:
2026-06-18 17:06:10 +08:00
parent 49c300a10e
commit 000984c676
4 changed files with 56 additions and 52 deletions
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using SepCore.DataTable;
using SepCore.Definition;
@@ -19,7 +20,7 @@ namespace SepCore.EnemyManager
private EnemyRegistry _enemyRegistry;
private EnemySpawnScheduler _spawnScheduler;
public List<EntityBase> Enemies => _enemyRegistry?.Enemies;
public IReadOnlyCollection<EntityBase> Enemies => _enemyRegistry?.Enemies;
[SerializeField] private int _spawnEnemyMaxCount = 5000;
[SerializeField] private int _spawnDistanceFromPlayer = 20;
@@ -31,6 +32,7 @@ namespace SepCore.EnemyManager
private Transform _player;
private ISpawnPositionStrategy _spawnPositionStrategy;
private CancellationTokenSource _spawnCts;
public float SpawnRateScale => _spawnScheduler?.SpawnRateScale ?? 1f;
public float BattleDuration => _duration;
@@ -44,6 +46,7 @@ namespace SepCore.EnemyManager
_entity = GameEntry.Entity;
_enemyRegistry = new EnemyRegistry();
_spawnScheduler = new EnemySpawnScheduler();
_spawnCts = CancellationTokenSource.CreateLinkedTokenSource(destroyCancellationToken);
GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
}
@@ -52,6 +55,8 @@ namespace SepCore.EnemyManager
{
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
_spawnCts?.Dispose();
_spawnCts = null;
_enemyRegistry = null;
_spawnScheduler = null;
_entity = null;
@@ -73,6 +78,7 @@ namespace SepCore.EnemyManager
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_enemyRegistry.PruneInvalidEntries();
var spawnRequests = _spawnScheduler.Tick(elapseSeconds);
foreach (var request in spawnRequests)
{
@@ -85,6 +91,10 @@ namespace SepCore.EnemyManager
public void OnReset()
{
_spawnCts.Cancel();
_spawnCts.Dispose();
_spawnCts = CancellationTokenSource.CreateLinkedTokenSource(destroyCancellationToken);
_spawnScheduler.Reset();
ClearEnemies();
_currentSpawnEnemyId = 0;
@@ -100,14 +110,20 @@ namespace SepCore.EnemyManager
if (_player == null) return;
if (_enemyRegistry.Count >= _spawnEnemyMaxCount) return;
int entityPoolId = _currentSpawnEnemyId % _spawnEnemyMaxCount;
var enemyData = EntityDataFactory.Create(entityPoolId, enemyType, _currentLevel);
int entityId = _currentSpawnEnemyId++;
var enemyData = EntityDataFactory.Create(entityId, enemyType, _currentLevel);
enemyData.Position = _spawnPositionStrategy.GetSpawnPosition(_player);
_currentSpawnEnemyId++;
EnemyBase enemy = await _entity.ShowEnemyAsync(enemyData);
if (enemy == null)
var ct = _spawnCts.Token;
var (isCanceled, enemy) =
await _entity.ShowEnemyAsync(enemyData, cancellationToken: ct).SuppressCancellationThrow();
if (isCanceled || ct.IsCancellationRequested || enemy == null || !enemy.Available)
{
if (enemy != null && enemy.Available)
{
_entity.HideEntity(enemy);
}
return;
}
@@ -115,12 +131,14 @@ namespace SepCore.EnemyManager
{
enemy.SetTarget(_player);
}
_enemyRegistry.Register(enemy);
}
public void ClearEnemies()
private void ClearEnemies()
{
foreach (var enemy in _enemyRegistry.Enemies)
var enemies = new List<EntityBase>(_enemyRegistry.Enemies);
foreach (var enemy in enemies)
{
if (enemy == null || !enemy.Available) continue;
_entity.HideEntity(enemy);