feat: 测试存档功能
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AibisDream.SaveSystem
|
||||
{
|
||||
public enum TestSaveCoverageRowKind
|
||||
{
|
||||
Recorded,
|
||||
Missing,
|
||||
Invalid
|
||||
}
|
||||
|
||||
public sealed class TestSaveCoverageRow
|
||||
{
|
||||
public TestSaveCoverageRowKind Kind { get; internal set; }
|
||||
public string NodeName { get; internal set; }
|
||||
public TestSaveEntry Entry { get; internal set; }
|
||||
public long SortOrder { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class TestSaveChapterCoverage
|
||||
{
|
||||
public string ChapterId { get; internal set; }
|
||||
public string Title { get; internal set; }
|
||||
public int ChapterOrder { get; internal set; }
|
||||
public int RecordedCount { get; internal set; }
|
||||
public int ExpectedCount { get; internal set; }
|
||||
public IReadOnlyList<TestSaveCoverageRow> Rows { get; internal set; } = Array.Empty<TestSaveCoverageRow>();
|
||||
public bool IsOrphanGroup { get; internal set; }
|
||||
}
|
||||
|
||||
public static class TestSaveCoverage
|
||||
{
|
||||
public const string OrphanChapterId = "__invalid_or_orphan__";
|
||||
|
||||
public static IReadOnlyList<TestSaveChapterCoverage> Build(
|
||||
IReadOnlyList<TalkSceneSO> chapters,
|
||||
IReadOnlyList<TestSaveEntry> entries)
|
||||
{
|
||||
chapters ??= Array.Empty<TalkSceneSO>();
|
||||
entries ??= Array.Empty<TestSaveEntry>();
|
||||
var result = new List<TestSaveChapterCoverage>();
|
||||
var consumed = new HashSet<TestSaveEntry>();
|
||||
|
||||
for (var chapterIndex = 0; chapterIndex < chapters.Count; chapterIndex++)
|
||||
{
|
||||
var chapter = chapters[chapterIndex];
|
||||
if (chapter == null) continue;
|
||||
|
||||
var expectedNodes = GetExpectedNodes(chapter);
|
||||
var chapterEntries = entries
|
||||
.Where(item => item.Meta != null
|
||||
&& string.Equals(item.Meta.sceneSoName, chapter.name, StringComparison.Ordinal)
|
||||
&& string.Equals(
|
||||
item.Meta.yarnProjectId,
|
||||
chapter.yarnProject?.name,
|
||||
StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
foreach (var entry in chapterEntries) consumed.Add(entry);
|
||||
|
||||
var validByNode = chapterEntries
|
||||
.Where(item => item.IsValid)
|
||||
.GroupBy(item => item.NodeName, StringComparer.Ordinal)
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group => group.OrderBy(item => item.Meta.firstSeenOrder).First(),
|
||||
StringComparer.Ordinal);
|
||||
var rows = new List<TestSaveCoverageRow>();
|
||||
foreach (var entry in validByNode.Values.OrderBy(item => item.Meta.firstSeenOrder))
|
||||
{
|
||||
rows.Add(new TestSaveCoverageRow
|
||||
{
|
||||
Kind = TestSaveCoverageRowKind.Recorded,
|
||||
NodeName = entry.NodeName,
|
||||
Entry = entry,
|
||||
SortOrder = entry.Meta.firstSeenOrder
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var nodeName in expectedNodes
|
||||
.Where(node => !validByNode.ContainsKey(node))
|
||||
.OrderBy(node => node, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
rows.Add(new TestSaveCoverageRow
|
||||
{
|
||||
Kind = TestSaveCoverageRowKind.Missing,
|
||||
NodeName = nodeName,
|
||||
SortOrder = long.MaxValue
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var entry in chapterEntries.Where(item => !item.IsValid)
|
||||
.OrderBy(item => item.Meta?.firstSeenOrder ?? long.MaxValue))
|
||||
{
|
||||
rows.Add(new TestSaveCoverageRow
|
||||
{
|
||||
Kind = TestSaveCoverageRowKind.Invalid,
|
||||
NodeName = entry.NodeName ?? "(未知节点)",
|
||||
Entry = entry,
|
||||
SortOrder = entry.Meta?.firstSeenOrder ?? long.MaxValue
|
||||
});
|
||||
}
|
||||
|
||||
result.Add(new TestSaveChapterCoverage
|
||||
{
|
||||
ChapterId = chapter.name,
|
||||
Title = string.IsNullOrWhiteSpace(chapter.title) ? chapter.name : chapter.title,
|
||||
ChapterOrder = chapterIndex,
|
||||
RecordedCount = validByNode.Keys.Count(expectedNodes.Contains),
|
||||
ExpectedCount = expectedNodes.Count,
|
||||
Rows = rows
|
||||
});
|
||||
}
|
||||
|
||||
var orphanEntries = entries.Where(item => !consumed.Contains(item)).ToArray();
|
||||
if (orphanEntries.Length > 0)
|
||||
{
|
||||
result.Add(new TestSaveChapterCoverage
|
||||
{
|
||||
ChapterId = OrphanChapterId,
|
||||
Title = "无效 / 已脱离当前流程",
|
||||
ChapterOrder = int.MaxValue,
|
||||
RecordedCount = 0,
|
||||
ExpectedCount = 0,
|
||||
IsOrphanGroup = true,
|
||||
Rows = orphanEntries
|
||||
.OrderBy(item => item.Meta?.firstSeenOrder ?? long.MaxValue)
|
||||
.Select(item => new TestSaveCoverageRow
|
||||
{
|
||||
Kind = TestSaveCoverageRowKind.Invalid,
|
||||
NodeName = item.NodeName ?? "(未知节点)",
|
||||
Entry = item,
|
||||
SortOrder = item.Meta?.firstSeenOrder ?? long.MaxValue
|
||||
})
|
||||
.ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static HashSet<string> GetExpectedNodes(TalkSceneSO chapter)
|
||||
{
|
||||
var result = new HashSet<string>(StringComparer.Ordinal);
|
||||
var project = chapter?.yarnProject;
|
||||
if (project?.Program?.Nodes == null) return result;
|
||||
|
||||
foreach (var pair in project.Program.Nodes)
|
||||
{
|
||||
var node = pair.Value;
|
||||
if (node == null || node.Name.StartsWith("$", StringComparison.Ordinal)) continue;
|
||||
var tagsValue = node.Headers
|
||||
.FirstOrDefault(header => string.Equals(header.Key, "tags", StringComparison.OrdinalIgnoreCase))
|
||||
?.Value;
|
||||
var tags = string.IsNullOrWhiteSpace(tagsValue)
|
||||
? Array.Empty<string>()
|
||||
: tagsValue.Split(Array.Empty<char>(), StringSplitOptions.RemoveEmptyEntries);
|
||||
if (SavePointEvaluator.EvaluateNodeTagsForAutoSaveSilently(node.Name, tags, out _))
|
||||
{
|
||||
result.Add(node.Name);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user