using System; using System.Collections.Generic; namespace AibisDream { /// /// 从现有 TalkSceneSO 图派生的运行时索引,不引入第二份章节创作数据。 /// public sealed class TalkSceneGraphIndex { private readonly List _runtimeChapters = new(); private readonly Dictionary _byName = new(StringComparer.Ordinal); private readonly HashSet _ambiguousNames = new(StringComparer.Ordinal); private readonly List _validationErrors = new(); public IReadOnlyList RuntimeChapters => _runtimeChapters; public IReadOnlyList ValidationErrors => _validationErrors; public TalkSceneGraphIndex(TalkSceneSO firstTalkScene) { var visited = new HashSet(); VisitRuntime(firstTalkScene, visited); } public bool TryFindByName(string sceneName, out TalkSceneSO scene, out string error) { scene = null; if (string.IsNullOrWhiteSpace(sceneName)) { error = "TalkSceneSO name is empty."; return false; } if (_ambiguousNames.Contains(sceneName)) { error = $"TalkSceneSO name is ambiguous: {sceneName}."; return false; } if (!_byName.TryGetValue(sceneName, out scene) || scene == null) { error = $"TalkSceneSO does not exist: {sceneName}."; return false; } error = null; return true; } private void VisitRuntime(TalkSceneSO scene, ISet visited) { if (scene == null || !visited.Add(scene)) { return; } _runtimeChapters.Add(scene); IndexByName(scene); if (scene.exits == null) { return; } foreach (var exit in scene.exits) { VisitRuntime(exit?.targetScene, visited); } } private void IndexByName(TalkSceneSO scene) { if (scene == null) { return; } var sceneName = scene.name; if (string.IsNullOrWhiteSpace(sceneName)) { _validationErrors.Add("TalkSceneSO has an empty asset name."); return; } if (_byName.TryGetValue(sceneName, out var existing)) { if (existing != scene && _ambiguousNames.Add(sceneName)) { _validationErrors.Add($"Duplicate TalkSceneSO name: {sceneName}."); } return; } _byName.Add(sceneName, scene); } } }