705 lines
24 KiB
C#
705 lines
24 KiB
C#
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using AibisDream.SaveSystem;
|
|
using AibisDream.Utility;
|
|
using Newtonsoft.Json;
|
|
using UnityEngine;
|
|
|
|
namespace AibisDream
|
|
{
|
|
/// <summary>
|
|
/// 开发用通用跳转工具:从 <see cref="ConstRef.TestSaveFilePath"/>(可提交)读档。
|
|
/// 目录约定:StreamingAssets/TestSaveFiles/{SectionId}/{EntryFolder}/snapshot.json
|
|
/// </summary>
|
|
public class DevSaveJumpTool : MonoBehaviour
|
|
{
|
|
private const float UiMargin = 16f;
|
|
private const float DevButtonWidth = 96f;
|
|
private const float DevButtonHeight = 34f;
|
|
private const float MenuWidth = 320f;
|
|
private const float MaxMenuHeight = 560f;
|
|
private const float RowHeight = 30f;
|
|
private const float HeaderHeight = 24f;
|
|
private const float StatusWidth = 360f;
|
|
private const float StatusHeight = 28f;
|
|
private static DevSaveJumpTool instance;
|
|
|
|
private bool isJumping;
|
|
private bool menuOpen;
|
|
private string status = "";
|
|
private float statusVisibleUntil;
|
|
private Vector2 menuScroll;
|
|
private GUIStyle buttonStyle;
|
|
private GUIStyle headerStyle;
|
|
private GUIStyle menuBoxStyle;
|
|
private GUIStyle statusStyle;
|
|
private List<DevSaveSection> sections = new();
|
|
private float cachedContentHeight;
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
|
private static void Bootstrap()
|
|
{
|
|
if (instance != null)
|
|
return;
|
|
|
|
var existing = FindObjectOfType<DevSaveJumpTool>();
|
|
if (existing != null)
|
|
{
|
|
instance = existing;
|
|
DontDestroyOnLoad(existing.gameObject);
|
|
return;
|
|
}
|
|
|
|
var toolObject = new GameObject("[Dev] Save Jump Tool");
|
|
DontDestroyOnLoad(toolObject);
|
|
instance = toolObject.AddComponent<DevSaveJumpTool>();
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
ReloadCatalog();
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
EnsureStyles();
|
|
|
|
float buttonX = Screen.width - UiMargin - DevButtonWidth;
|
|
float buttonY = Screen.height - UiMargin - DevButtonHeight;
|
|
var devButtonRect = new Rect(buttonX, buttonY, DevButtonWidth, DevButtonHeight);
|
|
|
|
if (menuOpen)
|
|
DrawCommandMenu(devButtonRect);
|
|
|
|
string label = isJumping ? "Busy..." : "Dev";
|
|
if (GUI.Button(devButtonRect, label, buttonStyle))
|
|
{
|
|
if (!menuOpen)
|
|
ReloadCatalog();
|
|
menuOpen = !menuOpen;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(status) && Time.unscaledTime < statusVisibleUntil)
|
|
{
|
|
float menuHeight = GetMenuHeight();
|
|
float statusY = menuOpen
|
|
? buttonY - menuHeight - StatusHeight - UiMargin
|
|
: buttonY - StatusHeight - 8f;
|
|
var statusRect = new Rect(
|
|
Screen.width - UiMargin - StatusWidth,
|
|
Mathf.Max(UiMargin, statusY),
|
|
StatusWidth,
|
|
StatusHeight);
|
|
GUI.Label(statusRect, status, statusStyle);
|
|
}
|
|
}
|
|
|
|
private void DrawCommandMenu(Rect devButtonRect)
|
|
{
|
|
float menuHeight = GetMenuHeight();
|
|
float menuX = Screen.width - UiMargin - MenuWidth;
|
|
float menuY = Mathf.Max(UiMargin, devButtonRect.y - menuHeight - 8f);
|
|
var menuRect = new Rect(menuX, menuY, MenuWidth, menuHeight);
|
|
|
|
GUI.Box(menuRect, GUIContent.none, menuBoxStyle);
|
|
|
|
var viewRect = new Rect(0f, 0f, MenuWidth - 28f, GetContentHeight());
|
|
var scrollRect = new Rect(
|
|
menuRect.x + 6f,
|
|
menuRect.y + 6f,
|
|
menuRect.width - 12f,
|
|
menuRect.height - 12f);
|
|
|
|
menuScroll = GUI.BeginScrollView(scrollRect, menuScroll, viewRect);
|
|
|
|
float y = 0f;
|
|
if (sections.Count == 0)
|
|
{
|
|
GUI.Label(new Rect(0f, y, MenuWidth - 32f, RowHeight), "No TestSaveFiles found", headerStyle);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < sections.Count; i++)
|
|
{
|
|
var section = sections[i];
|
|
DrawHeader(ref y, section.Title);
|
|
if (section.Entries.Count == 0)
|
|
{
|
|
GUI.Label(
|
|
new Rect(0f, y, MenuWidth - 32f, RowHeight),
|
|
"(empty — run Test Mode or promote saves)",
|
|
statusStyle);
|
|
y += RowHeight;
|
|
}
|
|
else
|
|
{
|
|
DrawSaveEntries(ref y, section.Entries);
|
|
}
|
|
|
|
if (i < sections.Count - 1)
|
|
y += 6f;
|
|
}
|
|
}
|
|
|
|
GUI.EndScrollView();
|
|
}
|
|
|
|
private void DrawHeader(ref float y, string label)
|
|
{
|
|
GUI.Label(new Rect(0f, y, MenuWidth - 32f, HeaderHeight), label, headerStyle);
|
|
y += HeaderHeight;
|
|
}
|
|
|
|
private void DrawSaveEntries(ref float y, List<DevSaveEntry> entries)
|
|
{
|
|
foreach (var entry in entries)
|
|
{
|
|
var rect = new Rect(0f, y, MenuWidth - 32f, RowHeight);
|
|
var captured = entry;
|
|
DrawCommandButton(rect, captured.Label, !isJumping, () => StartCoroutine(LoadDevSave(captured)));
|
|
y += RowHeight;
|
|
}
|
|
}
|
|
|
|
private float GetMenuHeight()
|
|
{
|
|
return Mathf.Min(MaxMenuHeight, GetContentHeight() + 12f);
|
|
}
|
|
|
|
private float GetContentHeight()
|
|
{
|
|
if (cachedContentHeight > 0f)
|
|
return cachedContentHeight;
|
|
|
|
float height = 0f;
|
|
if (sections.Count == 0)
|
|
{
|
|
height = RowHeight;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < sections.Count; i++)
|
|
{
|
|
height += HeaderHeight;
|
|
int rows = Math.Max(1, sections[i].Entries.Count);
|
|
height += rows * RowHeight;
|
|
if (i < sections.Count - 1)
|
|
height += 6f;
|
|
}
|
|
}
|
|
|
|
cachedContentHeight = height;
|
|
return height;
|
|
}
|
|
|
|
private void DrawCommandButton(Rect rect, string label, bool enabled, Action action)
|
|
{
|
|
GUI.enabled = enabled;
|
|
if (GUI.Button(rect, label, buttonStyle))
|
|
{
|
|
menuOpen = false;
|
|
action?.Invoke();
|
|
}
|
|
|
|
GUI.enabled = true;
|
|
}
|
|
|
|
private IEnumerator LoadDevSave(DevSaveEntry entry)
|
|
{
|
|
if (isJumping)
|
|
yield break;
|
|
|
|
if (!entry.TryLoadSnapshot(out var snapshot, out var validationError))
|
|
{
|
|
SetStatus($"Invalid save: {validationError}", 8f);
|
|
yield break;
|
|
}
|
|
|
|
isJumping = true;
|
|
try
|
|
{
|
|
if (NeedsResetToMainMenu())
|
|
{
|
|
SetStatus($"Resetting: {entry.Label}");
|
|
GameManager.Instance.QuitGame();
|
|
|
|
bool mainMenuReady = false;
|
|
yield return WaitForMainMenuReady(result => mainMenuReady = result);
|
|
if (!mainMenuReady)
|
|
{
|
|
SetStatus($"Reset timed out: {entry.Label}", 8f);
|
|
yield break;
|
|
}
|
|
}
|
|
|
|
SetStatus($"Loading: {entry.Label}");
|
|
yield return SaveRestoreOrchestrator.RestoreFromFile(entry.SnapshotPathWithoutExtension);
|
|
|
|
if (!ValidateRestoredRuntime(snapshot, out var restoreError))
|
|
{
|
|
SetStatus($"Restore failed: {restoreError}", 8f);
|
|
yield break;
|
|
}
|
|
|
|
SetStatus($"Loaded: {entry.Label}", 3f);
|
|
}
|
|
finally
|
|
{
|
|
isJumping = false;
|
|
}
|
|
}
|
|
|
|
private static bool NeedsResetToMainMenu()
|
|
{
|
|
var gameManager = GameManager.Instance;
|
|
if (gameManager == null)
|
|
return false;
|
|
|
|
if (gameManager.state.isInGame)
|
|
return true;
|
|
|
|
var sceneLoader = SceneLoader.Instance;
|
|
return sceneLoader != null && !string.IsNullOrEmpty(sceneLoader.CurrentSceneName);
|
|
}
|
|
|
|
private static IEnumerator WaitForMainMenuReady(Action<bool> onCompleted)
|
|
{
|
|
const float timeoutSeconds = 15f;
|
|
float elapsed = 0f;
|
|
|
|
while (elapsed < timeoutSeconds)
|
|
{
|
|
var gameManager = GameManager.Instance;
|
|
var sceneLoader = SceneLoader.Instance;
|
|
bool sceneCleared = sceneLoader == null || string.IsNullOrEmpty(sceneLoader.CurrentSceneName);
|
|
bool sceneIdle = sceneLoader == null || !sceneLoader.IsLoading;
|
|
bool notInGame = gameManager == null || !gameManager.state.isInGame;
|
|
|
|
if (sceneCleared && sceneIdle && notInGame)
|
|
{
|
|
onCompleted?.Invoke(true);
|
|
yield break;
|
|
}
|
|
|
|
elapsed += Time.unscaledDeltaTime;
|
|
yield return null;
|
|
}
|
|
|
|
Debug.LogWarning("[DevSaveJumpTool] 等待主界面超时,已取消读档。");
|
|
onCompleted?.Invoke(false);
|
|
}
|
|
|
|
private static bool ValidateRestoredRuntime(SaveSnapshot snapshot, out string error)
|
|
{
|
|
var sceneLoader = SceneLoader.Instance;
|
|
if (sceneLoader == null || sceneLoader.IsLoading)
|
|
{
|
|
error = "scene loader is not ready";
|
|
return false;
|
|
}
|
|
|
|
if (!string.Equals(sceneLoader.CurrentSceneName, snapshot.scene?.sceneName, StringComparison.Ordinal))
|
|
{
|
|
error = $"scene mismatch ({sceneLoader.CurrentSceneName ?? "none"})";
|
|
return false;
|
|
}
|
|
|
|
var runner = DialogController.Instance?.DialogueRunner;
|
|
if (runner?.YarnProject == null
|
|
|| !string.Equals(runner.YarnProject.name, snapshot.anchor?.yarnProjectId, StringComparison.Ordinal))
|
|
{
|
|
error = $"YarnProject mismatch ({runner?.YarnProject?.name ?? "none"})";
|
|
return false;
|
|
}
|
|
|
|
if (snapshot.sections != null
|
|
&& snapshot.sections.ContainsKey(SnapshotProviderIds.Fix)
|
|
&& (FixSystem.FixSystemCenter.Instance == null
|
|
|| !FixSystem.FixSystemCenter.Instance.IsDirectorReady))
|
|
{
|
|
error = "FixSystem is not ready";
|
|
return false;
|
|
}
|
|
|
|
error = null;
|
|
return true;
|
|
}
|
|
|
|
private void ReloadCatalog()
|
|
{
|
|
sections = DevSaveCatalog.Load(ConstRef.TestSaveFilePath);
|
|
cachedContentHeight = 0f;
|
|
}
|
|
|
|
private void SetStatus(string message, float visibleSeconds = 30f)
|
|
{
|
|
status = message;
|
|
statusVisibleUntil = Time.unscaledTime + visibleSeconds;
|
|
Debug.Log($"[DevSaveJumpTool] {message}");
|
|
}
|
|
|
|
private void EnsureStyles()
|
|
{
|
|
if (buttonStyle != null && statusStyle != null)
|
|
return;
|
|
|
|
buttonStyle = new GUIStyle(GUI.skin.button)
|
|
{
|
|
fontSize = 14,
|
|
alignment = TextAnchor.MiddleCenter
|
|
};
|
|
|
|
headerStyle = new GUIStyle(GUI.skin.label)
|
|
{
|
|
fontSize = 13,
|
|
fontStyle = FontStyle.Bold,
|
|
alignment = TextAnchor.MiddleLeft,
|
|
padding = new RectOffset(6, 0, 3, 0)
|
|
};
|
|
|
|
menuBoxStyle = new GUIStyle(GUI.skin.box)
|
|
{
|
|
padding = new RectOffset(6, 6, 6, 6)
|
|
};
|
|
|
|
statusStyle = new GUIStyle(GUI.skin.box)
|
|
{
|
|
fontSize = 13,
|
|
alignment = TextAnchor.MiddleLeft,
|
|
padding = new RectOffset(8, 8, 4, 4)
|
|
};
|
|
}
|
|
}
|
|
|
|
internal static class DevSaveCatalog
|
|
{
|
|
private const string CatalogFileName = "catalog.json";
|
|
|
|
public static List<DevSaveSection> Load(string root)
|
|
{
|
|
var result = new List<DevSaveSection>();
|
|
if (string.IsNullOrEmpty(root) || !Directory.Exists(root))
|
|
return result;
|
|
|
|
var catalogPath = Path.Combine(root, CatalogFileName);
|
|
if (File.Exists(catalogPath))
|
|
{
|
|
try
|
|
{
|
|
var catalog = JsonConvert.DeserializeObject<CatalogDto>(File.ReadAllText(catalogPath));
|
|
if (catalog?.sections != null)
|
|
{
|
|
foreach (var sectionDto in catalog.sections)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sectionDto.id))
|
|
continue;
|
|
|
|
var sectionDir = Path.Combine(root, sectionDto.id);
|
|
var title = string.IsNullOrWhiteSpace(sectionDto.title) ? sectionDto.id : sectionDto.title;
|
|
result.Add(BuildSection(
|
|
sectionDto.id,
|
|
title,
|
|
sectionDir,
|
|
sectionDto.entries,
|
|
sectionDto.expectedSceneName,
|
|
sectionDto.expectedYarnProjectId,
|
|
sectionDto.allowedNodes));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[DevSaveJumpTool] 读取 catalog.json 失败: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
foreach (var dir in Directory.GetDirectories(root).OrderBy(d => d, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
var id = Path.GetFileName(dir);
|
|
result.Add(BuildSection(id, id, dir, null, null, null, null));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static DevSaveSection BuildSection(
|
|
string id,
|
|
string title,
|
|
string sectionDir,
|
|
List<CatalogEntryDto> explicitEntries,
|
|
string expectedSceneName,
|
|
string expectedYarnProjectId,
|
|
List<string> allowedNodes)
|
|
{
|
|
var entries = new List<DevSaveEntry>();
|
|
var root = Directory.GetParent(sectionDir)?.FullName;
|
|
|
|
if (explicitEntries != null && explicitEntries.Count > 0 && root != null)
|
|
{
|
|
var latestTestArchives = BuildLatestTestArchiveIndex(
|
|
expectedSceneName,
|
|
expectedYarnProjectId,
|
|
allowedNodes);
|
|
|
|
foreach (var entryDto in explicitEntries)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(entryDto.path))
|
|
continue;
|
|
|
|
var relative = entryDto.path.Replace('/', Path.DirectorySeparatorChar);
|
|
var entryDir = Path.IsPathRooted(relative)
|
|
? relative
|
|
: Path.Combine(root, relative);
|
|
var snapshot = Path.Combine(entryDir, ConstRef.SaveSnapshotFileName);
|
|
var entry = new DevSaveEntry(
|
|
label: string.IsNullOrWhiteSpace(entryDto.label)
|
|
? FormatFolderLabel(Path.GetFileName(entryDir))
|
|
: entryDto.label,
|
|
snapshotPathWithoutExtension: snapshot,
|
|
expectedSceneName,
|
|
expectedYarnProjectId,
|
|
allowedNodes);
|
|
if (!entry.TryLoadSnapshot(out _, out var error))
|
|
{
|
|
if (error == "file missing"
|
|
&& !string.IsNullOrWhiteSpace(entryDto.anchorNode)
|
|
&& latestTestArchives.TryGetValue(entryDto.anchorNode, out var archiveSnapshot))
|
|
{
|
|
entry = new DevSaveEntry(
|
|
label: string.IsNullOrWhiteSpace(entryDto.label)
|
|
? entryDto.anchorNode
|
|
: entryDto.label,
|
|
snapshotPathWithoutExtension: archiveSnapshot,
|
|
expectedSceneName,
|
|
expectedYarnProjectId,
|
|
allowedNodes);
|
|
|
|
if (entry.TryLoadSnapshot(out _, out _))
|
|
{
|
|
entries.Add(entry);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Debug.LogWarning($"[DevSaveJumpTool] 跳过无效存档 {entryDto.path}: {error}");
|
|
continue;
|
|
}
|
|
|
|
entries.Add(entry);
|
|
}
|
|
}
|
|
else if (Directory.Exists(sectionDir))
|
|
{
|
|
foreach (var entryDir in Directory.GetDirectories(sectionDir)
|
|
.OrderBy(d => d, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
var snapshot = Path.Combine(entryDir, ConstRef.SaveSnapshotFileName);
|
|
var entry = new DevSaveEntry(
|
|
FormatFolderLabel(Path.GetFileName(entryDir)),
|
|
snapshot,
|
|
expectedSceneName,
|
|
expectedYarnProjectId,
|
|
allowedNodes);
|
|
if (!entry.TryLoadSnapshot(out _, out var error))
|
|
{
|
|
Debug.LogWarning($"[DevSaveJumpTool] 跳过无效存档 {entryDir}: {error}");
|
|
continue;
|
|
}
|
|
|
|
entries.Add(entry);
|
|
}
|
|
}
|
|
|
|
return new DevSaveSection(id, title, entries);
|
|
}
|
|
|
|
private static Dictionary<string, string> BuildLatestTestArchiveIndex(
|
|
string expectedSceneName,
|
|
string expectedYarnProjectId,
|
|
List<string> allowedNodes)
|
|
{
|
|
var result = new Dictionary<string, string>(StringComparer.Ordinal);
|
|
var savedAtByNode = new Dictionary<string, string>(StringComparer.Ordinal);
|
|
var archiveRoot = ConstRef.TestAutoSaveArchivePath;
|
|
if (!Directory.Exists(archiveRoot) || allowedNodes == null || allowedNodes.Count == 0)
|
|
return result;
|
|
|
|
foreach (var archiveDir in Directory.GetDirectories(archiveRoot))
|
|
{
|
|
var snapshotPath = Path.Combine(archiveDir, ConstRef.SaveSnapshotFileName);
|
|
var candidate = new DevSaveEntry(
|
|
string.Empty,
|
|
snapshotPath,
|
|
expectedSceneName,
|
|
expectedYarnProjectId,
|
|
allowedNodes);
|
|
if (!candidate.TryLoadSnapshot(out var snapshot, out _))
|
|
continue;
|
|
|
|
var nodeName = snapshot.anchor.nodeName;
|
|
var savedAt = snapshot.savedAt ?? string.Empty;
|
|
if (savedAtByNode.TryGetValue(nodeName, out var currentSavedAt)
|
|
&& string.CompareOrdinal(savedAt, currentSavedAt) <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
savedAtByNode[nodeName] = savedAt;
|
|
result[nodeName] = snapshotPath;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static string FormatFolderLabel(string folderName)
|
|
{
|
|
if (string.IsNullOrEmpty(folderName))
|
|
return folderName;
|
|
|
|
return folderName.Replace('_', ' ');
|
|
}
|
|
|
|
[Serializable]
|
|
private class CatalogDto
|
|
{
|
|
public List<CatalogSectionDto> sections;
|
|
}
|
|
|
|
[Serializable]
|
|
private class CatalogSectionDto
|
|
{
|
|
public string id;
|
|
public string title;
|
|
public List<CatalogEntryDto> entries;
|
|
public string expectedSceneName;
|
|
public string expectedYarnProjectId;
|
|
public List<string> allowedNodes;
|
|
}
|
|
|
|
[Serializable]
|
|
private class CatalogEntryDto
|
|
{
|
|
public string label;
|
|
public string path;
|
|
public string anchorNode;
|
|
}
|
|
}
|
|
|
|
internal sealed class DevSaveSection
|
|
{
|
|
public readonly string Id;
|
|
public readonly string Title;
|
|
public readonly List<DevSaveEntry> Entries;
|
|
|
|
public DevSaveSection(string id, string title, List<DevSaveEntry> entries)
|
|
{
|
|
Id = id;
|
|
Title = title;
|
|
Entries = entries ?? new List<DevSaveEntry>();
|
|
}
|
|
}
|
|
|
|
internal readonly struct DevSaveEntry
|
|
{
|
|
public readonly string Label;
|
|
public readonly string SnapshotPathWithoutExtension;
|
|
private readonly string expectedSceneName;
|
|
private readonly string expectedYarnProjectId;
|
|
private readonly List<string> allowedNodes;
|
|
|
|
public DevSaveEntry(
|
|
string label,
|
|
string snapshotPathWithoutExtension,
|
|
string expectedSceneName,
|
|
string expectedYarnProjectId,
|
|
List<string> allowedNodes)
|
|
{
|
|
Label = label;
|
|
SnapshotPathWithoutExtension = snapshotPathWithoutExtension;
|
|
this.expectedSceneName = expectedSceneName;
|
|
this.expectedYarnProjectId = expectedYarnProjectId;
|
|
this.allowedNodes = allowedNodes;
|
|
}
|
|
|
|
public bool TryLoadSnapshot(out SaveSnapshot snapshot, out string error)
|
|
{
|
|
snapshot = null;
|
|
error = null;
|
|
|
|
if (string.IsNullOrEmpty(SnapshotPathWithoutExtension)
|
|
|| !File.Exists(SnapshotPathWithoutExtension + ".json"))
|
|
{
|
|
error = "file missing";
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (SnapshotPersistence.IsLegacyFormat(SnapshotPathWithoutExtension))
|
|
{
|
|
error = "legacy format is not supported";
|
|
return false;
|
|
}
|
|
|
|
snapshot = SnapshotPersistence.Load(SnapshotPathWithoutExtension);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
error = $"invalid JSON ({ex.Message})";
|
|
return false;
|
|
}
|
|
|
|
if (snapshot == null || snapshot.schemaVersion != SaveSnapshotSchema.CurrentVersion)
|
|
{
|
|
error = $"unsupported schema ({snapshot?.schemaVersion.ToString() ?? "none"})";
|
|
return false;
|
|
}
|
|
|
|
if (snapshot.scene == null || string.IsNullOrWhiteSpace(snapshot.scene.sceneName))
|
|
{
|
|
error = "scene is missing";
|
|
return false;
|
|
}
|
|
|
|
if (snapshot.anchor == null
|
|
|| string.IsNullOrWhiteSpace(snapshot.anchor.yarnProjectId)
|
|
|| string.IsNullOrWhiteSpace(snapshot.anchor.nodeName))
|
|
{
|
|
error = "Yarn anchor is missing";
|
|
return false;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(expectedSceneName)
|
|
&& !string.Equals(snapshot.scene.sceneName, expectedSceneName, StringComparison.Ordinal))
|
|
{
|
|
error = $"scene must be {expectedSceneName}";
|
|
return false;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(expectedYarnProjectId)
|
|
&& !string.Equals(snapshot.anchor.yarnProjectId, expectedYarnProjectId, StringComparison.Ordinal))
|
|
{
|
|
error = $"YarnProject must be {expectedYarnProjectId}";
|
|
return false;
|
|
}
|
|
|
|
if (allowedNodes != null
|
|
&& allowedNodes.Count > 0
|
|
&& !allowedNodes.Contains(snapshot.anchor.nodeName, StringComparer.Ordinal))
|
|
{
|
|
error = $"node is not allowed ({snapshot.anchor.nodeName})";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
#endif
|