537 lines
23 KiB
C#
537 lines
23 KiB
C#
#if UNITY_EDITOR
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using AibisDream.SaveSystem;
|
||
using AibisDream.Utility;
|
||
using Newtonsoft.Json;
|
||
using NUnit.Framework;
|
||
using UnityEditor;
|
||
using UnityEditor.AddressableAssets;
|
||
using UnityEditor.AddressableAssets.Settings;
|
||
using UnityEngine;
|
||
using UnityEngine.TestTools;
|
||
|
||
namespace AibisDream.EditorTools
|
||
{
|
||
/// <summary>从本机 testsavs 选择章节、去重排序并提升为可提交测试档。</summary>
|
||
public sealed class DevSavePromoteWindow : EditorWindow
|
||
{
|
||
private readonly List<ArchiveGroup> groups = new();
|
||
private readonly List<Candidate> preview = new();
|
||
private int selectedGroup;
|
||
private string sectionId = string.Empty;
|
||
private string sectionTitle = string.Empty;
|
||
private string status = string.Empty;
|
||
private Vector2 scroll;
|
||
|
||
[MenuItem("Tools/Aibis/Dev Save Promote")]
|
||
public static void Open()
|
||
{
|
||
var window = GetWindow<DevSavePromoteWindow>("Dev Save Promote");
|
||
window.minSize = new Vector2(720f, 520f);
|
||
window.Show();
|
||
}
|
||
|
||
private void OnEnable() => RefreshGroups();
|
||
|
||
private void OnGUI()
|
||
{
|
||
EditorGUILayout.LabelField("测试存档提升与校验", EditorStyles.boldLabel);
|
||
EditorGUILayout.HelpBox(
|
||
"来源是本机 testsavs;目标是项目根目录的 DevSaveFiles。该目录可提交,但不会被 Unity 自动打包。",
|
||
MessageType.Info);
|
||
|
||
using (new EditorGUILayout.HorizontalScope())
|
||
{
|
||
if (GUILayout.Button("刷新本机归档", GUILayout.Height(26f))) RefreshGroups();
|
||
if (GUILayout.Button("校验全部已提交测试档", GUILayout.Height(26f))) ValidateCommittedCatalog();
|
||
}
|
||
|
||
if (groups.Count == 0)
|
||
{
|
||
EditorGUILayout.HelpBox("本机没有可用 testsavs。", MessageType.Warning);
|
||
DrawStatus();
|
||
return;
|
||
}
|
||
|
||
var labels = groups.Select(group =>
|
||
$"{group.SceneSoName} | {group.YarnProjectId} | {group.SceneName} ({group.Items.Count})").ToArray();
|
||
var nextGroup = EditorGUILayout.Popup("章节归档", Mathf.Clamp(selectedGroup, 0, labels.Length - 1), labels);
|
||
if (nextGroup != selectedGroup || preview.Count == 0)
|
||
{
|
||
selectedGroup = nextGroup;
|
||
SelectGroup(groups[selectedGroup]);
|
||
}
|
||
|
||
sectionId = EditorGUILayout.TextField("Section Id", sectionId);
|
||
sectionTitle = EditorGUILayout.TextField("显示名称", sectionTitle);
|
||
EditorGUILayout.LabelField("规则", "每个 Yarn 节点保留 savedAt 最新一份;默认按 savedAt 升序");
|
||
|
||
using (new EditorGUILayout.HorizontalScope())
|
||
{
|
||
GUILayout.FlexibleSpace();
|
||
if (GUILayout.Button("提升当前章节", GUILayout.Width(180f), GUILayout.Height(28f))) Promote();
|
||
}
|
||
|
||
EditorGUILayout.Space(5f);
|
||
EditorGUILayout.LabelField($"阶段预览:{preview.Count}", EditorStyles.boldLabel);
|
||
scroll = EditorGUILayout.BeginScrollView(scroll);
|
||
for (var i = 0; i < preview.Count; i++)
|
||
{
|
||
var item = preview[i];
|
||
using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox))
|
||
{
|
||
EditorGUILayout.LabelField($"{i + 1:000}", GUILayout.Width(38f));
|
||
item.Label = EditorGUILayout.TextField(item.Label, GUILayout.Width(210f));
|
||
EditorGUILayout.LabelField(item.NodeName, GUILayout.MinWidth(180f));
|
||
EditorGUILayout.LabelField(item.SavedAt, GUILayout.Width(140f));
|
||
using (new EditorGUI.DisabledScope(i == 0))
|
||
{
|
||
if (GUILayout.Button("↑", GUILayout.Width(26f))) Move(i, i - 1);
|
||
}
|
||
using (new EditorGUI.DisabledScope(i == preview.Count - 1))
|
||
{
|
||
if (GUILayout.Button("↓", GUILayout.Width(26f))) Move(i, i + 1);
|
||
}
|
||
}
|
||
}
|
||
EditorGUILayout.EndScrollView();
|
||
DrawStatus();
|
||
}
|
||
|
||
private void DrawStatus()
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(status))
|
||
EditorGUILayout.HelpBox(status, status.StartsWith("OK", StringComparison.Ordinal) ? MessageType.Info : MessageType.Warning);
|
||
}
|
||
|
||
private void RefreshGroups()
|
||
{
|
||
groups.Clear();
|
||
preview.Clear();
|
||
var root = ConstRef.TestAutoSaveArchivePath;
|
||
if (!Directory.Exists(root))
|
||
{
|
||
status = $"找不到本机归档:{root}";
|
||
return;
|
||
}
|
||
|
||
var candidates = new List<Candidate>();
|
||
foreach (var directory in Directory.GetDirectories(root))
|
||
{
|
||
var snapshotPath = Path.Combine(directory, $"{ConstRef.SaveSnapshotFileName}.json");
|
||
if (!File.Exists(snapshotPath)) continue;
|
||
try
|
||
{
|
||
var snapshot = SnapshotPersistence.Load(Path.Combine(directory, ConstRef.SaveSnapshotFileName));
|
||
if (snapshot?.anchor == null || string.IsNullOrWhiteSpace(snapshot.anchor.nodeName)) continue;
|
||
candidates.Add(new Candidate(
|
||
snapshotPath,
|
||
Path.Combine(directory, $"{ConstRef.SaveMetaFileName}.json"),
|
||
snapshot.scene?.sceneName,
|
||
snapshot.anchor.sceneSoName,
|
||
snapshot.anchor.yarnProjectId,
|
||
snapshot.anchor.nodeName,
|
||
snapshot.savedAt));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogWarning($"[DevSavePromote] 跳过损坏归档 {snapshotPath}: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
groups.AddRange(candidates
|
||
.GroupBy(item => $"{item.SceneName}\n{item.SceneSoName}\n{item.YarnProjectId}", StringComparer.Ordinal)
|
||
.Select(group => new ArchiveGroup(group.ToList()))
|
||
.OrderBy(group => group.SceneSoName, StringComparer.Ordinal));
|
||
selectedGroup = Mathf.Clamp(selectedGroup, 0, Math.Max(0, groups.Count - 1));
|
||
if (groups.Count > 0) SelectGroup(groups[selectedGroup]);
|
||
status = $"OK:读取 {candidates.Count} 份归档,分为 {groups.Count} 个章节。";
|
||
}
|
||
|
||
private void SelectGroup(ArchiveGroup group)
|
||
{
|
||
preview.Clear();
|
||
preview.AddRange(group.Items
|
||
.GroupBy(item => item.NodeName, StringComparer.Ordinal)
|
||
.Select(nodes => nodes.OrderByDescending(item => item.SavedAt, StringComparer.Ordinal).First())
|
||
.OrderBy(item => item.SavedAt, StringComparer.Ordinal));
|
||
sectionId = SanitizeIdentifier(group.SceneSoName);
|
||
sectionTitle = group.SceneSoName;
|
||
}
|
||
|
||
private void Move(int from, int to)
|
||
{
|
||
if (from < 0 || to < 0 || from >= preview.Count || to >= preview.Count) return;
|
||
var item = preview[from];
|
||
preview.RemoveAt(from);
|
||
preview.Insert(to, item);
|
||
GUI.FocusControl(null);
|
||
}
|
||
|
||
private void Promote()
|
||
{
|
||
if (preview.Count == 0 || string.IsNullOrWhiteSpace(sectionId))
|
||
{
|
||
status = "没有可提升阶段,或 Section Id 为空。";
|
||
return;
|
||
}
|
||
|
||
var validation = ValidateCandidates(preview);
|
||
if (validation.Count > 0)
|
||
{
|
||
status = string.Join("\n", validation);
|
||
return;
|
||
}
|
||
|
||
var group = groups[selectedGroup];
|
||
var root = ConstRef.TestSaveFilePath;
|
||
var targetSection = Path.Combine(root, sectionId);
|
||
if (Directory.Exists(targetSection)) Directory.Delete(targetSection, true);
|
||
Directory.CreateDirectory(targetSection);
|
||
|
||
var catalog = LoadCatalogForWrite();
|
||
catalog.sections.RemoveAll(section => string.Equals(section.id, sectionId, StringComparison.OrdinalIgnoreCase));
|
||
var sectionDto = new DevSaveCatalogSectionDto
|
||
{
|
||
order = FindDemoChapterOrder(group.SceneSoName),
|
||
id = sectionId,
|
||
title = string.IsNullOrWhiteSpace(sectionTitle) ? sectionId : sectionTitle,
|
||
expectedSceneName = group.SceneName,
|
||
expectedSceneSoName = group.SceneSoName,
|
||
expectedYarnProjectId = group.YarnProjectId
|
||
};
|
||
|
||
for (var i = 0; i < preview.Count; i++)
|
||
{
|
||
var item = preview[i];
|
||
var folder = $"{i + 1:000}_{SanitizeIdentifier(item.NodeName)}";
|
||
var destination = Path.Combine(targetSection, folder);
|
||
Directory.CreateDirectory(destination);
|
||
File.Copy(item.SnapshotPath, Path.Combine(destination, $"{ConstRef.SaveSnapshotFileName}.json"), true);
|
||
if (File.Exists(item.MetaPath))
|
||
File.Copy(item.MetaPath, Path.Combine(destination, $"{ConstRef.SaveMetaFileName}.json"), true);
|
||
sectionDto.entries.Add(new DevSaveCatalogEntryDto
|
||
{
|
||
order = i + 1,
|
||
label = string.IsNullOrWhiteSpace(item.Label) ? item.NodeName : item.Label,
|
||
path = $"{sectionId}/{folder}",
|
||
anchorNode = item.NodeName
|
||
});
|
||
}
|
||
|
||
catalog.sections.Add(sectionDto);
|
||
catalog.sections.Sort((left, right) => left.order.CompareTo(right.order));
|
||
WriteCatalog(catalog);
|
||
AssetDatabase.Refresh();
|
||
ValidateCommittedCatalog();
|
||
}
|
||
|
||
private static List<string> ValidateCandidates(IEnumerable<Candidate> candidates)
|
||
{
|
||
var errors = new List<string>();
|
||
var nodes = new HashSet<string>(StringComparer.Ordinal);
|
||
foreach (var item in candidates)
|
||
{
|
||
if (!nodes.Add(item.NodeName)) errors.Add($"重复节点:{item.NodeName}");
|
||
try
|
||
{
|
||
var snapshot = SnapshotPersistence.Load(Path.Combine(Path.GetDirectoryName(item.SnapshotPath)!, ConstRef.SaveSnapshotFileName));
|
||
if (snapshot.schemaVersion != SaveSnapshotSchema.CurrentVersion)
|
||
errors.Add($"{item.NodeName}: schema {snapshot.schemaVersion} 不兼容");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
errors.Add($"{item.NodeName}: {ex.Message}");
|
||
}
|
||
}
|
||
return errors;
|
||
}
|
||
|
||
private void ValidateCommittedCatalog()
|
||
{
|
||
var sections = DevSaveCatalog.Load(ConstRef.TestSaveFilePath);
|
||
var errors = new List<string>();
|
||
if (!string.IsNullOrWhiteSpace(DevSaveCatalog.LastError)) errors.Add(DevSaveCatalog.LastError);
|
||
foreach (var section in sections)
|
||
{
|
||
var orders = new HashSet<int>();
|
||
var nodes = new HashSet<string>(StringComparer.Ordinal);
|
||
foreach (var entry in section.Entries)
|
||
{
|
||
if (!orders.Add(entry.Order)) errors.Add($"{section.Id}: 重复 order {entry.Order}");
|
||
if (!nodes.Add(entry.AnchorNode)) errors.Add($"{section.Id}: 重复节点 {entry.AnchorNode}");
|
||
if (!entry.IsValid) errors.Add($"{section.Id}/{entry.Label}: {entry.Error}");
|
||
}
|
||
}
|
||
|
||
var dto = LoadCatalogForWrite();
|
||
foreach (var section in dto.sections)
|
||
{
|
||
if (!SceneAddressExists(section.expectedSceneName))
|
||
errors.Add($"{section.id}: Addressable 场景不存在 {section.expectedSceneName}");
|
||
var sceneSo = FindTalkSceneSo(section.expectedSceneSoName);
|
||
if (sceneSo == null)
|
||
errors.Add($"{section.id}: TalkSceneSO 不存在 {section.expectedSceneSoName}");
|
||
else if (sceneSo.yarnProject == null || sceneSo.yarnProject.name != section.expectedYarnProjectId)
|
||
errors.Add($"{section.id}: TalkSceneSO 的 YarnProject 不匹配 {section.expectedYarnProjectId}");
|
||
else
|
||
{
|
||
var nodeNames = new HashSet<string>(sceneSo.yarnProject.NodeNames, StringComparer.Ordinal);
|
||
foreach (var entry in section.entries.Where(entry => !nodeNames.Contains(entry.anchorNode)))
|
||
errors.Add($"{section.id}: Yarn 节点不存在 {entry.anchorNode}");
|
||
}
|
||
}
|
||
|
||
status = errors.Count == 0
|
||
? $"OK:{sections.Count} 个章节、{sections.Sum(s => s.Entries.Count)} 个阶段全部通过校验。"
|
||
: $"校验失败({errors.Count}):\n{string.Join("\n", errors)}";
|
||
if (errors.Count > 0) Debug.LogError($"[DevSavePromote] {status}");
|
||
else Debug.Log($"[DevSavePromote] {status}");
|
||
}
|
||
|
||
private static bool SceneAddressExists(string address)
|
||
{
|
||
var settings = AddressableAssetSettingsDefaultObject.Settings;
|
||
return settings != null && settings.groups
|
||
.Where(group => group != null)
|
||
.SelectMany(group => group.entries)
|
||
.Any(entry => string.Equals(entry.address, address, StringComparison.Ordinal));
|
||
}
|
||
|
||
private static TalkSceneSO FindTalkSceneSo(string assetName)
|
||
{
|
||
foreach (var guid in AssetDatabase.FindAssets($"t:TalkSceneSO {assetName}"))
|
||
{
|
||
var asset = AssetDatabase.LoadAssetAtPath<TalkSceneSO>(AssetDatabase.GUIDToAssetPath(guid));
|
||
if (asset != null && asset.name == assetName) return asset;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private static int FindDemoChapterOrder(string sceneSoName)
|
||
{
|
||
const string firstChapterPath = "Assets/ScriptableObjects/SceneSO/Demo/Day0_Prologue.asset";
|
||
var chapter = AssetDatabase.LoadAssetAtPath<TalkSceneSO>(firstChapterPath);
|
||
var visited = new HashSet<TalkSceneSO>();
|
||
var order = 1;
|
||
while (chapter != null && visited.Add(chapter))
|
||
{
|
||
if (string.Equals(chapter.name, sceneSoName, StringComparison.Ordinal)) return order;
|
||
chapter = chapter.GetNextScene();
|
||
order++;
|
||
}
|
||
|
||
return int.MaxValue;
|
||
}
|
||
|
||
private static DevSaveCatalogDto LoadCatalogForWrite()
|
||
{
|
||
var path = Path.Combine(ConstRef.TestSaveFilePath, "catalog.json");
|
||
if (!File.Exists(path)) return new DevSaveCatalogDto();
|
||
try
|
||
{
|
||
return JsonConvert.DeserializeObject<DevSaveCatalogDto>(File.ReadAllText(path, Encoding.UTF8))
|
||
?? new DevSaveCatalogDto();
|
||
}
|
||
catch
|
||
{
|
||
return new DevSaveCatalogDto();
|
||
}
|
||
}
|
||
|
||
private static void WriteCatalog(DevSaveCatalogDto catalog)
|
||
{
|
||
Directory.CreateDirectory(ConstRef.TestSaveFilePath);
|
||
File.WriteAllText(
|
||
Path.Combine(ConstRef.TestSaveFilePath, "catalog.json"),
|
||
JsonConvert.SerializeObject(catalog, Formatting.Indented),
|
||
new UTF8Encoding(false));
|
||
}
|
||
|
||
private static string SanitizeIdentifier(string value)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(value)) return "unnamed";
|
||
foreach (var character in Path.GetInvalidFileNameChars()) value = value.Replace(character, '_');
|
||
value = value.Replace(' ', '_');
|
||
return value.Length <= 48 ? value : value.Substring(0, 48);
|
||
}
|
||
|
||
private sealed class ArchiveGroup
|
||
{
|
||
public readonly List<Candidate> Items;
|
||
public string SceneName => Items[0].SceneName;
|
||
public string SceneSoName => Items[0].SceneSoName;
|
||
public string YarnProjectId => Items[0].YarnProjectId;
|
||
public ArchiveGroup(List<Candidate> items) => Items = items;
|
||
}
|
||
|
||
private sealed class Candidate
|
||
{
|
||
public readonly string SnapshotPath;
|
||
public readonly string MetaPath;
|
||
public readonly string SceneName;
|
||
public readonly string SceneSoName;
|
||
public readonly string YarnProjectId;
|
||
public readonly string NodeName;
|
||
public readonly string SavedAt;
|
||
public string Label;
|
||
|
||
public Candidate(string snapshotPath, string metaPath, string sceneName, string sceneSoName,
|
||
string yarnProjectId, string nodeName, string savedAt)
|
||
{
|
||
SnapshotPath = snapshotPath;
|
||
MetaPath = metaPath;
|
||
SceneName = sceneName;
|
||
SceneSoName = sceneSoName;
|
||
YarnProjectId = yarnProjectId;
|
||
NodeName = nodeName;
|
||
SavedAt = savedAt ?? string.Empty;
|
||
Label = nodeName;
|
||
}
|
||
}
|
||
}
|
||
|
||
[TestFixture]
|
||
public sealed class DevSaveCatalogTests
|
||
{
|
||
private string root;
|
||
|
||
[SetUp]
|
||
public void SetUp()
|
||
{
|
||
root = Path.Combine(Path.GetTempPath(), $"aibis-dev-save-{Guid.NewGuid():N}");
|
||
Directory.CreateDirectory(root);
|
||
}
|
||
|
||
[TearDown]
|
||
public void TearDown()
|
||
{
|
||
if (Directory.Exists(root)) Directory.Delete(root, true);
|
||
}
|
||
|
||
[Test]
|
||
public void CatalogUsesExplicitOrderAndKeepsInvalidEntriesVisible()
|
||
{
|
||
WriteCatalog(new DevSaveCatalogEntryDto { order = 20, label = "late", path = "S/late", anchorNode = "Late" },
|
||
new DevSaveCatalogEntryDto { order = 10, label = "early", path = "S/early", anchorNode = "Early" });
|
||
|
||
var section = DevSaveCatalog.Load(root).Single();
|
||
|
||
Assert.That(section.Entries.Select(entry => entry.Label), Is.EqualTo(new[] { "early", "late" }));
|
||
Assert.That(section.Entries.All(entry => !entry.IsValid), Is.True);
|
||
Assert.That(section.Entries.All(entry => entry.Error.Contains("缺失")), Is.True);
|
||
}
|
||
|
||
[Test]
|
||
public void CatalogRejectsPathTraversal()
|
||
{
|
||
WriteCatalog(new DevSaveCatalogEntryDto { order = 1, label = "unsafe", path = "../outside", anchorNode = "Start" });
|
||
|
||
var entry = DevSaveCatalog.Load(root).Single().Entries.Single();
|
||
|
||
Assert.That(entry.IsValid, Is.False);
|
||
Assert.That(entry.Error, Does.Contain("越过"));
|
||
}
|
||
|
||
[Test]
|
||
public void CatalogReportsCorruptSchemaAndAnchorMismatch()
|
||
{
|
||
WriteSnapshot("S/corrupt", "{");
|
||
WriteSnapshot("S/schema", JsonConvert.SerializeObject(BuildSnapshot("Node", schema: 99)));
|
||
WriteSnapshot("S/node", JsonConvert.SerializeObject(BuildSnapshot("Actual")));
|
||
WriteCatalog(
|
||
new DevSaveCatalogEntryDto { order = 1, label = "corrupt", path = "S/corrupt", anchorNode = "Node" },
|
||
new DevSaveCatalogEntryDto { order = 2, label = "schema", path = "S/schema", anchorNode = "Node" },
|
||
new DevSaveCatalogEntryDto { order = 3, label = "node", path = "S/node", anchorNode = "Expected" });
|
||
|
||
var entries = DevSaveCatalog.Load(root).Single().Entries;
|
||
|
||
Assert.That(entries[0].Error, Does.Contain("JSON"));
|
||
Assert.That(entries[1].Error, Does.Contain("版本"));
|
||
Assert.That(entries[2].Error, Does.Contain("节点不匹配"));
|
||
}
|
||
|
||
[Test]
|
||
public void CommittedCatalogContainsAllDemoChaptersWithValidUniqueEntries()
|
||
{
|
||
var sections = DevSaveCatalog.Load(ConstRef.TestSaveFilePath);
|
||
Assert.That(DevSaveCatalog.LastError, Is.Null);
|
||
Assert.That(sections.Select(section => section.Id), Is.EqualTo(new[]
|
||
{
|
||
"Day0Prologue", "Day1Begin", "Peipei1", "Day1Mid", "Day1Night", "Day1Sleep",
|
||
"Day2Begin", "Huoshan1", "Day2Mid", "Peipei2", "Day2Night", "Day2Sleep", "GeneralEnd"
|
||
}));
|
||
foreach (var section in sections)
|
||
{
|
||
Assert.That(section.Entries.All(entry => entry.IsValid), Is.True,
|
||
string.Join("\n", section.Entries.Where(entry => !entry.IsValid).Select(entry => $"{entry.Label}: {entry.Error}")));
|
||
Assert.That(section.Entries.Select(entry => entry.Order).Distinct().Count(), Is.EqualTo(section.Entries.Count));
|
||
Assert.That(section.Entries.Select(entry => entry.AnchorNode).Distinct().Count(), Is.EqualTo(section.Entries.Count));
|
||
}
|
||
}
|
||
|
||
[Test]
|
||
public void StrictRestoreContextTracksPhaseWarningsAndErrors()
|
||
{
|
||
var context = new SnapshotRestoreContext(new SaveSnapshot(), strictMode: true);
|
||
context.SetPhase("Provider restore");
|
||
context.Warn("optional state missing");
|
||
LogAssert.Expect(LogType.Error, "[SnapshotRestore] required state missing");
|
||
context.Error("required state missing");
|
||
|
||
Assert.That(context.StrictMode, Is.True);
|
||
Assert.That(context.CurrentPhase, Is.EqualTo("Provider restore"));
|
||
Assert.That(context.Warnings, Is.EqualTo(new[] { "optional state missing" }));
|
||
Assert.That(context.Errors, Is.EqualTo(new[] { "required state missing" }));
|
||
Assert.That(context.HasErrors, Is.True);
|
||
}
|
||
|
||
private void WriteCatalog(params DevSaveCatalogEntryDto[] entries)
|
||
{
|
||
var catalog = new DevSaveCatalogDto
|
||
{
|
||
sections = new List<DevSaveCatalogSectionDto>
|
||
{
|
||
new()
|
||
{
|
||
id = "S",
|
||
title = "Section",
|
||
expectedSceneName = "Scene/Test",
|
||
expectedSceneSoName = "TestSO",
|
||
expectedYarnProjectId = "TestYarn",
|
||
entries = entries.ToList()
|
||
}
|
||
}
|
||
};
|
||
File.WriteAllText(Path.Combine(root, "catalog.json"), JsonConvert.SerializeObject(catalog));
|
||
}
|
||
|
||
private void WriteSnapshot(string relativeDirectory, string json)
|
||
{
|
||
var directory = Path.Combine(root, relativeDirectory.Replace('/', Path.DirectorySeparatorChar));
|
||
Directory.CreateDirectory(directory);
|
||
File.WriteAllText(Path.Combine(directory, "snapshot.json"), json);
|
||
}
|
||
|
||
private static SaveSnapshot BuildSnapshot(string node, int schema = SaveSnapshotSchema.CurrentVersion)
|
||
{
|
||
return new SaveSnapshot
|
||
{
|
||
schemaVersion = schema,
|
||
scene = new SceneSnapshotDto { sceneName = "Scene/Test" },
|
||
anchor = new AnchorSnapshot
|
||
{
|
||
sceneSoName = "TestSO",
|
||
yarnProjectId = "TestYarn",
|
||
nodeName = node
|
||
}
|
||
};
|
||
}
|
||
}
|
||
}
|
||
#endif
|