using System; using System.IO; using System.Linq; using System.Text; using AibisDream.FrameAnimation.Editor; using NUnit.Framework; using UnityEditor; using UnityEngine; namespace AibisDream.FrameAnimation.Tests.EditMode { public sealed class FrameAnimationImportTests { private const string TestRoot = "Assets/Tests/FrameAnimation/GeneratedImportTests"; private const string TexturePath = TestRoot + "/Atlas.png"; private const string JsonPath = TestRoot + "/Atlas.json"; private const string GraphPath = TestRoot + "/ImportGraph.asset"; [SetUp] public void SetUp() { AssetDatabase.DeleteAsset(TestRoot); EnsureFolder(TestRoot); } [TearDown] public void TearDown() { AssetDatabase.DeleteAsset(TestRoot); AssetDatabase.Refresh(); } [Test] public void Parser_PreservesObjectAndArrayOrderAndUnicode() { Assert.That(AsepriteJsonParser.TryParse(ObjectJson(), "source", out var objectDocument, out var objectIssue), Is.True, objectIssue?.Message); Assert.That(AsepriteJsonParser.TryParse(ArrayJson(), "source", out var arrayDocument, out var arrayIssue), Is.True, arrayIssue?.Message); CollectionAssert.AreEqual( new[] { "第二帧.png", "第一帧.png" }, objectDocument.Frames.Select(frame => frame.FrameName)); CollectionAssert.AreEqual( objectDocument.Frames.Select(frame => frame.FrameName), arrayDocument.Frames.Select(frame => frame.FrameName)); CollectionAssert.AreEqual(new[] { "待机", "眨眼" }, objectDocument.Tags.Select(tag => tag.Name)); } [Test] public void Parser_GroupsSharedRectsWithoutCollapsingLogicalFrames() { Assert.That(AsepriteJsonParser.TryParse( SharedRectJson(), "source", out var document, out var issue), Is.True, issue?.Message); Assert.That(document.Frames.Count, Is.EqualTo(3)); Assert.That(document.SpriteSlots.Count, Is.EqualTo(2)); Assert.That(document.SpriteSlots[0].CanonicalFrameName, Is.EqualTo("FrameA")); CollectionAssert.AreEqual(new[] { 0, 1 }, document.SpriteSlots[0].SourceIndices); CollectionAssert.AreEqual(new[] { "FrameA", "FrameB" }, document.SpriteSlots[0].FrameNames); Assert.That(document.SpriteSlotBySourceIndex[0], Is.SameAs(document.SpriteSlotBySourceIndex[1])); Assert.That(document.SpriteSlotBySourceIndex[2], Is.Not.SameAs(document.SpriteSlotBySourceIndex[0])); } [TestCase("forward", new[] { 0, 1, 2, 3 })] [TestCase("reverse", new[] { 3, 2, 1, 0 })] [TestCase("pingpong", new[] { 0, 1, 2, 3, 2, 1 })] [TestCase("pingpong_reverse", new[] { 3, 2, 1, 0, 1, 2 })] public void TagExpansion_IsDeterministic(string direction, int[] expected) { var tag = new AsepriteSourceTag("测试", 0, 3, direction); Assert.That(AsepriteJsonParser.TryExpandTag(tag, 4, "source", out var actual, out var issue), Is.True, issue?.Message); CollectionAssert.AreEqual(expected, actual); } [TestCase("pingpong")] [TestCase("pingpong_reverse")] public void TagExpansion_SingleFrameDoesNotDuplicateEndpoint(string direction) { var tag = new AsepriteSourceTag("单帧", 1, 1, direction); Assert.That(AsepriteJsonParser.TryExpandTag(tag, 3, "source", out var actual, out _), Is.True); CollectionAssert.AreEqual(new[] { 1 }, actual); } [Test] public void WritableRefresh_CreatesAndUpdatesStableSubAssetsAndMissingState() { var graph = CreateWritableGraph(ObjectJson()); var source = graph.ImportSources.Single(); var firstPreview = FrameAnimationImportService.PreviewAll(graph); Assert.That(firstPreview.HasErrors, Is.False, JoinIssues(firstPreview)); Assert.That(firstPreview.Sources.Single().SpriteDiffs.Count(diff => diff.Kind == FrameAnimationSpriteChangeKind.Added), Is.EqualTo(2)); Assert.That(FrameAnimationImportService.Apply(firstPreview, true, out var firstError), Is.True, firstError); Assert.That(graph.Clips.Count, Is.EqualTo(2)); var idle = graph.Clips.Single(clip => clip.ImportInfo.SourceTagName == "待机"); Assert.That(AssetDatabase.IsSubAsset(idle), Is.True); Assert.That(AssetDatabase.GetAssetPath(idle), Is.EqualTo(GraphPath)); Assert.That(idle.Frames[0].FrameName, Is.EqualTo("第二帧.png")); Assert.That(source.LastSourceHash, Is.Not.Empty); Assert.That(AssetDatabase.TryGetGUIDAndLocalFileIdentifier(idle, out _, out long firstLocalId), Is.True); var idleSerialized = new SerializedObject(idle); idleSerialized.FindProperty("id").stringValue = "用户重命名待机"; idleSerialized.FindProperty("displayName").stringValue = "自定义显示名"; idleSerialized.FindProperty("speed").floatValue = 1.75f; idleSerialized.ApplyModifiedPropertiesWithoutUndo(); WriteJson(ObjectJson(duration: 180)); var updatePreview = FrameAnimationImportService.PreviewAll(graph); Assert.That(updatePreview.HasErrors, Is.False, JoinIssues(updatePreview)); Assert.That(FrameAnimationImportService.Apply(updatePreview, true, out var updateError), Is.True, updateError); Assert.That(AssetDatabase.TryGetGUIDAndLocalFileIdentifier(idle, out _, out long updatedLocalId), Is.True); Assert.That(updatedLocalId, Is.EqualTo(firstLocalId)); Assert.That(idle.Id, Is.EqualTo("用户重命名待机")); Assert.That(idle.DisplayName, Is.EqualTo("自定义显示名")); Assert.That(idle.Speed, Is.EqualTo(1.75f)); Assert.That(idle.Frames[0].DurationMs, Is.EqualTo(180)); WriteJson(ObjectJson(includeIdle: false)); var missingPreview = FrameAnimationImportService.PreviewAll(graph); Assert.That(FrameAnimationImportService.Apply(missingPreview, true, out var missingError), Is.True, missingError); Assert.That(idle.ImportInfo.IsMissingFromSource, Is.True); WriteJson(ObjectJson(duration: 220)); var restorePreview = FrameAnimationImportService.PreviewAll(graph); Assert.That(FrameAnimationImportService.Apply(restorePreview, true, out var restoreError), Is.True, restoreError); Assert.That(idle.ImportInfo.IsMissingFromSource, Is.False); Assert.That(idle.Frames[0].DurationMs, Is.EqualTo(220)); Assert.That(AssetDatabase.TryGetGUIDAndLocalFileIdentifier(idle, out _, out long restoredLocalId), Is.True); Assert.That(restoredLocalId, Is.EqualTo(firstLocalId)); } [Test] public void WritableRefresh_SharedRectsCreateOneSpriteAndPreserveLogicalFrames() { var graph = CreateWritableGraph(SharedRectJson()); var firstPreview = FrameAnimationImportService.PreviewAll(graph); Assert.That(firstPreview.HasErrors, Is.False, JoinIssues(firstPreview)); var sourcePreview = firstPreview.Sources.Single(); Assert.That(sourcePreview.Document.Frames.Count, Is.EqualTo(3)); Assert.That(sourcePreview.Document.SpriteSlots.Count, Is.EqualTo(2)); Assert.That(sourcePreview.SpriteDiffs.Count(diff => diff.Kind == FrameAnimationSpriteChangeKind.Added), Is.EqualTo(2)); Assert.That(sourcePreview.SpriteDiffs.Single(diff => diff.SourceFrameCount == 2).FrameName, Is.EqualTo("FrameA")); Assert.That(FrameAnimationImportService.Apply(firstPreview, true, out var firstError), Is.True, firstError); var sprites = AssetDatabase.LoadAllAssetsAtPath(TexturePath).OfType().ToArray(); Assert.That(sprites.Length, Is.EqualTo(2)); var clip = graph.Clips.Single(item => item.ImportInfo.SourceTagName == "Shared"); Assert.That(clip.FrameCount, Is.EqualTo(3)); Assert.That(clip.Frames[0].Sprite, Is.SameAs(clip.Frames[1].Sprite)); Assert.That(clip.Frames[2].Sprite, Is.Not.SameAs(clip.Frames[0].Sprite)); CollectionAssert.AreEqual(new[] { "FrameA", "FrameB", "FrameC" }, clip.Frames.Select(frame => frame.FrameName)); CollectionAssert.AreEqual(new[] { 0, 1, 2 }, clip.Frames.Select(frame => frame.SourceIndex)); CollectionAssert.AreEqual(new[] { 100, 300, 80 }, clip.Frames.Select(frame => frame.DurationMs)); var firstIds = sprites.ToDictionary(sprite => sprite.name, GetLocalFileId); var secondPreview = FrameAnimationImportService.PreviewAll(graph); Assert.That(secondPreview.HasErrors, Is.False, JoinIssues(secondPreview)); Assert.That(secondPreview.Sources.Single().SpriteDiffs.All(diff => diff.Kind == FrameAnimationSpriteChangeKind.Unchanged), Is.True); Assert.That(secondPreview.Sources.Single().ClipDiffs.All(diff => diff.Kind == FrameAnimationImportChangeKind.Unchanged), Is.True); Assert.That(FrameAnimationImportService.Apply(secondPreview, true, out var secondError), Is.True, secondError); var secondIds = AssetDatabase.LoadAllAssetsAtPath(TexturePath).OfType() .ToDictionary(sprite => sprite.name, GetLocalFileId); CollectionAssert.AreEquivalent(firstIds, secondIds); Assert.That(clip.Frames[0].Sprite, Is.SameAs(clip.Frames[1].Sprite)); } [Test] public void WritableRefresh_SharedSlotSplitAndMergeKeepDeterministicSpriteIdentity() { var graph = CreateWritableGraph(SharedPairJson(true)); var mergedPreview = FrameAnimationImportService.PreviewAll(graph); Assert.That(FrameAnimationImportService.Apply(mergedPreview, true, out var mergedError), Is.True, mergedError); var canonical = AssetDatabase.LoadAllAssetsAtPath(TexturePath).OfType().Single(); var canonicalId = GetLocalFileId(canonical); WriteJson(SharedPairJson(false)); var splitPreview = FrameAnimationImportService.PreviewAll(graph); Assert.That(splitPreview.HasErrors, Is.False, JoinIssues(splitPreview)); Assert.That(splitPreview.Sources.Single().SpriteDiffs.Count(diff => diff.Kind == FrameAnimationSpriteChangeKind.Added), Is.EqualTo(1)); Assert.That(FrameAnimationImportService.Apply(splitPreview, true, out var splitError), Is.True, splitError); var splitSprites = AssetDatabase.LoadAllAssetsAtPath(TexturePath).OfType() .ToDictionary(sprite => sprite.name); Assert.That(splitSprites.Keys, Is.EquivalentTo(new[] { "FrameA", "FrameB" })); Assert.That(GetLocalFileId(splitSprites["FrameA"]), Is.EqualTo(canonicalId)); var clip = graph.Clips.Single(item => item.ImportInfo.SourceTagName == "SharedPair"); Assert.That(clip.Frames[0].Sprite, Is.Not.SameAs(clip.Frames[1].Sprite)); WriteJson(SharedPairJson(true)); var mergedAgainPreview = FrameAnimationImportService.PreviewAll(graph); Assert.That(mergedAgainPreview.HasErrors, Is.False, JoinIssues(mergedAgainPreview)); Assert.That(mergedAgainPreview.Sources.Single().SpriteDiffs.Any(diff => diff.Kind == FrameAnimationSpriteChangeKind.Retained && diff.FrameName == "FrameB"), Is.True); Assert.That(FrameAnimationImportService.Apply(mergedAgainPreview, true, out var mergedAgainError), Is.True, mergedAgainError); Assert.That(GetLocalFileId(AssetDatabase.LoadAllAssetsAtPath(TexturePath).OfType() .Single(sprite => sprite.name == "FrameA")), Is.EqualTo(canonicalId)); Assert.That(clip.Frames[0].Sprite, Is.SameAs(clip.Frames[1].Sprite)); } [Test] public void ReadOnlyPreview_MatchesExistingSpritesWithoutChangingImporter() { var writableGraph = CreateWritableGraph(ObjectJson()); var writablePreview = FrameAnimationImportService.PreviewAll(writableGraph); Assert.That(FrameAnimationImportService.Apply(writablePreview, true, out var applyError), Is.True, applyError); var importer = (TextureImporter)AssetImporter.GetAtPath(TexturePath); var before = EditorJsonUtility.ToJson(importer); var readOnlyGraph = ScriptableObject.CreateInstance(); readOnlyGraph.Configure("ReadOnly", "Read Only", Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), string.Empty); readOnlyGraph.AddImportSource(new FrameAnimationImportSource( "Read Only", AssetDatabase.LoadAssetAtPath(TexturePath), AssetDatabase.LoadAssetAtPath(JsonPath), new Vector2(0.5f, 0.5f), false)); AssetDatabase.CreateAsset(readOnlyGraph, TestRoot + "/ReadOnlyGraph.asset"); AssetDatabase.SaveAssets(); var preview = FrameAnimationImportService.PreviewAll(readOnlyGraph); Assert.That(preview.HasErrors, Is.False, JoinIssues(preview)); Assert.That(preview.HasSpriteChanges, Is.False); Assert.That(EditorJsonUtility.ToJson(importer), Is.EqualTo(before)); } [Test] public void ReadOnlyRefresh_MapsSharedSourceFramesToOneExistingSprite() { var writableGraph = CreateWritableGraph(SharedRectJson()); var writablePreview = FrameAnimationImportService.PreviewAll(writableGraph); Assert.That(FrameAnimationImportService.Apply(writablePreview, true, out var applyError), Is.True, applyError); var importer = (TextureImporter)AssetImporter.GetAtPath(TexturePath); var before = EditorJsonUtility.ToJson(importer); var readOnlyGraph = ScriptableObject.CreateInstance(); readOnlyGraph.Configure("ReadOnlyShared", "Read Only Shared", Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), string.Empty); readOnlyGraph.AddImportSource(new FrameAnimationImportSource( "Read Only Shared", AssetDatabase.LoadAssetAtPath(TexturePath), AssetDatabase.LoadAssetAtPath(JsonPath), new Vector2(0.5f, 0.5f), false)); AssetDatabase.CreateAsset(readOnlyGraph, TestRoot + "/ReadOnlySharedGraph.asset"); AssetDatabase.SaveAssets(); var preview = FrameAnimationImportService.PreviewAll(readOnlyGraph); Assert.That(preview.HasErrors, Is.False, JoinIssues(preview)); Assert.That(preview.HasSpriteChanges, Is.False); Assert.That(preview.Sources.Single().SpritePlan.ReadOnlySpritesBySourceIndex[0], Is.SameAs(preview.Sources.Single().SpritePlan.ReadOnlySpritesBySourceIndex[1])); Assert.That(FrameAnimationImportService.Apply(preview, false, out var error), Is.True, error); var clip = readOnlyGraph.Clips.Single(item => item.ImportInfo.SourceTagName == "Shared"); Assert.That(clip.Frames[0].Sprite, Is.SameAs(clip.Frames[1].Sprite)); Assert.That(EditorJsonUtility.ToJson(importer), Is.EqualTo(before)); var secondPreview = FrameAnimationImportService.PreviewAll(readOnlyGraph); Assert.That(secondPreview.Sources.Single().ClipDiffs.All(diff => diff.Kind == FrameAnimationImportChangeKind.Unchanged), Is.True); } [Test] public void ReadOnlyPreview_BlocksWhenSharedRectHasNoSprite() { CreateTexture(); WriteJson(SharedPairJson(true)); var graph = ScriptableObject.CreateInstance(); graph.Configure("MissingSprite", "Missing Sprite", Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), string.Empty); graph.AddImportSource(new FrameAnimationImportSource( "Read Only", AssetDatabase.LoadAssetAtPath(TexturePath), AssetDatabase.LoadAssetAtPath(JsonPath), new Vector2(0.5f, 0.5f), false)); AssetDatabase.CreateAsset(graph, TestRoot + "/MissingSpriteGraph.asset"); AssetDatabase.SaveAssets(); var preview = FrameAnimationImportService.PreviewAll(graph); Assert.That(preview.HasErrors, Is.True); Assert.That(preview.Sources.Single().Issues.Any(issue => issue.Code == FrameAnimationImportIssueCode.SpriteMatchFailed), Is.True); Assert.That(FrameAnimationImportService.Apply(preview, false, out _), Is.False); Assert.That(graph.Clips, Is.Empty); } [Test] public void Preview_BlocksMultipleWritableOwnersWithoutMutation() { var graph = CreateWritableGraph(ObjectJson()); var source = graph.ImportSources.Single(); graph.AddImportSource(new FrameAnimationImportSource( "Second Owner", source.Texture, source.AsepriteJson, source.Pivot, true)); EditorUtility.SetDirty(graph); AssetDatabase.SaveAssets(); var importer = (TextureImporter)AssetImporter.GetAtPath(TexturePath); var before = EditorJsonUtility.ToJson(importer); var preview = FrameAnimationImportService.PreviewAll(graph); Assert.That(preview.HasErrors, Is.True); Assert.That(preview.Sources.SelectMany(item => item.Issues).Any(issue => issue.Code == FrameAnimationImportIssueCode.TextureOwnershipConflict), Is.True); Assert.That(FrameAnimationImportService.Apply(preview, true, out _), Is.False); Assert.That(EditorJsonUtility.ToJson(importer), Is.EqualTo(before)); Assert.That(graph.Clips, Is.Empty); } private static FrameAnimationGraph CreateWritableGraph(string json) { CreateTexture(); WriteJson(json); var graph = ScriptableObject.CreateInstance(); graph.Configure("ImportGraph", "Import Graph", Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), string.Empty); graph.AddImportSource(new FrameAnimationImportSource( "中文来源", AssetDatabase.LoadAssetAtPath(TexturePath), AssetDatabase.LoadAssetAtPath(JsonPath), new Vector2(0.5f, 0.5f), true)); AssetDatabase.CreateAsset(graph, GraphPath); AssetDatabase.SaveAssets(); return graph; } private static void CreateTexture() { var texture = new Texture2D(4, 2, TextureFormat.RGBA32, false); var pixels = Enumerable.Range(0, 8) .Select(index => index < 4 ? Color.red : Color.green) .ToArray(); texture.SetPixels(pixels); texture.Apply(); File.WriteAllBytes(TexturePath, texture.EncodeToPNG()); UnityEngine.Object.DestroyImmediate(texture); AssetDatabase.ImportAsset(TexturePath, ImportAssetOptions.ForceSynchronousImport); } private static void WriteJson(string json) { File.WriteAllText(JsonPath, json, new UTF8Encoding(false)); AssetDatabase.ImportAsset(JsonPath, ImportAssetOptions.ForceSynchronousImport); } private static string ObjectJson(int duration = 120, bool includeIdle = true) { var tags = includeIdle ? "{\"name\":\"待机\",\"from\":0,\"to\":0,\"direction\":\"forward\"}," + "{\"name\":\"眨眼\",\"from\":0,\"to\":1,\"direction\":\"pingpong\"}" : "{\"name\":\"眨眼\",\"from\":0,\"to\":1,\"direction\":\"pingpong\"}"; return "{" + "\"frames\":{" + $"\"第二帧.png\":{{\"frame\":{{\"x\":0,\"y\":0,\"w\":2,\"h\":2}},\"rotated\":false,\"trimmed\":false,\"duration\":{duration},\"spriteSourceSize\":{{\"x\":0,\"y\":0,\"w\":2,\"h\":2}},\"sourceSize\":{{\"w\":2,\"h\":2}}}}," + "\"第一帧.png\":{\"frame\":{\"x\":2,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":80,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}}," + $"\"meta\":{{\"image\":\"Atlas.png\",\"size\":{{\"w\":4,\"h\":2}},\"frameTags\":[{tags}]}}" + "}"; } private static string ArrayJson() { return "{" + "\"frames\":[" + "{\"filename\":\"第二帧.png\",\"frame\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":120,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}," + "{\"filename\":\"第一帧.png\",\"frame\":{\"x\":2,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":80,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}]," + "\"meta\":{\"image\":\"Atlas.png\",\"size\":{\"w\":4,\"h\":2},\"frameTags\":[" + "{\"name\":\"待机\",\"from\":0,\"to\":0,\"direction\":\"forward\"}," + "{\"name\":\"眨眼\",\"from\":0,\"to\":1,\"direction\":\"pingpong\"}]}}"; } private static string SharedRectJson() { return "{" + "\"frames\":{" + "\"FrameA\":{\"frame\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":100,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}," + "\"FrameB\":{\"frame\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":300,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}," + "\"FrameC\":{\"frame\":{\"x\":2,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":80,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}}," + "\"meta\":{\"image\":\"Atlas.png\",\"size\":{\"w\":4,\"h\":2},\"frameTags\":[" + "{\"name\":\"Shared\",\"from\":0,\"to\":2,\"direction\":\"forward\"}]}}"; } private static string SharedPairJson(bool merged) { var secondX = merged ? 0 : 2; return "{" + "\"frames\":{" + "\"FrameA\":{\"frame\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":100,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}," + $"\"FrameB\":{{\"frame\":{{\"x\":{secondX},\"y\":0,\"w\":2,\"h\":2}},\"rotated\":false,\"trimmed\":false,\"duration\":300,\"spriteSourceSize\":{{\"x\":0,\"y\":0,\"w\":2,\"h\":2}},\"sourceSize\":{{\"w\":2,\"h\":2}}}}}}," + "\"meta\":{\"image\":\"Atlas.png\",\"size\":{\"w\":4,\"h\":2},\"frameTags\":[" + "{\"name\":\"SharedPair\",\"from\":0,\"to\":1,\"direction\":\"forward\"}]}}"; } private static long GetLocalFileId(Sprite sprite) { Assert.That(AssetDatabase.TryGetGUIDAndLocalFileIdentifier(sprite, out _, out long localId), Is.True); return localId; } private static string JoinIssues(FrameAnimationImportPreview preview) { return string.Join("\n", preview.Sources.SelectMany(source => source.Issues).Select(issue => issue.Message)); } private static void EnsureFolder(string path) { var segments = path.Split('/'); var current = segments[0]; for (var index = 1; index < segments.Length; index++) { var next = current + "/" + segments[index]; if (!AssetDatabase.IsValidFolder(next)) { AssetDatabase.CreateFolder(current, segments[index]); } current = next; } } } }