fix(enemy): 配方校验改用一次性 builder 探测,不再干扰共享图缓存
This commit is contained in:
@@ -117,14 +117,15 @@ namespace BaseGames.Tests.EditMode.AI
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Validate_DoesNotLeaveCachedGraph()
|
public void Validate_DoesNotDisturbCachedGraph()
|
||||||
{
|
{
|
||||||
// 校验期试建的图不能留给运行时——否则改了模块后拿到的是校验时的旧图。
|
// 校验用一次性 builder 探测,不得动共享缓存——否则同一配方的下一只敌人
|
||||||
|
// 会拿到不同的图实例,破坏 flyweight 不变量。
|
||||||
var so = MakeE001Recipe();
|
var so = MakeE001Recipe();
|
||||||
|
var before = so.GetOrBuildGraph(); // 先把缓存捂热(不经 AssignModules)
|
||||||
Validate(so);
|
Validate(so);
|
||||||
so.AssignModules(new SinglePost(LocomotionMode.Idle),
|
var after = so.GetOrBuildGraph();
|
||||||
new RushEngagement("other", RushExit.OnLostTarget));
|
Assert.AreSame(before, after); // 校验前后必须是同一张图
|
||||||
Assert.AreEqual(SinglePost.Post, so.GetOrBuildGraph().EntryState);
|
|
||||||
Object.DestroyImmediate(so);
|
Object.DestroyImmediate(so);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,16 @@ namespace BaseGames.AI
|
|||||||
/// 不调的话,改动要等到下次进入 Play 才生效——静默的陈旧图。</summary>
|
/// 不调的话,改动要等到下次进入 Play 才生效——静默的陈旧图。</summary>
|
||||||
protected void InvalidateGraph() => _cached = null;
|
protected void InvalidateGraph() => _cached = null;
|
||||||
|
|
||||||
|
/// <summary>校验专用:用一次性 BrainBuilder 试建,只为暴露建图期异常。
|
||||||
|
/// 刻意不碰 _cached——走 GetOrBuildGraph 会在缓存已热时跳过 Build(等于没探测),
|
||||||
|
/// 且事后置空缓存会破坏"一个配方共享一张图"的 flyweight 不变量。</summary>
|
||||||
|
protected void ProbeBuild()
|
||||||
|
{
|
||||||
|
var b = new BrainBuilder();
|
||||||
|
Build(b);
|
||||||
|
b.Build();
|
||||||
|
}
|
||||||
|
|
||||||
public AiGraph GetOrBuildGraph()
|
public AiGraph GetOrBuildGraph()
|
||||||
{
|
{
|
||||||
if (_cached == null)
|
if (_cached == null)
|
||||||
|
|||||||
@@ -46,19 +46,24 @@ namespace BaseGames.Enemies
|
|||||||
yield return ValidationResult.Error($"{who}:未选择交战层模块。");
|
yield return ValidationResult.Error($"{who}:未选择交战层模块。");
|
||||||
if (_unaware == null || _engagement == null) yield break;
|
if (_unaware == null || _engagement == null) yield break;
|
||||||
|
|
||||||
|
bool moduleHasError = false;
|
||||||
foreach (var r in _engagement.Validate())
|
foreach (var r in _engagement.Validate())
|
||||||
|
{
|
||||||
|
if (r.Severity == ValidationSeverity.Error) moduleHasError = true;
|
||||||
yield return new ValidationResult(r.Severity, $"{who}:{r.Message}");
|
yield return new ValidationResult(r.Severity, $"{who}:{r.Message}");
|
||||||
|
}
|
||||||
|
if (moduleHasError) yield break; // 模块层已定位到问题,探测只会重复同一条
|
||||||
|
|
||||||
// 结构性校验:直接试建一次图,模块与骨架的建图期守卫会把问题抛出来
|
// 结构性校验:用一次性 builder 试建,模块与骨架的建图期守卫会把问题抛出来
|
||||||
// (能力漏配、Rest 不在 States、态未声明、跨模块状态名冲突等)。
|
// (能力漏配、Rest 不在 States、态未声明、跨模块状态名冲突等)。
|
||||||
// 这是最彻底的检查——凡是运行时会炸的,这里就会炸。
|
// 走 ProbeBuild 而非 GetOrBuildGraph:后者在缓存已热时会跳过 Build(等于没探测),
|
||||||
|
// 且事后置空缓存会破坏 flyweight 不变量。
|
||||||
ValidationResult? buildFailure = null;
|
ValidationResult? buildFailure = null;
|
||||||
try { GetOrBuildGraph(); }
|
try { ProbeBuild(); }
|
||||||
catch (System.Exception e)
|
catch (System.Exception e)
|
||||||
{
|
{
|
||||||
buildFailure = ValidationResult.Error($"{who}:建图失败 —— {e.Message}");
|
buildFailure = ValidationResult.Error($"{who}:建图失败 —— {e.Message}");
|
||||||
}
|
}
|
||||||
finally { InvalidateGraph(); } // 校验期建的图不留给运行时
|
|
||||||
|
|
||||||
if (buildFailure.HasValue) yield return buildFailure.Value;
|
if (buildFailure.HasValue) yield return buildFailure.Value;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user