diff --git a/samples/Tests/SampleTestFixture.cs b/samples/Tests/SampleTestFixture.cs index ed8d7bf898..743474d57a 100644 --- a/samples/Tests/SampleTestFixture.cs +++ b/samples/Tests/SampleTestFixture.cs @@ -32,7 +32,7 @@ private static void CompileSample(LoggerResult logger, string sampleName, Packag { var project = session.Projects.OfType().First(x => x.Platform == Core.PlatformType.Windows); - var buildResult = VSProjectHelper.CompileProjectAssemblyAsync(null, project.FullPath, logger, extraProperties: new Dictionary { { "StrideAutoTesting", "true" } }).BuildTask.Result; + var buildResult = VSProjectHelper.CompileProjectAssemblyAsync(project.FullPath, logger, extraProperties: new Dictionary { { "StrideAutoTesting", "true" } }).BuildTask.Result; if (logger.HasErrors) { throw new InvalidOperationException($"Error compiling sample {sampleName}:\r\n{logger.ToText()}"); diff --git a/samples/Tests/Stride.Samples.Tests.csproj b/samples/Tests/Stride.Samples.Tests.csproj index b9c9893bd6..4b474c2f80 100644 --- a/samples/Tests/Stride.Samples.Tests.csproj +++ b/samples/Tests/Stride.Samples.Tests.csproj @@ -5,6 +5,8 @@ false $(StrideEditorTargetFramework) win-x64 + enable + latest true --auto-module-initializer false diff --git a/sources/assets/Stride.Core.Assets.CompilerApp/Tasks/PackAssets.cs b/sources/assets/Stride.Core.Assets.CompilerApp/Tasks/PackAssets.cs index a73fc4a43b..fd3caa9337 100644 --- a/sources/assets/Stride.Core.Assets.CompilerApp/Tasks/PackAssets.cs +++ b/sources/assets/Stride.Core.Assets.CompilerApp/Tasks/PackAssets.cs @@ -99,7 +99,7 @@ void TryCopyResource(UFile resourceFilePath, UFile targetFilePath) } var assetOutputPath = UPath.Combine(outputPath, (UDirectory)"Assets"); - var assets = Package.ListAssetFiles(logger, package, true, true, null); + var assets = Package.ListAssetFiles(package, true, true); if (assets.Count > 0) { newPackage.AssetFolders.Add(new AssetFolder(assetOutputPath)); diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/AssetHierarchyHelper.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/AssetHierarchyHelper.cs index 6a771c323b..16eb5a12c1 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/AssetHierarchyHelper.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/AssetHierarchyHelper.cs @@ -1,80 +1,76 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using System.Text; using Stride.Core.Assets.Tests.Helpers; using Stride.Core.Extensions; -namespace Stride.Core.Assets.Quantum.Tests.Helpers +namespace Stride.Core.Assets.Quantum.Tests.Helpers; + +public static class AssetHierarchyHelper { - public static class AssetHierarchyHelper + public static string PrintHierarchy(AssetCompositeHierarchy asset) { - public static string PrintHierarchy(AssetCompositeHierarchy asset) + var stack = new Stack>(); + asset.Hierarchy.RootParts.Select(x => asset.Hierarchy.Parts[x.Id]).Reverse().ForEach(x => stack.Push(Tuple.Create(x, 0))); + var sb = new StringBuilder(); + while (stack.Count > 0) { - var stack = new Stack>(); - asset.Hierarchy.RootParts.Select(x => asset.Hierarchy.Parts[x.Id]).Reverse().ForEach(x => stack.Push(Tuple.Create(x, 0))); - var sb = new StringBuilder(); - while (stack.Count > 0) + var current = stack.Pop(); + sb.Append("".PadLeft(current.Item2 * 2)); + sb.AppendLine($"- {current.Item1.Part.Name} [{current.Item1.Part.Id}]"); + foreach (var child in asset.EnumerateChildPartDesigns(current.Item1, asset.Hierarchy, false).Reverse()) { - var current = stack.Pop(); - sb.Append("".PadLeft(current.Item2 * 2)); - sb.AppendLine($"- {current.Item1.Part.Name} [{current.Item1.Part.Id}]"); - foreach (var child in asset.EnumerateChildPartDesigns(current.Item1, asset.Hierarchy, false).Reverse()) - { - stack.Push(Tuple.Create(child, current.Item2 + 1)); - } + stack.Push(Tuple.Create(child, current.Item2 + 1)); } - var str = sb.ToString(); - return str; } + var str = sb.ToString(); + return str; + } - public static Types.MyAssetHierarchyPropertyGraph BuildAssetAndGraph(int rootCount, int depth, int childPerPart, Action> initializeProperties = null) - { - var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); - var asset = BuildHierarchy(rootCount, depth, childPerPart); - var assetItem = new AssetItem("MyAsset", asset); - initializeProperties?.Invoke(asset.Hierarchy); - var graph = (Types.MyAssetHierarchyPropertyGraph)AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); - return graph; - } + public static Types.MyAssetHierarchyPropertyGraph BuildAssetAndGraph(int rootCount, int depth, int childPerPart, Action> initializeProperties = null) + { + var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); + var asset = BuildHierarchy(rootCount, depth, childPerPart); + var assetItem = new AssetItem("MyAsset", asset); + initializeProperties?.Invoke(asset.Hierarchy); + return (Types.MyAssetHierarchyPropertyGraph)AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); + } - public static AssetTestContainer BuildAssetContainer(int rootCount, int depth, int childPerPart, AssetPropertyGraphContainer graphContainer = null, Action> initializeProperties = null) - { - graphContainer = graphContainer ?? new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); - var asset = BuildHierarchy(rootCount, depth, childPerPart); - initializeProperties?.Invoke(asset.Hierarchy); - var container = new AssetTestContainer(graphContainer, asset); - container.BuildGraph(); - return container; - } + public static AssetTestContainer BuildAssetContainer(int rootCount, int depth, int childPerPart, AssetPropertyGraphContainer graphContainer = null, Action> initializeProperties = null) + { + graphContainer ??= new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); + var asset = BuildHierarchy(rootCount, depth, childPerPart); + initializeProperties?.Invoke(asset.Hierarchy); + var container = new AssetTestContainer(graphContainer, asset); + container.BuildGraph(); + return container; + } - private static Types.MyAssetHierarchy BuildHierarchy(int rootCount, int depth, int childPerPart) + private static Types.MyAssetHierarchy BuildHierarchy(int rootCount, int depth, int childPerPart) + { + var asset = new Types.MyAssetHierarchy(); + var guid = 0; + for (var i = 0; i < rootCount; ++i) { - var asset = new Types.MyAssetHierarchy(); - var guid = 0; - for (var i = 0; i < rootCount; ++i) - { - var rootPart = BuildPart(asset, $"Part{i + 1}", depth - 1, childPerPart, ref guid); - asset.Hierarchy.RootParts.Add(rootPart.Part); - } - return asset; + var rootPart = BuildPart(asset, $"Part{i + 1}", depth - 1, childPerPart, ref guid); + asset.Hierarchy.RootParts.Add(rootPart.Part); } + return asset; + } - private static Types.MyPartDesign BuildPart(Types.MyAssetHierarchy asset, string name, int depth, int childPerPart, ref int guidCount) - { - var part = new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(++guidCount), Name = name } }; - asset.Hierarchy.Parts.Add(part); - if (depth <= 0) - return part; - - for (var i = 0; i < childPerPart; ++i) - { - var child = BuildPart(asset, name + $"-{i + 1}", depth - 1, childPerPart, ref guidCount); - part.Part.AddChild(child.Part); - } + private static Types.MyPartDesign BuildPart(Types.MyAssetHierarchy asset, string name, int depth, int childPerPart, ref int guidCount) + { + var part = new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(++guidCount), Name = name } }; + asset.Hierarchy.Parts.Add(part); + if (depth <= 0) return part; + + for (var i = 0; i < childPerPart; ++i) + { + var child = BuildPart(asset, name + $"-{i + 1}", depth - 1, childPerPart, ref guidCount); + part.Part.AddChild(child.Part); } + return part; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/AssetNodeInternalExtensions.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/AssetNodeInternalExtensions.cs index 5e3539b593..8b6f635e14 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/AssetNodeInternalExtensions.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/AssetNodeInternalExtensions.cs @@ -1,21 +1,21 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Assets.Quantum.Internal; using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Tests.Helpers +namespace Stride.Core.Assets.Quantum.Tests.Helpers; + +public static class AssetNodeInternalExtensions { - public static class AssetNodeInternalExtensions + public static OverrideType GetItemOverride(this IAssetNode node, NodeIndex index) { - public static OverrideType GetItemOverride(this IAssetNode node, NodeIndex index) - { - return ((IAssetObjectNodeInternal)node).GetItemOverride(index); - } + return ((IAssetObjectNodeInternal)node).GetItemOverride(index); + } - public static OverrideType GetKeyOverride(this IAssetNode node, NodeIndex index) - { - return ((IAssetObjectNodeInternal)node).GetKeyOverride(index); - } + public static OverrideType GetKeyOverride(this IAssetNode node, NodeIndex index) + { + return ((IAssetObjectNodeInternal)node).GetKeyOverride(index); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/AssetTestContainer.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/AssetTestContainer.cs index ab23ae80f3..143813782a 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/AssetTestContainer.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/AssetTestContainer.cs @@ -1,79 +1,77 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.IO; + using Xunit; -using Stride.Core.Annotations; using Stride.Core.Diagnostics; -namespace Stride.Core.Assets.Quantum.Tests.Helpers +namespace Stride.Core.Assets.Quantum.Tests.Helpers; + +public class AssetTestContainer { - public class AssetTestContainer + public AssetTestContainer(AssetPropertyGraphContainer container, Asset asset) { - public AssetTestContainer(AssetPropertyGraphContainer container, Asset asset) - { - Container = container; - AssetItem = new AssetItem("MyAsset", asset); - } - - public AssetPropertyGraphContainer Container { get; } + Container = container; + AssetItem = new AssetItem("MyAsset", asset); + } - public AssetItem AssetItem { get; } + public AssetPropertyGraphContainer Container { get; } + public AssetItem AssetItem { get; } - [NotNull] - public static Stream ToStream(string str) - { - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(str); - writer.Flush(); - stream.Position = 0; - return stream; - } + public static Stream ToStream(string str) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(str); + writer.Flush(); + stream.Position = 0; + return stream; } +} - public class AssetTestContainer : AssetTestContainer where TAsset : Asset where TAssetPropertyGraph : AssetPropertyGraph - { - private readonly LoggerResult logger = new LoggerResult(); +public class AssetTestContainer : AssetTestContainer + where TAsset : Asset + where TAssetPropertyGraph : AssetPropertyGraph +{ + private readonly LoggerResult logger = new(); - public AssetTestContainer(AssetPropertyGraphContainer container, TAsset asset) - : base(container, asset) - { - } + public AssetTestContainer(AssetPropertyGraphContainer container, TAsset asset) + : base(container, asset) + { + } - public AssetTestContainer(TAsset asset) - : base(new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }), asset) - { - } + public AssetTestContainer(TAsset asset) + : base(new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }), asset) + { + } - public TAsset Asset => (TAsset)AssetItem.Asset; + public TAsset Asset => (TAsset)AssetItem.Asset; - public TAssetPropertyGraph Graph { get; private set; } + public TAssetPropertyGraph Graph { get; private set; } - public void BuildGraph() - { - var baseGraph = AssetQuantumRegistry.ConstructPropertyGraph(Container, AssetItem, logger); - Container.RegisterGraph(baseGraph); - Assert.True(baseGraph is TAssetPropertyGraph); - Graph = (TAssetPropertyGraph)baseGraph; - } + public void BuildGraph() + { + var baseGraph = AssetQuantumRegistry.ConstructPropertyGraph(Container, AssetItem, logger); + Container.RegisterGraph(baseGraph); + Assert.True(baseGraph is TAssetPropertyGraph); + Graph = (TAssetPropertyGraph)baseGraph; + } - public AssetTestContainer DeriveAsset() - { - var derivedAsset = (TAsset)Asset.CreateDerivedAsset("MyAsset"); - var result = new AssetTestContainer(Container, derivedAsset); - result.BuildGraph(); - return result; - } + public AssetTestContainer DeriveAsset() + { + var derivedAsset = (TAsset)Asset.CreateDerivedAsset("MyAsset"); + var result = new AssetTestContainer(Container, derivedAsset); + result.BuildGraph(); + return result; + } - public static AssetTestContainer LoadFromYaml(string yaml) - { - var asset = AssetFileSerializer.Load(ToStream(yaml), $"MyAsset{Types.FileExtension}"); - var graphContainer = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); - var assetContainer = new AssetTestContainer(graphContainer, asset.Asset); - asset.YamlMetadata.CopyInto(assetContainer.AssetItem.YamlMetadata); - assetContainer.BuildGraph(); - return assetContainer; - } + public static AssetTestContainer LoadFromYaml(string yaml) + { + var asset = AssetFileSerializer.Load(ToStream(yaml), $"MyAsset{Types.FileExtension}"); + var graphContainer = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); + var assetContainer = new AssetTestContainer(graphContainer, asset.Asset); + asset.YamlMetadata.CopyInto(assetContainer.AssetItem.YamlMetadata); + assetContainer.BuildGraph(); + return assetContainer; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/DeriveAssetTest.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/DeriveAssetTest.cs index fa9a8bd98d..c15f258188 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/DeriveAssetTest.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/DeriveAssetTest.cs @@ -1,65 +1,66 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets.Quantum.Tests.Helpers + +namespace Stride.Core.Assets.Quantum.Tests.Helpers; + +public class DeriveAssetTest + where TAsset : Asset + where TAssetPropertyGraph : AssetPropertyGraph { - public class DeriveAssetTest where TAsset : Asset where TAssetPropertyGraph : AssetPropertyGraph + private DeriveAssetTest(AssetTestContainer baseAsset, AssetTestContainer derivedAsset, AssetTestContainer subDerivedAsset) { - private DeriveAssetTest(AssetTestContainer baseAsset, AssetTestContainer derivedAsset, AssetTestContainer subDerivedAsset) - { - Base = baseAsset; - Derived = derivedAsset; - SubDerived = subDerivedAsset; - } + Base = baseAsset; + Derived = derivedAsset; + SubDerived = subDerivedAsset; + } - public TAsset BaseAsset => (TAsset)BaseAssetItem.Asset; - public TAsset DerivedAsset => (TAsset)DerivedAssetItem.Asset; - public TAsset SubDerivedAsset => (TAsset)SubDerivedAssetItem.Asset; - public AssetItem BaseAssetItem => Base.AssetItem; - public AssetItem DerivedAssetItem => Derived.AssetItem; - public AssetItem SubDerivedAssetItem => SubDerived.AssetItem; - public TAssetPropertyGraph BaseGraph => Base.Graph; - public TAssetPropertyGraph DerivedGraph => Derived.Graph; - public TAssetPropertyGraph SubDerivedGraph => SubDerived.Graph; + public TAsset BaseAsset => (TAsset)BaseAssetItem.Asset; + public TAsset DerivedAsset => (TAsset)DerivedAssetItem.Asset; + public TAsset SubDerivedAsset => (TAsset)SubDerivedAssetItem.Asset; + public AssetItem BaseAssetItem => Base.AssetItem; + public AssetItem DerivedAssetItem => Derived.AssetItem; + public AssetItem SubDerivedAssetItem => SubDerived.AssetItem; + public TAssetPropertyGraph BaseGraph => Base.Graph; + public TAssetPropertyGraph DerivedGraph => Derived.Graph; + public TAssetPropertyGraph SubDerivedGraph => SubDerived.Graph; - public AssetTestContainer Base { get; } - public AssetTestContainer Derived { get; } - public AssetTestContainer SubDerived { get; } + public AssetTestContainer Base { get; } + public AssetTestContainer Derived { get; } + public AssetTestContainer SubDerived { get; } - public static DeriveAssetTest DeriveAsset(TAsset baseAsset, bool deriveTwice = true) + public static DeriveAssetTest DeriveAsset(TAsset baseAsset, bool deriveTwice = true) + { + var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); + var baseContainer = new AssetTestContainer(container, baseAsset); + baseContainer.BuildGraph(); + var derivedAsset = (TAsset)baseContainer.Asset.CreateDerivedAsset("MyAsset"); + var derivedContainer = new AssetTestContainer(baseContainer.Container, derivedAsset); + derivedContainer.BuildGraph(); + derivedContainer.Graph.RefreshBase(); + AssetTestContainer subDerivedContainer = null; + if (deriveTwice) { - var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); - var baseContainer = new AssetTestContainer(container, baseAsset); - baseContainer.BuildGraph(); - var derivedAsset = (TAsset)baseContainer.Asset.CreateDerivedAsset("MyAsset"); - var derivedContainer = new AssetTestContainer(baseContainer.Container, derivedAsset); - derivedContainer.BuildGraph(); - derivedContainer.Graph.RefreshBase(); - AssetTestContainer subDerivedContainer = null; - if (deriveTwice) - { - var subDerivedAsset = (TAsset)derivedContainer.Asset.CreateDerivedAsset("MySubAsset"); - subDerivedContainer = new AssetTestContainer(baseContainer.Container, subDerivedAsset); - subDerivedContainer.BuildGraph(); - subDerivedContainer.Graph.RefreshBase(); - } - var result = new DeriveAssetTest(baseContainer, derivedContainer, subDerivedContainer); - return result; + var subDerivedAsset = (TAsset)derivedContainer.Asset.CreateDerivedAsset("MySubAsset"); + subDerivedContainer = new AssetTestContainer(baseContainer.Container, subDerivedAsset); + subDerivedContainer.BuildGraph(); + subDerivedContainer.Graph.RefreshBase(); } + return new DeriveAssetTest(baseContainer, derivedContainer, subDerivedContainer); + } - public static DeriveAssetTest LoadFromYaml(string baseYaml, string derivedYaml) - { - var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); - var baseAsset = AssetFileSerializer.Load(AssetTestContainer.ToStream(baseYaml), $"MyAsset{Types.FileExtension}"); - var derivedAsset = AssetFileSerializer.Load(AssetTestContainer.ToStream(derivedYaml), $"MyDerivedAsset{Types.FileExtension}"); - var baseContainer = new AssetTestContainer(container, baseAsset.Asset); - var derivedContainer = new AssetTestContainer(container, derivedAsset.Asset); - baseAsset.YamlMetadata.CopyInto(baseContainer.AssetItem.YamlMetadata); - derivedAsset.YamlMetadata.CopyInto(derivedContainer.AssetItem.YamlMetadata); - baseContainer.BuildGraph(); - derivedContainer.BuildGraph(); - var result = new DeriveAssetTest(baseContainer, derivedContainer, null); - derivedContainer.Graph.RefreshBase(); - return result; - } + public static DeriveAssetTest LoadFromYaml(string baseYaml, string derivedYaml) + { + var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); + var baseAsset = AssetFileSerializer.Load(AssetTestContainer.ToStream(baseYaml), $"MyAsset{Types.FileExtension}"); + var derivedAsset = AssetFileSerializer.Load(AssetTestContainer.ToStream(derivedYaml), $"MyDerivedAsset{Types.FileExtension}"); + var baseContainer = new AssetTestContainer(container, baseAsset.Asset); + var derivedContainer = new AssetTestContainer(container, derivedAsset.Asset); + baseAsset.YamlMetadata.CopyInto(baseContainer.AssetItem.YamlMetadata); + derivedAsset.YamlMetadata.CopyInto(derivedContainer.AssetItem.YamlMetadata); + baseContainer.BuildGraph(); + derivedContainer.BuildGraph(); + var result = new DeriveAssetTest(baseContainer, derivedContainer, null); + derivedContainer.Graph.RefreshBase(); + return result; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/SerializationHelper.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/SerializationHelper.cs index 428081dd9b..f85ef165c8 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/SerializationHelper.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/SerializationHelper.cs @@ -1,47 +1,40 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; using Stride.Core.Assets.Tests.Helpers; using Stride.Core.Assets.Yaml; using Stride.Core.Reflection; using Stride.Core.Yaml; -namespace Stride.Core.Assets.Quantum.Tests.Helpers +namespace Stride.Core.Assets.Quantum.Tests.Helpers; + +public static class SerializationHelper { - public static class SerializationHelper - { - public static readonly AssetId BaseId = (AssetId)GuidGenerator.Get(1); - public static readonly AssetId DerivedId = (AssetId)GuidGenerator.Get(2); + public static readonly AssetId BaseId = (AssetId)GuidGenerator.Get(1); + public static readonly AssetId DerivedId = (AssetId)GuidGenerator.Get(2); - public static void SerializeAndCompare(AssetItem assetItem, AssetPropertyGraph graph, string expectedYaml, bool isDerived) - { - assetItem.Asset.Id = isDerived ? DerivedId : BaseId; - Assert.Equal(isDerived, assetItem.Asset.Archetype != null); - if (isDerived) - assetItem.Asset.Archetype = new AssetReference(BaseId, assetItem.Asset.Archetype?.Location); - graph.PrepareForSave(null, assetItem); - var stream = new MemoryStream(); - AssetFileSerializer.Save(stream, assetItem.Asset, assetItem.YamlMetadata, null); - stream.Position = 0; - var streamReader = new StreamReader(stream); - var yaml = streamReader.ReadToEnd(); - Assert.Equal(expectedYaml, yaml); - } + public static void SerializeAndCompare(AssetItem assetItem, AssetPropertyGraph graph, string expectedYaml, bool isDerived) + { + assetItem.Asset.Id = isDerived ? DerivedId : BaseId; + Assert.Equal(isDerived, assetItem.Asset.Archetype != null); + if (isDerived) + assetItem.Asset.Archetype = new AssetReference(BaseId, assetItem.Asset.Archetype?.Location); + graph.PrepareForSave(null, assetItem); + var stream = new MemoryStream(); + AssetFileSerializer.Save(stream, assetItem.Asset, assetItem.YamlMetadata, null); + stream.Position = 0; + var streamReader = new StreamReader(stream); + var yaml = streamReader.ReadToEnd(); + Assert.Equal(expectedYaml, yaml); + } - public static void SerializeAndCompare(object instance, YamlAssetMetadata overrides, string expectedYaml) - { - var stream = new MemoryStream(); - var metadata = new AttachedYamlAssetMetadata(); - metadata.AttachMetadata(AssetObjectSerializerBackend.OverrideDictionaryKey, overrides); - AssetFileSerializer.Default.Save(stream, instance, metadata, null); - stream.Position = 0; - var streamReader = new StreamReader(stream); - var yaml = streamReader.ReadToEnd(); - Assert.Equal(expectedYaml, yaml); - } + public static void SerializeAndCompare(object instance, YamlAssetMetadata overrides, string expectedYaml) + { + var stream = new MemoryStream(); + var metadata = new AttachedYamlAssetMetadata(); + metadata.AttachMetadata(AssetObjectSerializerBackend.OverrideDictionaryKey, overrides); + AssetFileSerializer.Default.Save(stream, instance, metadata, null); + stream.Position = 0; + var streamReader = new StreamReader(stream); + var yaml = streamReader.ReadToEnd(); + Assert.Equal(expectedYaml, yaml); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/Types.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/Types.cs index bb9c667c5c..d18706a190 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/Types.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/Helpers/Types.cs @@ -1,313 +1,304 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.ComponentModel; -using System.Linq; -using Stride.Core; using Stride.Core.Annotations; using Stride.Core.Diagnostics; using Stride.Core.Extensions; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Tests.Helpers +namespace Stride.Core.Assets.Quantum.Tests.Helpers; + +public static class Types { - public static class Types + public const string FileExtension = ".sdtest"; + + [DataContract] + public abstract class MyAssetBase : Asset; + + [DataContract] + [AssetDescription(FileExtension)] + public class MyAsset1 : MyAssetBase { - public const string FileExtension = ".sdtest"; + public string MyString { get; set; } + } - [DataContract] - public abstract class MyAssetBase : Asset - { - } + [DataContract] + [AssetDescription(FileExtension)] + public class MyAsset2 : MyAssetBase + { + public List MyStrings { get; set; } = []; + public StructWithList Struct = new() { MyStrings = [] }; + } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAsset1 : MyAssetBase - { - public string MyString { get; set; } - } + [DataContract] + [AssetDescription(FileExtension)] + public class MyAsset3 : MyAssetBase + { + public Dictionary MyDictionary { get; set; } = []; + } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAsset2 : MyAssetBase - { - public List MyStrings { get; set; } = new List(); - public StructWithList Struct = new StructWithList { MyStrings = new List() }; - } + [DataContract] + [AssetDescription(FileExtension)] + public class MyAsset4 : MyAssetBase + { + public List MyObjects { get; set; } = []; + } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAsset3 : MyAssetBase - { - public Dictionary MyDictionary { get; set; } = new Dictionary(); - } + [DataContract] + [AssetDescription(FileExtension)] + public class MyAsset5 : MyAssetBase + { + public List MyInterfaces { get; set; } = []; + public IMyInterface MyInterface { get; set; } + } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAsset4 : MyAssetBase - { - public List MyObjects { get; set; } = new List(); - } + [DataContract] + [AssetDescription(FileExtension)] + public class MyAsset6 : MyAssetBase + { + public Dictionary MyDictionary { get; set; } = []; + } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAsset5 : MyAssetBase - { - public List MyInterfaces { get; set; } = new List(); - public IMyInterface MyInterface { get; set; } - } + [DataContract] + [AssetDescription(FileExtension)] + public class MyAsset7 : MyAssetBase + { + public MyAsset2 MyAsset2 { get; set; } + public MyAsset3 MyAsset3 { get; set; } + public MyAsset4 MyAsset4 { get; set; } + } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAsset6 : MyAssetBase - { - public Dictionary MyDictionary { get; set; } = new Dictionary(); - } + [DataContract] + [AssetDescription(FileExtension)] + public class MyAsset8 : MyAssetBase + { + [NonIdentifiableCollectionItems] + public List MyObjects { get; set; } = []; + } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAsset7 : MyAssetBase - { - public MyAsset2 MyAsset2 { get; set; } - public MyAsset3 MyAsset3 { get; set; } - public MyAsset4 MyAsset4 { get; set; } - } + [DataContract] + [AssetDescription(FileExtension)] + public class MyAsset9 : MyAssetBase + { + public SomeObject MyObject { get; set; } + } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAsset8 : MyAssetBase - { - [NonIdentifiableCollectionItems] - public List MyObjects { get; set; } = new List(); - } + [DataContract] + [AssetDescription(FileExtension)] + public class MyAsset10 : MyAssetBase + { + [DefaultValue(true)] + public bool MyBool { get; set; } = true; + } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAsset9 : MyAssetBase - { - public SomeObject MyObject { get; set; } - } + [DataContract] + public class MyReferenceable : IIdentifiable + { + public MyReferenceable() { Id = Guid.NewGuid(); } + public string Value { get; set; } + [NonOverridable] + public Guid Id { get; set; } + public override string ToString() => $"[{Id}] {Value}"; + } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAsset10 : MyAssetBase - { - [DefaultValue(true)] - public bool MyBool { get; set; } = true; - } + [DataContract] + [AssetDescription(FileExtension)] + public class MyAssetWithRef : MyAssetBase + { + public MyReferenceable MyObject1 { get; set; } - [DataContract] - public class MyReferenceable : IIdentifiable - { - public MyReferenceable() { Id = Guid.NewGuid(); } - public string Value { get; set; } - [NonOverridable] - public Guid Id { get; set; } - public override string ToString() => $"[{Id}] {Value}"; - } + public MyReferenceable MyObject2 { get; set; } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAssetWithRef : MyAssetBase - { - public MyReferenceable MyObject1 { get; set; } + [DefaultValue(null)] + public MyReferenceable MyObject3 { get; set; } - public MyReferenceable MyObject2 { get; set; } + public List MyObjects { get; set; } = []; - [DefaultValue(null)] - public MyReferenceable MyObject3 { get; set; } + [NonIdentifiableCollectionItems] + public List MyNonIdObjects { get; set; } = []; + } - public List MyObjects { get; set; } = new List(); + [DataContract] + [AssetDescription(FileExtension)] + public class MyAssetWithRef2 : MyAssetBase + { + public MyReferenceable NonReference { get; set; } - [NonIdentifiableCollectionItems] - public List MyNonIdObjects { get; set; } = new List(); - } + public MyReferenceable Reference { get; set; } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAssetWithRef2 : MyAssetBase - { - public MyReferenceable NonReference { get; set; } + public List References { get; set; } = []; - public MyReferenceable Reference { get; set; } + public static int MemberCount => 4 + 3; // 4 (number of members in Asset) + 3 (number of members in Types.MyAssetWithRef2) + } - public List References { get; set; } = new List(); + [DataContract] + [AssetDescription(FileExtension)] + public class MyAssetWithStructWithPrimitives : MyAssetBase + { + public StructWithPrimitives StructValue { get; set; } + } - public static int MemberCount => 4 + 3; // 4 (number of members in Asset) + 3 (number of members in Types.MyAssetWithRef2) - } + [DataContract] + public struct StructWithList + { + public List MyStrings { get; set; } + } - [DataContract] - [AssetDescription(FileExtension)] - public class MyAssetWithStructWithPrimitives : MyAssetBase - { - public StructWithPrimitives StructValue { get; set; } - } + [DataContract] + [DataStyle(DataStyle.Compact)] + public struct StructWithPrimitives : IEquatable + { + [DefaultValue(0)] + public int Value1 { get; set; } + [DefaultValue(0)] + public int Value2 { get; set; } - [DataContract] - public struct StructWithList - { - public List MyStrings { get; set; } - } + public override readonly bool Equals(object obj) => obj is StructWithPrimitives value && Equals(value); + public readonly bool Equals(StructWithPrimitives other) => Value1 == other.Value1 && Value2 == other.Value2; - [DataContract] - [DataStyle(DataStyle.Compact)] - public struct StructWithPrimitives : IEquatable - { - [DefaultValue(0)] - public int Value1 { get; set; } - [DefaultValue(0)] - public int Value2 { get; set; } + public override readonly int GetHashCode() => HashCode.Combine(Value1, Value2); - public override readonly bool Equals(object obj) => obj is StructWithPrimitives value && Equals(value); - public readonly bool Equals(StructWithPrimitives other) => Value1 == other.Value1 && Value2 == other.Value2; + public override readonly string ToString() => $"(Value1: {Value1}, Value2: {Value2})"; + } - public override readonly int GetHashCode() => HashCode.Combine(Value1, Value2); + public interface IMyInterface + { + string Value { get; set; } + } - public override string ToString() => $"(Value1: {Value1}, Value2: {Value2})"; - } + [DataContract] + public class SomeObject : IMyInterface + { + public string Value { get; set; } + } - public interface IMyInterface + [DataContract] + public class SomeObject2 : IMyInterface + { + public string Value { get; set; } + public int Number { get; set; } + } + + [AssetPropertyGraphDefinition(typeof(MyAssetWithRef))] + public class AssetWithRefPropertyGraphDefinition : AssetPropertyGraphDefinition + { + public static Func IsObjectReferenceFunc { get; set; } + + public override bool IsMemberTargetObjectReference(IMemberNode member, object value) { - string Value { get; set; } + return IsObjectReferenceFunc?.Invoke(member, NodeIndex.Empty) ?? base.IsMemberTargetObjectReference(member, value); } - [DataContract] - public class SomeObject : IMyInterface + public override bool IsTargetItemObjectReference(IObjectNode collection, NodeIndex itemIndex, object value) { - public string Value { get; set; } + return IsObjectReferenceFunc?.Invoke(collection, itemIndex) ?? base.IsTargetItemObjectReference(collection, itemIndex, value); } + } - [DataContract] - public class SomeObject2 : IMyInterface + [AssetPropertyGraphDefinition(typeof(MyAssetWithRef2))] + public class AssetWithRefPropertyGraph2 : AssetPropertyGraphDefinition + { + public override bool IsMemberTargetObjectReference(IMemberNode member, object value) { - public string Value { get; set; } - public int Number { get; set; } + return member.Name == nameof(MyAssetWithRef2.Reference); } - [AssetPropertyGraphDefinition(typeof(MyAssetWithRef))] - public class AssetWithRefPropertyGraphDefinition : AssetPropertyGraphDefinition + public override bool IsTargetItemObjectReference(IObjectNode collection, NodeIndex itemIndex, object value) { - public static Func IsObjectReferenceFunc { get; set; } + return collection.Retrieve() is List; + } + } - public override bool IsMemberTargetObjectReference(IMemberNode member, object value) - { - return IsObjectReferenceFunc?.Invoke(member, NodeIndex.Empty) ?? base.IsMemberTargetObjectReference(member, value); - } + // TODO: we don't want to have to do this to detect children! + [DataContract] + public class ChildrenList : List; - public override bool IsTargetItemObjectReference(IObjectNode collection, NodeIndex itemIndex, object value) - { - return IsObjectReferenceFunc?.Invoke(collection, itemIndex) ?? base.IsTargetItemObjectReference(collection, itemIndex, value); - } - } + [DataContract("MyPart")] + public class MyPart : IIdentifiable + { + [NonOverridable] + public Guid Id { get; set; } + [DefaultValue(null)] public string Name { get; set; } + [DefaultValue(null)] public MyPart Parent { get; set; } + [DefaultValue(null)] public MyPart MyReference { get; set; } + [DefaultValue(null)] public List MyReferences { get; set; } + [DefaultValue(null)] public SomeObject Object { get; set; } + [NonIdentifiableCollectionItems] public List Children { get; } = []; + public void AddChild([NotNull] MyPart child) { Children.Add(child); child.Parent = this; } + public override string ToString() => $"{Name} [{Id}]"; + } - [AssetPropertyGraphDefinition(typeof(MyAssetWithRef2))] - public class AssetWithRefPropertyGraph2 : AssetPropertyGraphDefinition + [DataContract("MyPartDesign")] + public class MyPartDesign : IAssetPartDesign + { + [DefaultValue(null)] + public BasePart Base { get; set; } + IIdentifiable IAssetPartDesign.Part => Part; + // ReSharper disable once NotNullMemberIsNotInitialized + public MyPart Part { get; set; } + public override string ToString() => $"Design: {Part.Name} [{Part.Id}]"; + } + + [DataContract("MyAssetHierarchy")] + [AssetDescription(FileExtension)] + public class MyAssetHierarchy : AssetCompositeHierarchy + { + public override MyPart GetParent(MyPart part) => part.Parent; + public override int IndexOf(MyPart part) => GetParent(part)?.Children.IndexOf(part) ?? Hierarchy.RootParts.IndexOf(part); + public override MyPart GetChild(MyPart part, int index) => part.Children[index]; + public override int GetChildCount(MyPart part) => part.Children.Count; + public override IEnumerable EnumerateChildParts(MyPart part, bool isRecursive) => isRecursive ? part.Children.DepthFirst(t => t.Children) : part.Children; + public AssetCompositeHierarchyData CreatePartInstances() { - public override bool IsMemberTargetObjectReference(IMemberNode member, object value) - { - return member.Name == nameof(MyAssetWithRef2.Reference); - } - - public override bool IsTargetItemObjectReference(IObjectNode collection, NodeIndex itemIndex, object value) - { - return collection.Retrieve() is List; - } + var instance = (MyAssetHierarchy)CreateDerivedAsset("", out _); + return instance.Hierarchy; } + } - // TODO: we don't want to have to do this to detect children! - [DataContract] - public class ChildrenList : List { } - - [DataContract("MyPart")] - public class MyPart : IIdentifiable + [AssetPropertyGraph(typeof(MyAssetHierarchy))] + // ReSharper disable once ClassNeverInstantiated.Local + public class MyAssetHierarchyPropertyGraph : AssetCompositeHierarchyPropertyGraph + { + public MyAssetHierarchyPropertyGraph(AssetPropertyGraphContainer container, AssetItem assetItem, ILogger logger) : base(container, assetItem, logger) { } + public override bool IsChildPartReference(IGraphNode node, NodeIndex index) => node.Type == typeof(ChildrenList); + protected override void AddChildPartToParentPart(MyPart parentPart, MyPart childPart, int index) { - [NonOverridable] - public Guid Id { get; set; } - [DefaultValue(null)] public string Name { get; set; } - [DefaultValue(null)] public MyPart Parent { get; set; } - [DefaultValue(null)] public MyPart MyReference { get; set; } - [DefaultValue(null)] public List MyReferences { get; set; } - [DefaultValue(null)] public SomeObject Object { get; set; } - [NonIdentifiableCollectionItems] public List Children { get; } = new List(); - public void AddChild([NotNull] MyPart child) { Children.Add(child); child.Parent = this; } - public override string ToString() => $"{Name} [{Id}]"; + Container.NodeContainer.GetNode(parentPart)[nameof(MyPart.Children)].Target.Add(childPart, new NodeIndex(index)); + Container.NodeContainer.GetNode(childPart)[nameof(MyPart.Parent)].Update(parentPart); } - [DataContract("MyPartDesign")] - public class MyPartDesign : IAssetPartDesign + protected override void RemoveChildPartFromParentPart(MyPart parentPart, MyPart childPart) { - [DefaultValue(null)] - public BasePart Base { get; set; } - IIdentifiable IAssetPartDesign.Part => Part; - // ReSharper disable once NotNullMemberIsNotInitialized - public MyPart Part { get; set; } - public override string ToString() => $"Design: {Part.Name} [{Part.Id}]"; + Container.NodeContainer.GetNode(parentPart)[nameof(MyPart.Children)].Target.Remove(childPart, new NodeIndex(parentPart.Children.IndexOf(childPart))); + Container.NodeContainer.GetNode(childPart)[nameof(MyPart.Parent)].Update(null); } - [DataContract("MyAssetHierarchy")] - [AssetDescription(FileExtension)] - public class MyAssetHierarchy : AssetCompositeHierarchy + protected override Guid GetIdFromChildPart(object part) => ((MyPart)part).Id; + protected override IEnumerable RetrieveChildPartNodes(MyPart part) { - public override MyPart GetParent(MyPart part) => part.Parent; - public override int IndexOf(MyPart part) => GetParent(part)?.Children.IndexOf(part) ?? Hierarchy.RootParts.IndexOf(part); - public override MyPart GetChild(MyPart part, int index) => part.Children[index]; - public override int GetChildCount(MyPart part) => part.Children.Count; - public override IEnumerable EnumerateChildParts(MyPart part, bool isRecursive) => isRecursive ? part.Children.DepthFirst(t => t.Children) : part.Children; - public AssetCompositeHierarchyData CreatePartInstances() - { - Dictionary idRemapping; - var instance = (MyAssetHierarchy)CreateDerivedAsset("", out idRemapping); - return instance.Hierarchy; - } + yield return Container.NodeContainer.GetNode(part.Children); } + } + + [AssetPropertyGraph(typeof(MyAssetBase))] + public class MyAssetBasePropertyGraph : AssetPropertyGraph + { + private readonly Dictionary customBases = []; - [AssetPropertyGraph(typeof(MyAssetHierarchy))] - // ReSharper disable once ClassNeverInstantiated.Local - public class MyAssetHierarchyPropertyGraph : AssetCompositeHierarchyPropertyGraph + public MyAssetBasePropertyGraph(AssetPropertyGraphContainer container, AssetItem assetItem, ILogger logger) + : base(container, assetItem, logger) { - public MyAssetHierarchyPropertyGraph(AssetPropertyGraphContainer container, AssetItem assetItem, ILogger logger) : base(container, assetItem, logger) { } - public override bool IsChildPartReference(IGraphNode node, NodeIndex index) => node.Type == typeof(ChildrenList); - protected override void AddChildPartToParentPart(MyPart parentPart, MyPart childPart, int index) - { - Container.NodeContainer.GetNode(parentPart)[nameof(MyPart.Children)].Target.Add(childPart, new NodeIndex(index)); - Container.NodeContainer.GetNode(childPart)[nameof(MyPart.Parent)].Update(parentPart); - } - - protected override void RemoveChildPartFromParentPart(MyPart parentPart, MyPart childPart) - { - Container.NodeContainer.GetNode(parentPart)[nameof(MyPart.Children)].Target.Remove(childPart, new NodeIndex(parentPart.Children.IndexOf(childPart))); - Container.NodeContainer.GetNode(childPart)[nameof(MyPart.Parent)].Update(null); - } - - protected override Guid GetIdFromChildPart(object part) => ((MyPart)part).Id; - protected override IEnumerable RetrieveChildPartNodes(MyPart part) - { - yield return Container.NodeContainer.GetNode(part.Children); - } } + public void RegisterCustomBaseLink(IGraphNode node, IGraphNode baseNode) + { + customBases.Add(node, baseNode); + } - [AssetPropertyGraph(typeof(MyAssetBase))] - public class MyAssetBasePropertyGraph : AssetPropertyGraph + public override IGraphNode FindTarget(IGraphNode sourceNode, IGraphNode target) { - private readonly Dictionary customBases = new Dictionary(); - - public MyAssetBasePropertyGraph(AssetPropertyGraphContainer container, AssetItem assetItem, ILogger logger) - : base(container, assetItem, logger) - { - } - - public void RegisterCustomBaseLink(IGraphNode node, IGraphNode baseNode) - { - customBases.Add(node, baseNode); - } - - public override IGraphNode FindTarget(IGraphNode sourceNode, IGraphNode target) - { - IGraphNode baseNode; - return customBases.TryGetValue(sourceNode, out baseNode) ? baseNode : base.FindTarget(sourceNode, target); - } + return customBases.TryGetValue(sourceNode, out var baseNode) ? baseNode : base.FindTarget(sourceNode, target); } } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/Module.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/Module.cs index dfd69d02a3..80b4cd723c 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/Module.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/Module.cs @@ -4,17 +4,16 @@ using System.Runtime.CompilerServices; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +// Somehow it helps Resharper NUnit to run module initializer first (to determine unit test configuration). +public class Module { - // Somehow it helps Resharper NUnit to run module initializer first (to determine unit test configuration). - public class Module + [ModuleInitializer] + internal static void Initialize() { - [ModuleInitializer] - internal static void Initialize() - { - AssemblyRegistry.Register(typeof(Module).Assembly, AssemblyCommonCategories.Assets); - AssetQuantumRegistry.RegisterAssembly(typeof(Module).Assembly); - RuntimeHelpers.RunModuleConstructor(typeof(Asset).Module.ModuleHandle); - } + AssemblyRegistry.Register(typeof(Module).Assembly, AssemblyCommonCategories.Assets); + AssetQuantumRegistry.RegisterAssembly(typeof(Module).Assembly); + RuntimeHelpers.RunModuleConstructor(typeof(Asset).Module.ModuleHandle); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/Properties/AssemblyInfo.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/Properties/AssemblyInfo.cs index 05b3499f94..a573b5040d 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/Properties/AssemblyInfo.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/Properties/AssemblyInfo.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Reflection; -using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/Stride.Core.Assets.Quantum.Tests.csproj b/sources/assets/Stride.Core.Assets.Quantum.Tests/Stride.Core.Assets.Quantum.Tests.csproj index c5d7fd9a65..007ec779cd 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/Stride.Core.Assets.Quantum.Tests.csproj +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/Stride.Core.Assets.Quantum.Tests.csproj @@ -3,6 +3,8 @@ $(StrideXplatEditorTargetFramework) linux-x64;win-x64 + enable + latest LinuxTools;WindowsTools diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestArchetypesAdvanced.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestArchetypesAdvanced.cs index de9682c9f5..d8256e7501 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestArchetypesAdvanced.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestArchetypesAdvanced.cs @@ -1,400 +1,399 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Linq; + using Xunit; using Stride.Core.Assets.Quantum.Tests.Helpers; using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +public class TestArchetypesAdvanced { - public class TestArchetypesAdvanced + [Fact] + public void TestSimpleDictionaryAddWithCollision() { - [Fact] - public void TestSimpleDictionaryAddWithCollision() - { - var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - // Update a key to derived and then the same key to the base - derivedPropertyNode.Target.Add("String3", new NodeIndex("Key3")); - basePropertyNode.Target.Add("String4", new NodeIndex("Key3")); + // Update a key to derived and then the same key to the base + derivedPropertyNode.Target.Add("String3", new NodeIndex("Key3")); + basePropertyNode.Target.Add("String4", new NodeIndex("Key3")); - Assert.Equal(3, context.BaseAsset.MyDictionary.Count); - Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key3"))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); - Assert.NotEqual(baseIds, derivedIds); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(1, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - Assert.NotEqual(baseIds["Key3"], derivedIds["Key3"]); - Assert.Equal(baseIds["Key3"], derivedIds.DeletedItems.Single()); - Assert.Equal(3, context.BaseAsset.MyDictionary.Count); - Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); - } + Assert.Equal(3, context.BaseAsset.MyDictionary.Count); + Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key3"))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); + Assert.NotEqual(baseIds, derivedIds); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(1, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + Assert.NotEqual(baseIds["Key3"], derivedIds["Key3"]); + Assert.Equal(baseIds["Key3"], derivedIds.DeletedItems.Single()); + Assert.Equal(3, context.BaseAsset.MyDictionary.Count); + Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); + } - [Fact] - public void TestSimpleCollectionRemoveDeleted() - { - var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2", "String3", "String4" } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + [Fact] + public void TestSimpleCollectionRemoveDeleted() + { + var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2", "String3", "String4" } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - // Delete an item from the derived and then delete the same from the base - var derivedDeletedId = derivedIds[2]; - var baseDeletedId = baseIds[2]; - derivedPropertyNode.Target.Remove("String3", new NodeIndex(2)); - basePropertyNode.Target.Remove("String3", new NodeIndex(2)); - Assert.Equal(3, context.BaseAsset.MyStrings.Count); - Assert.Equal(3, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - Assert.Equal(baseIds[2], derivedIds[2]); - Assert.False(derivedIds.IsDeleted(derivedDeletedId)); - Assert.False(baseIds.IsDeleted(baseDeletedId)); - } + // Delete an item from the derived and then delete the same from the base + var derivedDeletedId = derivedIds[2]; + var baseDeletedId = baseIds[2]; + derivedPropertyNode.Target.Remove("String3", new NodeIndex(2)); + basePropertyNode.Target.Remove("String3", new NodeIndex(2)); + Assert.Equal(3, context.BaseAsset.MyStrings.Count); + Assert.Equal(3, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + Assert.Equal(baseIds[2], derivedIds[2]); + Assert.False(derivedIds.IsDeleted(derivedDeletedId)); + Assert.False(baseIds.IsDeleted(baseDeletedId)); + } - [Fact] - public void TestSimpleDictionaryRemoveDeleted() - { - var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" }, { "Key3", "String3" }, { "Key4", "String4" } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + [Fact] + public void TestSimpleDictionaryRemoveDeleted() + { + var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" }, { "Key3", "String3" }, { "Key4", "String4" } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - // Delete an item from the derived and then delete the same from the base - var derivedDeletedId = derivedIds["Key3"]; - derivedPropertyNode.Target.Remove("String3", new NodeIndex("Key3")); - var baseDeletedId = baseIds["Key3"]; - basePropertyNode.Target.Remove("String3", new NodeIndex("Key3")); - Assert.Equal(3, context.BaseAsset.MyDictionary.Count); - Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); - Assert.False(derivedIds.IsDeleted(derivedDeletedId)); - Assert.False(baseIds.IsDeleted(baseDeletedId)); - } + // Delete an item from the derived and then delete the same from the base + var derivedDeletedId = derivedIds["Key3"]; + derivedPropertyNode.Target.Remove("String3", new NodeIndex("Key3")); + var baseDeletedId = baseIds["Key3"]; + basePropertyNode.Target.Remove("String3", new NodeIndex("Key3")); + Assert.Equal(3, context.BaseAsset.MyDictionary.Count); + Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); + Assert.False(derivedIds.IsDeleted(derivedDeletedId)); + Assert.False(baseIds.IsDeleted(baseDeletedId)); + } - [Fact] - public void TestSimpleCollectionUpdateDeleted() - { - var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2", "String3", "String4" } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + [Fact] + public void TestSimpleCollectionUpdateDeleted() + { + var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2", "String3", "String4" } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - // Delete an item from the derived and then update the same from the base - var derivedDeletedId = derivedIds[2]; - derivedPropertyNode.Target.Remove("String3", new NodeIndex(2)); - basePropertyNode.Target.Update("String3.5", new NodeIndex(2)); - Assert.Equal(4, context.BaseAsset.MyStrings.Count); - Assert.Equal(3, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String3.5", basePropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(3))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(3))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(4, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(1, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - Assert.Equal(baseIds[3], derivedIds[2]); - Assert.True(derivedIds.IsDeleted(derivedDeletedId)); - } + // Delete an item from the derived and then update the same from the base + var derivedDeletedId = derivedIds[2]; + derivedPropertyNode.Target.Remove("String3", new NodeIndex(2)); + basePropertyNode.Target.Update("String3.5", new NodeIndex(2)); + Assert.Equal(4, context.BaseAsset.MyStrings.Count); + Assert.Equal(3, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String3.5", basePropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(3))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(3))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(4, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(1, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + Assert.Equal(baseIds[3], derivedIds[2]); + Assert.True(derivedIds.IsDeleted(derivedDeletedId)); + } - [Fact] - public void TestSimpleDictionaryUpdateDeleted() - { - var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" }, { "Key3", "String3" }, { "Key4", "String4" } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + [Fact] + public void TestSimpleDictionaryUpdateDeleted() + { + var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" }, { "Key3", "String3" }, { "Key4", "String4" } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - // Delete an item from the derived and then update the same from the base - var derivedDeletedId = derivedIds["Key3"]; - derivedPropertyNode.Target.Remove("String3", new NodeIndex("Key3")); - basePropertyNode.Target.Update("String3.5", new NodeIndex("Key3")); - Assert.Equal(4, context.BaseAsset.MyDictionary.Count); - Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String3.5", basePropertyNode.Retrieve(new NodeIndex("Key3"))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.Equal(4, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(1, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); - Assert.True(derivedIds.IsDeleted(derivedDeletedId)); - } + // Delete an item from the derived and then update the same from the base + var derivedDeletedId = derivedIds["Key3"]; + derivedPropertyNode.Target.Remove("String3", new NodeIndex("Key3")); + basePropertyNode.Target.Update("String3.5", new NodeIndex("Key3")); + Assert.Equal(4, context.BaseAsset.MyDictionary.Count); + Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String3.5", basePropertyNode.Retrieve(new NodeIndex("Key3"))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.Equal(4, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(1, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); + Assert.True(derivedIds.IsDeleted(derivedDeletedId)); + } - [Fact] - public void TestSimpleCollectionAddMultipleAndCheckOrder() - { - var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2", "String3", "String4" } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + [Fact] + public void TestSimpleCollectionAddMultipleAndCheckOrder() + { + var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2", "String3", "String4" } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - derivedPropertyNode.Target.Add("String3.5", new NodeIndex(3)); - derivedPropertyNode.Target.Add("String1.5", new NodeIndex(1)); - Assert.Equal(6, context.DerivedAsset.MyStrings.Count); - AssertCollection(derivedPropertyNode, "String1", "String1.5", "String2", "String3", "String3.5", "String4"); + derivedPropertyNode.Target.Add("String3.5", new NodeIndex(3)); + derivedPropertyNode.Target.Add("String1.5", new NodeIndex(1)); + Assert.Equal(6, context.DerivedAsset.MyStrings.Count); + AssertCollection(derivedPropertyNode, "String1", "String1.5", "String2", "String3", "String3.5", "String4"); - basePropertyNode.Target.Add("String0.1", new NodeIndex(0)); - Assert.Equal(5, context.BaseAsset.MyStrings.Count); - AssertCollection(basePropertyNode, "String0.1", "String1", "String2", "String3", "String4"); - Assert.Equal(7, context.DerivedAsset.MyStrings.Count); - AssertCollection(derivedPropertyNode, "String0.1", "String1", "String1.5", "String2", "String3", "String3.5", "String4"); + basePropertyNode.Target.Add("String0.1", new NodeIndex(0)); + Assert.Equal(5, context.BaseAsset.MyStrings.Count); + AssertCollection(basePropertyNode, "String0.1", "String1", "String2", "String3", "String4"); + Assert.Equal(7, context.DerivedAsset.MyStrings.Count); + AssertCollection(derivedPropertyNode, "String0.1", "String1", "String1.5", "String2", "String3", "String3.5", "String4"); - basePropertyNode.Target.Add("String1.1", new NodeIndex(2)); - Assert.Equal(6, context.BaseAsset.MyStrings.Count); - AssertCollection(basePropertyNode, "String0.1", "String1", "String1.1", "String2", "String3", "String4"); - Assert.Equal(8, context.DerivedAsset.MyStrings.Count); - AssertCollection(derivedPropertyNode, "String0.1", "String1", "String1.1", "String1.5", "String2", "String3", "String3.5", "String4"); + basePropertyNode.Target.Add("String1.1", new NodeIndex(2)); + Assert.Equal(6, context.BaseAsset.MyStrings.Count); + AssertCollection(basePropertyNode, "String0.1", "String1", "String1.1", "String2", "String3", "String4"); + Assert.Equal(8, context.DerivedAsset.MyStrings.Count); + AssertCollection(derivedPropertyNode, "String0.1", "String1", "String1.1", "String1.5", "String2", "String3", "String3.5", "String4"); - basePropertyNode.Target.Add("String2.1", new NodeIndex(4)); - Assert.Equal(7, context.BaseAsset.MyStrings.Count); - AssertCollection(basePropertyNode, "String0.1", "String1", "String1.1", "String2", "String2.1", "String3", "String4"); - Assert.Equal(9, context.DerivedAsset.MyStrings.Count); - AssertCollection(derivedPropertyNode, "String0.1", "String1", "String1.1", "String1.5", "String2", "String2.1", "String3", "String3.5", "String4"); + basePropertyNode.Target.Add("String2.1", new NodeIndex(4)); + Assert.Equal(7, context.BaseAsset.MyStrings.Count); + AssertCollection(basePropertyNode, "String0.1", "String1", "String1.1", "String2", "String2.1", "String3", "String4"); + Assert.Equal(9, context.DerivedAsset.MyStrings.Count); + AssertCollection(derivedPropertyNode, "String0.1", "String1", "String1.1", "String1.5", "String2", "String2.1", "String3", "String3.5", "String4"); - basePropertyNode.Target.Add("String3.1", new NodeIndex(6)); - Assert.Equal(8, context.BaseAsset.MyStrings.Count); - AssertCollection(basePropertyNode, "String0.1", "String1", "String1.1", "String2", "String2.1", "String3", "String3.1", "String4"); - Assert.Equal(10, context.DerivedAsset.MyStrings.Count); - AssertCollection(derivedPropertyNode, "String0.1", "String1", "String1.1", "String1.5", "String2", "String2.1", "String3", "String3.1", "String3.5", "String4"); + basePropertyNode.Target.Add("String3.1", new NodeIndex(6)); + Assert.Equal(8, context.BaseAsset.MyStrings.Count); + AssertCollection(basePropertyNode, "String0.1", "String1", "String1.1", "String2", "String2.1", "String3", "String3.1", "String4"); + Assert.Equal(10, context.DerivedAsset.MyStrings.Count); + AssertCollection(derivedPropertyNode, "String0.1", "String1", "String1.1", "String1.5", "String2", "String2.1", "String3", "String3.1", "String3.5", "String4"); - basePropertyNode.Target.Add("String4.1", new NodeIndex(8)); - Assert.Equal(9, context.BaseAsset.MyStrings.Count); - AssertCollection(basePropertyNode, "String0.1", "String1", "String1.1", "String2", "String2.1", "String3", "String3.1", "String4", "String4.1"); - Assert.Equal(11, context.DerivedAsset.MyStrings.Count); - AssertCollection(derivedPropertyNode, "String0.1", "String1", "String1.1", "String1.5", "String2", "String2.1", "String3", "String3.1", "String3.5", "String4", "String4.1"); + basePropertyNode.Target.Add("String4.1", new NodeIndex(8)); + Assert.Equal(9, context.BaseAsset.MyStrings.Count); + AssertCollection(basePropertyNode, "String0.1", "String1", "String1.1", "String2", "String2.1", "String3", "String3.1", "String4", "String4.1"); + Assert.Equal(11, context.DerivedAsset.MyStrings.Count); + AssertCollection(derivedPropertyNode, "String0.1", "String1", "String1.1", "String1.5", "String2", "String2.1", "String3", "String3.1", "String3.5", "String4", "String4.1"); - Assert.Equal(9, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(11, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - Assert.Equal(baseIds[2], derivedIds[2]); - Assert.Equal(baseIds[3], derivedIds[4]); - Assert.Equal(baseIds[4], derivedIds[5]); - Assert.Equal(baseIds[5], derivedIds[6]); - Assert.Equal(baseIds[6], derivedIds[7]); - Assert.Equal(baseIds[7], derivedIds[9]); - Assert.Equal(baseIds[8], derivedIds[10]); - } + Assert.Equal(9, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(11, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + Assert.Equal(baseIds[2], derivedIds[2]); + Assert.Equal(baseIds[3], derivedIds[4]); + Assert.Equal(baseIds[4], derivedIds[5]); + Assert.Equal(baseIds[5], derivedIds[6]); + Assert.Equal(baseIds[6], derivedIds[7]); + Assert.Equal(baseIds[7], derivedIds[9]); + Assert.Equal(baseIds[8], derivedIds[10]); + } - [Fact] - public void TestRemoveBaseAddDerivedWithSubDerived() - { - var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2" } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - var subDerivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.SubDerivedAsset.MyStrings); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var subDerivedPropertyNode = context.SubDerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + [Fact] + public void TestRemoveBaseAddDerivedWithSubDerived() + { + var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2" } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + var subDerivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.SubDerivedAsset.MyStrings); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var subDerivedPropertyNode = context.SubDerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - // Delete an item from the derived and then update the same from the base - var derivedDeletedId = derivedIds[1]; - derivedPropertyNode.Target.Remove("String2", new NodeIndex(1)); - basePropertyNode.Target.Add("String3"); - Assert.Equal(3, context.BaseAsset.MyStrings.Count); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal(2, context.SubDerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", subDerivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String3", subDerivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, subDerivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, subDerivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, subDerivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(1, derivedIds.DeletedCount); - Assert.Equal(2, subDerivedIds.KeyCount); - Assert.Equal(0, subDerivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[2], derivedIds[1]); - Assert.Equal(derivedIds[0], subDerivedIds[0]); - Assert.Equal(derivedIds[1], subDerivedIds[1]); - Assert.True(derivedIds.IsDeleted(derivedDeletedId)); - } + // Delete an item from the derived and then update the same from the base + var derivedDeletedId = derivedIds[1]; + derivedPropertyNode.Target.Remove("String2", new NodeIndex(1)); + basePropertyNode.Target.Add("String3"); + Assert.Equal(3, context.BaseAsset.MyStrings.Count); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal(2, context.SubDerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", subDerivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String3", subDerivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, subDerivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, subDerivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, subDerivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(1, derivedIds.DeletedCount); + Assert.Equal(2, subDerivedIds.KeyCount); + Assert.Equal(0, subDerivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[2], derivedIds[1]); + Assert.Equal(derivedIds[0], subDerivedIds[0]); + Assert.Equal(derivedIds[1], subDerivedIds[1]); + Assert.True(derivedIds.IsDeleted(derivedDeletedId)); + } - [Fact] - public void TestAddBaseRemoveDerivedAndAddInBaseWithSubDerived() - { - var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2" } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - var subDerivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.SubDerivedAsset.MyStrings); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var subDerivedPropertyNode = context.SubDerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + [Fact] + public void TestAddBaseRemoveDerivedAndAddInBaseWithSubDerived() + { + var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2" } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + var subDerivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.SubDerivedAsset.MyStrings); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var subDerivedPropertyNode = context.SubDerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - // Delete an item from the derived and then update the same from the base - basePropertyNode.Target.Add("String3"); - var derivedDeletedId = derivedIds[2]; - derivedPropertyNode.Target.Remove("String3", new NodeIndex(2)); - basePropertyNode.Target.Add("String4"); - Assert.Equal(4, context.BaseAsset.MyStrings.Count); - Assert.Equal(3, context.DerivedAsset.MyStrings.Count); - Assert.Equal(3, context.SubDerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(3))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String1", subDerivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", subDerivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String4", subDerivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(3))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, subDerivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, subDerivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, subDerivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, subDerivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(4, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(1, derivedIds.DeletedCount); - Assert.Equal(3, subDerivedIds.KeyCount); - Assert.Equal(0, subDerivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - Assert.Equal(baseIds[3], derivedIds[2]); - Assert.Equal(derivedIds[0], subDerivedIds[0]); - Assert.Equal(derivedIds[1], subDerivedIds[1]); - Assert.Equal(derivedIds[2], subDerivedIds[2]); - Assert.True(derivedIds.IsDeleted(derivedDeletedId)); - } + // Delete an item from the derived and then update the same from the base + basePropertyNode.Target.Add("String3"); + var derivedDeletedId = derivedIds[2]; + derivedPropertyNode.Target.Remove("String3", new NodeIndex(2)); + basePropertyNode.Target.Add("String4"); + Assert.Equal(4, context.BaseAsset.MyStrings.Count); + Assert.Equal(3, context.DerivedAsset.MyStrings.Count); + Assert.Equal(3, context.SubDerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(3))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String1", subDerivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", subDerivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String4", subDerivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(3))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, subDerivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, subDerivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, subDerivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, subDerivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(4, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(1, derivedIds.DeletedCount); + Assert.Equal(3, subDerivedIds.KeyCount); + Assert.Equal(0, subDerivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + Assert.Equal(baseIds[3], derivedIds[2]); + Assert.Equal(derivedIds[0], subDerivedIds[0]); + Assert.Equal(derivedIds[1], subDerivedIds[1]); + Assert.Equal(derivedIds[2], subDerivedIds[2]); + Assert.True(derivedIds.IsDeleted(derivedDeletedId)); + } - private static void AssertCollection(IGraphNode node, params string[] items) + private static void AssertCollection(IGraphNode node, params string[] items) + { + for (var i = 0; i < items.Length; i++) { - for (var i = 0; i < items.Length; i++) - { - var item = items[i]; - Assert.Equal(item, node.Retrieve(new NodeIndex(i))); - } + var item = items[i]; + Assert.Equal(item, node.Retrieve(new NodeIndex(i))); } } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestArchetypesBasic.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestArchetypesBasic.cs index a1ff160902..aa963957eb 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestArchetypesBasic.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestArchetypesBasic.cs @@ -1,1676 +1,1674 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Linq; + using Xunit; using Stride.Core.Assets.Quantum.Internal; using Stride.Core.Assets.Quantum.Tests.Helpers; using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +public abstract class TestArchetypesRun { - public abstract class TestArchetypesRun + public static TestArchetypesRun Create(DeriveAssetTest context) where TAsset : Asset where TAssetPropertyGraph : AssetPropertyGraph { - public static TestArchetypesRun Create(DeriveAssetTest context) where TAsset : Asset where TAssetPropertyGraph : AssetPropertyGraph - { - return new TestArchetypesRun(context); - } + return new TestArchetypesRun(context); + } - public Action InitialCheck { get; set; } - public Action FirstChange { get; set; } - public Action FirstChangeCheck { get; set; } - public Action SecondChange { get; set; } - public Action SecondChangeCheck { get; set; } + public Action InitialCheck { get; set; } + public Action FirstChange { get; set; } + public Action FirstChangeCheck { get; set; } + public Action SecondChange { get; set; } + public Action SecondChangeCheck { get; set; } +} + +public class TestArchetypesRun : TestArchetypesRun where TAsset : Asset where TAssetPropertyGraph : AssetPropertyGraph +{ + public TestArchetypesRun(DeriveAssetTest context) + { + Context = context; } - public class TestArchetypesRun : TestArchetypesRun where TAsset : Asset where TAssetPropertyGraph : AssetPropertyGraph + public DeriveAssetTest Context { get; set; } +} + +public class TestArchetypesBasic +{ + private static void RunTest(TestArchetypesRun run) { - public TestArchetypesRun(DeriveAssetTest context) - { - Context = context; - } + run.InitialCheck(); + run.FirstChange(); + run.FirstChangeCheck(); + run.SecondChange(); + run.SecondChangeCheck(); + } - public DeriveAssetTest Context { get; set; } + [Fact] + public void TestSimplePropertyChange() + { + RunTest(PrepareSimplePropertyChange()); } - public class TestArchetypesBasic + public static TestArchetypesRun PrepareSimplePropertyChange() { - private static void RunTest(TestArchetypesRun run) - { - run.InitialCheck(); - run.FirstChange(); - run.FirstChangeCheck(); - run.SecondChange(); - run.SecondChangeCheck(); - } + var asset = new Types.MyAsset1 { MyString = "String" }; + var context = DeriveAssetTest.DeriveAsset(asset); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset1.MyString)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset1.MyString)]; - [Fact] - public void TestSimplePropertyChange() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => { - RunTest(PrepareSimplePropertyChange()); - } - - public static TestArchetypesRun PrepareSimplePropertyChange() + // Initial checks + Assert.Equal("String", basePropertyNode.Retrieve()); + Assert.Equal("String", derivedPropertyNode.Retrieve()); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + }; + test.FirstChange = () => { basePropertyNode.Update("MyBaseString"); }; + test.FirstChangeCheck = () => { - var asset = new Types.MyAsset1 { MyString = "String" }; - var context = DeriveAssetTest.DeriveAsset(asset); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset1.MyString)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset1.MyString)]; - - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - // Initial checks - Assert.Equal("String", basePropertyNode.Retrieve()); - Assert.Equal("String", derivedPropertyNode.Retrieve()); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - }; - test.FirstChange = () => { basePropertyNode.Update("MyBaseString"); }; - test.FirstChangeCheck = () => - { - Assert.Equal("MyBaseString", basePropertyNode.Retrieve()); - Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve()); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - }; - test.SecondChange = () => { derivedPropertyNode.Update("MyDerivedString"); }; - test.SecondChangeCheck = () => - { - Assert.Equal("MyBaseString", basePropertyNode.Retrieve()); - Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve()); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.GetContentOverride()); - }; - return test; - } - - [Fact] - public void TestAbstractPropertyChange() + Assert.Equal("MyBaseString", basePropertyNode.Retrieve()); + Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve()); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + }; + test.SecondChange = () => { derivedPropertyNode.Update("MyDerivedString"); }; + test.SecondChangeCheck = () => { - RunTest(PrepareAbstractPropertyChange()); - } + Assert.Equal("MyBaseString", basePropertyNode.Retrieve()); + Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve()); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.GetContentOverride()); + }; + return test; + } - public static TestArchetypesRun PrepareAbstractPropertyChange() - { - var asset = new Types.MyAsset5 { MyInterface = new Types.SomeObject2 { Value = "String1" } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset5.MyInterface)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset5.MyInterface)]; + [Fact] + public void TestAbstractPropertyChange() + { + RunTest(PrepareAbstractPropertyChange()); + } - var objB = asset.MyInterface; - var objD = context.DerivedAsset.MyInterface; - var newObjB = new Types.SomeObject { Value = "MyBaseString" }; - var newObjD = new Types.SomeObject2 { Value = "MyDerivedString" }; + public static TestArchetypesRun PrepareAbstractPropertyChange() + { + var asset = new Types.MyAsset5 { MyInterface = new Types.SomeObject2 { Value = "String1" } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset5.MyInterface)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset5.MyInterface)]; - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(objB, basePropertyNode.Retrieve()); - // NOTE: we're using this code to test undo/redo and in this case, we have different objects in the derived object after undoing due to the fact that the type of the instance has changed - //Assert.Equal(objD, derivedPropertyNode.Content.Retrieve()); - Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve()).Value); - Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve()).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target[nameof(Types.SomeObject.Value)].GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target[nameof(Types.SomeObject.Value)].GetContentOverride()); - }; - test.FirstChange = () => { basePropertyNode.Update(newObjB); }; - test.FirstChangeCheck = () => - { - Assert.Equal(newObjB, basePropertyNode.Retrieve()); - Assert.NotEqual(objD, derivedPropertyNode.Retrieve()); - Assert.Equal("MyBaseString", ((Types.IMyInterface)basePropertyNode.Retrieve()).Value); - Assert.Equal("MyBaseString", ((Types.IMyInterface)derivedPropertyNode.Retrieve()).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target[nameof(Types.SomeObject.Value)].GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target[nameof(Types.SomeObject.Value)].GetContentOverride()); - }; - test.SecondChange = () => { derivedPropertyNode.Update(newObjD); }; - test.SecondChangeCheck = () => - { - Assert.Equal(newObjB, basePropertyNode.Retrieve()); - Assert.Equal(newObjD, derivedPropertyNode.Retrieve()); - Assert.Equal("MyBaseString", ((Types.IMyInterface)basePropertyNode.Retrieve()).Value); - Assert.Equal("MyDerivedString", ((Types.IMyInterface)derivedPropertyNode.Retrieve()).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target[nameof(Types.SomeObject.Value)].GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target[nameof(Types.SomeObject2.Value)].GetContentOverride()); - }; - return test; - } + var objB = asset.MyInterface; + var objD = context.DerivedAsset.MyInterface; + var newObjB = new Types.SomeObject { Value = "MyBaseString" }; + var newObjD = new Types.SomeObject2 { Value = "MyDerivedString" }; - [Fact] - public void TestSimpleCollectionUpdate() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => { - RunTest(PrepareSimpleCollectionUpdate()); - } - - public static TestArchetypesRun PrepareSimpleCollectionUpdate() + Assert.Equal(objB, basePropertyNode.Retrieve()); + // NOTE: we're using this code to test undo/redo and in this case, we have different objects in the derived object after undoing due to the fact that the type of the instance has changed + //Assert.Equal(objD, derivedPropertyNode.Content.Retrieve()); + Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve()).Value); + Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve()).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target[nameof(Types.SomeObject.Value)].GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target[nameof(Types.SomeObject.Value)].GetContentOverride()); + }; + test.FirstChange = () => { basePropertyNode.Update(newObjB); }; + test.FirstChangeCheck = () => { - var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2" } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + Assert.Equal(newObjB, basePropertyNode.Retrieve()); + Assert.NotEqual(objD, derivedPropertyNode.Retrieve()); + Assert.Equal("MyBaseString", ((Types.IMyInterface)basePropertyNode.Retrieve()).Value); + Assert.Equal("MyBaseString", ((Types.IMyInterface)derivedPropertyNode.Retrieve()).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target[nameof(Types.SomeObject.Value)].GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target[nameof(Types.SomeObject.Value)].GetContentOverride()); + }; + test.SecondChange = () => { derivedPropertyNode.Update(newObjD); }; + test.SecondChangeCheck = () => + { + Assert.Equal(newObjB, basePropertyNode.Retrieve()); + Assert.Equal(newObjD, derivedPropertyNode.Retrieve()); + Assert.Equal("MyBaseString", ((Types.IMyInterface)basePropertyNode.Retrieve()).Value); + Assert.Equal("MyDerivedString", ((Types.IMyInterface)derivedPropertyNode.Retrieve()).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target[nameof(Types.SomeObject.Value)].GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target[nameof(Types.SomeObject2.Value)].GetContentOverride()); + }; + return test; + } - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.FirstChange = () => { basePropertyNode.Target.Update("MyBaseString", new NodeIndex(1)); }; - test.FirstChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.SecondChange = () => { derivedPropertyNode.Target.Update("MyDerivedString", new NodeIndex(0)); }; - test.SecondChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - return test; - } + [Fact] + public void TestSimpleCollectionUpdate() + { + RunTest(PrepareSimpleCollectionUpdate()); + } - [Fact] - public void TestSimpleCollectionAdd() - { - RunTest(PrepareSimpleCollectionAdd()); - } + public static TestArchetypesRun PrepareSimpleCollectionUpdate() + { + var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2" } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - public static TestArchetypesRun PrepareSimpleCollectionAdd() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.FirstChange = () => { basePropertyNode.Target.Update("MyBaseString", new NodeIndex(1)); }; + test.FirstChangeCheck = () => { - var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2" } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.SecondChange = () => { derivedPropertyNode.Target.Update("MyDerivedString", new NodeIndex(0)); }; + test.SecondChangeCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + return test; + } + + [Fact] + public void TestSimpleCollectionAdd() + { + RunTest(PrepareSimpleCollectionAdd()); + } - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.FirstChange = () => { derivedPropertyNode.Target.Add("String3"); }; - test.FirstChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal(3, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.SecondChange = () => { basePropertyNode.Target.Add("String4"); }; - test.SecondChangeCheck = () => - { - Assert.Equal(3, context.BaseAsset.MyStrings.Count); - Assert.Equal(4, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex(3))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(3))); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(4, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - Assert.Equal(baseIds[2], derivedIds[2]); - }; - return test; - } + public static TestArchetypesRun PrepareSimpleCollectionAdd() + { + var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2" } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - [Fact] - public void TestSimpleCollectionRemove() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => { - RunTest(PrepareSimpleCollectionRemove()); - } - - public static TestArchetypesRun PrepareSimpleCollectionRemove() + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.FirstChange = () => { derivedPropertyNode.Target.Add("String3"); }; + test.FirstChangeCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal(3, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.SecondChange = () => { basePropertyNode.Target.Add("String4"); }; + test.SecondChangeCheck = () => { - var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2", "String3", "String4" } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - ItemId derivedDeletedId = ItemId.Empty; - ItemId baseDeletedId = ItemId.Empty; + Assert.Equal(3, context.BaseAsset.MyStrings.Count); + Assert.Equal(4, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex(3))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(3))); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(4, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + Assert.Equal(baseIds[2], derivedIds[2]); + }; + return test; + } - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(4, context.BaseAsset.MyStrings.Count); - Assert.Equal(4, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(3))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(3))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(3))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(3))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(4, baseIds.KeyCount); - Assert.Equal(4, derivedIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - Assert.Equal(baseIds[2], derivedIds[2]); - Assert.Equal(baseIds[3], derivedIds[3]); - }; - test.FirstChange = () => - { - derivedDeletedId = derivedIds[2]; - derivedPropertyNode.Target.Remove("String3", new NodeIndex(2)); - }; - test.FirstChangeCheck = () => - { - Assert.Equal(4, context.BaseAsset.MyStrings.Count); - Assert.Equal(3, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(3))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(3))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(4, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(1, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - Assert.Equal(baseIds[3], derivedIds[2]); - Assert.True(derivedIds.IsDeleted(derivedDeletedId)); - }; - test.SecondChange = () => - { - baseDeletedId = baseIds[3]; - basePropertyNode.Target.Remove("String4", new NodeIndex(3)); - }; - test.SecondChangeCheck = () => - { - Assert.Equal(3, context.BaseAsset.MyStrings.Count); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(1, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - Assert.True(derivedIds.IsDeleted(derivedDeletedId)); - Assert.True(!baseIds.IsDeleted(baseDeletedId)); - }; - return test; - } + [Fact] + public void TestSimpleCollectionRemove() + { + RunTest(PrepareSimpleCollectionRemove()); + } - [Fact] - public void TestCollectionInStructUpdate() - { - RunTest(PrepareCollectionInStructUpdate()); - } + public static TestArchetypesRun PrepareSimpleCollectionRemove() + { + var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2", "String3", "String4" } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + ItemId derivedDeletedId = ItemId.Empty; + ItemId baseDeletedId = ItemId.Empty; - public static TestArchetypesRun PrepareCollectionInStructUpdate() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => + { + Assert.Equal(4, context.BaseAsset.MyStrings.Count); + Assert.Equal(4, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(3))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(3))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(3))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(3))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(4, baseIds.KeyCount); + Assert.Equal(4, derivedIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + Assert.Equal(baseIds[2], derivedIds[2]); + Assert.Equal(baseIds[3], derivedIds[3]); + }; + test.FirstChange = () => + { + derivedDeletedId = derivedIds[2]; + derivedPropertyNode.Target.Remove("String3", new NodeIndex(2)); + }; + test.FirstChangeCheck = () => { - var asset = new Types.MyAsset2(); - asset.Struct.MyStrings.Add("String1"); - asset.Struct.MyStrings.Add("String2"); - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.Struct.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.Struct.MyStrings); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.Struct)].Target[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.Struct)].Target[nameof(Types.MyAsset2.MyStrings)]; + Assert.Equal(4, context.BaseAsset.MyStrings.Count); + Assert.Equal(3, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(3))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(3))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(4, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(1, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + Assert.Equal(baseIds[3], derivedIds[2]); + Assert.True(derivedIds.IsDeleted(derivedDeletedId)); + }; + test.SecondChange = () => + { + baseDeletedId = baseIds[3]; + basePropertyNode.Target.Remove("String4", new NodeIndex(3)); + }; + test.SecondChangeCheck = () => + { + Assert.Equal(3, context.BaseAsset.MyStrings.Count); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(1, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + Assert.True(derivedIds.IsDeleted(derivedDeletedId)); + Assert.False(baseIds.IsDeleted(baseDeletedId)); + }; + return test; + } + + [Fact] + public void TestCollectionInStructUpdate() + { + RunTest(PrepareCollectionInStructUpdate()); + } - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(2, context.BaseAsset.Struct.MyStrings.Count); - Assert.Equal(2, context.DerivedAsset.Struct.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.FirstChange = () => { basePropertyNode.Target.Update("MyBaseString", new NodeIndex(1)); }; - test.FirstChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.Struct.MyStrings.Count); - Assert.Equal(2, context.DerivedAsset.Struct.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.SecondChange = () => { derivedPropertyNode.Target.Update("MyDerivedString", new NodeIndex(0)); }; - test.SecondChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.Struct.MyStrings.Count); - Assert.Equal(2, context.DerivedAsset.Struct.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - return test; - } + public static TestArchetypesRun PrepareCollectionInStructUpdate() + { + var asset = new Types.MyAsset2(); + asset.Struct.MyStrings.Add("String1"); + asset.Struct.MyStrings.Add("String2"); + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.Struct.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.Struct.MyStrings); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.Struct)].Target[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.Struct)].Target[nameof(Types.MyAsset2.MyStrings)]; - [Fact] - public void TestSimpleDictionaryUpdate() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => { - RunTest(PrepareSimpleDictionaryUpdate()); - } - - public static TestArchetypesRun PrepareSimpleDictionaryUpdate() + Assert.Equal(2, context.BaseAsset.Struct.MyStrings.Count); + Assert.Equal(2, context.DerivedAsset.Struct.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.FirstChange = () => { basePropertyNode.Target.Update("MyBaseString", new NodeIndex(1)); }; + test.FirstChangeCheck = () => { - var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + Assert.Equal(2, context.BaseAsset.Struct.MyStrings.Count); + Assert.Equal(2, context.DerivedAsset.Struct.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.SecondChange = () => { derivedPropertyNode.Target.Update("MyDerivedString", new NodeIndex(0)); }; + test.SecondChangeCheck = () => + { + Assert.Equal(2, context.BaseAsset.Struct.MyStrings.Count); + Assert.Equal(2, context.DerivedAsset.Struct.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + return test; + } - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - }; - test.FirstChange = () => { basePropertyNode.Target.Update("MyBaseString", new NodeIndex("Key2")); }; - test.FirstChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - }; - test.SecondChange = () => { derivedPropertyNode.Target.Update("MyDerivedString", new NodeIndex("Key1")); }; - test.SecondChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - }; - return test; - } + [Fact] + public void TestSimpleDictionaryUpdate() + { + RunTest(PrepareSimpleDictionaryUpdate()); + } - [Fact] - public void TestSimpleDictionaryAdd() - { - RunTest(PrepareSimpleDictionaryAdd()); - } + public static TestArchetypesRun PrepareSimpleDictionaryUpdate() + { + var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - public static TestArchetypesRun PrepareSimpleDictionaryAdd() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + }; + test.FirstChange = () => { basePropertyNode.Target.Update("MyBaseString", new NodeIndex("Key2")); }; + test.FirstChangeCheck = () => { - var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + }; + test.SecondChange = () => { derivedPropertyNode.Target.Update("MyDerivedString", new NodeIndex("Key1")); }; + test.SecondChangeCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + }; + return test; + } + + [Fact] + public void TestSimpleDictionaryAdd() + { + RunTest(PrepareSimpleDictionaryAdd()); + } - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - }; - test.FirstChange = () => { derivedPropertyNode.Target.Add("String3", new NodeIndex("Key3")); }; - test.FirstChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); - }; - test.SecondChange = () => { basePropertyNode.Target.Add("String4", new NodeIndex("Key4")); }; - test.SecondChangeCheck = () => - { - Assert.Equal(3, context.BaseAsset.MyDictionary.Count); - Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(4, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); - Assert.Equal(3, context.BaseAsset.MyDictionary.Count); - Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); - }; - return test; - } + public static TestArchetypesRun PrepareSimpleDictionaryAdd() + { + var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - [Fact] - public void TestSimpleDictionaryRemove() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => { - RunTest(PrepareSimpleDictionaryRemove()); - } - - public static TestArchetypesRun PrepareSimpleDictionaryRemove() + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + }; + test.FirstChange = () => { derivedPropertyNode.Target.Add("String3", new NodeIndex("Key3")); }; + test.FirstChangeCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); + }; + test.SecondChange = () => { basePropertyNode.Target.Add("String4", new NodeIndex("Key4")); }; + test.SecondChangeCheck = () => { - var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" }, { "Key3", "String3" }, { "Key4", "String4" } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - ItemId derivedDeletedId = ItemId.Empty; - ItemId baseDeletedId = ItemId.Empty; + Assert.Equal(3, context.BaseAsset.MyDictionary.Count); + Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(4, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); + Assert.Equal(3, context.BaseAsset.MyDictionary.Count); + Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); + }; + return test; + } - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(4, context.BaseAsset.MyDictionary.Count); - Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex("Key3"))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(4, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(4, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - Assert.Equal(baseIds["Key3"], derivedIds["Key3"]); - Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); - }; - test.FirstChange = () => - { - derivedDeletedId = derivedIds["Key3"]; - derivedPropertyNode.Target.Remove("String3", new NodeIndex("Key3")); - }; - test.FirstChangeCheck = () => - { - Assert.Equal(4, context.BaseAsset.MyDictionary.Count); - Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex("Key3"))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(4, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(1, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); - Assert.True(derivedIds.IsDeleted(derivedDeletedId)); - }; - test.SecondChange = () => - { - baseDeletedId = baseIds["Key4"]; - basePropertyNode.Target.Remove("String4", new NodeIndex("Key4")); - }; - test.SecondChangeCheck = () => - { - Assert.Equal(3, context.BaseAsset.MyDictionary.Count); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex("Key3"))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(1, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - Assert.True(derivedIds.IsDeleted(derivedDeletedId)); - Assert.True(!baseIds.IsDeleted(baseDeletedId)); - }; - return test; - } + [Fact] + public void TestSimpleDictionaryRemove() + { + RunTest(PrepareSimpleDictionaryRemove()); + } - [Fact] - public void TestObjectCollectionUpdate() - { - RunTest(PrepareObjectCollectionUpdate()); - } + public static TestArchetypesRun PrepareSimpleDictionaryRemove() + { + var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" }, { "Key3", "String3" }, { "Key4", "String4" } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + ItemId derivedDeletedId = ItemId.Empty; + ItemId baseDeletedId = ItemId.Empty; - public static TestArchetypesRun PrepareObjectCollectionUpdate() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => + { + Assert.Equal(4, context.BaseAsset.MyDictionary.Count); + Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex("Key3"))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(4, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(4, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + Assert.Equal(baseIds["Key3"], derivedIds["Key3"]); + Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); + }; + test.FirstChange = () => + { + derivedDeletedId = derivedIds["Key3"]; + derivedPropertyNode.Target.Remove("String3", new NodeIndex("Key3")); + }; + test.FirstChangeCheck = () => + { + Assert.Equal(4, context.BaseAsset.MyDictionary.Count); + Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex("Key3"))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(4, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(1, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); + Assert.True(derivedIds.IsDeleted(derivedDeletedId)); + }; + test.SecondChange = () => { - var asset = new Types.MyAsset4 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyObjects); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyObjects); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + baseDeletedId = baseIds["Key4"]; + basePropertyNode.Target.Remove("String4", new NodeIndex("Key4")); + }; + test.SecondChangeCheck = () => + { + Assert.Equal(3, context.BaseAsset.MyDictionary.Count); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String3", basePropertyNode.Retrieve(new NodeIndex("Key3"))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(1, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + Assert.True(derivedIds.IsDeleted(derivedDeletedId)); + Assert.False(baseIds.IsDeleted(baseDeletedId)); + }; + return test; + } + + [Fact] + public void TestObjectCollectionUpdate() + { + RunTest(PrepareObjectCollectionUpdate()); + } - var objB0 = asset.MyObjects[0]; - var objB1 = asset.MyObjects[1]; - var objD0 = context.DerivedAsset.MyObjects[0]; - var objD1 = context.DerivedAsset.MyObjects[1]; - var newObjB = new Types.SomeObject { Value = "MyBaseString" }; - var newObjD = new Types.SomeObject { Value = "MyDerivedString" }; + public static TestArchetypesRun PrepareObjectCollectionUpdate() + { + var asset = new Types.MyAsset4 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyObjects); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyObjects); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyObjects.Count); - Assert.Equal(2, context.DerivedAsset.MyObjects.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.FirstChange = () => { basePropertyNode.Target.Update(newObjB, new NodeIndex(1)); }; - test.FirstChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyObjects.Count); - Assert.Equal(2, context.DerivedAsset.MyObjects.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.SecondChange = () => { derivedPropertyNode.Target.Update(newObjD, new NodeIndex(0)); }; - test.SecondChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyObjects.Count); - Assert.Equal(2, context.DerivedAsset.MyObjects.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("MyDerivedString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - return test; - } + var objB0 = asset.MyObjects[0]; + var objB1 = asset.MyObjects[1]; + var objD0 = context.DerivedAsset.MyObjects[0]; + var objD1 = context.DerivedAsset.MyObjects[1]; + var newObjB = new Types.SomeObject { Value = "MyBaseString" }; + var newObjD = new Types.SomeObject { Value = "MyDerivedString" }; - [Fact] - public void TestObjectCollectionAdd() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => { - RunTest(PrepareObjectCollectionAdd()); - } - - public static TestArchetypesRun PrepareObjectCollectionAdd() + Assert.Equal(2, context.BaseAsset.MyObjects.Count); + Assert.Equal(2, context.DerivedAsset.MyObjects.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.FirstChange = () => { basePropertyNode.Target.Update(newObjB, new NodeIndex(1)); }; + test.FirstChangeCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyObjects.Count); + Assert.Equal(2, context.DerivedAsset.MyObjects.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.SecondChange = () => { derivedPropertyNode.Target.Update(newObjD, new NodeIndex(0)); }; + test.SecondChangeCheck = () => { - var asset = new Types.MyAsset4 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyObjects); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyObjects); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + Assert.Equal(2, context.BaseAsset.MyObjects.Count); + Assert.Equal(2, context.DerivedAsset.MyObjects.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("MyDerivedString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + return test; + } - var objB0 = asset.MyObjects[0]; - var objB1 = asset.MyObjects[1]; - var objD0 = context.DerivedAsset.MyObjects[0]; - var objD1 = context.DerivedAsset.MyObjects[1]; - var newObjD = new Types.SomeObject { Value = "String3" }; - var newObjB = new Types.SomeObject { Value = "String4" }; + [Fact] + public void TestObjectCollectionAdd() + { + RunTest(PrepareObjectCollectionAdd()); + } - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyObjects.Count); - Assert.Equal(2, context.DerivedAsset.MyObjects.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.FirstChange = () => { derivedPropertyNode.Target.Add(newObjD); }; - test.FirstChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyObjects.Count); - Assert.Equal(3, context.DerivedAsset.MyObjects.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String3", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(2))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.SecondChange = () => { basePropertyNode.Target.Add(newObjB); }; - test.SecondChangeCheck = () => - { - Assert.Equal(3, context.BaseAsset.MyObjects.Count); - Assert.Equal(4, context.DerivedAsset.MyObjects.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.NotEqual(newObjB, derivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex(3))); - Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String4", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(2))).Value); - Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String4", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(2))).Value); - Assert.Equal("String3", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(3))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(3))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(3)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(4, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - Assert.Equal(baseIds[2], derivedIds[2]); - }; - return test; - } + public static TestArchetypesRun PrepareObjectCollectionAdd() + { + var asset = new Types.MyAsset4 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyObjects); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyObjects); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - [Fact] - public void TestAbstractCollectionUpdate() - { - RunTest(PrepareAbstractCollectionUpdate()); - } + var objB0 = asset.MyObjects[0]; + var objB1 = asset.MyObjects[1]; + var objD0 = context.DerivedAsset.MyObjects[0]; + var objD1 = context.DerivedAsset.MyObjects[1]; + var newObjD = new Types.SomeObject { Value = "String3" }; + var newObjB = new Types.SomeObject { Value = "String4" }; - public static TestArchetypesRun PrepareAbstractCollectionUpdate() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyObjects.Count); + Assert.Equal(2, context.DerivedAsset.MyObjects.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.FirstChange = () => { derivedPropertyNode.Target.Add(newObjD); }; + test.FirstChangeCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyObjects.Count); + Assert.Equal(3, context.DerivedAsset.MyObjects.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String3", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(2))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.SecondChange = () => { basePropertyNode.Target.Add(newObjB); }; + test.SecondChangeCheck = () => { - var asset = new Types.MyAsset5 { MyInterfaces = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject2 { Value = "String2" } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyInterfaces); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyInterfaces); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset5.MyInterfaces)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset5.MyInterfaces)]; + Assert.Equal(3, context.BaseAsset.MyObjects.Count); + Assert.Equal(4, context.DerivedAsset.MyObjects.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.NotEqual(newObjB, derivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex(3))); + Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String4", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(2))).Value); + Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String4", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(2))).Value); + Assert.Equal("String3", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(3))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(3))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(3)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(4, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + Assert.Equal(baseIds[2], derivedIds[2]); + }; + return test; + } + + [Fact] + public void TestAbstractCollectionUpdate() + { + RunTest(PrepareAbstractCollectionUpdate()); + } - var objB0 = asset.MyInterfaces[0]; - var objB1 = asset.MyInterfaces[1]; - var objD0 = context.DerivedAsset.MyInterfaces[0]; - var objD1 = context.DerivedAsset.MyInterfaces[1]; - var newObjB = new Types.SomeObject { Value = "MyBaseString" }; - var newObjD = new Types.SomeObject2 { Value = "MyDerivedString" }; + public static TestArchetypesRun PrepareAbstractCollectionUpdate() + { + var asset = new Types.MyAsset5 { MyInterfaces = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject2 { Value = "String2" } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyInterfaces); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyInterfaces); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset5.MyInterfaces)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset5.MyInterfaces)]; - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyInterfaces.Count); - Assert.Equal(2, context.DerivedAsset.MyInterfaces.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); - // NOTE: we're using this code to test undo/redo and in this case, we have different objects in the derived object after undoing due to the fact that the type of the instance has changed - //Assert.Equal(objD0, derivedPropertyNode.Content.Retrieve(new Index(0))); - //Assert.Equal(objD1, derivedPropertyNode.Content.Retrieve(new Index(1))); - Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.FirstChange = () => { basePropertyNode.Target.Update(newObjB, new NodeIndex(1)); }; - test.FirstChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyInterfaces.Count); - Assert.Equal(2, context.DerivedAsset.MyInterfaces.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.NotEqual(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.SecondChange = () => { derivedPropertyNode.Target.Update(newObjD, new NodeIndex(0)); }; - test.SecondChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyInterfaces.Count); - Assert.Equal(2, context.DerivedAsset.MyInterfaces.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.NotEqual(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("MyDerivedString", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - return test; - } + var objB0 = asset.MyInterfaces[0]; + var objB1 = asset.MyInterfaces[1]; + var objD0 = context.DerivedAsset.MyInterfaces[0]; + var objD1 = context.DerivedAsset.MyInterfaces[1]; + var newObjB = new Types.SomeObject { Value = "MyBaseString" }; + var newObjD = new Types.SomeObject2 { Value = "MyDerivedString" }; - [Fact] - public void TestAbstractCollectionAdd() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => { - RunTest(PrepareAbstractCollectionAdd()); - } - - public static TestArchetypesRun PrepareAbstractCollectionAdd() + Assert.Equal(2, context.BaseAsset.MyInterfaces.Count); + Assert.Equal(2, context.DerivedAsset.MyInterfaces.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); + // NOTE: we're using this code to test undo/redo and in this case, we have different objects in the derived object after undoing due to the fact that the type of the instance has changed + //Assert.Equal(objD0, derivedPropertyNode.Content.Retrieve(new Index(0))); + //Assert.Equal(objD1, derivedPropertyNode.Content.Retrieve(new Index(1))); + Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.FirstChange = () => { basePropertyNode.Target.Update(newObjB, new NodeIndex(1)); }; + test.FirstChangeCheck = () => { - var asset = new Types.MyAsset5 { MyInterfaces = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyInterfaces); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyInterfaces); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset5.MyInterfaces)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset5.MyInterfaces)]; + Assert.Equal(2, context.BaseAsset.MyInterfaces.Count); + Assert.Equal(2, context.DerivedAsset.MyInterfaces.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.NotEqual(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.SecondChange = () => { derivedPropertyNode.Target.Update(newObjD, new NodeIndex(0)); }; + test.SecondChangeCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyInterfaces.Count); + Assert.Equal(2, context.DerivedAsset.MyInterfaces.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.NotEqual(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("MyDerivedString", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + return test; + } - var objB0 = asset.MyInterfaces[0]; - var objB1 = asset.MyInterfaces[1]; - var objD0 = context.DerivedAsset.MyInterfaces[0]; - var objD1 = context.DerivedAsset.MyInterfaces[1]; - var newObjD = new Types.SomeObject { Value = "String3" }; - var newObjB = new Types.SomeObject2 { Value = "String4" }; + [Fact] + public void TestAbstractCollectionAdd() + { + RunTest(PrepareAbstractCollectionAdd()); + } - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyInterfaces.Count); - Assert.Equal(2, context.DerivedAsset.MyInterfaces.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.FirstChange = () => { derivedPropertyNode.Target.Add(newObjD); }; - test.FirstChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyInterfaces.Count); - Assert.Equal(3, context.DerivedAsset.MyInterfaces.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String3", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(2))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - }; - test.SecondChange = () => { basePropertyNode.Target.Add(newObjB); }; - test.SecondChangeCheck = () => - { - Assert.Equal(3, context.BaseAsset.MyInterfaces.Count); - Assert.Equal(4, context.DerivedAsset.MyInterfaces.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.NotEqual(newObjB, derivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex(3))); - Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String4", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(2))).Value); - Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String4", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(2))).Value); - Assert.Equal("String3", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(3))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(3))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(3)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(4, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - Assert.Equal(baseIds[2], derivedIds[2]); - }; - return test; - } + public static TestArchetypesRun PrepareAbstractCollectionAdd() + { + var asset = new Types.MyAsset5 { MyInterfaces = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyInterfaces); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyInterfaces); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset5.MyInterfaces)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset5.MyInterfaces)]; - [Fact] - public void TestAbstractDictionaryUpdate() - { - RunTest(PrepareAbstractDictionaryUpdate()); - } + var objB0 = asset.MyInterfaces[0]; + var objB1 = asset.MyInterfaces[1]; + var objD0 = context.DerivedAsset.MyInterfaces[0]; + var objD1 = context.DerivedAsset.MyInterfaces[1]; + var newObjD = new Types.SomeObject { Value = "String3" }; + var newObjB = new Types.SomeObject2 { Value = "String4" }; - public static TestArchetypesRun PrepareAbstractDictionaryUpdate() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => { - var asset = new Types.MyAsset6 { MyDictionary = { { "Key1", new Types.SomeObject { Value = "String1" } }, { "Key2", new Types.SomeObject2 { Value = "String2" } } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset6.MyDictionary)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset6.MyDictionary)]; + Assert.Equal(2, context.BaseAsset.MyInterfaces.Count); + Assert.Equal(2, context.DerivedAsset.MyInterfaces.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.FirstChange = () => { derivedPropertyNode.Target.Add(newObjD); }; + test.FirstChangeCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyInterfaces.Count); + Assert.Equal(3, context.DerivedAsset.MyInterfaces.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String3", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(2))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + }; + test.SecondChange = () => { basePropertyNode.Target.Add(newObjB); }; + test.SecondChangeCheck = () => + { + Assert.Equal(3, context.BaseAsset.MyInterfaces.Count); + Assert.Equal(4, context.DerivedAsset.MyInterfaces.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.NotEqual(newObjB, derivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex(3))); + Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String4", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex(2))).Value); + Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String4", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(2))).Value); + Assert.Equal("String3", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex(3))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(3))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(3)].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(4, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + Assert.Equal(baseIds[2], derivedIds[2]); + }; + return test; + } - var objB0 = asset.MyDictionary["Key1"]; - var objB1 = asset.MyDictionary["Key2"]; - var objD0 = context.DerivedAsset.MyDictionary["Key1"]; - var objD1 = context.DerivedAsset.MyDictionary["Key2"]; - var newObjB = new Types.SomeObject { Value = "MyBaseString" }; - var newObjD = new Types.SomeObject2 { Value = "MyDerivedString" }; + [Fact] + public void TestAbstractDictionaryUpdate() + { + RunTest(PrepareAbstractDictionaryUpdate()); + } - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex("Key2"))); - // NOTE: we're using this code to test undo/redo and in this case, we have different objects in the derived object after undoing due to the fact that the type of the instance has changed - //Assert.Equal(objD0, derivedPropertyNode.Content.Retrieve(new Index("Key1"))); - //Assert.Equal(objD1, derivedPropertyNode.Content.Retrieve(new Index("Key2"))); - Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key1"))).Value); - Assert.Equal("String2", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key2"))).Value); - Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key1"))).Value); - Assert.Equal("String2", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key2"))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - }; - test.FirstChange = () => { basePropertyNode.Target.Update(newObjB, new NodeIndex("Key2")); }; - test.FirstChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.NotEqual(objD1, derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key1"))).Value); - Assert.Equal("MyBaseString", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key2"))).Value); - Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key1"))).Value); - Assert.Equal("MyBaseString", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key2"))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - }; - test.SecondChange = () => { derivedPropertyNode.Target.Update(newObjD, new NodeIndex("Key1")); }; - test.SecondChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.NotEqual(objD1, derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key1"))).Value); - Assert.Equal("MyBaseString", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key2"))).Value); - Assert.Equal("MyDerivedString", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key1"))).Value); - Assert.Equal("MyBaseString", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key2"))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - }; - return test; - } + public static TestArchetypesRun PrepareAbstractDictionaryUpdate() + { + var asset = new Types.MyAsset6 { MyDictionary = { { "Key1", new Types.SomeObject { Value = "String1" } }, { "Key2", new Types.SomeObject2 { Value = "String2" } } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset6.MyDictionary)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset6.MyDictionary)]; - [Fact] - public void TestAbstractDictionaryAdd() - { - RunTest(PrepareAbstractDictionaryAdd()); - } + var objB0 = asset.MyDictionary["Key1"]; + var objB1 = asset.MyDictionary["Key2"]; + var objD0 = context.DerivedAsset.MyDictionary["Key1"]; + var objD1 = context.DerivedAsset.MyDictionary["Key2"]; + var newObjB = new Types.SomeObject { Value = "MyBaseString" }; + var newObjD = new Types.SomeObject2 { Value = "MyDerivedString" }; - public static TestArchetypesRun PrepareAbstractDictionaryAdd() + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => { - var asset = new Types.MyAsset6 { MyDictionary = { { "Key1", new Types.SomeObject { Value = "String1" } }, { "Key2", new Types.SomeObject2 { Value = "String2" } } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset6.MyDictionary)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset6.MyDictionary)]; + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex("Key2"))); + // NOTE: we're using this code to test undo/redo and in this case, we have different objects in the derived object after undoing due to the fact that the type of the instance has changed + //Assert.Equal(objD0, derivedPropertyNode.Content.Retrieve(new Index("Key1"))); + //Assert.Equal(objD1, derivedPropertyNode.Content.Retrieve(new Index("Key2"))); + Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key1"))).Value); + Assert.Equal("String2", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key2"))).Value); + Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key1"))).Value); + Assert.Equal("String2", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key2"))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + }; + test.FirstChange = () => { basePropertyNode.Target.Update(newObjB, new NodeIndex("Key2")); }; + test.FirstChangeCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.NotEqual(objD1, derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key1"))).Value); + Assert.Equal("MyBaseString", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key2"))).Value); + Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key1"))).Value); + Assert.Equal("MyBaseString", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key2"))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + }; + test.SecondChange = () => { derivedPropertyNode.Target.Update(newObjD, new NodeIndex("Key1")); }; + test.SecondChangeCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.NotEqual(objD1, derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key1"))).Value); + Assert.Equal("MyBaseString", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key2"))).Value); + Assert.Equal("MyDerivedString", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key1"))).Value); + Assert.Equal("MyBaseString", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key2"))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + }; + return test; + } - var objB0 = asset.MyDictionary["Key1"]; - var objB1 = asset.MyDictionary["Key2"]; - var objD0 = context.DerivedAsset.MyDictionary["Key1"]; - var objD1 = context.DerivedAsset.MyDictionary["Key2"]; - var newObjD = new Types.SomeObject { Value = "String3" }; - var newObjB = new Types.SomeObject2 { Value = "String4" }; + [Fact] + public void TestAbstractDictionaryAdd() + { + RunTest(PrepareAbstractDictionaryAdd()); + } + + public static TestArchetypesRun PrepareAbstractDictionaryAdd() + { + var asset = new Types.MyAsset6 { MyDictionary = { { "Key1", new Types.SomeObject { Value = "String1" } }, { "Key2", new Types.SomeObject2 { Value = "String2" } } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset6.MyDictionary)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset6.MyDictionary)]; + + var objB0 = asset.MyDictionary["Key1"]; + var objB1 = asset.MyDictionary["Key2"]; + var objD0 = context.DerivedAsset.MyDictionary["Key1"]; + var objD1 = context.DerivedAsset.MyDictionary["Key2"]; + var newObjD = new Types.SomeObject { Value = "String3" }; + var newObjB = new Types.SomeObject2 { Value = "String4" }; - var test = TestArchetypesRun.Create(context); - test.InitialCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key1"))).Value); - Assert.Equal("String2", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key2"))).Value); - Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key1"))).Value); - Assert.Equal("String2", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key2"))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - }; - test.FirstChange = () => { derivedPropertyNode.Target.Add(newObjD, new NodeIndex("Key3")); }; - test.FirstChangeCheck = () => - { - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); - Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key1"))).Value); - Assert.Equal("String2", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key2"))).Value); - Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key1"))).Value); - Assert.Equal("String2", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key2"))).Value); - Assert.Equal("String3", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key3"))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key3")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - }; - test.SecondChange = () => { basePropertyNode.Target.Add(newObjB, new NodeIndex("Key4")); }; - test.SecondChangeCheck = () => - { - Assert.Equal(3, context.BaseAsset.MyDictionary.Count); - Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); - Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.NotEqual(newObjB, derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); - Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key1"))).Value); - Assert.Equal("String2", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key2"))).Value); - Assert.Equal("String4", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key4"))).Value); - Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key1"))).Value); - Assert.Equal("String2", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key2"))).Value); - Assert.Equal("String4", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key4"))).Value); - Assert.Equal("String3", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key3"))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key4")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key3")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key4")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); - Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); - Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(4, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); - }; - return test; - } + var test = TestArchetypesRun.Create(context); + test.InitialCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key1"))).Value); + Assert.Equal("String2", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key2"))).Value); + Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key1"))).Value); + Assert.Equal("String2", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key2"))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + }; + test.FirstChange = () => { derivedPropertyNode.Target.Add(newObjD, new NodeIndex("Key3")); }; + test.FirstChangeCheck = () => + { + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); + Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key1"))).Value); + Assert.Equal("String2", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key2"))).Value); + Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key1"))).Value); + Assert.Equal("String2", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key2"))).Value); + Assert.Equal("String3", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key3"))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key3")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + }; + test.SecondChange = () => { basePropertyNode.Target.Add(newObjB, new NodeIndex("Key4")); }; + test.SecondChangeCheck = () => + { + Assert.Equal(3, context.BaseAsset.MyDictionary.Count); + Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); + Assert.Equal(objB0, basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal(objB1, basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(newObjB, basePropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal(objD0, derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal(objD1, derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.NotEqual(newObjB, derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal(newObjD, derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); + Assert.Equal("String1", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key1"))).Value); + Assert.Equal("String2", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key2"))).Value); + Assert.Equal("String4", ((Types.IMyInterface)basePropertyNode.Retrieve(new NodeIndex("Key4"))).Value); + Assert.Equal("String1", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key1"))).Value); + Assert.Equal("String2", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key2"))).Value); + Assert.Equal("String4", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key4"))).Value); + Assert.Equal("String3", ((Types.IMyInterface)derivedPropertyNode.Retrieve(new NodeIndex("Key3"))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex("Key4")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key1")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key2")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key3")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex("Key4")].TargetNode[nameof(Types.IMyInterface.Value)]).GetContentOverride()); + Assert.Single(derivedPropertyNode.Target.GetOverriddenItemIndices()); + Assert.Empty(derivedPropertyNode.Target.GetOverriddenKeyIndices()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(4, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); + }; + return test; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetCompositeHierarchyBases.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetCompositeHierarchyBases.cs index 226d65dbbc..189d1e8150 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetCompositeHierarchyBases.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetCompositeHierarchyBases.cs @@ -1,117 +1,116 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Linq; + using Xunit; using Stride.Core.Assets.Quantum.Tests.Helpers; -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +public class TestAssetCompositeHierarchyBases { - public class TestAssetCompositeHierarchyBases + [Fact] + public void TestSimplePropertyChangeInBase() { - [Fact] - public void TestSimplePropertyChangeInBase() - { - var baseAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, null, x => x.Parts[x.RootParts.Single().Id].Part.Name = "BaseName"); - var derivedAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, baseAsset.Container); - var instances = baseAsset.Asset.CreatePartInstances(); - var baseRootId = baseAsset.Asset.Hierarchy.RootParts.Single().Id; - var derivedRootId = instances.RootParts.Single().Id; - derivedAsset.Graph.AddPartToAsset(instances.Parts, instances.Parts[derivedRootId], null, 1); - derivedAsset.Graph.RefreshBase(); - Assert.Equal(2, derivedAsset.Asset.Hierarchy.RootParts.Count); - Assert.Equal(baseRootId, derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Base?.BasePartId); - Assert.Equal("BaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); - var baseRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(baseAsset.Asset.Hierarchy.Parts[baseRootId].Part); - var derivedRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part); - baseRootPartNode[nameof(Types.MyPart.Name)].Update("NewBaseName"); - Assert.Equal("NewBaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); - baseRootPartNode[nameof(Types.MyPart.Name)].Update("NewBaseName2"); - Assert.Equal("NewBaseName2", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); - derivedRootPartNode[nameof(Types.MyPart.Name)].Update("NewDerivedName"); - Assert.True(derivedRootPartNode[nameof(Types.MyPart.Name)].IsContentOverridden()); - Assert.Equal("NewDerivedName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); - baseRootPartNode[nameof(Types.MyPart.Name)].Update("NewBaseName3"); - Assert.True(derivedRootPartNode[nameof(Types.MyPart.Name)].IsContentOverridden()); - Assert.Equal("NewDerivedName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); - } - - [Fact] - public void TestSimpleNestedPropertyChangeInBase() - { - var baseAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, null, x => x.Parts[x.RootParts.Single().Id].Part.Object = new Types.SomeObject { Value = "BaseName" }); - var derivedAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, baseAsset.Container); - var instances = baseAsset.Asset.CreatePartInstances(); - var baseRootId = baseAsset.Asset.Hierarchy.RootParts.Single().Id; - var derivedRootId = instances.RootParts.Single().Id; - derivedAsset.Graph.AddPartToAsset(instances.Parts, instances.Parts[derivedRootId], null, 1); - derivedAsset.Graph.RefreshBase(); - Assert.Equal(2, derivedAsset.Asset.Hierarchy.RootParts.Count); - Assert.Equal(baseRootId, derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Base?.BasePartId); - Assert.Equal("BaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); - var baseRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(baseAsset.Asset.Hierarchy.Parts[baseRootId].Part.Object); - var derivedRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object); - baseRootPartNode[nameof(Types.SomeObject.Value)].Update("NewBaseName"); - Assert.Equal("NewBaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); - baseRootPartNode[nameof(Types.SomeObject.Value)].Update("NewBaseName2"); - Assert.Equal("NewBaseName2", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); - derivedRootPartNode[nameof(Types.SomeObject.Value)].Update("NewDerivedName"); - Assert.True(derivedRootPartNode[nameof(Types.SomeObject.Value)].IsContentOverridden()); - Assert.Equal("NewDerivedName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); - baseRootPartNode[nameof(Types.SomeObject.Value)].Update("NewBaseName3"); - Assert.True(derivedRootPartNode[nameof(Types.SomeObject.Value)].IsContentOverridden()); - Assert.Equal("NewDerivedName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); - } + var baseAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, null, x => x.Parts[x.RootParts.Single().Id].Part.Name = "BaseName"); + var derivedAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, baseAsset.Container); + var instances = baseAsset.Asset.CreatePartInstances(); + var baseRootId = baseAsset.Asset.Hierarchy.RootParts.Single().Id; + var derivedRootId = instances.RootParts.Single().Id; + derivedAsset.Graph.AddPartToAsset(instances.Parts, instances.Parts[derivedRootId], null, 1); + derivedAsset.Graph.RefreshBase(); + Assert.Equal(2, derivedAsset.Asset.Hierarchy.RootParts.Count); + Assert.Equal(baseRootId, derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Base?.BasePartId); + Assert.Equal("BaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); + var baseRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(baseAsset.Asset.Hierarchy.Parts[baseRootId].Part); + var derivedRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part); + baseRootPartNode[nameof(Types.MyPart.Name)].Update("NewBaseName"); + Assert.Equal("NewBaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); + baseRootPartNode[nameof(Types.MyPart.Name)].Update("NewBaseName2"); + Assert.Equal("NewBaseName2", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); + derivedRootPartNode[nameof(Types.MyPart.Name)].Update("NewDerivedName"); + Assert.True(derivedRootPartNode[nameof(Types.MyPart.Name)].IsContentOverridden()); + Assert.Equal("NewDerivedName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); + baseRootPartNode[nameof(Types.MyPart.Name)].Update("NewBaseName3"); + Assert.True(derivedRootPartNode[nameof(Types.MyPart.Name)].IsContentOverridden()); + Assert.Equal("NewDerivedName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); + } - [Fact(Skip = "Overriding an object does not override its member currently, so this test cannot pass")] - public void TestObjectPropertyChangeInBase() - { - var baseAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, null, x => x.Parts[x.RootParts.Single().Id].Part.Object = new Types.SomeObject { Value = "BaseName" }); - var derivedAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, baseAsset.Container); - var instances = baseAsset.Asset.CreatePartInstances(); - var baseRootId = baseAsset.Asset.Hierarchy.RootParts.Single().Id; - var derivedRootId = instances.RootParts.Single().Id; - derivedAsset.Graph.AddPartToAsset(instances.Parts, instances.Parts[derivedRootId], null, 1); - derivedAsset.Graph.RefreshBase(); - Assert.Equal(2, derivedAsset.Asset.Hierarchy.RootParts.Count); - Assert.Equal(baseRootId, derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Base?.BasePartId); - Assert.Equal("BaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); - var baseRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(baseAsset.Asset.Hierarchy.Parts[baseRootId].Part); - baseRootPartNode[nameof(Types.MyPart.Object)].Update(new Types.SomeObject { Value = "NewBaseName" }); - Assert.Equal("NewBaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); - baseRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(baseAsset.Asset.Hierarchy.Parts[baseRootId].Part); - baseRootPartNode[nameof(Types.MyPart.Object)].Update(new Types.SomeObject { Value = "NewBaseName2" }); - Assert.Equal("NewBaseName2", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); - var derivedRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part); - derivedRootPartNode[nameof(Types.MyPart.Object)].Update(new Types.SomeObject { Value = "NewDerivedName" }); - Assert.True(derivedRootPartNode[nameof(Types.MyPart.Object)].IsContentOverridden()); - Assert.Equal("NewDerivedName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); - baseRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(baseAsset.Asset.Hierarchy.Parts[baseRootId].Part); - derivedRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part); - baseRootPartNode[nameof(Types.MyPart.Object)].Update(new Types.SomeObject { Value = "NewBaseName3" }); - Assert.True(derivedRootPartNode[nameof(Types.MyPart.Object)].IsContentOverridden()); - Assert.Equal("NewDerivedName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); - } + [Fact] + public void TestSimpleNestedPropertyChangeInBase() + { + var baseAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, null, x => x.Parts[x.RootParts.Single().Id].Part.Object = new Types.SomeObject { Value = "BaseName" }); + var derivedAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, baseAsset.Container); + var instances = baseAsset.Asset.CreatePartInstances(); + var baseRootId = baseAsset.Asset.Hierarchy.RootParts.Single().Id; + var derivedRootId = instances.RootParts.Single().Id; + derivedAsset.Graph.AddPartToAsset(instances.Parts, instances.Parts[derivedRootId], null, 1); + derivedAsset.Graph.RefreshBase(); + Assert.Equal(2, derivedAsset.Asset.Hierarchy.RootParts.Count); + Assert.Equal(baseRootId, derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Base?.BasePartId); + Assert.Equal("BaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); + var baseRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(baseAsset.Asset.Hierarchy.Parts[baseRootId].Part.Object); + var derivedRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object); + baseRootPartNode[nameof(Types.SomeObject.Value)].Update("NewBaseName"); + Assert.Equal("NewBaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); + baseRootPartNode[nameof(Types.SomeObject.Value)].Update("NewBaseName2"); + Assert.Equal("NewBaseName2", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); + derivedRootPartNode[nameof(Types.SomeObject.Value)].Update("NewDerivedName"); + Assert.True(derivedRootPartNode[nameof(Types.SomeObject.Value)].IsContentOverridden()); + Assert.Equal("NewDerivedName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); + baseRootPartNode[nameof(Types.SomeObject.Value)].Update("NewBaseName3"); + Assert.True(derivedRootPartNode[nameof(Types.SomeObject.Value)].IsContentOverridden()); + Assert.Equal("NewDerivedName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); + } - [Fact] - public void TestMultiplePropertyChangesInBase() - { - var baseAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, null, x => x.Parts[x.RootParts.Single().Id].Part.Name = "BaseName"); - var derivedAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, baseAsset.Container); - var instances = baseAsset.Asset.CreatePartInstances(); - var baseRootId = baseAsset.Asset.Hierarchy.RootParts.Single().Id; - var derivedRootId = instances.RootParts.Single().Id; - derivedAsset.Graph.AddPartToAsset(instances.Parts, instances.Parts[derivedRootId], null, 1); - derivedAsset.Graph.RefreshBase(); - Assert.Equal(2, derivedAsset.Asset.Hierarchy.RootParts.Count); - Assert.Equal(baseRootId, derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Base?.BasePartId); - Assert.Equal("BaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); - var baseRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(baseAsset.Asset.Hierarchy.Parts[baseRootId].Part); - baseRootPartNode[nameof(Types.MyPart.Object)].Update(new Types.SomeObject { Value = "NewBaseValue" }); - Assert.NotNull(derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object); - Assert.Equal("NewBaseValue", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); - baseRootPartNode[nameof(Types.MyPart.Name)].Update("NewBaseName"); - Assert.Equal("NewBaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); - } + [Fact(Skip = "Overriding an object does not override its member currently, so this test cannot pass")] + public void TestObjectPropertyChangeInBase() + { + var baseAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, null, x => x.Parts[x.RootParts.Single().Id].Part.Object = new Types.SomeObject { Value = "BaseName" }); + var derivedAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, baseAsset.Container); + var instances = baseAsset.Asset.CreatePartInstances(); + var baseRootId = baseAsset.Asset.Hierarchy.RootParts.Single().Id; + var derivedRootId = instances.RootParts.Single().Id; + derivedAsset.Graph.AddPartToAsset(instances.Parts, instances.Parts[derivedRootId], null, 1); + derivedAsset.Graph.RefreshBase(); + Assert.Equal(2, derivedAsset.Asset.Hierarchy.RootParts.Count); + Assert.Equal(baseRootId, derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Base?.BasePartId); + Assert.Equal("BaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); + var baseRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(baseAsset.Asset.Hierarchy.Parts[baseRootId].Part); + baseRootPartNode[nameof(Types.MyPart.Object)].Update(new Types.SomeObject { Value = "NewBaseName" }); + Assert.Equal("NewBaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); + baseRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(baseAsset.Asset.Hierarchy.Parts[baseRootId].Part); + baseRootPartNode[nameof(Types.MyPart.Object)].Update(new Types.SomeObject { Value = "NewBaseName2" }); + Assert.Equal("NewBaseName2", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); + var derivedRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part); + derivedRootPartNode[nameof(Types.MyPart.Object)].Update(new Types.SomeObject { Value = "NewDerivedName" }); + Assert.True(derivedRootPartNode[nameof(Types.MyPart.Object)].IsContentOverridden()); + Assert.Equal("NewDerivedName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); + baseRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(baseAsset.Asset.Hierarchy.Parts[baseRootId].Part); + derivedRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part); + baseRootPartNode[nameof(Types.MyPart.Object)].Update(new Types.SomeObject { Value = "NewBaseName3" }); + Assert.True(derivedRootPartNode[nameof(Types.MyPart.Object)].IsContentOverridden()); + Assert.Equal("NewDerivedName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); + } + [Fact] + public void TestMultiplePropertyChangesInBase() + { + var baseAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, null, x => x.Parts[x.RootParts.Single().Id].Part.Name = "BaseName"); + var derivedAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, baseAsset.Container); + var instances = baseAsset.Asset.CreatePartInstances(); + var baseRootId = baseAsset.Asset.Hierarchy.RootParts.Single().Id; + var derivedRootId = instances.RootParts.Single().Id; + derivedAsset.Graph.AddPartToAsset(instances.Parts, instances.Parts[derivedRootId], null, 1); + derivedAsset.Graph.RefreshBase(); + Assert.Equal(2, derivedAsset.Asset.Hierarchy.RootParts.Count); + Assert.Equal(baseRootId, derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Base?.BasePartId); + Assert.Equal("BaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); + var baseRootPartNode = (IAssetObjectNode)baseAsset.Graph.Container.NodeContainer.GetNode(baseAsset.Asset.Hierarchy.Parts[baseRootId].Part); + baseRootPartNode[nameof(Types.MyPart.Object)].Update(new Types.SomeObject { Value = "NewBaseValue" }); + Assert.NotNull(derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object); + Assert.Equal("NewBaseValue", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Object.Value); + baseRootPartNode[nameof(Types.MyPart.Name)].Update("NewBaseName"); + Assert.Equal("NewBaseName", derivedAsset.Asset.Hierarchy.Parts[derivedRootId].Part.Name); } + } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetCompositeHierarchyCloning.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetCompositeHierarchyCloning.cs index 9a318052da..df5199e18a 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetCompositeHierarchyCloning.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetCompositeHierarchyCloning.cs @@ -1,381 +1,367 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.Diagnostics; -using System.Linq; using Xunit; using Stride.Core.Assets.Quantum.Internal; using Stride.Core.Assets.Quantum.Tests.Helpers; using Stride.Core.Assets.Tests.Helpers; -using Stride.Core; using Stride.Core.Extensions; -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +public class TestAssetCompositeHierarchyCloning { - public class TestAssetCompositeHierarchyCloning + [Fact] + public void TestSimpleCloneSubHierarchy() { - [Fact] - public void TestSimpleCloneSubHierarchy() + var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2); + Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); + var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; + var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.None, out var remapping); + var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; + Assert.Empty(remapping); + Assert.Equal(3, clone.Parts.Count); + Assert.Single(clone.RootParts); + foreach (var rootPart in clone.RootParts) { - var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2); - Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); - var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; - Dictionary remapping; - var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.None, out remapping); - var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; - Assert.Empty(remapping); - Assert.Equal(3, clone.Parts.Count); - Assert.Single(clone.RootParts); - foreach (var rootPart in clone.RootParts) - { - Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); - } - foreach (var part in clone.Parts.Values) - { - var matchingPart = graph.Asset.Hierarchy.Parts[part.Part.Id]; - Assert.NotEqual(matchingPart, part); - Assert.NotEqual(matchingPart.Part, part.Part); - Assert.Equal(matchingPart.Part.Id, part.Part.Id); - Assert.Equal(matchingPart.Part.Name, part.Part.Name); - } - Assert.Equal(originalRoot.Part.Id, cloneRoot.Part.Id); - Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); - Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); - Assert.Equal(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); - Assert.Equal(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); - Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); - Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); + Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); } - - [Fact] - public void TestCloneSubHierarchyWithInternalReference() + foreach (var part in clone.Parts.Values) { - var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2, x => x.Parts[GuidGenerator.Get(5)].Part.MyReference = x.Parts[GuidGenerator.Get(6)].Part); - Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); - var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; - Dictionary remapping; - var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.None, out remapping); - var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; - Assert.Empty(remapping); - Assert.Equal(3, clone.Parts.Count); - Assert.Single(clone.RootParts); - foreach (var rootPart in clone.RootParts) - { - Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); - } - foreach (var part in clone.Parts.Values) - { - var matchingPart = graph.Asset.Hierarchy.Parts[part.Part.Id]; - Assert.NotEqual(matchingPart, part); - Assert.NotEqual(matchingPart.Part, part.Part); - Assert.Equal(matchingPart.Part.Id, part.Part.Id); - Assert.Equal(matchingPart.Part.Name, part.Part.Name); - } - Assert.Equal(originalRoot.Part.Id, cloneRoot.Part.Id); - Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); - Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); - Assert.Equal(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); - Assert.Equal(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); - Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); - Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); - Assert.Equal(cloneRoot.Part.Children[1], cloneRoot.Part.Children[0].MyReference); + var matchingPart = graph.Asset.Hierarchy.Parts[part.Part.Id]; + Assert.NotEqual(matchingPart, part); + Assert.NotEqual(matchingPart.Part, part.Part); + Assert.Equal(matchingPart.Part.Id, part.Part.Id); + Assert.Equal(matchingPart.Part.Name, part.Part.Name); } + Assert.Equal(originalRoot.Part.Id, cloneRoot.Part.Id); + Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); + Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); + Assert.Equal(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); + Assert.Equal(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); + Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); + Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); + } - [Fact] - public void TestCloneSubHierarchyWithExternalReferences() + [Fact] + public void TestCloneSubHierarchyWithInternalReference() + { + var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2, x => x.Parts[GuidGenerator.Get(5)].Part.MyReference = x.Parts[GuidGenerator.Get(6)].Part); + Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); + var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; + var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.None, out var remapping); + var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; + Assert.Empty(remapping); + Assert.Equal(3, clone.Parts.Count); + Assert.Single(clone.RootParts); + foreach (var rootPart in clone.RootParts) { - var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2, x => x.Parts[GuidGenerator.Get(5)].Part.MyReferences = new List { x.Parts[GuidGenerator.Get(2)].Part }); - Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); - var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; - Dictionary remapping; - var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.None, out remapping); - var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; - Assert.Empty(remapping); - Assert.Equal(3, clone.Parts.Count); - Assert.Single(clone.RootParts); - foreach (var rootPart in clone.RootParts) - { - Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); - } - foreach (var part in clone.Parts.Values) - { - var matchingPart = graph.Asset.Hierarchy.Parts[part.Part.Id]; - Assert.NotEqual(matchingPart, part); - Assert.NotEqual(matchingPart.Part, part.Part); - Assert.Equal(matchingPart.Part.Id, part.Part.Id); - Assert.Equal(matchingPart.Part.Name, part.Part.Name); - } - Assert.Equal(originalRoot.Part.Id, cloneRoot.Part.Id); - Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); - Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); - Assert.Equal(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); - Assert.Equal(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); - Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); - Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); - Assert.Equal(graph.Asset.Hierarchy.Parts[GuidGenerator.Get(2)].Part, cloneRoot.Part.Children[0].MyReferences[0]); + Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); } + foreach (var part in clone.Parts.Values) + { + var matchingPart = graph.Asset.Hierarchy.Parts[part.Part.Id]; + Assert.NotEqual(matchingPart, part); + Assert.NotEqual(matchingPart.Part, part.Part); + Assert.Equal(matchingPart.Part.Id, part.Part.Id); + Assert.Equal(matchingPart.Part.Name, part.Part.Name); + } + Assert.Equal(originalRoot.Part.Id, cloneRoot.Part.Id); + Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); + Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); + Assert.Equal(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); + Assert.Equal(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); + Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); + Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); + Assert.Equal(cloneRoot.Part.Children[1], cloneRoot.Part.Children[0].MyReference); + } - [Fact] - public void TestCloneSubHierarchyWithCleanExternalReferences() + [Fact] + public void TestCloneSubHierarchyWithExternalReferences() + { + var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2, x => x.Parts[GuidGenerator.Get(5)].Part.MyReferences = [x.Parts[GuidGenerator.Get(2)].Part]); + Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); + var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; + var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.None, out var remapping); + var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; + Assert.Empty(remapping); + Assert.Equal(3, clone.Parts.Count); + Assert.Single(clone.RootParts); + foreach (var rootPart in clone.RootParts) { - var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2); - Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); - var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; - Dictionary remapping; - var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.CleanExternalReferences, out remapping); - var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; - Assert.Empty(remapping); - Assert.Equal(3, clone.Parts.Count); - Assert.Single(clone.RootParts); - foreach (var rootPart in clone.RootParts) - { - Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); - } - foreach (var part in clone.Parts.Values) - { - var matchingPart = graph.Asset.Hierarchy.Parts[part.Part.Id]; - Assert.NotEqual(matchingPart, part); - Assert.NotEqual(matchingPart.Part, part.Part); - Assert.Equal(matchingPart.Part.Id, part.Part.Id); - Assert.Equal(matchingPart.Part.Name, part.Part.Name); - } - Assert.Equal(originalRoot.Part.Id, cloneRoot.Part.Id); - Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); - Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); - Assert.Equal(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); - Assert.Equal(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); - Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); - Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); + Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); } + foreach (var part in clone.Parts.Values) + { + var matchingPart = graph.Asset.Hierarchy.Parts[part.Part.Id]; + Assert.NotEqual(matchingPart, part); + Assert.NotEqual(matchingPart.Part, part.Part); + Assert.Equal(matchingPart.Part.Id, part.Part.Id); + Assert.Equal(matchingPart.Part.Name, part.Part.Name); + } + Assert.Equal(originalRoot.Part.Id, cloneRoot.Part.Id); + Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); + Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); + Assert.Equal(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); + Assert.Equal(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); + Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); + Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); + Assert.Equal(graph.Asset.Hierarchy.Parts[GuidGenerator.Get(2)].Part, cloneRoot.Part.Children[0].MyReferences[0]); + } - [Fact] - public void TestCloneSubHierarchyWithInternalReferenceWithCleanExternalReferences() + [Fact] + public void TestCloneSubHierarchyWithCleanExternalReferences() + { + var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2); + Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); + var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; + var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.CleanExternalReferences, out var remapping); + var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; + Assert.Empty(remapping); + Assert.Equal(3, clone.Parts.Count); + Assert.Single(clone.RootParts); + foreach (var rootPart in clone.RootParts) + { + Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); + } + foreach (var part in clone.Parts.Values) { - var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2, x => x.Parts[GuidGenerator.Get(5)].Part.MyReference = x.Parts[GuidGenerator.Get(6)].Part); - Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); - var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; - Dictionary remapping; - var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.CleanExternalReferences, out remapping); - var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; - Assert.Empty(remapping); - Assert.Equal(3, clone.Parts.Count); - Assert.Single(clone.RootParts); - foreach (var rootPart in clone.RootParts) - { - Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); - } - foreach (var part in clone.Parts.Values) - { - var matchingPart = graph.Asset.Hierarchy.Parts[part.Part.Id]; - Assert.NotEqual(matchingPart, part); - Assert.NotEqual(matchingPart.Part, part.Part); - Assert.Equal(matchingPart.Part.Id, part.Part.Id); - Assert.Equal(matchingPart.Part.Name, part.Part.Name); - } - Assert.Equal(originalRoot.Part.Id, cloneRoot.Part.Id); - Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); - Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); - Assert.Equal(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); - Assert.Equal(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); - Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); - Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); - Assert.Equal(cloneRoot.Part.Children[1], cloneRoot.Part.Children[0].MyReference); + var matchingPart = graph.Asset.Hierarchy.Parts[part.Part.Id]; + Assert.NotEqual(matchingPart, part); + Assert.NotEqual(matchingPart.Part, part.Part); + Assert.Equal(matchingPart.Part.Id, part.Part.Id); + Assert.Equal(matchingPart.Part.Name, part.Part.Name); } + Assert.Equal(originalRoot.Part.Id, cloneRoot.Part.Id); + Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); + Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); + Assert.Equal(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); + Assert.Equal(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); + Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); + Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); + } - [Fact] - public void TestCloneSubHierarchyWithExternalReferencesWithCleanExternalReferences() + [Fact] + public void TestCloneSubHierarchyWithInternalReferenceWithCleanExternalReferences() + { + var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2, x => x.Parts[GuidGenerator.Get(5)].Part.MyReference = x.Parts[GuidGenerator.Get(6)].Part); + Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); + var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; + var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.CleanExternalReferences, out var remapping); + var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; + Assert.Empty(remapping); + Assert.Equal(3, clone.Parts.Count); + Assert.Single(clone.RootParts); + foreach (var rootPart in clone.RootParts) + { + Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); + } + foreach (var part in clone.Parts.Values) { - var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2, x => x.Parts[GuidGenerator.Get(5)].Part.MyReferences = new List { x.Parts[GuidGenerator.Get(2)].Part }); - Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); - var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; - Dictionary remapping; - var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.CleanExternalReferences, out remapping); - var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; - Assert.Empty(remapping); - Assert.Equal(3, clone.Parts.Count); - Assert.Single(clone.RootParts); - foreach (var rootPart in clone.RootParts) - { - Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); - } - foreach (var part in clone.Parts.Values) - { - var matchingPart = graph.Asset.Hierarchy.Parts[part.Part.Id]; - Assert.NotEqual(matchingPart, part); - Assert.NotEqual(matchingPart.Part, part.Part); - Assert.Equal(matchingPart.Part.Id, part.Part.Id); - Assert.Equal(matchingPart.Part.Name, part.Part.Name); - } - Assert.Equal(originalRoot.Part.Id, cloneRoot.Part.Id); - Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); - Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); - Assert.Equal(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); - Assert.Equal(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); - Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); - Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); - Assert.Null(cloneRoot.Part.Children[0].MyReferences[0]); + var matchingPart = graph.Asset.Hierarchy.Parts[part.Part.Id]; + Assert.NotEqual(matchingPart, part); + Assert.NotEqual(matchingPart.Part, part.Part); + Assert.Equal(matchingPart.Part.Id, part.Part.Id); + Assert.Equal(matchingPart.Part.Name, part.Part.Name); } + Assert.Equal(originalRoot.Part.Id, cloneRoot.Part.Id); + Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); + Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); + Assert.Equal(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); + Assert.Equal(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); + Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); + Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); + Assert.Equal(cloneRoot.Part.Children[1], cloneRoot.Part.Children[0].MyReference); + } + [Fact] + public void TestCloneSubHierarchyWithExternalReferencesWithCleanExternalReferences() + { + var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2, x => x.Parts[GuidGenerator.Get(5)].Part.MyReferences = [x.Parts[GuidGenerator.Get(2)].Part]); + Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); + var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; + var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.CleanExternalReferences, out var remapping); + var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; + Assert.Empty(remapping); + Assert.Equal(3, clone.Parts.Count); + Assert.Single(clone.RootParts); + foreach (var rootPart in clone.RootParts) + { + Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); + } + foreach (var part in clone.Parts.Values) + { + var matchingPart = graph.Asset.Hierarchy.Parts[part.Part.Id]; + Assert.NotEqual(matchingPart, part); + Assert.NotEqual(matchingPart.Part, part.Part); + Assert.Equal(matchingPart.Part.Id, part.Part.Id); + Assert.Equal(matchingPart.Part.Name, part.Part.Name); + } + Assert.Equal(originalRoot.Part.Id, cloneRoot.Part.Id); + Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); + Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); + Assert.Equal(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); + Assert.Equal(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); + Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); + Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); + Assert.Null(cloneRoot.Part.Children[0].MyReferences[0]); + } - [Fact] - public void TestCloneSubHierarchyWithGenerateNewIdsForIdentifiableObjects() + [Fact] + public void TestCloneSubHierarchyWithGenerateNewIdsForIdentifiableObjects() + { + var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2); + Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); + var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; + var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.GenerateNewIdsForIdentifiableObjects, out var remapping); + var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; + Assert.NotNull(remapping); + Assert.Equal(3, clone.Parts.Count); + Assert.Single(clone.RootParts); + foreach (var rootPart in clone.RootParts) + { + Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); + } + foreach (var part in clone.Parts.Values) { - var graph = AssetHierarchyHelper.BuildAssetAndGraph(2, 2, 2); - Debug.Write(AssetHierarchyHelper.PrintHierarchy(graph.Asset)); - var originalRoot = graph.Asset.Hierarchy.Parts[graph.Asset.Hierarchy.RootParts[1].Id]; - Dictionary remapping; - var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(graph.Container.NodeContainer, graph.Asset, originalRoot.Part.Id.Yield(), SubHierarchyCloneFlags.GenerateNewIdsForIdentifiableObjects, out remapping); - var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; - Assert.NotNull(remapping); - Assert.Equal(3, clone.Parts.Count); - Assert.Single(clone.RootParts); - foreach (var rootPart in clone.RootParts) - { - Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); - } - foreach (var part in clone.Parts.Values) - { - Assert.Contains(part.Part.Id, remapping.Values); - var matchingId = remapping.Single(x => x.Value == part.Part.Id).Key; - Assert.NotEqual(part.Part.Id, matchingId); - var matchingPart = graph.Asset.Hierarchy.Parts[matchingId]; - Assert.NotEqual(matchingPart, part); - Assert.NotEqual(matchingPart.Part, part.Part); - Assert.NotEqual(matchingPart.Part.Id, part.Part.Id); - Assert.Equal(matchingPart.Part.Name, part.Part.Name); - } - Assert.NotEqual(originalRoot.Part.Id, cloneRoot.Part.Id); - Assert.Contains(originalRoot.Part.Id, remapping.Keys); - Assert.Equal(remapping[originalRoot.Part.Id], cloneRoot.Part.Id); - Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); - Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); - Assert.NotEqual(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); - Assert.Contains(originalRoot.Part.Children[0].Id, remapping.Keys); - Assert.Equal(remapping[originalRoot.Part.Children[0].Id], cloneRoot.Part.Children[0].Id); - Assert.NotEqual(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); - Assert.Contains(originalRoot.Part.Children[1].Id, remapping.Keys); - Assert.Equal(remapping[originalRoot.Part.Children[1].Id], cloneRoot.Part.Children[1].Id); - Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); - Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); + Assert.Contains(part.Part.Id, remapping.Values); + var matchingId = remapping.Single(x => x.Value == part.Part.Id).Key; + Assert.NotEqual(part.Part.Id, matchingId); + var matchingPart = graph.Asset.Hierarchy.Parts[matchingId]; + Assert.NotEqual(matchingPart, part); + Assert.NotEqual(matchingPart.Part, part.Part); + Assert.NotEqual(matchingPart.Part.Id, part.Part.Id); + Assert.Equal(matchingPart.Part.Name, part.Part.Name); } + Assert.NotEqual(originalRoot.Part.Id, cloneRoot.Part.Id); + Assert.Contains(originalRoot.Part.Id, remapping.Keys); + Assert.Equal(remapping[originalRoot.Part.Id], cloneRoot.Part.Id); + Assert.NotEqual(originalRoot.Part.Children[0], cloneRoot.Part.Children[0]); + Assert.NotEqual(originalRoot.Part.Children[1], cloneRoot.Part.Children[1]); + Assert.NotEqual(originalRoot.Part.Children[0].Id, cloneRoot.Part.Children[0].Id); + Assert.Contains(originalRoot.Part.Children[0].Id, remapping.Keys); + Assert.Equal(remapping[originalRoot.Part.Children[0].Id], cloneRoot.Part.Children[0].Id); + Assert.NotEqual(originalRoot.Part.Children[1].Id, cloneRoot.Part.Children[1].Id); + Assert.Contains(originalRoot.Part.Children[1].Id, remapping.Keys); + Assert.Equal(remapping[originalRoot.Part.Children[1].Id], cloneRoot.Part.Children[1].Id); + Assert.NotEqual(originalRoot.Part.Children[0].Parent, cloneRoot.Part.Children[0].Parent); + Assert.NotEqual(originalRoot.Part.Children[1].Parent, cloneRoot.Part.Children[1].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); + } - [Fact] - public void TestCloneSubHierarchyInstanceWithoutRemoveOverrides() + [Fact] + public void TestCloneSubHierarchyInstanceWithoutRemoveOverrides() + { + var baseAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, null, x => x.Parts[x.RootParts.Single().Id].Part.Name = "BaseName"); + var container = baseAsset.Container.NodeContainer; + var derivedAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, baseAsset.Container); + var instances = baseAsset.Asset.CreatePartInstances(); + var derivedRootId = instances.RootParts.Single().Id; + derivedAsset.Graph.AddPartToAsset(instances.Parts, instances.Parts[derivedRootId], derivedAsset.Asset.Hierarchy.RootParts.Single(), 1); + derivedAsset.Graph.RefreshBase(); + var partToChange = (AssetObjectNode)container.GetNode(instances.Parts.Single(x => x.Value.Part.Name == "BaseName").Value.Part); + partToChange[nameof(Types.MyPart.Name)].Update("Overridden"); + Debug.Write(AssetHierarchyHelper.PrintHierarchy(derivedAsset.Asset)); + var originalRoot = derivedAsset.Asset.Hierarchy.RootParts.Single(); + var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(container, derivedAsset.Asset, originalRoot.Id.Yield(), SubHierarchyCloneFlags.None, out var remapping); + var cloneAsset = AssetHierarchyHelper.BuildAssetContainer(0, 0, 0, baseAsset.Container); + var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; + cloneAsset.Graph.AddPartToAsset(clone.Parts, cloneRoot, null, 0); + cloneAsset.Graph.RefreshBase(); + Assert.Empty(remapping); + Assert.Equal(4, clone.Parts.Count); + Assert.Single(clone.RootParts); + foreach (var rootPart in clone.RootParts) + { + Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); + } + foreach (var part in clone.Parts.Values) { - var baseAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, null, x => x.Parts[x.RootParts.Single().Id].Part.Name = "BaseName"); - var container = baseAsset.Container.NodeContainer; - var derivedAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, baseAsset.Container); - var instances = baseAsset.Asset.CreatePartInstances(); - var derivedRootId = instances.RootParts.Single().Id; - derivedAsset.Graph.AddPartToAsset(instances.Parts, instances.Parts[derivedRootId], derivedAsset.Asset.Hierarchy.RootParts.Single(), 1); - derivedAsset.Graph.RefreshBase(); - var partToChange = (AssetObjectNode)container.GetNode(instances.Parts.Single(x => x.Value.Part.Name == "BaseName").Value.Part); - partToChange[nameof(Types.MyPart.Name)].Update("Overridden"); - Debug.Write(AssetHierarchyHelper.PrintHierarchy(derivedAsset.Asset)); - var originalRoot = derivedAsset.Asset.Hierarchy.RootParts.Single(); - Dictionary remapping; - var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(container, derivedAsset.Asset, originalRoot.Id.Yield(), SubHierarchyCloneFlags.None, out remapping); - var cloneAsset = AssetHierarchyHelper.BuildAssetContainer(0, 0, 0, baseAsset.Container); - var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; - cloneAsset.Graph.AddPartToAsset(clone.Parts, cloneRoot, null, 0); - cloneAsset.Graph.RefreshBase(); - Assert.Empty(remapping); - Assert.Equal(4, clone.Parts.Count); - Assert.Single(clone.RootParts); - foreach (var rootPart in clone.RootParts) - { - Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); - } - foreach (var part in clone.Parts.Values) - { - var matchingPart = derivedAsset.Asset.Hierarchy.Parts[part.Part.Id]; - Assert.NotEqual(matchingPart, part); - Assert.NotEqual(matchingPart.Part, part.Part); - Assert.Equal(matchingPart.Part.Id, part.Part.Id); - Assert.Equal(matchingPart.Part.Name, part.Part.Name); - } - Assert.Equal(originalRoot.Id, cloneRoot.Part.Id); - Assert.NotEqual(originalRoot.Children[0], cloneRoot.Part.Children[0]); - Assert.NotEqual(originalRoot.Children[1], cloneRoot.Part.Children[1]); - Assert.Equal(originalRoot.Children[0].Id, cloneRoot.Part.Children[0].Id); - Assert.Equal(originalRoot.Children[1].Id, cloneRoot.Part.Children[1].Id); - Assert.NotEqual(originalRoot.Children[0].Parent, cloneRoot.Part.Children[0].Parent); - Assert.NotEqual(originalRoot.Children[1].Parent, cloneRoot.Part.Children[1].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); - var clonedChangedPart = (AssetObjectNode)container.GetNode(clone.Parts.Single(x => x.Value.Part.Id == (Guid)partToChange[nameof(IIdentifiable.Id)].Retrieve()).Value.Part); - Assert.True(clonedChangedPart[nameof(Types.MyPart.Name)].IsContentOverridden()); - Assert.False(clonedChangedPart[nameof(Types.MyPart.Name)].IsContentInherited()); - Assert.Equal("Overridden", clonedChangedPart[nameof(Types.MyPart.Name)].Retrieve()); - Assert.Equal(partToChange[nameof(Types.MyPart.Name)].BaseNode, clonedChangedPart[nameof(Types.MyPart.Name)].BaseNode); + var matchingPart = derivedAsset.Asset.Hierarchy.Parts[part.Part.Id]; + Assert.NotEqual(matchingPart, part); + Assert.NotEqual(matchingPart.Part, part.Part); + Assert.Equal(matchingPart.Part.Id, part.Part.Id); + Assert.Equal(matchingPart.Part.Name, part.Part.Name); } + Assert.Equal(originalRoot.Id, cloneRoot.Part.Id); + Assert.NotEqual(originalRoot.Children[0], cloneRoot.Part.Children[0]); + Assert.NotEqual(originalRoot.Children[1], cloneRoot.Part.Children[1]); + Assert.Equal(originalRoot.Children[0].Id, cloneRoot.Part.Children[0].Id); + Assert.Equal(originalRoot.Children[1].Id, cloneRoot.Part.Children[1].Id); + Assert.NotEqual(originalRoot.Children[0].Parent, cloneRoot.Part.Children[0].Parent); + Assert.NotEqual(originalRoot.Children[1].Parent, cloneRoot.Part.Children[1].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); + var clonedChangedPart = (AssetObjectNode)container.GetNode(clone.Parts.Single(x => x.Value.Part.Id == (Guid)partToChange[nameof(IIdentifiable.Id)].Retrieve()).Value.Part); + Assert.True(clonedChangedPart[nameof(Types.MyPart.Name)].IsContentOverridden()); + Assert.False(clonedChangedPart[nameof(Types.MyPart.Name)].IsContentInherited()); + Assert.Equal("Overridden", clonedChangedPart[nameof(Types.MyPart.Name)].Retrieve()); + Assert.Equal(partToChange[nameof(Types.MyPart.Name)].BaseNode, clonedChangedPart[nameof(Types.MyPart.Name)].BaseNode); + } - [Fact] - public void TestCloneSubHierarchyInstanceWithRemoveOverrides() + [Fact] + public void TestCloneSubHierarchyInstanceWithRemoveOverrides() + { + var baseAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, null, x => x.Parts[x.RootParts.Single().Id].Part.Name = "BaseName"); + var container = baseAsset.Container.NodeContainer; + var derivedAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, baseAsset.Container); + var instances = baseAsset.Asset.CreatePartInstances(); + var derivedRootId = instances.RootParts.Single().Id; + derivedAsset.Graph.AddPartToAsset(instances.Parts, instances.Parts[derivedRootId], derivedAsset.Asset.Hierarchy.RootParts.Single(), 1); + derivedAsset.Graph.RefreshBase(); + var partToChange = (AssetObjectNode)container.GetNode(instances.Parts.Single(x => x.Value.Part.Name == "BaseName").Value.Part); + partToChange[nameof(Types.MyPart.Name)].Update("Overridden"); + Debug.Write(AssetHierarchyHelper.PrintHierarchy(derivedAsset.Asset)); + var originalRoot = derivedAsset.Asset.Hierarchy.RootParts.Single(); + var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(container, derivedAsset.Asset, originalRoot.Id.Yield(), SubHierarchyCloneFlags.RemoveOverrides, out var remapping); + var cloneAsset = AssetHierarchyHelper.BuildAssetContainer(0, 0, 0, baseAsset.Container); + var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; + cloneAsset.Graph.AddPartToAsset(clone.Parts, cloneRoot, null, 0); + cloneAsset.Graph.RefreshBase(); + Assert.Empty(remapping); + Assert.Equal(4, clone.Parts.Count); + Assert.Single(clone.RootParts); + foreach (var rootPart in clone.RootParts) + { + Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); + } + foreach (var part in clone.Parts.Values) { - var baseAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, null, x => x.Parts[x.RootParts.Single().Id].Part.Name = "BaseName"); - var container = baseAsset.Container.NodeContainer; - var derivedAsset = AssetHierarchyHelper.BuildAssetContainer(1, 2, 1, baseAsset.Container); - var instances = baseAsset.Asset.CreatePartInstances(); - var derivedRootId = instances.RootParts.Single().Id; - derivedAsset.Graph.AddPartToAsset(instances.Parts, instances.Parts[derivedRootId], derivedAsset.Asset.Hierarchy.RootParts.Single(), 1); - derivedAsset.Graph.RefreshBase(); - var partToChange = (AssetObjectNode)container.GetNode(instances.Parts.Single(x => x.Value.Part.Name == "BaseName").Value.Part); - partToChange[nameof(Types.MyPart.Name)].Update("Overridden"); - Debug.Write(AssetHierarchyHelper.PrintHierarchy(derivedAsset.Asset)); - var originalRoot = derivedAsset.Asset.Hierarchy.RootParts.Single(); - Dictionary remapping; - var clone = AssetCompositeHierarchyPropertyGraph.CloneSubHierarchies(container, derivedAsset.Asset, originalRoot.Id.Yield(), SubHierarchyCloneFlags.RemoveOverrides, out remapping); - var cloneAsset = AssetHierarchyHelper.BuildAssetContainer(0, 0, 0, baseAsset.Container); - var cloneRoot = clone.Parts[clone.RootParts.Single().Id]; - cloneAsset.Graph.AddPartToAsset(clone.Parts, cloneRoot, null, 0); - cloneAsset.Graph.RefreshBase(); - Assert.Empty(remapping); - Assert.Equal(4, clone.Parts.Count); - Assert.Single(clone.RootParts); - foreach (var rootPart in clone.RootParts) - { - Assert.Contains(rootPart, clone.Parts.Values.Select(x => x.Part)); - } - foreach (var part in clone.Parts.Values) - { - var matchingPart = derivedAsset.Asset.Hierarchy.Parts[part.Part.Id]; - Assert.NotEqual(matchingPart, part); - Assert.NotEqual(matchingPart.Part, part.Part); - Assert.Equal(matchingPart.Part.Id, part.Part.Id); - Assert.Equal(matchingPart.Part.Name, part.Part.Name); - } - Assert.Equal(originalRoot.Id, cloneRoot.Part.Id); - Assert.NotEqual(originalRoot.Children[0], cloneRoot.Part.Children[0]); - Assert.NotEqual(originalRoot.Children[1], cloneRoot.Part.Children[1]); - Assert.Equal(originalRoot.Children[0].Id, cloneRoot.Part.Children[0].Id); - Assert.Equal(originalRoot.Children[1].Id, cloneRoot.Part.Children[1].Id); - Assert.NotEqual(originalRoot.Children[0].Parent, cloneRoot.Part.Children[0].Parent); - Assert.NotEqual(originalRoot.Children[1].Parent, cloneRoot.Part.Children[1].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); - Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); - var clonedChangedPart = (AssetObjectNode)container.GetNode(clone.Parts.Single(x => x.Value.Part.Id == (Guid)partToChange[nameof(IIdentifiable.Id)].Retrieve()).Value.Part); - // Note: currently, using RemoveOverrides does not clear the base (it just clears the overrides), so we should expect to still have the base linked. - // This behavior could be changed in the future - Assert.False(clonedChangedPart[nameof(Types.MyPart.Name)].IsContentOverridden()); - Assert.True(clonedChangedPart[nameof(Types.MyPart.Name)].IsContentInherited()); - Assert.Equal("Overridden", clonedChangedPart[nameof(Types.MyPart.Name)].Retrieve()); - Assert.Equal(partToChange[nameof(Types.MyPart.Name)].BaseNode, clonedChangedPart[nameof(Types.MyPart.Name)].BaseNode); + var matchingPart = derivedAsset.Asset.Hierarchy.Parts[part.Part.Id]; + Assert.NotEqual(matchingPart, part); + Assert.NotEqual(matchingPart.Part, part.Part); + Assert.Equal(matchingPart.Part.Id, part.Part.Id); + Assert.Equal(matchingPart.Part.Name, part.Part.Name); } + Assert.Equal(originalRoot.Id, cloneRoot.Part.Id); + Assert.NotEqual(originalRoot.Children[0], cloneRoot.Part.Children[0]); + Assert.NotEqual(originalRoot.Children[1], cloneRoot.Part.Children[1]); + Assert.Equal(originalRoot.Children[0].Id, cloneRoot.Part.Children[0].Id); + Assert.Equal(originalRoot.Children[1].Id, cloneRoot.Part.Children[1].Id); + Assert.NotEqual(originalRoot.Children[0].Parent, cloneRoot.Part.Children[0].Parent); + Assert.NotEqual(originalRoot.Children[1].Parent, cloneRoot.Part.Children[1].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[0].Parent); + Assert.Equal(cloneRoot.Part, cloneRoot.Part.Children[1].Parent); + var clonedChangedPart = (AssetObjectNode)container.GetNode(clone.Parts.Single(x => x.Value.Part.Id == (Guid)partToChange[nameof(IIdentifiable.Id)].Retrieve()).Value.Part); + // Note: currently, using RemoveOverrides does not clear the base (it just clears the overrides), so we should expect to still have the base linked. + // This behavior could be changed in the future + Assert.False(clonedChangedPart[nameof(Types.MyPart.Name)].IsContentOverridden()); + Assert.True(clonedChangedPart[nameof(Types.MyPart.Name)].IsContentInherited()); + Assert.Equal("Overridden", clonedChangedPart[nameof(Types.MyPart.Name)].Retrieve()); + Assert.Equal(partToChange[nameof(Types.MyPart.Name)].BaseNode, clonedChangedPart[nameof(Types.MyPart.Name)].BaseNode); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetCompositeHierarchySerialization.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetCompositeHierarchySerialization.cs index 58494708d5..f81a2c58fb 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetCompositeHierarchySerialization.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetCompositeHierarchySerialization.cs @@ -2,152 +2,160 @@ using Stride.Core.Assets.Quantum.Tests.Helpers; using Stride.Core.Assets.Tests.Helpers; -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +using SerializationHelper = Helpers.SerializationHelper; + +public class TestAssetCompositeHierarchySerialization { - using SerializationHelper = Helpers.SerializationHelper; + const string SimpleHierarchyYaml = + """ + !MyAssetHierarchy + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + Hierarchy: + RootParts: + - ref!! 00000002-0002-0000-0200-000002000000 + - ref!! 00000001-0001-0000-0100-000001000000 + Parts: + - Part: + Id: 00000001-0001-0000-0100-000001000000 + Children: [] + - Part: + Id: 00000002-0002-0000-0200-000002000000 + Children: [] - public class TestAssetCompositeHierarchySerialization - { - const string SimpleHierarchyYaml = @"!MyAssetHierarchy -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -Hierarchy: - RootParts: - - ref!! 00000002-0002-0000-0200-000002000000 - - ref!! 00000001-0001-0000-0100-000001000000 - Parts: - - Part: - Id: 00000001-0001-0000-0100-000001000000 - Children: [] - - Part: - Id: 00000002-0002-0000-0200-000002000000 - Children: [] -"; + """; + + const string NestedHierarchyYaml = + """ + !MyAssetHierarchy + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + Hierarchy: + RootParts: + - ref!! 00000002-0002-0000-0200-000002000000 + - ref!! 00000001-0001-0000-0100-000001000000 + Parts: + - Part: + Id: 00000001-0001-0000-0100-000001000000 + Children: [] + - Part: + Id: 00000002-0002-0000-0200-000002000000 + Children: [] + - Part: + Id: 00000003-0003-0000-0300-000003000000 + Children: + - ref!! 00000002-0002-0000-0200-000002000000 + - Part: + Id: 00000004-0004-0000-0400-000004000000 + Children: + - ref!! 00000001-0001-0000-0100-000001000000 - const string NestedHierarchyYaml = @"!MyAssetHierarchy -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -Hierarchy: - RootParts: - - ref!! 00000002-0002-0000-0200-000002000000 - - ref!! 00000001-0001-0000-0100-000001000000 - Parts: - - Part: - Id: 00000001-0001-0000-0100-000001000000 - Children: [] - - Part: - Id: 00000002-0002-0000-0200-000002000000 - Children: [] - - Part: - Id: 00000003-0003-0000-0300-000003000000 - Children: - - ref!! 00000002-0002-0000-0200-000002000000 - - Part: - Id: 00000004-0004-0000-0400-000004000000 - Children: - - ref!! 00000001-0001-0000-0100-000001000000 -"; + """; - const string MissortedHierarchyYaml = @"!MyAssetHierarchy -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -Hierarchy: - RootParts: - - ref!! 00000002-0002-0000-0200-000002000000 - - ref!! 00000001-0001-0000-0100-000001000000 - Parts: - - Part: - Id: 00000003-0003-0000-0300-000003000000 - Children: - - ref!! 00000002-0002-0000-0200-000002000000 - - Part: - Id: 00000002-0002-0000-0200-000002000000 - Children: [] - - Part: - Id: 00000001-0001-0000-0100-000001000000 - Children: [] - - Part: - Id: 00000004-0004-0000-0400-000004000000 - Children: - - ref!! 00000001-0001-0000-0100-000001000000 -"; + const string MissortedHierarchyYaml = + """ + !MyAssetHierarchy + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + Hierarchy: + RootParts: + - ref!! 00000002-0002-0000-0200-000002000000 + - ref!! 00000001-0001-0000-0100-000001000000 + Parts: + - Part: + Id: 00000003-0003-0000-0300-000003000000 + Children: + - ref!! 00000002-0002-0000-0200-000002000000 + - Part: + Id: 00000002-0002-0000-0200-000002000000 + Children: [] + - Part: + Id: 00000001-0001-0000-0100-000001000000 + Children: [] + - Part: + Id: 00000004-0004-0000-0400-000004000000 + Children: + - ref!! 00000001-0001-0000-0100-000001000000 - [Fact] - public void TestSimpleDeserialization() - { - var asset = AssetFileSerializer.Load(AssetTestContainer.ToStream(SimpleHierarchyYaml), $"MyAsset{Types.FileExtension}"); - Assert.Equal(2, asset.Asset.Hierarchy.RootParts.Count); - Assert.Equal(GuidGenerator.Get(2), asset.Asset.Hierarchy.RootParts[0].Id); - Assert.Equal(GuidGenerator.Get(1), asset.Asset.Hierarchy.RootParts[1].Id); - Assert.Equal(2, asset.Asset.Hierarchy.Parts.Count); - Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(1))); - Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(2))); - } + """; + + [Fact] + public void TestSimpleDeserialization() + { + var asset = AssetFileSerializer.Load(AssetTestContainer.ToStream(SimpleHierarchyYaml), $"MyAsset{Types.FileExtension}"); + Assert.Equal(2, asset.Asset.Hierarchy.RootParts.Count); + Assert.Equal(GuidGenerator.Get(2), asset.Asset.Hierarchy.RootParts[0].Id); + Assert.Equal(GuidGenerator.Get(1), asset.Asset.Hierarchy.RootParts[1].Id); + Assert.Equal(2, asset.Asset.Hierarchy.Parts.Count); + Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(1))); + Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(2))); + } - [Fact] - public void TestSimpleSerialization() - { - //var asset = AssetFileSerializer.Load(AssetTestContainer.ToStream(text), $"MyAsset{Types.FileExtension}"); - var asset = new Types.MyAssetHierarchy(); - asset.Hierarchy.Parts.Add(new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(1) } }); - asset.Hierarchy.Parts.Add(new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(2) } }); - asset.Hierarchy.RootParts.Add(asset.Hierarchy.Parts[GuidGenerator.Get(2)].Part); - asset.Hierarchy.RootParts.Add(asset.Hierarchy.Parts[GuidGenerator.Get(1)].Part); - var context = new AssetTestContainer(asset); - context.BuildGraph(); - SerializationHelper.SerializeAndCompare(context.AssetItem, context.Graph, SimpleHierarchyYaml, false); - } + [Fact] + public void TestSimpleSerialization() + { + //var asset = AssetFileSerializer.Load(AssetTestContainer.ToStream(text), $"MyAsset{Types.FileExtension}"); + var asset = new Types.MyAssetHierarchy(); + asset.Hierarchy.Parts.Add(new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(1) } }); + asset.Hierarchy.Parts.Add(new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(2) } }); + asset.Hierarchy.RootParts.Add(asset.Hierarchy.Parts[GuidGenerator.Get(2)].Part); + asset.Hierarchy.RootParts.Add(asset.Hierarchy.Parts[GuidGenerator.Get(1)].Part); + var context = new AssetTestContainer(asset); + context.BuildGraph(); + SerializationHelper.SerializeAndCompare(context.AssetItem, context.Graph, SimpleHierarchyYaml, false); + } - [Fact] - public void TestNestedDeserialization() - { - var asset = AssetFileSerializer.Load(AssetTestContainer.ToStream(NestedHierarchyYaml), $"MyAsset{Types.FileExtension}"); - Assert.Equal(2, asset.Asset.Hierarchy.RootParts.Count); - Assert.Equal(GuidGenerator.Get(2), asset.Asset.Hierarchy.RootParts[0].Id); - Assert.Equal(GuidGenerator.Get(1), asset.Asset.Hierarchy.RootParts[1].Id); - Assert.Equal(4, asset.Asset.Hierarchy.Parts.Count); - Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(1))); - Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(2))); - Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(3))); - Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(4))); - Assert.Single(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(3)].Part.Children); - Assert.Equal(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(2)].Part, asset.Asset.Hierarchy.Parts[GuidGenerator.Get(3)].Part.Children[0]); - Assert.Single(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(4)].Part.Children); - Assert.Equal(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(1)].Part, asset.Asset.Hierarchy.Parts[GuidGenerator.Get(4)].Part.Children[0]); - } + [Fact] + public void TestNestedDeserialization() + { + var asset = AssetFileSerializer.Load(AssetTestContainer.ToStream(NestedHierarchyYaml), $"MyAsset{Types.FileExtension}"); + Assert.Equal(2, asset.Asset.Hierarchy.RootParts.Count); + Assert.Equal(GuidGenerator.Get(2), asset.Asset.Hierarchy.RootParts[0].Id); + Assert.Equal(GuidGenerator.Get(1), asset.Asset.Hierarchy.RootParts[1].Id); + Assert.Equal(4, asset.Asset.Hierarchy.Parts.Count); + Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(1))); + Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(2))); + Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(3))); + Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(4))); + Assert.Single(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(3)].Part.Children); + Assert.Equal(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(2)].Part, asset.Asset.Hierarchy.Parts[GuidGenerator.Get(3)].Part.Children[0]); + Assert.Single(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(4)].Part.Children); + Assert.Equal(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(1)].Part, asset.Asset.Hierarchy.Parts[GuidGenerator.Get(4)].Part.Children[0]); + } - [Fact] - public void TestNestedSerialization() - { - //var asset = AssetFileSerializer.Load(AssetTestContainer.ToStream(text), $"MyAsset{Types.FileExtension}"); - var asset = new Types.MyAssetHierarchy(); - asset.Hierarchy.Parts.Add(new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(1) } }); - asset.Hierarchy.Parts.Add(new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(2) } }); - asset.Hierarchy.Parts.Add(new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(3), Children = { asset.Hierarchy.Parts[GuidGenerator.Get(2)].Part } } }); - asset.Hierarchy.Parts.Add(new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(4), Children = { asset.Hierarchy.Parts[GuidGenerator.Get(1)].Part } } }); - asset.Hierarchy.RootParts.Add(asset.Hierarchy.Parts[GuidGenerator.Get(2)].Part); - asset.Hierarchy.RootParts.Add(asset.Hierarchy.Parts[GuidGenerator.Get(1)].Part); - var context = new AssetTestContainer(asset); - context.BuildGraph(); - SerializationHelper.SerializeAndCompare(context.AssetItem, context.Graph, NestedHierarchyYaml, false); - } + [Fact] + public void TestNestedSerialization() + { + //var asset = AssetFileSerializer.Load(AssetTestContainer.ToStream(text), $"MyAsset{Types.FileExtension}"); + var asset = new Types.MyAssetHierarchy(); + asset.Hierarchy.Parts.Add(new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(1) } }); + asset.Hierarchy.Parts.Add(new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(2) } }); + asset.Hierarchy.Parts.Add(new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(3), Children = { asset.Hierarchy.Parts[GuidGenerator.Get(2)].Part } } }); + asset.Hierarchy.Parts.Add(new Types.MyPartDesign { Part = new Types.MyPart { Id = GuidGenerator.Get(4), Children = { asset.Hierarchy.Parts[GuidGenerator.Get(1)].Part } } }); + asset.Hierarchy.RootParts.Add(asset.Hierarchy.Parts[GuidGenerator.Get(2)].Part); + asset.Hierarchy.RootParts.Add(asset.Hierarchy.Parts[GuidGenerator.Get(1)].Part); + var context = new AssetTestContainer(asset); + context.BuildGraph(); + SerializationHelper.SerializeAndCompare(context.AssetItem, context.Graph, NestedHierarchyYaml, false); + } - [Fact] - public void TestMissortedPartsDeserialization() - { - var asset = AssetFileSerializer.Load(AssetTestContainer.ToStream(MissortedHierarchyYaml), $"MyAsset{Types.FileExtension}"); - Assert.Equal(2, asset.Asset.Hierarchy.RootParts.Count); - Assert.Equal(GuidGenerator.Get(2), asset.Asset.Hierarchy.RootParts[0].Id); - Assert.Equal(GuidGenerator.Get(1), asset.Asset.Hierarchy.RootParts[1].Id); - Assert.Equal(4, asset.Asset.Hierarchy.Parts.Count); - Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(1))); - Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(2))); - Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(3))); - Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(4))); - Assert.Single(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(3)].Part.Children); - Assert.Equal(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(2)].Part, asset.Asset.Hierarchy.Parts[GuidGenerator.Get(3)].Part.Children[0]); - Assert.Single(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(4)].Part.Children); - Assert.Equal(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(1)].Part, asset.Asset.Hierarchy.Parts[GuidGenerator.Get(4)].Part.Children[0]); - } + [Fact] + public void TestMissortedPartsDeserialization() + { + var asset = AssetFileSerializer.Load(AssetTestContainer.ToStream(MissortedHierarchyYaml), $"MyAsset{Types.FileExtension}"); + Assert.Equal(2, asset.Asset.Hierarchy.RootParts.Count); + Assert.Equal(GuidGenerator.Get(2), asset.Asset.Hierarchy.RootParts[0].Id); + Assert.Equal(GuidGenerator.Get(1), asset.Asset.Hierarchy.RootParts[1].Id); + Assert.Equal(4, asset.Asset.Hierarchy.Parts.Count); + Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(1))); + Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(2))); + Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(3))); + Assert.True(asset.Asset.Hierarchy.Parts.ContainsKey(GuidGenerator.Get(4))); + Assert.Single(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(3)].Part.Children); + Assert.Equal(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(2)].Part, asset.Asset.Hierarchy.Parts[GuidGenerator.Get(3)].Part.Children[0]); + Assert.Single(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(4)].Part.Children); + Assert.Equal(asset.Asset.Hierarchy.Parts[GuidGenerator.Get(1)].Part, asset.Asset.Hierarchy.Parts[GuidGenerator.Get(4)].Part.Children[0]); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetPropertyGraph.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetPropertyGraph.cs index 4d7562de5b..f770cb8790 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetPropertyGraph.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestAssetPropertyGraph.cs @@ -1,81 +1,79 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Xunit; using Stride.Core.Assets.Quantum.Internal; using Stride.Core.Assets.Quantum.Tests.Helpers; using Stride.Core.Assets.Tests.Helpers; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +public class TestAssetPropertyGraph { - public class TestAssetPropertyGraph + [Fact] + public void TestSimpleConstruction() { - [Fact] - public void TestSimpleConstruction() - { - var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); - var asset = new Types.MyAsset1 { MyString = "String" }; - var assetItem = new AssetItem("MyAsset", asset); - var graph = AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); - Assert.IsAssignableFrom(graph.RootNode); - } + var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); + var asset = new Types.MyAsset1 { MyString = "String" }; + var assetItem = new AssetItem("MyAsset", asset); + var graph = AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); + Assert.IsAssignableFrom(graph.RootNode); + } - [Fact] - public void TestCollectionConstruction() - { - var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); - var asset = new Types.MyAsset2 { MyStrings = { "aaa", "bbb", "ccc" } }; - var assetItem = new AssetItem("MyAsset", asset); - var graph = AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); - Assert.IsAssignableFrom(graph.RootNode); - CollectionItemIdentifiers ids; - Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(asset.MyStrings, out ids)); - Assert.Equal(3, ids.KeyCount); - Assert.Equal(0, ids.DeletedCount); - Assert.True(ids.ContainsKey(0)); - Assert.True(ids.ContainsKey(1)); - Assert.True(ids.ContainsKey(2)); - } + [Fact] + public void TestCollectionConstruction() + { + var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); + var asset = new Types.MyAsset2 { MyStrings = { "aaa", "bbb", "ccc" } }; + var assetItem = new AssetItem("MyAsset", asset); + var graph = AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); + Assert.IsAssignableFrom(graph.RootNode); + Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(asset.MyStrings, out var ids)); + Assert.Equal(3, ids.KeyCount); + Assert.Equal(0, ids.DeletedCount); + Assert.True(ids.ContainsKey(0)); + Assert.True(ids.ContainsKey(1)); + Assert.True(ids.ContainsKey(2)); + } - [Fact] - public void TestNestedCollectionConstruction() - { - var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); - var asset = new Types.MyAsset7 { MyAsset2 = new Types.MyAsset2 { MyStrings = { "aaa", "bbb", "ccc" } } }; - var assetItem = new AssetItem("MyAsset", asset); - var graph = AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); - Assert.IsAssignableFrom(graph.RootNode); - CollectionItemIdentifiers ids; - Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(asset.MyAsset2.MyStrings, out ids)); - Assert.Equal(3, ids.KeyCount); - Assert.Equal(0, ids.DeletedCount); - Assert.True(ids.ContainsKey(0)); - Assert.True(ids.ContainsKey(1)); - Assert.True(ids.ContainsKey(2)); - } + [Fact] + public void TestNestedCollectionConstruction() + { + var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); + var asset = new Types.MyAsset7 { MyAsset2 = new Types.MyAsset2 { MyStrings = { "aaa", "bbb", "ccc" } } }; + var assetItem = new AssetItem("MyAsset", asset); + var graph = AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); + Assert.IsAssignableFrom(graph.RootNode); + Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(asset.MyAsset2.MyStrings, out var ids)); + Assert.Equal(3, ids.KeyCount); + Assert.Equal(0, ids.DeletedCount); + Assert.True(ids.ContainsKey(0)); + Assert.True(ids.ContainsKey(1)); + Assert.True(ids.ContainsKey(2)); + } - [Fact] - public void TestCollectionItemIdentifierWithDuplicates() - { - var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); - var asset = new Types.MyAsset2 { MyStrings = { "aaa", "bbb", "ccc" } }; - var ids = CollectionItemIdHelper.GetCollectionItemIds(asset.MyStrings); - ids.Add(0, IdentifierGenerator.Get(100)); - ids.Add(1, IdentifierGenerator.Get(200)); - ids.Add(2, IdentifierGenerator.Get(100)); - var assetItem = new AssetItem("MyAsset", asset); - Assert.Equal(IdentifierGenerator.Get(100), ids[0]); - Assert.Equal(IdentifierGenerator.Get(200), ids[1]); - Assert.Equal(IdentifierGenerator.Get(100), ids[2]); - var graph = AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); - Assert.IsAssignableFrom(graph.RootNode); - Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(asset.MyStrings, out ids)); - Assert.Equal(3, ids.KeyCount); - Assert.Equal(0, ids.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(100), ids[0]); - Assert.Equal(IdentifierGenerator.Get(200), ids[1]); - Assert.NotEqual(IdentifierGenerator.Get(100), ids[2]); - Assert.NotEqual(IdentifierGenerator.Get(200), ids[2]); - } + [Fact] + public void TestCollectionItemIdentifierWithDuplicates() + { + var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); + var asset = new Types.MyAsset2 { MyStrings = { "aaa", "bbb", "ccc" } }; + var ids = CollectionItemIdHelper.GetCollectionItemIds(asset.MyStrings); + ids.Add(0, IdentifierGenerator.Get(100)); + ids.Add(1, IdentifierGenerator.Get(200)); + ids.Add(2, IdentifierGenerator.Get(100)); + var assetItem = new AssetItem("MyAsset", asset); + Assert.Equal(IdentifierGenerator.Get(100), ids[0]); + Assert.Equal(IdentifierGenerator.Get(200), ids[1]); + Assert.Equal(IdentifierGenerator.Get(100), ids[2]); + var graph = AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); + Assert.IsAssignableFrom(graph.RootNode); + Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(asset.MyStrings, out ids)); + Assert.Equal(3, ids.KeyCount); + Assert.Equal(0, ids.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(100), ids[0]); + Assert.Equal(IdentifierGenerator.Get(200), ids[1]); + Assert.NotEqual(IdentifierGenerator.Get(100), ids[2]); + Assert.NotEqual(IdentifierGenerator.Get(200), ids[2]); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestCollectionUpdates.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestCollectionUpdates.cs index a6cda00d3a..6dcc8c421b 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestCollectionUpdates.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestCollectionUpdates.cs @@ -1,23 +1,21 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Xunit; using Stride.Core.Assets.Quantum.Tests.Helpers; -using Stride.Core.Reflection; -using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +public class TestCollectionUpdates { - public class TestCollectionUpdates + [Fact] + public void TestSimpleCollectionUpdate() { - [Fact] - public void TestSimpleCollectionUpdate() - { - var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); - var asset = new Types.MyAsset2 { MyStrings = { "aaa", "bbb", "ccc" } }; - var assetItem = new AssetItem("MyAsset", asset); - var graph = AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); - var node = graph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - //var ids = CollectionItemIdHelper.TryGetCollectionItemIds(asset.MyStrings, out itemIds); - } + var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); + var asset = new Types.MyAsset2 { MyStrings = { "aaa", "bbb", "ccc" } }; + var assetItem = new AssetItem("MyAsset", asset); + var graph = AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); + var node = graph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + //var ids = CollectionItemIdHelper.TryGetCollectionItemIds(asset.MyStrings, out itemIds); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestObjectReferenceGraph.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestObjectReferenceGraph.cs index 5fc0cb9810..3f59bb9ec4 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestObjectReferenceGraph.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestObjectReferenceGraph.cs @@ -1,70 +1,69 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Xunit; using Stride.Core.Assets.Quantum.Tests.Helpers; using Stride.Core.Assets.Tests.Helpers; -using Stride.Core.Diagnostics; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +public class TestObjectReferenceGraph { - public class TestObjectReferenceGraph + [Fact] + public void TestSimpleObjectReferenceGraph() { - [Fact] - public void TestSimpleObjectReferenceGraph() - { - var obj = new Types.MyReferenceable { Id = GuidGenerator.Get(2), Value = "MyInstance" }; - var asset = new Types.MyAssetWithRef2 { NonReference = obj, Reference = obj }; - var context = DeriveAssetTest.DeriveAsset(asset); - Assert.Equal(Types.MyAssetWithRef2.MemberCount, context.BaseGraph.RootNode.Members.Count); - Assert.True(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].IsReference); - Assert.Equal(obj, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target.Retrieve()); - Assert.True(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].IsReference); - Assert.Equal(obj, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].Target.Retrieve()); + var obj = new Types.MyReferenceable { Id = GuidGenerator.Get(2), Value = "MyInstance" }; + var asset = new Types.MyAssetWithRef2 { NonReference = obj, Reference = obj }; + var context = DeriveAssetTest.DeriveAsset(asset); + Assert.Equal(Types.MyAssetWithRef2.MemberCount, context.BaseGraph.RootNode.Members.Count); + Assert.True(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].IsReference); + Assert.Equal(obj, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target.Retrieve()); + Assert.True(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].IsReference); + Assert.Equal(obj, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].Target.Retrieve()); - Assert.True(context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].IsReference); - Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)], context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].BaseNode); - Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target, context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target.BaseNode); - Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)], context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].BaseNode); - Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].Target, context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].Target.BaseNode); - } + Assert.True(context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].IsReference); + Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)], context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].BaseNode); + Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target, context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target.BaseNode); + Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)], context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].BaseNode); + Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].Target, context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].Target.BaseNode); + } - [Fact] - public void TestUpdateObjectReferenceGraph() - { - var obj = new Types.MyReferenceable { Id = GuidGenerator.Get(2), Value = "MyInstance" }; - var asset = new Types.MyAssetWithRef2 { NonReference = obj, Reference = obj }; - var context = DeriveAssetTest.DeriveAsset(asset); - Assert.Equal(Types.MyAssetWithRef2.MemberCount, context.BaseGraph.RootNode.Members.Count); - Assert.True(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].IsReference); - Assert.Equal(obj, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target.Retrieve()); - Assert.True(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].IsReference); - Assert.Equal(obj, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].Target.Retrieve()); + [Fact] + public void TestUpdateObjectReferenceGraph() + { + var obj = new Types.MyReferenceable { Id = GuidGenerator.Get(2), Value = "MyInstance" }; + var asset = new Types.MyAssetWithRef2 { NonReference = obj, Reference = obj }; + var context = DeriveAssetTest.DeriveAsset(asset); + Assert.Equal(Types.MyAssetWithRef2.MemberCount, context.BaseGraph.RootNode.Members.Count); + Assert.True(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].IsReference); + Assert.Equal(obj, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target.Retrieve()); + Assert.True(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].IsReference); + Assert.Equal(obj, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].Target.Retrieve()); - Assert.True(context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].IsReference); - Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)], context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].BaseNode); - Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target, context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target.BaseNode); - Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)], context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].BaseNode); - Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].Target, context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].Target.BaseNode); - // TODO - } + Assert.True(context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].IsReference); + Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)], context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].BaseNode); + Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target, context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target.BaseNode); + Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)], context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].BaseNode); + Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].Target, context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.Reference)].Target.BaseNode); + // TODO + } - [Fact] - public void TestCollectionObjectReferenceGraph() - { - var obj = new Types.MyReferenceable { Id = GuidGenerator.Get(2), Value = "MyInstance" }; - var asset = new Types.MyAssetWithRef2 { NonReference = obj, References = { obj } }; - var context = DeriveAssetTest.DeriveAsset(asset); - Assert.Equal(Types.MyAssetWithRef2.MemberCount, context.BaseGraph.RootNode.Members.Count); - Assert.True(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].IsReference); - Assert.Equal(obj, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target.Retrieve()); - Assert.True(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].IsReference); - Assert.Equal(obj, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].Target.Retrieve(new NodeIndex(0))); - Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].Target.IndexedTarget(new NodeIndex(0))); + [Fact] + public void TestCollectionObjectReferenceGraph() + { + var obj = new Types.MyReferenceable { Id = GuidGenerator.Get(2), Value = "MyInstance" }; + var asset = new Types.MyAssetWithRef2 { NonReference = obj, References = { obj } }; + var context = DeriveAssetTest.DeriveAsset(asset); + Assert.Equal(Types.MyAssetWithRef2.MemberCount, context.BaseGraph.RootNode.Members.Count); + Assert.True(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].IsReference); + Assert.Equal(obj, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target.Retrieve()); + Assert.True(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].IsReference); + Assert.Equal(obj, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].Target.Retrieve(new NodeIndex(0))); + Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target, context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].Target.IndexedTarget(new NodeIndex(0))); - Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.References)], context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].BaseNode); - Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].Target, context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].Target.BaseNode); - Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target, context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].Target.IndexedTarget(new NodeIndex(0)).BaseNode); - } + Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.References)], context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].BaseNode); + Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].Target, context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].Target.BaseNode); + Assert.Equal(context.BaseGraph.RootNode[nameof(Types.MyAssetWithRef2.NonReference)].Target, context.DerivedGraph.RootNode[nameof(Types.MyAssetWithRef2.References)].Target.IndexedTarget(new NodeIndex(0)).BaseNode); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestObjectReferenceSerialization.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestObjectReferenceSerialization.cs index e4903a63a4..1b8bb17028 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestObjectReferenceSerialization.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestObjectReferenceSerialization.cs @@ -1,6 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.IO; + using Xunit; using Stride.Core.Assets.Quantum.Tests.Helpers; using Stride.Core.Assets.Tests.Helpers; @@ -8,39 +8,38 @@ // ReSharper disable ConvertToLambdaExpression -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +using SerializationHelper = Helpers.SerializationHelper; + +public class TestObjectReferenceSerialization { - using SerializationHelper = Helpers.SerializationHelper; + private const string SimpleReferenceYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + MyObject1: + Value: MyInstance + Id: 00000002-0002-0000-0200-000002000000 + MyObject2: ref!! 00000002-0002-0000-0200-000002000000 + MyObjects: {} + MyNonIdObjects: [] - public class TestObjectReferenceSerialization + """; + + [Fact] + public void TestSimpleReference() { - private const string SimpleReferenceYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -MyObject1: - Value: MyInstance - Id: 00000002-0002-0000-0200-000002000000 -MyObject2: ref!! 00000002-0002-0000-0200-000002000000 -MyObjects: {} -MyNonIdObjects: [] -"; - - [Fact] - public void TestSimpleReference() - { - Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, index) => - { - return (targetNode as IMemberNode)?.Name == nameof(Types.MyAssetWithRef.MyObject2); - }; - var obj = new Types.MyReferenceable { Id = GuidGenerator.Get(2), Value = "MyInstance" }; - var asset = new Types.MyAssetWithRef { MyObject1 = obj, MyObject2 = obj }; - var context = new AssetTestContainer(asset); - context.BuildGraph(); - SerializationHelper.SerializeAndCompare(context.AssetItem, context.Graph, SimpleReferenceYaml, false); - - context = AssetTestContainer.LoadFromYaml(SimpleReferenceYaml); - Assert.Equal(context.Asset.MyObject1, context.Asset.MyObject2); - Assert.Equal(GuidGenerator.Get(2), context.Asset.MyObject1.Id); - } + Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, _) => targetNode is IMemberNode { Name: nameof(Types.MyAssetWithRef.MyObject2) }; + var obj = new Types.MyReferenceable { Id = GuidGenerator.Get(2), Value = "MyInstance" }; + var asset = new Types.MyAssetWithRef { MyObject1 = obj, MyObject2 = obj }; + var context = new AssetTestContainer(asset); + context.BuildGraph(); + SerializationHelper.SerializeAndCompare(context.AssetItem, context.Graph, SimpleReferenceYaml, false); + + context = AssetTestContainer.LoadFromYaml(SimpleReferenceYaml); + Assert.Equal(context.Asset.MyObject1, context.Asset.MyObject2); + Assert.Equal(GuidGenerator.Get(2), context.Asset.MyObject1.Id); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestOverrideSerialization.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestOverrideSerialization.cs index b87152aed3..66db20a701 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestOverrideSerialization.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestOverrideSerialization.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.IO; -using System.Linq; + using Xunit; using Stride.Core.Assets.Quantum.Internal; using Stride.Core.Assets.Quantum.Tests.Helpers; @@ -11,840 +10,900 @@ using Stride.Core.Yaml; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +using SerializationHelper = Helpers.SerializationHelper; + +public class TestOverrideSerialization { - using SerializationHelper = Helpers.SerializationHelper; + /* test TODO: + * Non-abstract class (test result recursively) : simple prop + in collection + * Abstract (interface) override with different type + * class prop set to null + */ + + private const string SimplePropertyUpdateBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset1,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + MyString: MyBaseString + + """; + private const string SimplePropertyUpdateDerivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset1,Stride.Core.Assets.Quantum.Tests + Id: 00000002-0002-0000-0200-000002000000 + Tags: [] + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyString*: MyDerivedString + + """; + private const string SimplePropertyWithOverrideToDefaultValueBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset10,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + MyBool: false + + """; + private const string SimplePropertyWithOverrideToDefaultValueDerivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset10,Stride.Core.Assets.Quantum.Tests + Id: 00000002-0002-0000-0200-000002000000 + Tags: [] + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyBool*: true + + """; + private const string SimpleCollectionUpdateBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000: String1 + 14000000140000001400000014000000: MyBaseString + + """; + private const string SimpleCollectionUpdateDerivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 00000002-0002-0000-0200-000002000000 + Tags: [] + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000*: MyDerivedString + 14000000140000001400000014000000: MyBaseString + + """; + private const string SimpleDictionaryUpdateBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 14000000140000001400000014000000~Key2: MyBaseString + + """; + private const string SimpleDictionaryUpdateDerivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 00000002-0002-0000-0200-000002000000 + Tags: [] + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyDictionary: + 0a0000000a0000000a0000000a000000*~Key1: MyDerivedString + 14000000140000001400000014000000~Key2: MyBaseString + + """; + private const string CollectionInStructBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + Struct: + MyStrings: + 0a0000000a0000000a0000000a000000: String1 + 14000000140000001400000014000000: MyBaseString + MyStrings: {} + + """; + private const string CollectionInStructDerivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 00000002-0002-0000-0200-000002000000 + Tags: [] + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + Struct: + MyStrings: + 0a0000000a0000000a0000000a000000*: MyDerivedString + 14000000140000001400000014000000: MyBaseString + MyStrings: {} + + """; + private const string SimpleCollectionAddBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000: String1 + 14000000140000001400000014000000: String2 + {0}: String4 + + """; + private const string SimpleCollectionAddDerivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 00000002-0002-0000-0200-000002000000 + Tags: [] + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000: String1 + 14000000140000001400000014000000: String2 + {0}: String4 + {1}*: String3 + + """; + private const string SimpleDictionaryAddBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 14000000140000001400000014000000~Key2: String2 + {0}~Key4: String4 + + """; + private const string SimpleDictionaryAddDerivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 00000002-0002-0000-0200-000002000000 + Tags: [] + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 14000000140000001400000014000000~Key2: String2 + {1}*~Key3: String3 + {0}~Key4: String4 + + """; + private const string ObjectCollectionUpdateBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset4,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + MyObjects: + 0a0000000a0000000a0000000a000000: + Value: String1 + 14000000140000001400000014000000: + Value: MyBaseString + + """; + private const string ObjectCollectionUpdateDerivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset4,Stride.Core.Assets.Quantum.Tests + Id: 00000002-0002-0000-0200-000002000000 + Tags: [] + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyObjects: + 0a0000000a0000000a0000000a000000*: + Value: MyDerivedString + 14000000140000001400000014000000: + Value: MyBaseString + + """; + private const string ObjectCollectionAddBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset4,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + MyObjects: + 0a0000000a0000000a0000000a000000: + Value: String1 + 14000000140000001400000014000000: + Value: String2 + {0}: + Value: String4 + + """; + private const string ObjectCollectionAddDerivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset4,Stride.Core.Assets.Quantum.Tests + Id: 00000002-0002-0000-0200-000002000000 + Tags: [] + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyObjects: + 0a0000000a0000000a0000000a000000: + Value: String1 + 14000000140000001400000014000000: + Value: String2 + {0}: + Value: String4 + {1}*: + Value: String3 + + """; + private const string ObjectCollectionPropertyUpdateBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset4,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + MyObjects: + 0a0000000a0000000a0000000a000000: + Value: String1 + 14000000140000001400000014000000: + Value: MyBaseString + + """; + private const string ObjectCollectionPropertyUpdateDerivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset4,Stride.Core.Assets.Quantum.Tests + Id: 00000002-0002-0000-0200-000002000000 + Tags: [] + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyObjects: + 0a0000000a0000000a0000000a000000: + Value*: MyDerivedString + 14000000140000001400000014000000: + Value: MyBaseString + + """; + private const string NonIdentifiableObjectCollectionPropertyUpdateBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset8,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + Tags: [] + MyObjects: + - Value: String1 + - Value: MyBaseString + + """; + private const string NonIdentifiableObjectCollectionPropertyUpdateDerivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset8,Stride.Core.Assets.Quantum.Tests + Id: 00000002-0002-0000-0200-000002000000 + Tags: [] + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyObjects: + - Value*: MyDerivedString + - Value: MyBaseString + + """; + + [Fact] + public void TestSimplePropertySerialization() + { + var asset = new Types.MyAsset1 { MyString = "String" }; + var context = DeriveAssetTest.DeriveAsset(asset); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset1.MyString)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset1.MyString)]; + + basePropertyNode.Update("MyBaseString"); + derivedPropertyNode.Update("MyDerivedString"); + SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, SimplePropertyUpdateBaseYaml, false); + SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, SimplePropertyUpdateDerivedYaml, true); + } - public class TestOverrideSerialization + [Fact] + public void TestSimplePropertyDeserialization() { - /* test TODO: - * Non-abstract class (test result recursively) : simple prop + in collection - * Abstract (interface) override with different type - * class prop set to null - */ - - private const string SimplePropertyUpdateBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset1,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -MyString: MyBaseString -"; - private const string SimplePropertyUpdateDerivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset1,Stride.Core.Assets.Quantum.Tests -Id: 00000002-0002-0000-0200-000002000000 -Tags: [] -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyString*: MyDerivedString -"; - private const string SimplePropertyWithOverrideToDefaultValueBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset10,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -MyBool: false -"; - private const string SimplePropertyWithOverrideToDefaultValueDerivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset10,Stride.Core.Assets.Quantum.Tests -Id: 00000002-0002-0000-0200-000002000000 -Tags: [] -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyBool*: true -"; - private const string SimpleCollectionUpdateBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000: String1 - 14000000140000001400000014000000: MyBaseString -"; - private const string SimpleCollectionUpdateDerivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 00000002-0002-0000-0200-000002000000 -Tags: [] -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000*: MyDerivedString - 14000000140000001400000014000000: MyBaseString -"; - private const string SimpleDictionaryUpdateBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 14000000140000001400000014000000~Key2: MyBaseString -"; - private const string SimpleDictionaryUpdateDerivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 00000002-0002-0000-0200-000002000000 -Tags: [] -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyDictionary: - 0a0000000a0000000a0000000a000000*~Key1: MyDerivedString - 14000000140000001400000014000000~Key2: MyBaseString -"; - private const string CollectionInStructBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -Struct: - MyStrings: - 0a0000000a0000000a0000000a000000: String1 - 14000000140000001400000014000000: MyBaseString -MyStrings: {} -"; - private const string CollectionInStructDerivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 00000002-0002-0000-0200-000002000000 -Tags: [] -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -Struct: - MyStrings: - 0a0000000a0000000a0000000a000000*: MyDerivedString - 14000000140000001400000014000000: MyBaseString -MyStrings: {} -"; - private const string SimpleCollectionAddBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000: String1 - 14000000140000001400000014000000: String2 - {0}: String4 -"; - private const string SimpleCollectionAddDerivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 00000002-0002-0000-0200-000002000000 -Tags: [] -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000: String1 - 14000000140000001400000014000000: String2 - {0}: String4 - {1}*: String3 -"; - private const string SimpleDictionaryAddBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 14000000140000001400000014000000~Key2: String2 - {0}~Key4: String4 -"; - private const string SimpleDictionaryAddDerivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 00000002-0002-0000-0200-000002000000 -Tags: [] -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 14000000140000001400000014000000~Key2: String2 - {1}*~Key3: String3 - {0}~Key4: String4 -"; - private const string ObjectCollectionUpdateBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset4,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -MyObjects: - 0a0000000a0000000a0000000a000000: - Value: String1 - 14000000140000001400000014000000: - Value: MyBaseString -"; - private const string ObjectCollectionUpdateDerivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset4,Stride.Core.Assets.Quantum.Tests -Id: 00000002-0002-0000-0200-000002000000 -Tags: [] -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyObjects: - 0a0000000a0000000a0000000a000000*: - Value: MyDerivedString - 14000000140000001400000014000000: - Value: MyBaseString -"; - private const string ObjectCollectionAddBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset4,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -MyObjects: - 0a0000000a0000000a0000000a000000: - Value: String1 - 14000000140000001400000014000000: - Value: String2 - {0}: - Value: String4 -"; - private const string ObjectCollectionAddDerivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset4,Stride.Core.Assets.Quantum.Tests -Id: 00000002-0002-0000-0200-000002000000 -Tags: [] -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyObjects: - 0a0000000a0000000a0000000a000000: - Value: String1 - 14000000140000001400000014000000: - Value: String2 - {0}: - Value: String4 - {1}*: - Value: String3 -"; - private const string ObjectCollectionPropertyUpdateBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset4,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -MyObjects: - 0a0000000a0000000a0000000a000000: - Value: String1 - 14000000140000001400000014000000: - Value: MyBaseString -"; - private const string ObjectCollectionPropertyUpdateDerivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset4,Stride.Core.Assets.Quantum.Tests -Id: 00000002-0002-0000-0200-000002000000 -Tags: [] -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyObjects: - 0a0000000a0000000a0000000a000000: - Value*: MyDerivedString - 14000000140000001400000014000000: - Value: MyBaseString -"; - private const string NonIdentifiableObjectCollectionPropertyUpdateBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset8,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -Tags: [] -MyObjects: - - Value: String1 - - Value: MyBaseString -"; - private const string NonIdentifiableObjectCollectionPropertyUpdateDerivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset8,Stride.Core.Assets.Quantum.Tests -Id: 00000002-0002-0000-0200-000002000000 -Tags: [] -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyObjects: - - Value*: MyDerivedString - - Value: MyBaseString -"; + var context = DeriveAssetTest.LoadFromYaml(SimplePropertyUpdateBaseYaml, SimplePropertyUpdateDerivedYaml); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset1.MyString)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset1.MyString)]; + + Assert.Equal("MyBaseString", basePropertyNode.Retrieve()); + Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve()); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.GetContentOverride()); + } + + [Fact] + public void TestSimplePropertyWithOverrideToDefaultValueSerialization() + { + var asset = new Types.MyAsset10 { MyBool = false }; + var context = DeriveAssetTest.DeriveAsset(asset); + var derivedPropertyNode = (AssetMemberNode)context.DerivedGraph.RootNode[nameof(Types.MyAsset10.MyBool)]; + + derivedPropertyNode.Update(true); + SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, SimplePropertyWithOverrideToDefaultValueBaseYaml, false); + SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, SimplePropertyWithOverrideToDefaultValueDerivedYaml, true); + } + + [Fact] + public void TestSimplePropertyWithOverrideToDefaultValueDeserialization() + { + var context = DeriveAssetTest.LoadFromYaml(SimplePropertyWithOverrideToDefaultValueBaseYaml, SimplePropertyWithOverrideToDefaultValueDerivedYaml); + var basePropertyNode = (AssetMemberNode)context.BaseGraph.RootNode[nameof(Types.MyAsset10.MyBool)]; + var derivedPropertyNode = (AssetMemberNode)context.DerivedGraph.RootNode[nameof(Types.MyAsset10.MyBool)]; + + Assert.Equal(false, basePropertyNode.Retrieve()); + Assert.Equal(true, derivedPropertyNode.Retrieve()); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.GetContentOverride()); + } + + [Fact] + public void TestSimpleCollectionUpdateSerialization() + { + var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2" } }; + var ids = CollectionItemIdHelper.GetCollectionItemIds(asset.MyStrings); + ids.Add(0, IdentifierGenerator.Get(10)); + ids.Add(1, IdentifierGenerator.Get(20)); + var context = DeriveAssetTest.DeriveAsset(asset); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + + basePropertyNode.Target.Update("MyBaseString", new NodeIndex(1)); + derivedPropertyNode.Target.Update("MyDerivedString", new NodeIndex(0)); + SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, SimpleCollectionUpdateBaseYaml, false); + SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, SimpleCollectionUpdateDerivedYaml, true); + } + + [Fact] + public void TestSimpleCollectionUpdateDeserialization() + { + var context = DeriveAssetTest.LoadFromYaml(SimpleCollectionUpdateBaseYaml, SimpleCollectionUpdateDerivedYaml); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + } + + [Fact] + public void TestSimpleDictionaryUpdateSerialization() + { + var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" } } }; + var ids = CollectionItemIdHelper.GetCollectionItemIds(asset.MyDictionary); + ids.Add("Key1", IdentifierGenerator.Get(10)); + ids.Add("Key2", IdentifierGenerator.Get(20)); + var context = DeriveAssetTest.DeriveAsset(asset); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + + basePropertyNode.Target.Update("MyBaseString", new NodeIndex("Key2")); + derivedPropertyNode.Target.Update("MyDerivedString", new NodeIndex("Key1")); + SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, SimpleDictionaryUpdateBaseYaml, false); + SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, SimpleDictionaryUpdateDerivedYaml, true); + + context = DeriveAssetTest.LoadFromYaml(SimpleDictionaryUpdateBaseYaml, SimpleDictionaryUpdateDerivedYaml); + basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + } + + [Fact] + public void TestSimpleDictionaryDeserialization() + { + var context = DeriveAssetTest.LoadFromYaml(SimpleDictionaryUpdateBaseYaml, SimpleDictionaryUpdateDerivedYaml); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + } - [Fact] - public void TestSimplePropertySerialization() - { - var asset = new Types.MyAsset1 { MyString = "String" }; - var context = DeriveAssetTest.DeriveAsset(asset); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset1.MyString)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset1.MyString)]; - - basePropertyNode.Update("MyBaseString"); - derivedPropertyNode.Update("MyDerivedString"); - SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, SimplePropertyUpdateBaseYaml, false); - SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, SimplePropertyUpdateDerivedYaml, true); - } - - [Fact] - public void TestSimplePropertyDeserialization() - { - var context = DeriveAssetTest.LoadFromYaml(SimplePropertyUpdateBaseYaml, SimplePropertyUpdateDerivedYaml); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset1.MyString)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset1.MyString)]; - - Assert.Equal("MyBaseString", basePropertyNode.Retrieve()); - Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve()); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.GetContentOverride()); - } - - [Fact] - public void TestSimplePropertyWithOverrideToDefaultValueSerialization() - { - var asset = new Types.MyAsset10 { MyBool = false }; - var context = DeriveAssetTest.DeriveAsset(asset); - var derivedPropertyNode = (AssetMemberNode)context.DerivedGraph.RootNode[nameof(Types.MyAsset10.MyBool)]; - - derivedPropertyNode.Update(true); - SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, SimplePropertyWithOverrideToDefaultValueBaseYaml, false); - SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, SimplePropertyWithOverrideToDefaultValueDerivedYaml, true); - } - - [Fact] - public void TestSimplePropertyWithOverrideToDefaultValueDeserialization() - { - var context = DeriveAssetTest.LoadFromYaml(SimplePropertyWithOverrideToDefaultValueBaseYaml, SimplePropertyWithOverrideToDefaultValueDerivedYaml); - var basePropertyNode = (AssetMemberNode)context.BaseGraph.RootNode[nameof(Types.MyAsset10.MyBool)]; - var derivedPropertyNode = (AssetMemberNode)context.DerivedGraph.RootNode[nameof(Types.MyAsset10.MyBool)]; - - Assert.Equal(false, basePropertyNode.Retrieve()); - Assert.Equal(true, derivedPropertyNode.Retrieve()); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.GetContentOverride()); - } - - [Fact] - public void TestSimpleCollectionUpdateSerialization() - { - var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2" } }; - var ids = CollectionItemIdHelper.GetCollectionItemIds(asset.MyStrings); - ids.Add(0, IdentifierGenerator.Get(10)); - ids.Add(1, IdentifierGenerator.Get(20)); - var context = DeriveAssetTest.DeriveAsset(asset); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - - basePropertyNode.Target.Update("MyBaseString", new NodeIndex(1)); - derivedPropertyNode.Target.Update("MyDerivedString", new NodeIndex(0)); - SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, SimpleCollectionUpdateBaseYaml, false); - SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, SimpleCollectionUpdateDerivedYaml, true); - } - - [Fact] - public void TestSimpleCollectionUpdateDeserialization() - { - var context = DeriveAssetTest.LoadFromYaml(SimpleCollectionUpdateBaseYaml, SimpleCollectionUpdateDerivedYaml); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - } - - [Fact] - public void TestSimpleDictionaryUpdateSerialization() - { - var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" } } }; - var ids = CollectionItemIdHelper.GetCollectionItemIds(asset.MyDictionary); - ids.Add("Key1", IdentifierGenerator.Get(10)); - ids.Add("Key2", IdentifierGenerator.Get(20)); - var context = DeriveAssetTest.DeriveAsset(asset); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - - basePropertyNode.Target.Update("MyBaseString", new NodeIndex("Key2")); - derivedPropertyNode.Target.Update("MyDerivedString", new NodeIndex("Key1")); - SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, SimpleDictionaryUpdateBaseYaml, false); - SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, SimpleDictionaryUpdateDerivedYaml, true); - - context = DeriveAssetTest.LoadFromYaml(SimpleDictionaryUpdateBaseYaml, SimpleDictionaryUpdateDerivedYaml); - basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - } - - [Fact] - public void TestSimpleDictionaryDeserialization() - { - var context = DeriveAssetTest.LoadFromYaml(SimpleDictionaryUpdateBaseYaml, SimpleDictionaryUpdateDerivedYaml); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - } - - [Fact] - public void TestCollectionInStructUpdateSerialization() - { - var asset = new Types.MyAsset2(); - asset.Struct.MyStrings.Add("String1"); - asset.Struct.MyStrings.Add("String2"); - var ids = CollectionItemIdHelper.GetCollectionItemIds(asset.Struct.MyStrings); - ids.Add(0, IdentifierGenerator.Get(10)); - ids.Add(1, IdentifierGenerator.Get(20)); - var context = DeriveAssetTest.DeriveAsset(asset); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.Struct)].Target[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.Struct)].Target[nameof(Types.MyAsset2.MyStrings)]; - - basePropertyNode.Target.Update("MyBaseString", new NodeIndex(1)); - derivedPropertyNode.Target.Update("MyDerivedString", new NodeIndex(0)); - SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, CollectionInStructBaseYaml, false); - SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, CollectionInStructDerivedYaml, true); - } - - [Fact] - public void TestCollectionInStructUpdateDeserialization() - { - var context = DeriveAssetTest.LoadFromYaml(CollectionInStructBaseYaml, CollectionInStructDerivedYaml); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.Struct)].Target[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.Struct)].Target[nameof(Types.MyAsset2.MyStrings)]; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.Struct.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.Struct.MyStrings); - - Assert.Equal(2, context.BaseAsset.Struct.MyStrings.Count); - Assert.Equal(2, context.DerivedAsset.Struct.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - } - - [Fact] - public void TestSimpleCollectionAddSerialization() - { - var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2" } }; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyStrings); - baseIds.Add(0, IdentifierGenerator.Get(10)); - baseIds.Add(1, IdentifierGenerator.Get(20)); - var context = DeriveAssetTest.DeriveAsset(asset); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - - derivedPropertyNode.Target.Add("String3"); - basePropertyNode.Target.Add("String4"); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - var expectedBaseYaml = string.Format(SimpleCollectionAddBaseYaml.Replace("{}", "{{}}"), baseIds[2]); - var expectedDerivedYaml = string.Format(SimpleCollectionAddDerivedYaml.Replace("{}", "{{}}"), baseIds[2], derivedIds[3]); - SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, expectedBaseYaml, false); - SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, expectedDerivedYaml, true); - } - - [Fact] - public void TestSimpleCollectionAddDeserialization() - { - var expectedBaseYaml = string.Format(SimpleCollectionAddBaseYaml.Replace("{}", "{{}}"), IdentifierGenerator.Get(30)); - var expectedDerivedYaml = string.Format(SimpleCollectionAddDerivedYaml.Replace("{}", "{{}}"), IdentifierGenerator.Get(30), IdentifierGenerator.Get(40)); - var context = DeriveAssetTest.LoadFromYaml(expectedBaseYaml, expectedDerivedYaml); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - - Assert.Equal(3, context.BaseAsset.MyStrings.Count); - Assert.Equal(4, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(2))); - Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex(3))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(3))); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(4, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - Assert.Equal(baseIds[2], derivedIds[2]); - } - - [Fact] - public void TestSimpleDictionaryAddSerialization() - { - var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" } } }; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyDictionary); - baseIds.Add("Key1", IdentifierGenerator.Get(10)); - baseIds.Add("Key2", IdentifierGenerator.Get(20)); - var context = DeriveAssetTest.DeriveAsset(asset); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - - // Update derived and check - derivedPropertyNode.Target.Add("String3", new NodeIndex("Key3")); - basePropertyNode.Target.Add("String4", new NodeIndex("Key4")); - - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - var expectedBaseYaml = string.Format(SimpleDictionaryAddBaseYaml.Replace("{}", "{{}}"), baseIds["Key4"]); - var expectedDerivedYaml = string.Format(SimpleDictionaryAddDerivedYaml.Replace("{}", "{{}}"), baseIds["Key4"], derivedIds["Key3"]); - SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, expectedBaseYaml, false); - SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, expectedDerivedYaml, true); - } - - [Fact] - public void TestSimpleDictionaryAddDeserialization() - { - var expectedBaseYaml = string.Format(SimpleDictionaryAddBaseYaml.Replace("{}", "{{}}"), IdentifierGenerator.Get(30)); - var expectedDerivedYaml = string.Format(SimpleDictionaryAddDerivedYaml.Replace("{}", "{{}}"), IdentifierGenerator.Get(30), IdentifierGenerator.Get(40)); - var context = DeriveAssetTest.LoadFromYaml(expectedBaseYaml, expectedDerivedYaml); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - - Assert.Equal(3, context.BaseAsset.MyDictionary.Count); - Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); - Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); - Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); - Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(4, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); - Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); - Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); - Assert.Equal(3, context.BaseAsset.MyDictionary.Count); - Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); - } - - [Fact] - public void TestObjectCollectionUpdateSerialization() - { - var asset = new Types.MyAsset4 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyObjects); - baseIds.Add(0, IdentifierGenerator.Get(10)); - baseIds.Add(1, IdentifierGenerator.Get(20)); - var context = DeriveAssetTest.DeriveAsset(asset); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - - basePropertyNode.Target.Update(new Types.SomeObject { Value = "MyBaseString" }, new NodeIndex(1)); - derivedPropertyNode.Target.Update(new Types.SomeObject { Value = "MyDerivedString" }, new NodeIndex(0)); - SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, ObjectCollectionUpdateBaseYaml, false); - SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, ObjectCollectionUpdateDerivedYaml, true); - } - - [Fact] - public void TestObjectCollectionUpdateDeserialization() - { - var context = DeriveAssetTest.LoadFromYaml(ObjectCollectionUpdateBaseYaml, ObjectCollectionUpdateDerivedYaml); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyObjects); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyObjects); - - Assert.Equal(2, context.BaseAsset.MyObjects.Count); - Assert.Equal(2, context.DerivedAsset.MyObjects.Count); - Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("MyDerivedString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - } - - [Fact] - public void TestObjectCollectionAddSerialization() - { - var asset = new Types.MyAsset4 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyObjects); - baseIds.Add(0, IdentifierGenerator.Get(10)); - baseIds.Add(1, IdentifierGenerator.Get(20)); - var context = DeriveAssetTest.DeriveAsset(asset); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyObjects); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - - derivedPropertyNode.Target.Add(new Types.SomeObject { Value = "String3" }); - basePropertyNode.Target.Add(new Types.SomeObject { Value = "String4" }); - var expectedBaseYaml = string.Format(ObjectCollectionAddBaseYaml.Replace("{}", "{{}}"), baseIds[2]); - var expectedDerivedYaml = string.Format(ObjectCollectionAddDerivedYaml.Replace("{}", "{{}}"), baseIds[2], derivedIds[3]); - SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, expectedBaseYaml, false); - SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, expectedDerivedYaml, true); - } - - [Fact] - public void TestObjectCollectionAddDeserialization() - { - var expectedBaseYaml = string.Format(ObjectCollectionAddBaseYaml.Replace("{}", "{{}}"), IdentifierGenerator.Get(30)); - var expectedDerivedYaml = string.Format(ObjectCollectionAddDerivedYaml.Replace("{}", "{{}}"), IdentifierGenerator.Get(30), IdentifierGenerator.Get(40)); - var context = DeriveAssetTest.LoadFromYaml(expectedBaseYaml, expectedDerivedYaml); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyObjects); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyObjects); - - Assert.Equal(3, context.BaseAsset.MyObjects.Count); - Assert.Equal(4, context.DerivedAsset.MyObjects.Count); - Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String4", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(2))).Value); - Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("String4", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(2))).Value); - Assert.Equal("String3", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(3))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); - Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(3))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(3)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(4, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - Assert.Equal(baseIds[2], derivedIds[2]); - } - - [Fact] - public void TestObjectCollectionPropertyUpdateSerialization() - { - var asset = new Types.MyAsset4 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyObjects); - baseIds.Add(0, IdentifierGenerator.Get(10)); - baseIds.Add(1, IdentifierGenerator.Get(20)); - var context = DeriveAssetTest.DeriveAsset(asset); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - - basePropertyNode.Target.IndexedTarget(new NodeIndex(1))[nameof(Types.SomeObject.Value)].Update("MyBaseString"); - derivedPropertyNode.Target.IndexedTarget(new NodeIndex(0))[nameof(Types.SomeObject.Value)].Update("MyDerivedString"); - SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, ObjectCollectionPropertyUpdateBaseYaml, false); - SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, ObjectCollectionPropertyUpdateDerivedYaml, true); - } - - [Fact] - public void TestObjectCollectionPropertyUpdateDeserialization() - { - var context = DeriveAssetTest.LoadFromYaml(ObjectCollectionPropertyUpdateBaseYaml, ObjectCollectionPropertyUpdateDerivedYaml); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyObjects); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyObjects); - - Assert.Equal(2, context.BaseAsset.MyObjects.Count); - Assert.Equal(2, context.DerivedAsset.MyObjects.Count); - Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("MyDerivedString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.New, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.NotSame(baseIds, derivedIds); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(baseIds[0], derivedIds[0]); - Assert.Equal(baseIds[1], derivedIds[1]); - } - - [Fact] - public void TestNonIdentifiableObjectCollectionUpdateSerialization() - { - var asset = new Types.MyAsset8 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset8.MyObjects)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset8.MyObjects)]; - // Manually link base of non-identifiable items - this simulates a scenario similar to prefabs - context.DerivedGraph.RegisterCustomBaseLink(derivedPropertyNode.Target.IndexedTarget(new NodeIndex(0)), basePropertyNode.Target.IndexedTarget(new NodeIndex(0))); - context.DerivedGraph.RegisterCustomBaseLink(derivedPropertyNode.Target.IndexedTarget(new NodeIndex(1)), basePropertyNode.Target.IndexedTarget(new NodeIndex(1))); - context.DerivedGraph.RefreshBase(); - - basePropertyNode.Target.IndexedTarget(new NodeIndex(1))[nameof(Types.SomeObject.Value)].Update("MyBaseString"); - derivedPropertyNode.Target.IndexedTarget(new NodeIndex(0))[nameof(Types.SomeObject.Value)].Update("MyDerivedString"); - SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, NonIdentifiableObjectCollectionPropertyUpdateBaseYaml, false); - SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, NonIdentifiableObjectCollectionPropertyUpdateDerivedYaml, true); - } - - [Fact] - public void TestNonIdentifiableObjectCollectionUpdateDeserialization() - { - var context = DeriveAssetTest.LoadFromYaml(NonIdentifiableObjectCollectionPropertyUpdateBaseYaml, NonIdentifiableObjectCollectionPropertyUpdateDerivedYaml); - var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset8.MyObjects)]; - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset8.MyObjects)]; - // Manually link base of non-identifiable items - this simulates a scenario similar to prefabs - context.DerivedGraph.RegisterCustomBaseLink(derivedPropertyNode.Target.IndexedTarget(new NodeIndex(0)), basePropertyNode.Target.IndexedTarget(new NodeIndex(0))); - context.DerivedGraph.RegisterCustomBaseLink(derivedPropertyNode.Target.IndexedTarget(new NodeIndex(1)), basePropertyNode.Target.IndexedTarget(new NodeIndex(1))); - context.DerivedGraph.RefreshBase(); - - Assert.Equal(2, context.BaseAsset.MyObjects.Count); - Assert.Equal(2, context.DerivedAsset.MyObjects.Count); - Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal("MyDerivedString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); - Assert.Equal("MyBaseString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); - Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); - Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); - Assert.Equal(OverrideType.New, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); - } - - [Fact] - public void TestGenerateOverridesForSerializationOfObjectMember() - { - const string expectedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+SomeObject,Stride.Core.Assets.Quantum.Tests + [Fact] + public void TestCollectionInStructUpdateSerialization() + { + var asset = new Types.MyAsset2(); + asset.Struct.MyStrings.Add("String1"); + asset.Struct.MyStrings.Add("String2"); + var ids = CollectionItemIdHelper.GetCollectionItemIds(asset.Struct.MyStrings); + ids.Add(0, IdentifierGenerator.Get(10)); + ids.Add(1, IdentifierGenerator.Get(20)); + var context = DeriveAssetTest.DeriveAsset(asset); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.Struct)].Target[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.Struct)].Target[nameof(Types.MyAsset2.MyStrings)]; + + basePropertyNode.Target.Update("MyBaseString", new NodeIndex(1)); + derivedPropertyNode.Target.Update("MyDerivedString", new NodeIndex(0)); + SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, CollectionInStructBaseYaml, false); + SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, CollectionInStructDerivedYaml, true); + } + + [Fact] + public void TestCollectionInStructUpdateDeserialization() + { + var context = DeriveAssetTest.LoadFromYaml(CollectionInStructBaseYaml, CollectionInStructDerivedYaml); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.Struct)].Target[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.Struct)].Target[nameof(Types.MyAsset2.MyStrings)]; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.Struct.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.Struct.MyStrings); + + Assert.Equal(2, context.BaseAsset.Struct.MyStrings.Count); + Assert.Equal(2, context.DerivedAsset.Struct.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("MyBaseString", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("MyDerivedString", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("MyBaseString", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + } + + [Fact] + public void TestSimpleCollectionAddSerialization() + { + var asset = new Types.MyAsset2 { MyStrings = { "String1", "String2" } }; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyStrings); + baseIds.Add(0, IdentifierGenerator.Get(10)); + baseIds.Add(1, IdentifierGenerator.Get(20)); + var context = DeriveAssetTest.DeriveAsset(asset); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + + derivedPropertyNode.Target.Add("String3"); + basePropertyNode.Target.Add("String4"); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + var expectedBaseYaml = string.Format(SimpleCollectionAddBaseYaml.Replace("{}", "{{}}"), baseIds[2]); + var expectedDerivedYaml = string.Format(SimpleCollectionAddDerivedYaml.Replace("{}", "{{}}"), baseIds[2], derivedIds[3]); + SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, expectedBaseYaml, false); + SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, expectedDerivedYaml, true); + } + + [Fact] + public void TestSimpleCollectionAddDeserialization() + { + var expectedBaseYaml = string.Format(SimpleCollectionAddBaseYaml.Replace("{}", "{{}}"), IdentifierGenerator.Get(30)); + var expectedDerivedYaml = string.Format(SimpleCollectionAddDerivedYaml.Replace("{}", "{{}}"), IdentifierGenerator.Get(30), IdentifierGenerator.Get(40)); + var context = DeriveAssetTest.LoadFromYaml(expectedBaseYaml, expectedDerivedYaml); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset2.MyStrings)]; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + + Assert.Equal(3, context.BaseAsset.MyStrings.Count); + Assert.Equal(4, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex(0))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex(1))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex(2))); + Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex(3))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(3))); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(4, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + Assert.Equal(baseIds[2], derivedIds[2]); + } + + [Fact] + public void TestSimpleDictionaryAddSerialization() + { + var asset = new Types.MyAsset3 { MyDictionary = { { "Key1", "String1" }, { "Key2", "String2" } } }; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyDictionary); + baseIds.Add("Key1", IdentifierGenerator.Get(10)); + baseIds.Add("Key2", IdentifierGenerator.Get(20)); + var context = DeriveAssetTest.DeriveAsset(asset); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + + // Update derived and check + derivedPropertyNode.Target.Add("String3", new NodeIndex("Key3")); + basePropertyNode.Target.Add("String4", new NodeIndex("Key4")); + + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + var expectedBaseYaml = string.Format(SimpleDictionaryAddBaseYaml.Replace("{}", "{{}}"), baseIds["Key4"]); + var expectedDerivedYaml = string.Format(SimpleDictionaryAddDerivedYaml.Replace("{}", "{{}}"), baseIds["Key4"], derivedIds["Key3"]); + SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, expectedBaseYaml, false); + SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, expectedDerivedYaml, true); + } + + [Fact] + public void TestSimpleDictionaryAddDeserialization() + { + var expectedBaseYaml = string.Format(SimpleDictionaryAddBaseYaml.Replace("{}", "{{}}"), IdentifierGenerator.Get(30)); + var expectedDerivedYaml = string.Format(SimpleDictionaryAddDerivedYaml.Replace("{}", "{{}}"), IdentifierGenerator.Get(30), IdentifierGenerator.Get(40)); + var context = DeriveAssetTest.LoadFromYaml(expectedBaseYaml, expectedDerivedYaml); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset3.MyDictionary)]; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + + Assert.Equal(3, context.BaseAsset.MyDictionary.Count); + Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", basePropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", basePropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String4", basePropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal("String1", derivedPropertyNode.Retrieve(new NodeIndex("Key1"))); + Assert.Equal("String2", derivedPropertyNode.Retrieve(new NodeIndex("Key2"))); + Assert.Equal("String3", derivedPropertyNode.Retrieve(new NodeIndex("Key3"))); + Assert.Equal("String4", derivedPropertyNode.Retrieve(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key1"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key2"))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key3"))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex("Key4"))); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(4, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds["Key1"], derivedIds["Key1"]); + Assert.Equal(baseIds["Key2"], derivedIds["Key2"]); + Assert.Equal(baseIds["Key4"], derivedIds["Key4"]); + Assert.Equal(3, context.BaseAsset.MyDictionary.Count); + Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); + } + + [Fact] + public void TestObjectCollectionUpdateSerialization() + { + var asset = new Types.MyAsset4 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyObjects); + baseIds.Add(0, IdentifierGenerator.Get(10)); + baseIds.Add(1, IdentifierGenerator.Get(20)); + var context = DeriveAssetTest.DeriveAsset(asset); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + + basePropertyNode.Target.Update(new Types.SomeObject { Value = "MyBaseString" }, new NodeIndex(1)); + derivedPropertyNode.Target.Update(new Types.SomeObject { Value = "MyDerivedString" }, new NodeIndex(0)); + SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, ObjectCollectionUpdateBaseYaml, false); + SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, ObjectCollectionUpdateDerivedYaml, true); + } + + [Fact] + public void TestObjectCollectionUpdateDeserialization() + { + var context = DeriveAssetTest.LoadFromYaml(ObjectCollectionUpdateBaseYaml, ObjectCollectionUpdateDerivedYaml); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyObjects); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyObjects); + + Assert.Equal(2, context.BaseAsset.MyObjects.Count); + Assert.Equal(2, context.DerivedAsset.MyObjects.Count); + Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("MyDerivedString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + } + + [Fact] + public void TestObjectCollectionAddSerialization() + { + var asset = new Types.MyAsset4 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyObjects); + baseIds.Add(0, IdentifierGenerator.Get(10)); + baseIds.Add(1, IdentifierGenerator.Get(20)); + var context = DeriveAssetTest.DeriveAsset(asset); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyObjects); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + + derivedPropertyNode.Target.Add(new Types.SomeObject { Value = "String3" }); + basePropertyNode.Target.Add(new Types.SomeObject { Value = "String4" }); + var expectedBaseYaml = string.Format(ObjectCollectionAddBaseYaml.Replace("{}", "{{}}"), baseIds[2]); + var expectedDerivedYaml = string.Format(ObjectCollectionAddDerivedYaml.Replace("{}", "{{}}"), baseIds[2], derivedIds[3]); + SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, expectedBaseYaml, false); + SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, expectedDerivedYaml, true); + } + + [Fact] + public void TestObjectCollectionAddDeserialization() + { + var expectedBaseYaml = string.Format(ObjectCollectionAddBaseYaml.Replace("{}", "{{}}"), IdentifierGenerator.Get(30)); + var expectedDerivedYaml = string.Format(ObjectCollectionAddDerivedYaml.Replace("{}", "{{}}"), IdentifierGenerator.Get(30), IdentifierGenerator.Get(40)); + var context = DeriveAssetTest.LoadFromYaml(expectedBaseYaml, expectedDerivedYaml); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyObjects); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyObjects); + + Assert.Equal(3, context.BaseAsset.MyObjects.Count); + Assert.Equal(4, context.DerivedAsset.MyObjects.Count); + Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String4", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(2))).Value); + Assert.Equal("String1", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("String2", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("String4", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(2))).Value); + Assert.Equal("String3", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(3))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(2))); + Assert.Equal(OverrideType.New, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(3))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(2)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(3)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(4, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + Assert.Equal(baseIds[2], derivedIds[2]); + } + + [Fact] + public void TestObjectCollectionPropertyUpdateSerialization() + { + var asset = new Types.MyAsset4 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(asset.MyObjects); + baseIds.Add(0, IdentifierGenerator.Get(10)); + baseIds.Add(1, IdentifierGenerator.Get(20)); + var context = DeriveAssetTest.DeriveAsset(asset); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + + basePropertyNode.Target.IndexedTarget(new NodeIndex(1))[nameof(Types.SomeObject.Value)].Update("MyBaseString"); + derivedPropertyNode.Target.IndexedTarget(new NodeIndex(0))[nameof(Types.SomeObject.Value)].Update("MyDerivedString"); + SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, ObjectCollectionPropertyUpdateBaseYaml, false); + SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, ObjectCollectionPropertyUpdateDerivedYaml, true); + } + + [Fact] + public void TestObjectCollectionPropertyUpdateDeserialization() + { + var context = DeriveAssetTest.LoadFromYaml(ObjectCollectionPropertyUpdateBaseYaml, ObjectCollectionPropertyUpdateDerivedYaml); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)]; + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyObjects); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyObjects); + + Assert.Equal(2, context.BaseAsset.MyObjects.Count); + Assert.Equal(2, context.DerivedAsset.MyObjects.Count); + Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("MyDerivedString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.New, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.NotSame(baseIds, derivedIds); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(baseIds[0], derivedIds[0]); + Assert.Equal(baseIds[1], derivedIds[1]); + } + + [Fact] + public void TestNonIdentifiableObjectCollectionUpdateSerialization() + { + var asset = new Types.MyAsset8 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset8.MyObjects)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset8.MyObjects)]; + // Manually link base of non-identifiable items - this simulates a scenario similar to prefabs + context.DerivedGraph.RegisterCustomBaseLink(derivedPropertyNode.Target.IndexedTarget(new NodeIndex(0)), basePropertyNode.Target.IndexedTarget(new NodeIndex(0))); + context.DerivedGraph.RegisterCustomBaseLink(derivedPropertyNode.Target.IndexedTarget(new NodeIndex(1)), basePropertyNode.Target.IndexedTarget(new NodeIndex(1))); + context.DerivedGraph.RefreshBase(); + + basePropertyNode.Target.IndexedTarget(new NodeIndex(1))[nameof(Types.SomeObject.Value)].Update("MyBaseString"); + derivedPropertyNode.Target.IndexedTarget(new NodeIndex(0))[nameof(Types.SomeObject.Value)].Update("MyDerivedString"); + SerializationHelper.SerializeAndCompare(context.BaseAssetItem, context.BaseGraph, NonIdentifiableObjectCollectionPropertyUpdateBaseYaml, false); + SerializationHelper.SerializeAndCompare(context.DerivedAssetItem, context.DerivedGraph, NonIdentifiableObjectCollectionPropertyUpdateDerivedYaml, true); + } + + [Fact] + public void TestNonIdentifiableObjectCollectionUpdateDeserialization() + { + var context = DeriveAssetTest.LoadFromYaml(NonIdentifiableObjectCollectionPropertyUpdateBaseYaml, NonIdentifiableObjectCollectionPropertyUpdateDerivedYaml); + var basePropertyNode = context.BaseGraph.RootNode[nameof(Types.MyAsset8.MyObjects)]; + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset8.MyObjects)]; + // Manually link base of non-identifiable items - this simulates a scenario similar to prefabs + context.DerivedGraph.RegisterCustomBaseLink(derivedPropertyNode.Target.IndexedTarget(new NodeIndex(0)), basePropertyNode.Target.IndexedTarget(new NodeIndex(0))); + context.DerivedGraph.RegisterCustomBaseLink(derivedPropertyNode.Target.IndexedTarget(new NodeIndex(1)), basePropertyNode.Target.IndexedTarget(new NodeIndex(1))); + context.DerivedGraph.RefreshBase(); + + Assert.Equal(2, context.BaseAsset.MyObjects.Count); + Assert.Equal(2, context.DerivedAsset.MyObjects.Count); + Assert.Equal("String1", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.SomeObject)basePropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal("MyDerivedString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(0))).Value); + Assert.Equal("MyBaseString", ((Types.SomeObject)derivedPropertyNode.Retrieve(new NodeIndex(1))).Value); + Assert.Equal(OverrideType.Base, basePropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, basePropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)basePropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.GetContentOverride()); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(0))); + Assert.Equal(OverrideType.Base, derivedPropertyNode.Target.GetItemOverride(new NodeIndex(1))); + Assert.Equal(OverrideType.New, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(0)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + Assert.Equal(OverrideType.Base, ((AssetMemberNode)derivedPropertyNode.Target.ItemReferences[new NodeIndex(1)].TargetNode[nameof(Types.SomeObject.Value)]).GetContentOverride()); + } + + [Fact] + public void TestGenerateOverridesForSerializationOfObjectMember() + { + const string expectedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+SomeObject,Stride.Core.Assets.Quantum.Tests Value*: OverriddenString "; - var asset = new Types.MyAsset9 { MyObject = new Types.SomeObject { Value = "String1" } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset9.MyObject)]; - derivedPropertyNode.Target[nameof(Types.SomeObject.Value)].Update("OverriddenString"); - var expectedPath = new YamlAssetPath(); - expectedPath.PushMember(nameof(Types.SomeObject.Value)); - - var overrides = AssetPropertyGraph.GenerateOverridesForSerialization(derivedPropertyNode); - var overridesAsDictionary = overrides.ToDictionary(x => x.Key, x => x.Value, YamlAssetPathComparer.Default); - Assert.Single(overridesAsDictionary); - Assert.True(overridesAsDictionary.ContainsKey(expectedPath)); - Assert.Equal(OverrideType.New, overridesAsDictionary[expectedPath]); - - // We expect the same resulting path both from the member node and the target object node - overrides = AssetPropertyGraph.GenerateOverridesForSerialization(derivedPropertyNode.Target); - overridesAsDictionary = overrides.ToDictionary(x => x.Key, x => x.Value, YamlAssetPathComparer.Default); - Assert.Single(overridesAsDictionary); - Assert.True(overridesAsDictionary.ContainsKey(expectedPath)); - Assert.Equal(OverrideType.New, overridesAsDictionary[expectedPath]); - - // Test deserialization - SerializationHelper.SerializeAndCompare(context.DerivedAsset.MyObject, overrides, expectedYaml); - bool aliasOccurred; - AttachedYamlAssetMetadata metadata; - var instance = (Types.SomeObject)AssetFileSerializer.Default.Load(AssetTestContainer.ToStream(expectedYaml), null, null, true, out aliasOccurred, out metadata); - overrides = metadata.RetrieveMetadata(AssetObjectSerializerBackend.OverrideDictionaryKey); - Assert.NotNull(overrides); - overridesAsDictionary = overrides.ToDictionary(x => x.Key, x => x.Value, YamlAssetPathComparer.Default); - Assert.Equal("OverriddenString", instance.Value); - Assert.Single(overridesAsDictionary); - Assert.True(overridesAsDictionary.ContainsKey(expectedPath)); - Assert.Equal(OverrideType.New, overridesAsDictionary[expectedPath]); - } - - [Fact] - public void TestGenerateOverridesForSerializationOfCollectionItem() - { - const string expectedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+SomeObject,Stride.Core.Assets.Quantum.Tests + var asset = new Types.MyAsset9 { MyObject = new Types.SomeObject { Value = "String1" } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var derivedPropertyNode = context.DerivedGraph.RootNode[nameof(Types.MyAsset9.MyObject)]; + derivedPropertyNode.Target[nameof(Types.SomeObject.Value)].Update("OverriddenString"); + var expectedPath = new YamlAssetPath(); + expectedPath.PushMember(nameof(Types.SomeObject.Value)); + + var overrides = AssetPropertyGraph.GenerateOverridesForSerialization(derivedPropertyNode); + var overridesAsDictionary = overrides.ToDictionary(x => x.Key, x => x.Value, YamlAssetPathComparer.Default); + Assert.Single(overridesAsDictionary); + Assert.True(overridesAsDictionary.ContainsKey(expectedPath)); + Assert.Equal(OverrideType.New, overridesAsDictionary[expectedPath]); + + // We expect the same resulting path both from the member node and the target object node + overrides = AssetPropertyGraph.GenerateOverridesForSerialization(derivedPropertyNode.Target); + overridesAsDictionary = overrides.ToDictionary(x => x.Key, x => x.Value, YamlAssetPathComparer.Default); + Assert.Single(overridesAsDictionary); + Assert.True(overridesAsDictionary.ContainsKey(expectedPath)); + Assert.Equal(OverrideType.New, overridesAsDictionary[expectedPath]); + + // Test deserialization + SerializationHelper.SerializeAndCompare(context.DerivedAsset.MyObject, overrides, expectedYaml); + var instance = (Types.SomeObject)AssetFileSerializer.Default.Load(AssetTestContainer.ToStream(expectedYaml), null, null, true, out var aliasOccurred, out var metadata); + overrides = metadata.RetrieveMetadata(AssetObjectSerializerBackend.OverrideDictionaryKey); + Assert.NotNull(overrides); + overridesAsDictionary = overrides.ToDictionary(x => x.Key, x => x.Value, YamlAssetPathComparer.Default); + Assert.Equal("OverriddenString", instance.Value); + Assert.Single(overridesAsDictionary); + Assert.True(overridesAsDictionary.ContainsKey(expectedPath)); + Assert.Equal(OverrideType.New, overridesAsDictionary[expectedPath]); + } + + [Fact] + public void TestGenerateOverridesForSerializationOfCollectionItem() + { + const string expectedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+SomeObject,Stride.Core.Assets.Quantum.Tests Value*: OverriddenString "; - var asset = new Types.MyAsset4 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; - var context = DeriveAssetTest.DeriveAsset(asset); - var derivedPropertyNode = (AssetObjectNode)context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)].Target.IndexedTarget(new NodeIndex(1)); - derivedPropertyNode[nameof(Types.SomeObject.Value)].Update("OverriddenString"); - var expectedPath = new YamlAssetPath(); - expectedPath.PushMember(nameof(Types.SomeObject.Value)); - - var overrides = AssetPropertyGraph.GenerateOverridesForSerialization(derivedPropertyNode); - var overridesAsDictionary = overrides.ToDictionary(x => x.Key, x => x.Value, YamlAssetPathComparer.Default); - Assert.Single(overridesAsDictionary); - Assert.True(overridesAsDictionary.ContainsKey(expectedPath)); - Assert.Equal(OverrideType.New, overridesAsDictionary[expectedPath]); - - // Test deserialization - SerializationHelper.SerializeAndCompare(context.DerivedAsset.MyObjects[1], overrides, expectedYaml); - bool aliasOccurred; - AttachedYamlAssetMetadata metadata; - var instance = (Types.SomeObject)AssetFileSerializer.Default.Load(AssetTestContainer.ToStream(expectedYaml), null, null, true, out aliasOccurred, out metadata); - overrides = metadata.RetrieveMetadata(AssetObjectSerializerBackend.OverrideDictionaryKey); - Assert.NotNull(overrides); - overridesAsDictionary = overrides.ToDictionary(x => x.Key, x => x.Value, YamlAssetPathComparer.Default); - Assert.Equal("OverriddenString", instance.Value); - Assert.Single(overridesAsDictionary); - Assert.True(overridesAsDictionary.ContainsKey(expectedPath)); - Assert.Equal(OverrideType.New, overridesAsDictionary[expectedPath]); - } + var asset = new Types.MyAsset4 { MyObjects = { new Types.SomeObject { Value = "String1" }, new Types.SomeObject { Value = "String2" } } }; + var context = DeriveAssetTest.DeriveAsset(asset); + var derivedPropertyNode = (AssetObjectNode)context.DerivedGraph.RootNode[nameof(Types.MyAsset4.MyObjects)].Target.IndexedTarget(new NodeIndex(1)); + derivedPropertyNode[nameof(Types.SomeObject.Value)].Update("OverriddenString"); + var expectedPath = new YamlAssetPath(); + expectedPath.PushMember(nameof(Types.SomeObject.Value)); + + var overrides = AssetPropertyGraph.GenerateOverridesForSerialization(derivedPropertyNode); + var overridesAsDictionary = overrides.ToDictionary(x => x.Key, x => x.Value, YamlAssetPathComparer.Default); + Assert.Single(overridesAsDictionary); + Assert.True(overridesAsDictionary.ContainsKey(expectedPath)); + Assert.Equal(OverrideType.New, overridesAsDictionary[expectedPath]); + + // Test deserialization + SerializationHelper.SerializeAndCompare(context.DerivedAsset.MyObjects[1], overrides, expectedYaml); + var instance = (Types.SomeObject)AssetFileSerializer.Default.Load(AssetTestContainer.ToStream(expectedYaml), null, null, true, out var aliasOccurred, out var metadata); + overrides = metadata.RetrieveMetadata(AssetObjectSerializerBackend.OverrideDictionaryKey); + Assert.NotNull(overrides); + overridesAsDictionary = overrides.ToDictionary(x => x.Key, x => x.Value, YamlAssetPathComparer.Default); + Assert.Equal("OverriddenString", instance.Value); + Assert.Single(overridesAsDictionary); + Assert.True(overridesAsDictionary.ContainsKey(expectedPath)); + Assert.Equal(OverrideType.New, overridesAsDictionary[expectedPath]); } } - diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestReconcileObjectReferencesWithBase.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestReconcileObjectReferencesWithBase.cs index 5c0f64de62..73dd700bd0 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestReconcileObjectReferencesWithBase.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestReconcileObjectReferencesWithBase.cs @@ -1,346 +1,411 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Xunit; using Stride.Core.Assets.Quantum.Tests.Helpers; using Stride.Core.Assets.Tests.Helpers; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +public class TestReconcileObjectReferencesWithBase { - public class TestReconcileObjectReferencesWithBase + [Fact] + public void TestWithCorrectObjectReferences() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + MyObject1: + Value: MyInstance + Id: 00000002-0002-0000-0200-000002000000 + MyObject2: ref!! 00000002-0002-0000-0200-000002000000 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyObject1: + Value: MyModifiedInstance + Id: 00000003-0003-0000-0300-000003000000 + MyObject2: ref!! 00000003-0003-0000-0300-000003000000 + + """; + Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, _) => targetNode is IMemberNode { Name: nameof(Types.MyAssetWithRef.MyObject2) }; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var prevInstance = context.DerivedAsset.MyObject2; + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(GuidGenerator.Get(2), context.BaseAsset.MyObject1.Id); + Assert.Equal(context.BaseAsset.MyObject1, context.BaseAsset.MyObject2); + Assert.Equal(GuidGenerator.Get(3), context.DerivedAsset.MyObject1.Id); + Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); + Assert.Equal(prevInstance, context.DerivedAsset.MyObject2); + } + + [Fact] + public void TestWithIncorrectObjectReferences() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + MyObject1: + Value: MyInstance + Id: 00000002-0002-0000-0200-000002000000 + MyObject2: ref!! 00000002-0002-0000-0200-000002000000 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyObject1: + Value: MyModifiedInstance + Id: 00000003-0003-0000-0300-000003000000 + MyObject2: ref!! 00000004-0004-0000-0400-000004000000 + MyObject3: + Value: MyModifiedInstance + Id: 00000004-0004-0000-0400-000004000000 + + """; + Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, _) => targetNode is IMemberNode { Name: nameof(Types.MyAssetWithRef.MyObject2) }; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var prevInstance = context.DerivedAsset.MyObject2; + Assert.NotEqual(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); + context.DerivedGraph.ReconcileWithBase(); + Assert.NotEqual(prevInstance, context.DerivedAsset.MyObject2); + Assert.NotEqual(context.BaseAsset.MyObject1, context.DerivedAsset.MyObject1); + Assert.NotEqual(context.BaseAsset.MyObject2, context.DerivedAsset.MyObject2); + Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); + Assert.Null(context.DerivedAsset.MyObject3); + } + + [Fact] + public void TestWithOverriddenObjectReferences() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + MyObject1: + Value: MyInstance + Id: 00000002-0002-0000-0200-000002000000 + MyObject2: ref!! 00000002-0002-0000-0200-000002000000 + MyObject3: + Value: MyInstance + Id: 00000003-0003-0003-0300-000003000000 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyObject1: + Value: MyModifiedInstance + Id: 00000003-0003-0003-0300-000003000000 + MyObject2*: ref!! 00000004-0004-0000-0400-000004000000 + MyObject3: + Value: MyInstance + Id: 00000004-0004-0000-0400-000004000000 + + """; + Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, _) => targetNode is IMemberNode { Name: nameof(Types.MyAssetWithRef.MyObject2) }; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var prevInstance = context.DerivedAsset.MyObject2; + Assert.Equal(context.DerivedAsset.MyObject3, context.DerivedAsset.MyObject2); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(prevInstance, context.DerivedAsset.MyObject2); + Assert.NotEqual(context.BaseAsset.MyObject1, context.DerivedAsset.MyObject1); + Assert.NotEqual(context.BaseAsset.MyObject2, context.DerivedAsset.MyObject2); + Assert.NotEqual(context.BaseAsset.MyObject3, context.DerivedAsset.MyObject3); + Assert.NotEqual(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); + Assert.Equal(context.DerivedAsset.MyObject3, context.DerivedAsset.MyObject2); + Assert.Equal(GuidGenerator.Get(4), context.DerivedAsset.MyObject3.Id); + } + + [Fact] + public void TestWithInvalidObjectReferencesAndMissingTarget() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + MyObject1: + Value: MyInstance + Id: 00000002-0002-0000-0200-000002000000 + MyObject2: ref!! 00000002-0002-0000-0200-000002000000 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyObject1: null + MyObject2: ref!! 00000004-0004-0004-0400-000004000000 + + """; + Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, _) => targetNode is IMemberNode { Name: nameof(Types.MyAssetWithRef.MyObject2) }; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var prevInstance = context.DerivedAsset.MyObject2; + context.DerivedGraph.ReconcileWithBase(); + Assert.NotEqual(prevInstance, context.DerivedAsset.MyObject2); + Assert.NotEqual(context.BaseAsset.MyObject1, context.DerivedAsset.MyObject1); + Assert.NotEqual(context.BaseAsset.MyObject2, context.DerivedAsset.MyObject2); + Assert.NotNull(context.DerivedAsset.MyObject1); + Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); + } + + [Fact] + public void TestWithCorrectObjectReferencesInList() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + MyObject1: + Value: MyInstance + Id: 00000002-0002-0000-0200-000002000000 + MyObjects: + 0a0000000a0000000a0000000a000000: ref!! 00000002-0002-0000-0200-000002000000 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyObject1: + Value: MyInstance + Id: 00000003-0003-0000-0300-000003000000 + MyObjects: + 0a0000000a0000000a0000000a000000: ref!! 00000003-0003-0000-0300-000003000000 + + """; + + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var prevInstance = context.DerivedAsset.MyObjects[0]; + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(GuidGenerator.Get(2), context.BaseAsset.MyObject1.Id); + Assert.Equal(context.BaseAsset.MyObject1, context.BaseAsset.MyObjects[0]); + Assert.Equal(GuidGenerator.Get(3), context.DerivedAsset.MyObject1.Id); + Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObjects[0]); + Assert.Equal(prevInstance, context.DerivedAsset.MyObjects[0]); + } + + [Fact] + public void TestWithIncorrectObjectReferencesInList() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + MyObject1: + Value: MyInstance + Id: 00000002-0002-0000-0200-000002000000 + MyObjects: + 0a0000000a0000000a0000000a000000: ref!! 00000002-0002-0000-0200-000002000000 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyObject1: + Value: MyModifiedInstance + Id: 00000003-0003-0000-0300-000003000000 + MyObject2: + Value: MyModifiedInstance + Id: 00000004-0004-0000-0400-000004000000 + MyObjects: + 0a0000000a0000000a0000000a000000: ref!! 00000004-0004-0000-0400-000004000000 + + """; + Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, _) => targetNode is IObjectNode { ItemReferences: not null }; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var prevInstance = context.DerivedAsset.MyObjects[0]; + Assert.NotEqual(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObjects[0]); + context.DerivedGraph.ReconcileWithBase(); + Assert.NotEqual(prevInstance, context.DerivedAsset.MyObjects[0]); + Assert.NotEqual(context.BaseAsset.MyObject1, context.DerivedAsset.MyObject1); + Assert.NotEqual(context.BaseAsset.MyObjects[0], context.DerivedAsset.MyObjects[0]); + Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObjects[0]); + Assert.Null(context.DerivedAsset.MyObject2); + } + + [Fact] + public void TestWithOverriddenObjectReferencesInList() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + MyObject1: + Value: MyInstance + Id: 00000002-0002-0000-0200-000002000000 + MyObject2: + Value: MyInstance + Id: 00000003-0003-0003-0300-000003000000 + MyObjects: + 0a0000000a0000000a0000000a000000: ref!! 00000002-0002-0000-0200-000002000000 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyObject1: + Value: MyModifiedInstance + Id: 00000003-0003-0003-0300-000003000000 + MyObject2: + Value: MyInstance + Id: 00000004-0004-0000-0400-000004000000 + MyObjects: + 0a0000000a0000000a0000000a000000*: ref!! 00000004-0004-0000-0400-000004000000 + + """; + Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, _) => targetNode is IObjectNode { ItemReferences: not null }; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var prevInstance = context.DerivedAsset.MyObjects[0]; + Assert.Equal(context.DerivedAsset.MyObject2, context.DerivedAsset.MyObjects[0]); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(prevInstance, context.DerivedAsset.MyObjects[0]); + Assert.NotEqual(context.BaseAsset.MyObject1, context.DerivedAsset.MyObject1); + Assert.NotEqual(context.BaseAsset.MyObjects[0], context.DerivedAsset.MyObjects[0]); + Assert.NotEqual(context.BaseAsset.MyObject2, context.DerivedAsset.MyObject2); + Assert.NotEqual(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObjects[0]); + Assert.Equal(context.DerivedAsset.MyObject2, context.DerivedAsset.MyObjects[0]); + Assert.Equal(GuidGenerator.Get(4), context.DerivedAsset.MyObject2.Id); + } + + [Fact] + public void TestWithInvalidObjectReferencesAndMissingTargetInList() { - [Fact] - public void TestWithCorrectObjectReferences() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -MyObject1: - Value: MyInstance - Id: 00000002-0002-0000-0200-000002000000 -MyObject2: ref!! 00000002-0002-0000-0200-000002000000 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyObject1: - Value: MyModifiedInstance - Id: 00000003-0003-0000-0300-000003000000 -MyObject2: ref!! 00000003-0003-0000-0300-000003000000 -"; - Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, index) => (targetNode as IMemberNode)?.Name == nameof(Types.MyAssetWithRef.MyObject2); - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var prevInstance = context.DerivedAsset.MyObject2; - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(GuidGenerator.Get(2), context.BaseAsset.MyObject1.Id); - Assert.Equal(context.BaseAsset.MyObject1, context.BaseAsset.MyObject2); - Assert.Equal(GuidGenerator.Get(3), context.DerivedAsset.MyObject1.Id); - Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); - Assert.Equal(prevInstance, context.DerivedAsset.MyObject2); - } - - [Fact] - public void TestWithIncorrectObjectReferences() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -MyObject1: - Value: MyInstance - Id: 00000002-0002-0000-0200-000002000000 -MyObject2: ref!! 00000002-0002-0000-0200-000002000000 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyObject1: - Value: MyModifiedInstance - Id: 00000003-0003-0000-0300-000003000000 -MyObject2: ref!! 00000004-0004-0000-0400-000004000000 -MyObject3: - Value: MyModifiedInstance - Id: 00000004-0004-0000-0400-000004000000 -"; - Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, index) => (targetNode as IMemberNode)?.Name == nameof(Types.MyAssetWithRef.MyObject2); - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var prevInstance = context.DerivedAsset.MyObject2; - Assert.NotEqual(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); - context.DerivedGraph.ReconcileWithBase(); - Assert.NotEqual(prevInstance, context.DerivedAsset.MyObject2); - Assert.NotEqual(context.BaseAsset.MyObject1, context.DerivedAsset.MyObject1); - Assert.NotEqual(context.BaseAsset.MyObject2, context.DerivedAsset.MyObject2); - Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); - Assert.Null(context.DerivedAsset.MyObject3); - } - - [Fact] - public void TestWithOverriddenObjectReferences() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -MyObject1: - Value: MyInstance - Id: 00000002-0002-0000-0200-000002000000 -MyObject2: ref!! 00000002-0002-0000-0200-000002000000 -MyObject3: - Value: MyInstance - Id: 00000003-0003-0003-0300-000003000000 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyObject1: - Value: MyModifiedInstance - Id: 00000003-0003-0003-0300-000003000000 -MyObject2*: ref!! 00000004-0004-0000-0400-000004000000 -MyObject3: - Value: MyInstance - Id: 00000004-0004-0000-0400-000004000000 -"; - Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, index) => (targetNode as IMemberNode)?.Name == nameof(Types.MyAssetWithRef.MyObject2); - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var prevInstance = context.DerivedAsset.MyObject2; - Assert.Equal(context.DerivedAsset.MyObject3, context.DerivedAsset.MyObject2); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(prevInstance, context.DerivedAsset.MyObject2); - Assert.NotEqual(context.BaseAsset.MyObject1, context.DerivedAsset.MyObject1); - Assert.NotEqual(context.BaseAsset.MyObject2, context.DerivedAsset.MyObject2); - Assert.NotEqual(context.BaseAsset.MyObject3, context.DerivedAsset.MyObject3); - Assert.NotEqual(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); - Assert.Equal(context.DerivedAsset.MyObject3, context.DerivedAsset.MyObject2); - Assert.Equal(GuidGenerator.Get(4), context.DerivedAsset.MyObject3.Id); - } - - [Fact] - public void TestWithInvalidObjectReferencesAndMissingTarget() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -MyObject1: - Value: MyInstance - Id: 00000002-0002-0000-0200-000002000000 -MyObject2: ref!! 00000002-0002-0000-0200-000002000000 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyObject1: null -MyObject2: ref!! 00000004-0004-0004-0400-000004000000 -"; - Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, index) => (targetNode as IMemberNode)?.Name == nameof(Types.MyAssetWithRef.MyObject2); - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var prevInstance = context.DerivedAsset.MyObject2; - context.DerivedGraph.ReconcileWithBase(); - Assert.NotEqual(prevInstance, context.DerivedAsset.MyObject2); - Assert.NotEqual(context.BaseAsset.MyObject1, context.DerivedAsset.MyObject1); - Assert.NotEqual(context.BaseAsset.MyObject2, context.DerivedAsset.MyObject2); - Assert.NotNull(context.DerivedAsset.MyObject1); - Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); - } - - [Fact] - public void TestWithCorrectObjectReferencesInList() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -MyObject1: - Value: MyInstance - Id: 00000002-0002-0000-0200-000002000000 -MyObjects: - 0a0000000a0000000a0000000a000000: ref!! 00000002-0002-0000-0200-000002000000 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyObject1: - Value: MyInstance - Id: 00000003-0003-0000-0300-000003000000 -MyObjects: - 0a0000000a0000000a0000000a000000: ref!! 00000003-0003-0000-0300-000003000000 -"; - - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var prevInstance = context.DerivedAsset.MyObjects[0]; - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(GuidGenerator.Get(2), context.BaseAsset.MyObject1.Id); - Assert.Equal(context.BaseAsset.MyObject1, context.BaseAsset.MyObjects[0]); - Assert.Equal(GuidGenerator.Get(3), context.DerivedAsset.MyObject1.Id); - Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObjects[0]); - Assert.Equal(prevInstance, context.DerivedAsset.MyObjects[0]); - } - - [Fact] - public void TestWithIncorrectObjectReferencesInList() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -MyObject1: - Value: MyInstance - Id: 00000002-0002-0000-0200-000002000000 -MyObjects: - 0a0000000a0000000a0000000a000000: ref!! 00000002-0002-0000-0200-000002000000 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyObject1: - Value: MyModifiedInstance - Id: 00000003-0003-0000-0300-000003000000 -MyObject2: - Value: MyModifiedInstance - Id: 00000004-0004-0000-0400-000004000000 -MyObjects: - 0a0000000a0000000a0000000a000000: ref!! 00000004-0004-0000-0400-000004000000 -"; - Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, index) => (targetNode as IObjectNode)?.ItemReferences != null; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var prevInstance = context.DerivedAsset.MyObjects[0]; - Assert.NotEqual(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObjects[0]); - context.DerivedGraph.ReconcileWithBase(); - Assert.NotEqual(prevInstance, context.DerivedAsset.MyObjects[0]); - Assert.NotEqual(context.BaseAsset.MyObject1, context.DerivedAsset.MyObject1); - Assert.NotEqual(context.BaseAsset.MyObjects[0], context.DerivedAsset.MyObjects[0]); - Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObjects[0]); - Assert.Null(context.DerivedAsset.MyObject2); - } - - [Fact] - public void TestWithOverriddenObjectReferencesInList() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -MyObject1: - Value: MyInstance - Id: 00000002-0002-0000-0200-000002000000 -MyObject2: - Value: MyInstance - Id: 00000003-0003-0003-0300-000003000000 -MyObjects: - 0a0000000a0000000a0000000a000000: ref!! 00000002-0002-0000-0200-000002000000 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyObject1: - Value: MyModifiedInstance - Id: 00000003-0003-0003-0300-000003000000 -MyObject2: - Value: MyInstance - Id: 00000004-0004-0000-0400-000004000000 -MyObjects: - 0a0000000a0000000a0000000a000000*: ref!! 00000004-0004-0000-0400-000004000000 -"; - Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, index) => (targetNode as IObjectNode)?.ItemReferences != null; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var prevInstance = context.DerivedAsset.MyObjects[0]; - Assert.Equal(context.DerivedAsset.MyObject2, context.DerivedAsset.MyObjects[0]); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(prevInstance, context.DerivedAsset.MyObjects[0]); - Assert.NotEqual(context.BaseAsset.MyObject1, context.DerivedAsset.MyObject1); - Assert.NotEqual(context.BaseAsset.MyObjects[0], context.DerivedAsset.MyObjects[0]); - Assert.NotEqual(context.BaseAsset.MyObject2, context.DerivedAsset.MyObject2); - Assert.NotEqual(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObjects[0]); - Assert.Equal(context.DerivedAsset.MyObject2, context.DerivedAsset.MyObjects[0]); - Assert.Equal(GuidGenerator.Get(4), context.DerivedAsset.MyObject2.Id); - } - - [Fact] - public void TestWithInvalidObjectReferencesAndMissingTargetInList() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -MyObject1: - Value: MyInstance - Id: 00000002-0002-0000-0200-000002000000 -MyObjects: - 0a0000000a0000000a0000000a000000: ref!! 00000002-0002-0000-0200-000002000000 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -MyObject1: null -MyObjects: - 0a0000000a0000000a0000000a000000: ref!! 00000002-0002-0000-0200-000002000000 -"; - Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, index) => (targetNode as IObjectNode)?.ItemReferences != null; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var prevInstance = context.DerivedAsset.MyObjects[0]; - context.DerivedGraph.ReconcileWithBase(); - Assert.NotEqual(prevInstance, context.DerivedAsset.MyObjects[0]); - Assert.NotEqual(context.BaseAsset.MyObject1, context.DerivedAsset.MyObject1); - Assert.NotEqual(context.BaseAsset.MyObjects[0], context.DerivedAsset.MyObjects[0]); - Assert.NotNull(context.DerivedAsset.MyObject1); - Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObjects[0]); - } - - [Fact] - public void TestAllMissing() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -MyObject1: - Value: MyInstance - Id: 00000002-0002-0000-0200-000002000000 -MyObject2: ref!! 00000002-0002-0000-0200-000002000000 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -"; - Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, index) => (targetNode as IMemberNode)?.Name == nameof(Types.MyAssetWithRef.MyObject2); - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(GuidGenerator.Get(2), context.BaseAsset.MyObject1.Id); - Assert.Equal(context.BaseAsset.MyObject1, context.BaseAsset.MyObject2); - Assert.NotEqual(GuidGenerator.Get(2), context.DerivedAsset.MyObject1.Id); - Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); - } - - [Fact] - public void TestAllMissingInvertOrder() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -MyObject1: ref!! 00000002-0002-0000-0200-000002000000 -MyObject2: - Value: MyInstance - Id: 00000002-0002-0000-0200-000002000000 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -"; - Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, index) => (targetNode as IMemberNode)?.Name == nameof(Types.MyAssetWithRef.MyObject1); - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(GuidGenerator.Get(2), context.BaseAsset.MyObject1.Id); - Assert.Equal(context.BaseAsset.MyObject1, context.BaseAsset.MyObject2); - Assert.NotEqual(GuidGenerator.Get(2), context.DerivedAsset.MyObject1.Id); - Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); - } - - [Fact] - public void TestAllMissingInList() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 00000001-0001-0000-0100-000001000000 -MyObjects: - 0a0000000a0000000a0000000a000000: - Value: MyInstance - Id: 00000002-0002-0000-0200-000002000000 - 0a0000000b0000000b0000000b000000: ref!! 00000002-0002-0000-0200-000002000000 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 00000001-0001-0000-0100-000001000000:MyAsset -"; - Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, index) => index.IsInt && index.Int == 1; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(GuidGenerator.Get(2), context.BaseAsset.MyObjects[0].Id); - Assert.Equal(context.BaseAsset.MyObjects[1], context.BaseAsset.MyObjects[0]); - Assert.Equal(2, context.DerivedAsset.MyObjects.Count); - Assert.NotEqual(GuidGenerator.Get(2), context.DerivedAsset.MyObjects[0].Id); - Assert.Equal(context.DerivedAsset.MyObjects[1], context.DerivedAsset.MyObjects[0]); - } + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + MyObject1: + Value: MyInstance + Id: 00000002-0002-0000-0200-000002000000 + MyObjects: + 0a0000000a0000000a0000000a000000: ref!! 00000002-0002-0000-0200-000002000000 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + MyObject1: null + MyObjects: + 0a0000000a0000000a0000000a000000: ref!! 00000002-0002-0000-0200-000002000000 + + """; + Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, _) => targetNode is IObjectNode { ItemReferences: not null }; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var prevInstance = context.DerivedAsset.MyObjects[0]; + context.DerivedGraph.ReconcileWithBase(); + Assert.NotEqual(prevInstance, context.DerivedAsset.MyObjects[0]); + Assert.NotEqual(context.BaseAsset.MyObject1, context.DerivedAsset.MyObject1); + Assert.NotEqual(context.BaseAsset.MyObjects[0], context.DerivedAsset.MyObjects[0]); + Assert.NotNull(context.DerivedAsset.MyObject1); + Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObjects[0]); + } + + [Fact] + public void TestAllMissing() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + MyObject1: + Value: MyInstance + Id: 00000002-0002-0000-0200-000002000000 + MyObject2: ref!! 00000002-0002-0000-0200-000002000000 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + + """; + Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, _) => targetNode is IMemberNode { Name: nameof(Types.MyAssetWithRef.MyObject2) }; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(GuidGenerator.Get(2), context.BaseAsset.MyObject1.Id); + Assert.Equal(context.BaseAsset.MyObject1, context.BaseAsset.MyObject2); + Assert.NotEqual(GuidGenerator.Get(2), context.DerivedAsset.MyObject1.Id); + Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); + } + + [Fact] + public void TestAllMissingInvertOrder() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + MyObject1: ref!! 00000002-0002-0000-0200-000002000000 + MyObject2: + Value: MyInstance + Id: 00000002-0002-0000-0200-000002000000 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + + """; + Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (targetNode, _) => targetNode is IMemberNode { Name: nameof(Types.MyAssetWithRef.MyObject1) }; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(GuidGenerator.Get(2), context.BaseAsset.MyObject1.Id); + Assert.Equal(context.BaseAsset.MyObject1, context.BaseAsset.MyObject2); + Assert.NotEqual(GuidGenerator.Get(2), context.DerivedAsset.MyObject1.Id); + Assert.Equal(context.DerivedAsset.MyObject1, context.DerivedAsset.MyObject2); + } + + [Fact] + public void TestAllMissingInList() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 00000001-0001-0000-0100-000001000000 + MyObjects: + 0a0000000a0000000a0000000a000000: + Value: MyInstance + Id: 00000002-0002-0000-0200-000002000000 + 0a0000000b0000000b0000000b000000: ref!! 00000002-0002-0000-0200-000002000000 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithRef,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 00000001-0001-0000-0100-000001000000:MyAsset + """; + Types.AssetWithRefPropertyGraphDefinition.IsObjectReferenceFunc = (_, index) => index.IsInt && index.Int == 1; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(GuidGenerator.Get(2), context.BaseAsset.MyObjects[0].Id); + Assert.Equal(context.BaseAsset.MyObjects[1], context.BaseAsset.MyObjects[0]); + Assert.Equal(2, context.DerivedAsset.MyObjects.Count); + Assert.NotEqual(GuidGenerator.Get(2), context.DerivedAsset.MyObjects[0].Id); + Assert.Equal(context.DerivedAsset.MyObjects[1], context.DerivedAsset.MyObjects[0]); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestReconcileWithBase.cs b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestReconcileWithBase.cs index ad788bdfb9..b3007cea90 100644 --- a/sources/assets/Stride.Core.Assets.Quantum.Tests/TestReconcileWithBase.cs +++ b/sources/assets/Stride.Core.Assets.Quantum.Tests/TestReconcileWithBase.cs @@ -1,707 +1,787 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Linq; + using Xunit; using Stride.Core.Assets.Quantum.Tests.Helpers; using Stride.Core.Assets.Tests.Helpers; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Quantum.Tests +namespace Stride.Core.Assets.Quantum.Tests; + +public class TestReconcileWithBase { - public class TestReconcileWithBase + [Fact] + public void TestPrimitiveMember() + { + const string primitiveMemberBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset1,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + MyString: MyBaseString + + """; + const string primitiveMemberOverridenYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset1,Stride.Core.Assets.Quantum.Tests + Id: 30000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + MyString*: MyDerivedString + + """; + const string primitiveMemberToReconcileYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset1,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + MyString: MyDerivedString + + """; + var context = DeriveAssetTest.LoadFromYaml(primitiveMemberBaseYaml, primitiveMemberOverridenYaml); + Assert.Equal("MyBaseString", context.BaseAsset.MyString); + Assert.Equal("MyDerivedString", context.DerivedAsset.MyString); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal("MyBaseString", context.BaseAsset.MyString); + Assert.Equal("MyDerivedString", context.DerivedAsset.MyString); + + context = DeriveAssetTest.LoadFromYaml(primitiveMemberBaseYaml, primitiveMemberToReconcileYaml); + Assert.Equal("MyBaseString", context.BaseAsset.MyString); + Assert.Equal("MyDerivedString", context.DerivedAsset.MyString); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal("MyBaseString", context.BaseAsset.MyString); + Assert.Equal("MyBaseString", context.DerivedAsset.MyString); + } + + [Fact] + public void TestStructWithPrimitivesOverrideMember() + { + const string primitiveMemberBaseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithStructWithPrimitives,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + StructValue: {} + + """; + const string primitiveMemberOverriddenYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithStructWithPrimitives,Stride.Core.Assets.Quantum.Tests + Id: 30000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + StructValue*: {Value1: 10, Value2: 20} + + """; + var expectedOverriddenValue = new Types.StructWithPrimitives { Value1 = 10, Value2 = 20 }; + var context = DeriveAssetTest.LoadFromYaml(primitiveMemberBaseYaml, primitiveMemberOverriddenYaml); + Assert.Equal(default, context.BaseAsset.StructValue); + Assert.Equal(expectedOverriddenValue, context.DerivedAsset.StructValue); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(default, context.BaseAsset.StructValue); + Assert.Equal(expectedOverriddenValue, context.DerivedAsset.StructValue); + } + + [Fact] + public void TestCollectionMismatchItem() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000: String1 + 14000000140000001400000014000000: String2 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000*: MyDerivedString + 14000000140000001400000014000000: MyBaseString + + """; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal("String1", context.BaseAsset.MyStrings[0]); + Assert.Equal("String2", context.BaseAsset.MyStrings[1]); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("MyDerivedString", context.DerivedAsset.MyStrings[0]); + Assert.Equal("MyBaseString", context.DerivedAsset.MyStrings[1]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds[1]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds[1]); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal("String1", context.BaseAsset.MyStrings[0]); + Assert.Equal("String2", context.BaseAsset.MyStrings[1]); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("MyDerivedString", context.DerivedAsset.MyStrings[0]); + Assert.Equal("String2", context.DerivedAsset.MyStrings[1]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds[1]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds[1]); + } + + [Fact] + public void TestCollectionMismatchId() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000: String1 + 14000000140000001400000014000000: String2 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + Struct: + MyStrings: {} + MyStrings: + 1a0000001a0000001a0000001a000000: String1 + 14000000140000001400000014000000: String2 + + """; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal("String1", context.BaseAsset.MyStrings[0]); + Assert.Equal("String2", context.BaseAsset.MyStrings[1]); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); + Assert.Equal("String2", context.DerivedAsset.MyStrings[1]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds[1]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(26), derivedIds[0]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds[1]); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal("String1", context.BaseAsset.MyStrings[0]); + Assert.Equal("String2", context.BaseAsset.MyStrings[1]); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); + Assert.Equal("String2", context.DerivedAsset.MyStrings[1]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds[1]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds[1]); + } + + [Fact] + public void TestCollectionAddedItemInBase() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000: String1 + 15000000150000001500000015000000: String2.5 + 14000000140000001400000014000000: String2 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000: String1 + 14000000140000001400000014000000: String2 + + """; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + Assert.Equal(3, context.BaseAsset.MyStrings.Count); + Assert.Equal("String1", context.BaseAsset.MyStrings[0]); + Assert.Equal("String2.5", context.BaseAsset.MyStrings[1]); + Assert.Equal("String2", context.BaseAsset.MyStrings[2]); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); + Assert.Equal("String2", context.DerivedAsset.MyStrings[1]); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); + Assert.Equal(IdentifierGenerator.Get(21), baseIds[1]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds[2]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds[1]); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(3, context.BaseAsset.MyStrings.Count); + Assert.Equal("String1", context.BaseAsset.MyStrings[0]); + Assert.Equal("String2.5", context.BaseAsset.MyStrings[1]); + Assert.Equal("String2", context.BaseAsset.MyStrings[2]); + Assert.Equal(3, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); + Assert.Equal("String2.5", context.DerivedAsset.MyStrings[1]); + Assert.Equal("String2", context.DerivedAsset.MyStrings[2]); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); + Assert.Equal(IdentifierGenerator.Get(21), baseIds[1]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds[2]); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); + Assert.Equal(IdentifierGenerator.Get(21), derivedIds[1]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds[2]); + } + + [Fact] + public void TestCollectionRemovedItemFromBase() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000: String1 + 14000000140000001400000014000000: String3 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000: String1 + 24000000240000002400000024000000: String2 + 14000000140000001400000014000000: String3 + + """; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal("String1", context.BaseAsset.MyStrings[0]); + Assert.Equal("String3", context.BaseAsset.MyStrings[1]); + Assert.Equal(3, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); + Assert.Equal("String2", context.DerivedAsset.MyStrings[1]); + Assert.Equal("String3", context.DerivedAsset.MyStrings[2]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds[1]); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); + Assert.Equal(IdentifierGenerator.Get(36), derivedIds[1]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds[2]); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal("String1", context.BaseAsset.MyStrings[0]); + Assert.Equal("String3", context.BaseAsset.MyStrings[1]); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); + Assert.Equal("String3", context.DerivedAsset.MyStrings[1]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds[1]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds[1]); + } + + [Fact] + public void TestCollectionRemovedDeletedItemFromBase() { - [Fact] - public void TestPrimitiveMember() - { - const string primitiveMemberBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset1,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -MyString: MyBaseString -"; - const string primitiveMemberOverridenYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset1,Stride.Core.Assets.Quantum.Tests -Id: 30000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -MyString*: MyDerivedString -"; - const string primitiveMemberToReconcileYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset1,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -MyString: MyDerivedString -"; - var context = DeriveAssetTest.LoadFromYaml(primitiveMemberBaseYaml, primitiveMemberOverridenYaml); - Assert.Equal("MyBaseString", context.BaseAsset.MyString); - Assert.Equal("MyDerivedString", context.DerivedAsset.MyString); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal("MyBaseString", context.BaseAsset.MyString); - Assert.Equal("MyDerivedString", context.DerivedAsset.MyString); - - context = DeriveAssetTest.LoadFromYaml(primitiveMemberBaseYaml, primitiveMemberToReconcileYaml); - Assert.Equal("MyBaseString", context.BaseAsset.MyString); - Assert.Equal("MyDerivedString", context.DerivedAsset.MyString); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal("MyBaseString", context.BaseAsset.MyString); - Assert.Equal("MyBaseString", context.DerivedAsset.MyString); - } - - [Fact] - public void TestStructWithPrimitivesOverrideMember() - { - const string primitiveMemberBaseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithStructWithPrimitives,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -StructValue: {} -"; - const string primitiveMemberOverriddenYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAssetWithStructWithPrimitives,Stride.Core.Assets.Quantum.Tests -Id: 30000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -StructValue*: {Value1: 10, Value2: 20} -"; - var expectedOverriddenValue = new Types.StructWithPrimitives { Value1 = 10, Value2 = 20 }; - var context = DeriveAssetTest.LoadFromYaml(primitiveMemberBaseYaml, primitiveMemberOverriddenYaml); - Assert.Equal(default, context.BaseAsset.StructValue); - Assert.Equal(expectedOverriddenValue, context.DerivedAsset.StructValue); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(default, context.BaseAsset.StructValue); - Assert.Equal(expectedOverriddenValue, context.DerivedAsset.StructValue); - } - - [Fact] - public void TestCollectionMismatchItem() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000: String1 - 14000000140000001400000014000000: String2 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000*: MyDerivedString - 14000000140000001400000014000000: MyBaseString -"; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal("String1", context.BaseAsset.MyStrings[0]); - Assert.Equal("String2", context.BaseAsset.MyStrings[1]); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("MyDerivedString", context.DerivedAsset.MyStrings[0]); - Assert.Equal("MyBaseString", context.DerivedAsset.MyStrings[1]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds[1]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds[1]); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal("String1", context.BaseAsset.MyStrings[0]); - Assert.Equal("String2", context.BaseAsset.MyStrings[1]); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("MyDerivedString", context.DerivedAsset.MyStrings[0]); - Assert.Equal("String2", context.DerivedAsset.MyStrings[1]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds[1]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds[1]); - } - - [Fact] - public void TestCollectionMismatchId() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000: String1 - 14000000140000001400000014000000: String2 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -Struct: - MyStrings: {} -MyStrings: - 1a0000001a0000001a0000001a000000: String1 - 14000000140000001400000014000000: String2 -"; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal("String1", context.BaseAsset.MyStrings[0]); - Assert.Equal("String2", context.BaseAsset.MyStrings[1]); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); - Assert.Equal("String2", context.DerivedAsset.MyStrings[1]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds[1]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(26), derivedIds[0]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds[1]); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal("String1", context.BaseAsset.MyStrings[0]); - Assert.Equal("String2", context.BaseAsset.MyStrings[1]); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); - Assert.Equal("String2", context.DerivedAsset.MyStrings[1]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds[1]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds[1]); - } - - [Fact] - public void TestCollectionAddedItemInBase() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000: String1 - 15000000150000001500000015000000: String2.5 - 14000000140000001400000014000000: String2 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000: String1 - 14000000140000001400000014000000: String2 -"; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - Assert.Equal(3, context.BaseAsset.MyStrings.Count); - Assert.Equal("String1", context.BaseAsset.MyStrings[0]); - Assert.Equal("String2.5", context.BaseAsset.MyStrings[1]); - Assert.Equal("String2", context.BaseAsset.MyStrings[2]); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); - Assert.Equal("String2", context.DerivedAsset.MyStrings[1]); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); - Assert.Equal(IdentifierGenerator.Get(21), baseIds[1]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds[2]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds[1]); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(3, context.BaseAsset.MyStrings.Count); - Assert.Equal("String1", context.BaseAsset.MyStrings[0]); - Assert.Equal("String2.5", context.BaseAsset.MyStrings[1]); - Assert.Equal("String2", context.BaseAsset.MyStrings[2]); - Assert.Equal(3, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); - Assert.Equal("String2.5", context.DerivedAsset.MyStrings[1]); - Assert.Equal("String2", context.DerivedAsset.MyStrings[2]); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); - Assert.Equal(IdentifierGenerator.Get(21), baseIds[1]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds[2]); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); - Assert.Equal(IdentifierGenerator.Get(21), derivedIds[1]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds[2]); - } - - [Fact] - public void TestCollectionRemovedItemFromBase() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000: String1 - 14000000140000001400000014000000: String3 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000: String1 - 24000000240000002400000024000000: String2 - 14000000140000001400000014000000: String3 -"; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal("String1", context.BaseAsset.MyStrings[0]); - Assert.Equal("String3", context.BaseAsset.MyStrings[1]); - Assert.Equal(3, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); - Assert.Equal("String2", context.DerivedAsset.MyStrings[1]); - Assert.Equal("String3", context.DerivedAsset.MyStrings[2]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds[1]); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); - Assert.Equal(IdentifierGenerator.Get(36), derivedIds[1]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds[2]); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal("String1", context.BaseAsset.MyStrings[0]); - Assert.Equal("String3", context.BaseAsset.MyStrings[1]); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); - Assert.Equal("String3", context.DerivedAsset.MyStrings[1]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds[1]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds[1]); - } - - [Fact] - public void TestCollectionRemovedDeletedItemFromBase() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000: String1 - 24000000240000002400000024000000: String3 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -Struct: - MyStrings: {} -MyStrings: - 0a0000000a0000000a0000000a000000: String1 - 24000000240000002400000024000000: String2 - 14000000140000001400000014000000: ~(Deleted) -"; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal("String1", context.BaseAsset.MyStrings[0]); - Assert.Equal("String3", context.BaseAsset.MyStrings[1]); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); - Assert.Equal("String2", context.DerivedAsset.MyStrings[1]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); - Assert.Equal(IdentifierGenerator.Get(36), baseIds[1]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(1, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); - Assert.Equal(IdentifierGenerator.Get(36), derivedIds[1]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds.DeletedItems.Single()); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(2, context.BaseAsset.MyStrings.Count); - Assert.Equal("String1", context.BaseAsset.MyStrings[0]); - Assert.Equal("String3", context.BaseAsset.MyStrings[1]); - Assert.Equal(2, context.DerivedAsset.MyStrings.Count); - Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); - Assert.Equal("String3", context.DerivedAsset.MyStrings[1]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); - Assert.Equal(IdentifierGenerator.Get(36), baseIds[1]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); - Assert.Equal(IdentifierGenerator.Get(36), derivedIds[1]); - } - - [Fact] - public void TestDictionaryMismatchValue() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 14000000140000001400000014000000~Key2: String2 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000*~Key1: MyDerivedString - 14000000140000001400000014000000~Key2: MyBaseString -"; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); - Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2"]); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("MyDerivedString", context.DerivedAsset.MyDictionary["Key1"]); - Assert.Equal("MyBaseString", context.DerivedAsset.MyDictionary["Key2"]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key2"]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key2"]); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); - Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2"]); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("MyDerivedString", context.DerivedAsset.MyDictionary["Key1"]); - Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key2"]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key2"]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key2"]); - } - - [Fact] - public void TestDictionaryAddedKeyInBase() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 15000000150000001500000015000000~Key2.5: String2.5 - 14000000140000001400000014000000~Key2: String2 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 14000000140000001400000014000000~Key2: String2 -"; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - Assert.Equal(3, context.BaseAsset.MyDictionary.Count); - Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); - Assert.Equal("String2.5", context.BaseAsset.MyDictionary["Key2.5"]); - Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2"]); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); - Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key2"]); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(21), baseIds["Key2.5"]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key2"]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key2"]); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(3, context.BaseAsset.MyDictionary.Count); - Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); - Assert.Equal("String2.5", context.BaseAsset.MyDictionary["Key2.5"]); - Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2"]); - Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); - Assert.Equal("String2.5", context.DerivedAsset.MyDictionary["Key2.5"]); - Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key2"]); - Assert.Equal(3, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(21), baseIds["Key2.5"]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key2"]); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(21), derivedIds["Key2.5"]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key2"]); - } - - [Fact] - public void TestDictionaryKeyCollision() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 14000000140000001400000014000000~Key2: String2 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 15000000150000001500000015000000*~Key2: String3 -"; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); - Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2"]); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); - Assert.Equal("String3", context.DerivedAsset.MyDictionary["Key2"]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key2"]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(21), derivedIds["Key2"]); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); - Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2"]); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); - Assert.Equal("String3", context.DerivedAsset.MyDictionary["Key2"]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key2"]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(1, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(21), derivedIds["Key2"]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds.DeletedItems.Single()); - } - - [Fact] - public void TestDictionaryRemovedItemFromBase() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 14000000140000001400000014000000~Key3: String3 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 24000000240000002400000024000000~Key2: String2 - 14000000140000001400000014000000~Key3: String3 -"; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); - Assert.Equal("String3", context.BaseAsset.MyDictionary["Key3"]); - Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); - Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key2"]); - Assert.Equal("String3", context.DerivedAsset.MyDictionary["Key3"]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key3"]); - Assert.Equal(3, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(36), derivedIds["Key2"]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key3"]); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); - Assert.Equal("String3", context.BaseAsset.MyDictionary["Key3"]); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); - Assert.Equal("String3", context.DerivedAsset.MyDictionary["Key3"]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key3"]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key3"]); - } - - [Fact] - public void TestDictionaryRemovedDeletedItemFromBase() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 24000000240000002400000024000000~Key3: String3 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 24000000240000002400000024000000~Key3: String2 - 14000000140000001400000014000000~: ~(Deleted) -"; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); - Assert.Equal("String3", context.BaseAsset.MyDictionary["Key3"]); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); - Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key3"]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(36), baseIds["Key3"]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(1, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(36), derivedIds["Key3"]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds.DeletedItems.Single()); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(2, context.BaseAsset.MyDictionary.Count); - Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); - Assert.Equal("String3", context.BaseAsset.MyDictionary["Key3"]); - Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); - Assert.Equal("String3", context.DerivedAsset.MyDictionary["Key3"]); - Assert.Equal(2, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(36), baseIds["Key3"]); - Assert.Equal(2, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(36), derivedIds["Key3"]); - } - - [Fact] - public void TestDictionaryRenameItemFromBase() - { - const string baseYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 10000000-0000-0000-0000-000000000000 -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 24000000240000002400000024000000~Key2Renamed: String2 - 14000000140000001400000014000000~Key3Renamed: String3 - 34000000340000003400000034000000~Key4Renamed: String4 -"; - const string derivedYaml = @"!Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests -Id: 20000000-0000-0000-0000-000000000000 -Archetype: 10000000-0000-0000-0000-000000000000:MyAsset -Tags: [] -MyDictionary: - 0a0000000a0000000a0000000a000000~Key1: String1 - 24000000240000002400000024000000~Key2: String2 - 14000000140000001400000014000000*~Key3: MyDerivedString - 34000000340000003400000034000000~Key4*: MyDerivedString -"; - var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); - var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); - var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); - Assert.Equal(4, context.BaseAsset.MyDictionary.Count); - Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); - Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2Renamed"]); - Assert.Equal("String3", context.BaseAsset.MyDictionary["Key3Renamed"]); - Assert.Equal("String4", context.BaseAsset.MyDictionary["Key4Renamed"]); - Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); - Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key2"]); - Assert.Equal("MyDerivedString", context.DerivedAsset.MyDictionary["Key3"]); - Assert.Equal("MyDerivedString", context.DerivedAsset.MyDictionary["Key4"]); - Assert.Equal(4, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(36), baseIds["Key2Renamed"]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key3Renamed"]); - Assert.Equal(IdentifierGenerator.Get(52), baseIds["Key4Renamed"]); - Assert.Equal(4, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(36), derivedIds["Key2"]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key3"]); - Assert.Equal(IdentifierGenerator.Get(52), derivedIds["Key4"]); - context.DerivedGraph.ReconcileWithBase(); - Assert.Equal(4, context.BaseAsset.MyDictionary.Count); - Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); - Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2Renamed"]); - Assert.Equal("String3", context.BaseAsset.MyDictionary["Key3Renamed"]); - Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); - Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); - Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key2Renamed"]); - Assert.Equal("MyDerivedString", context.DerivedAsset.MyDictionary["Key3Renamed"]); - Assert.Equal("String4", context.DerivedAsset.MyDictionary["Key4"]); - Assert.Equal(4, baseIds.KeyCount); - Assert.Equal(0, baseIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(36), baseIds["Key2Renamed"]); - Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key3Renamed"]); - Assert.Equal(IdentifierGenerator.Get(52), baseIds["Key4Renamed"]); - Assert.Equal(4, derivedIds.KeyCount); - Assert.Equal(0, derivedIds.DeletedCount); - Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); - Assert.Equal(IdentifierGenerator.Get(36), derivedIds["Key2Renamed"]); - Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key3Renamed"]); - Assert.Equal(IdentifierGenerator.Get(52), derivedIds["Key4"]); - } + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000: String1 + 24000000240000002400000024000000: String3 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset2,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + Struct: + MyStrings: {} + MyStrings: + 0a0000000a0000000a0000000a000000: String1 + 24000000240000002400000024000000: String2 + 14000000140000001400000014000000: ~(Deleted) + + """; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyStrings); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyStrings); + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal("String1", context.BaseAsset.MyStrings[0]); + Assert.Equal("String3", context.BaseAsset.MyStrings[1]); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); + Assert.Equal("String2", context.DerivedAsset.MyStrings[1]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); + Assert.Equal(IdentifierGenerator.Get(36), baseIds[1]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(1, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); + Assert.Equal(IdentifierGenerator.Get(36), derivedIds[1]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds.DeletedItems.Single()); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(2, context.BaseAsset.MyStrings.Count); + Assert.Equal("String1", context.BaseAsset.MyStrings[0]); + Assert.Equal("String3", context.BaseAsset.MyStrings[1]); + Assert.Equal(2, context.DerivedAsset.MyStrings.Count); + Assert.Equal("String1", context.DerivedAsset.MyStrings[0]); + Assert.Equal("String3", context.DerivedAsset.MyStrings[1]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds[0]); + Assert.Equal(IdentifierGenerator.Get(36), baseIds[1]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds[0]); + Assert.Equal(IdentifierGenerator.Get(36), derivedIds[1]); + } + + [Fact] + public void TestDictionaryMismatchValue() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 14000000140000001400000014000000~Key2: String2 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000*~Key1: MyDerivedString + 14000000140000001400000014000000~Key2: MyBaseString + + """; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); + Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2"]); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("MyDerivedString", context.DerivedAsset.MyDictionary["Key1"]); + Assert.Equal("MyBaseString", context.DerivedAsset.MyDictionary["Key2"]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key2"]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key2"]); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); + Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2"]); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("MyDerivedString", context.DerivedAsset.MyDictionary["Key1"]); + Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key2"]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key2"]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key2"]); + } + + [Fact] + public void TestDictionaryAddedKeyInBase() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 15000000150000001500000015000000~Key2.5: String2.5 + 14000000140000001400000014000000~Key2: String2 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 14000000140000001400000014000000~Key2: String2 + + """; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + Assert.Equal(3, context.BaseAsset.MyDictionary.Count); + Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); + Assert.Equal("String2.5", context.BaseAsset.MyDictionary["Key2.5"]); + Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2"]); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); + Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key2"]); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(21), baseIds["Key2.5"]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key2"]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key2"]); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(3, context.BaseAsset.MyDictionary.Count); + Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); + Assert.Equal("String2.5", context.BaseAsset.MyDictionary["Key2.5"]); + Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2"]); + Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); + Assert.Equal("String2.5", context.DerivedAsset.MyDictionary["Key2.5"]); + Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key2"]); + Assert.Equal(3, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(21), baseIds["Key2.5"]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key2"]); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(21), derivedIds["Key2.5"]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key2"]); + } + + [Fact] + public void TestDictionaryKeyCollision() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 14000000140000001400000014000000~Key2: String2 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 15000000150000001500000015000000*~Key2: String3 + + """; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); + Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2"]); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); + Assert.Equal("String3", context.DerivedAsset.MyDictionary["Key2"]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key2"]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(21), derivedIds["Key2"]); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); + Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2"]); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); + Assert.Equal("String3", context.DerivedAsset.MyDictionary["Key2"]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key2"]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(1, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(21), derivedIds["Key2"]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds.DeletedItems.Single()); + } + + [Fact] + public void TestDictionaryRemovedItemFromBase() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 14000000140000001400000014000000~Key3: String3 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 24000000240000002400000024000000~Key2: String2 + 14000000140000001400000014000000~Key3: String3 + + """; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); + Assert.Equal("String3", context.BaseAsset.MyDictionary["Key3"]); + Assert.Equal(3, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); + Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key2"]); + Assert.Equal("String3", context.DerivedAsset.MyDictionary["Key3"]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key3"]); + Assert.Equal(3, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(36), derivedIds["Key2"]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key3"]); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); + Assert.Equal("String3", context.BaseAsset.MyDictionary["Key3"]); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); + Assert.Equal("String3", context.DerivedAsset.MyDictionary["Key3"]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key3"]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key3"]); + } + + [Fact] + public void TestDictionaryRemovedDeletedItemFromBase() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 24000000240000002400000024000000~Key3: String3 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 24000000240000002400000024000000~Key3: String2 + 14000000140000001400000014000000~: ~(Deleted) + + """; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); + Assert.Equal("String3", context.BaseAsset.MyDictionary["Key3"]); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); + Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key3"]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(36), baseIds["Key3"]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(1, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(36), derivedIds["Key3"]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds.DeletedItems.Single()); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(2, context.BaseAsset.MyDictionary.Count); + Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); + Assert.Equal("String3", context.BaseAsset.MyDictionary["Key3"]); + Assert.Equal(2, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); + Assert.Equal("String3", context.DerivedAsset.MyDictionary["Key3"]); + Assert.Equal(2, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(36), baseIds["Key3"]); + Assert.Equal(2, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(36), derivedIds["Key3"]); + } + + [Fact] + public void TestDictionaryRenameItemFromBase() + { + const string baseYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 10000000-0000-0000-0000-000000000000 + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 24000000240000002400000024000000~Key2Renamed: String2 + 14000000140000001400000014000000~Key3Renamed: String3 + 34000000340000003400000034000000~Key4Renamed: String4 + + """; + const string derivedYaml = + """ + !Stride.Core.Assets.Quantum.Tests.Helpers.Types+MyAsset3,Stride.Core.Assets.Quantum.Tests + Id: 20000000-0000-0000-0000-000000000000 + Archetype: 10000000-0000-0000-0000-000000000000:MyAsset + Tags: [] + MyDictionary: + 0a0000000a0000000a0000000a000000~Key1: String1 + 24000000240000002400000024000000~Key2: String2 + 14000000140000001400000014000000*~Key3: MyDerivedString + 34000000340000003400000034000000~Key4*: MyDerivedString + + """; + var context = DeriveAssetTest.LoadFromYaml(baseYaml, derivedYaml); + var baseIds = CollectionItemIdHelper.GetCollectionItemIds(context.BaseAsset.MyDictionary); + var derivedIds = CollectionItemIdHelper.GetCollectionItemIds(context.DerivedAsset.MyDictionary); + Assert.Equal(4, context.BaseAsset.MyDictionary.Count); + Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); + Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2Renamed"]); + Assert.Equal("String3", context.BaseAsset.MyDictionary["Key3Renamed"]); + Assert.Equal("String4", context.BaseAsset.MyDictionary["Key4Renamed"]); + Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); + Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key2"]); + Assert.Equal("MyDerivedString", context.DerivedAsset.MyDictionary["Key3"]); + Assert.Equal("MyDerivedString", context.DerivedAsset.MyDictionary["Key4"]); + Assert.Equal(4, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(36), baseIds["Key2Renamed"]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key3Renamed"]); + Assert.Equal(IdentifierGenerator.Get(52), baseIds["Key4Renamed"]); + Assert.Equal(4, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(36), derivedIds["Key2"]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key3"]); + Assert.Equal(IdentifierGenerator.Get(52), derivedIds["Key4"]); + context.DerivedGraph.ReconcileWithBase(); + Assert.Equal(4, context.BaseAsset.MyDictionary.Count); + Assert.Equal("String1", context.BaseAsset.MyDictionary["Key1"]); + Assert.Equal("String2", context.BaseAsset.MyDictionary["Key2Renamed"]); + Assert.Equal("String3", context.BaseAsset.MyDictionary["Key3Renamed"]); + Assert.Equal(4, context.DerivedAsset.MyDictionary.Count); + Assert.Equal("String1", context.DerivedAsset.MyDictionary["Key1"]); + Assert.Equal("String2", context.DerivedAsset.MyDictionary["Key2Renamed"]); + Assert.Equal("MyDerivedString", context.DerivedAsset.MyDictionary["Key3Renamed"]); + Assert.Equal("String4", context.DerivedAsset.MyDictionary["Key4"]); + Assert.Equal(4, baseIds.KeyCount); + Assert.Equal(0, baseIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), baseIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(36), baseIds["Key2Renamed"]); + Assert.Equal(IdentifierGenerator.Get(20), baseIds["Key3Renamed"]); + Assert.Equal(IdentifierGenerator.Get(52), baseIds["Key4Renamed"]); + Assert.Equal(4, derivedIds.KeyCount); + Assert.Equal(0, derivedIds.DeletedCount); + Assert.Equal(IdentifierGenerator.Get(10), derivedIds["Key1"]); + Assert.Equal(IdentifierGenerator.Get(36), derivedIds["Key2Renamed"]); + Assert.Equal(IdentifierGenerator.Get(20), derivedIds["Key3Renamed"]); + Assert.Equal(IdentifierGenerator.Get(52), derivedIds["Key4"]); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetBaseToDerivedRegistry.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetBaseToDerivedRegistry.cs index 3f3a7e43b7..98d70fd2a3 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetBaseToDerivedRegistry.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetBaseToDerivedRegistry.cs @@ -1,70 +1,66 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; + using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +internal class AssetBaseToDerivedRegistry : IBaseToDerivedRegistry { - internal class AssetBaseToDerivedRegistry : IBaseToDerivedRegistry + private readonly AssetPropertyGraph propertyGraph; + private readonly Dictionary baseToDerived = []; + + public AssetBaseToDerivedRegistry(AssetPropertyGraph propertyGraph) { - private readonly AssetPropertyGraph propertyGraph; - private readonly Dictionary baseToDerived = new Dictionary(); + this.propertyGraph = propertyGraph; + } - public AssetBaseToDerivedRegistry(AssetPropertyGraph propertyGraph) - { - this.propertyGraph = propertyGraph; - } + public void RegisterBaseToDerived(IAssetNode? baseNode, IAssetNode derivedNode) + { + var baseValue = baseNode?.Retrieve(); + if (baseValue == null) + return; - public void RegisterBaseToDerived(IAssetNode baseNode, IAssetNode derivedNode) + if (baseValue is IIdentifiable) { - var baseValue = baseNode?.Retrieve(); - if (baseValue == null) - return; - - if (baseValue is IIdentifiable) + baseToDerived[baseNode!] = derivedNode; + var baseMemberNode = baseNode as IAssetMemberNode; + if (baseMemberNode?.Target is not null && !propertyGraph.Definition.IsMemberTargetObjectReference(baseMemberNode, baseValue)) { - baseToDerived[baseNode] = derivedNode; - var baseMemberNode = baseNode as IAssetMemberNode; - if (baseMemberNode?.Target != null && !propertyGraph.Definition.IsMemberTargetObjectReference(baseMemberNode, baseValue)) - { - baseToDerived[baseMemberNode.Target] = ((IAssetMemberNode)derivedNode).Target; - } + baseToDerived[baseMemberNode.Target] = ((IAssetMemberNode)derivedNode).Target; } + } - var derivedObjectNode = derivedNode as IObjectNode; - var baseObjectNode = baseNode as IObjectNode; - if (derivedObjectNode?.ItemReferences != null && baseObjectNode?.ItemReferences != null) + var derivedObjectNode = derivedNode as IObjectNode; + var baseObjectNode = baseNode as IObjectNode; + if (derivedObjectNode?.ItemReferences is not null && baseObjectNode?.ItemReferences is not null) + { + foreach (var reference in derivedObjectNode.ItemReferences) { - foreach (var reference in derivedObjectNode.ItemReferences) - { - var target = propertyGraph.baseLinker.FindTargetReference(derivedNode, baseNode, reference); - if (target == null) - continue; + var target = propertyGraph.baseLinker.FindTargetReference(derivedNode, baseNode, reference); + if (target == null) + continue; - baseValue = target.TargetNode?.Retrieve(); - if (!propertyGraph.Definition.IsTargetItemObjectReference(baseObjectNode, target.Index, baseNode.Retrieve(target.Index))) + baseValue = target.TargetNode?.Retrieve(); + if (!propertyGraph.Definition.IsTargetItemObjectReference(baseObjectNode, target.Index, baseNode.Retrieve(target.Index))) + { + if (baseValue is IIdentifiable) { - if (baseValue is IIdentifiable) - { - baseToDerived[(IAssetNode)target.TargetNode] = (IAssetNode)derivedObjectNode.IndexedTarget(reference.Index); - } + baseToDerived[(IAssetNode)target.TargetNode] = (IAssetNode)derivedObjectNode.IndexedTarget(reference.Index); } } } } + } - public IIdentifiable ResolveFromBase(object baseObjectReference, IAssetNode derivedReferencerNode) - { - if (derivedReferencerNode == null) throw new ArgumentNullException(nameof(derivedReferencerNode)); - if (baseObjectReference == null) - return null; + public IIdentifiable? ResolveFromBase(object? baseObjectReference, IAssetNode derivedReferencerNode) + { + ArgumentNullException.ThrowIfNull(derivedReferencerNode); + if (baseObjectReference == null) + return null; - var baseNode = (IAssetNode)propertyGraph.Container.NodeContainer.GetNode(baseObjectReference); - IAssetNode derivedNode; - baseToDerived.TryGetValue(baseNode, out derivedNode); - return derivedNode?.Retrieve() as IIdentifiable; - } + var baseNode = (IAssetNode)propertyGraph.Container.NodeContainer.GetNode(baseObjectReference); + baseToDerived.TryGetValue(baseNode, out var derivedNode); + return derivedNode?.Retrieve() as IIdentifiable; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetCloningHelper.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetCloningHelper.cs index dce66efa4b..929c9549f8 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetCloningHelper.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetCloningHelper.cs @@ -1,67 +1,63 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Stride.Core.Assets.Yaml; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +/// +/// A class containing helper methods for asset cloning. +/// +public static class AssetCloningHelper { /// - /// A class containing helper methods for asset cloning. + /// Updates the paths in the given instance to reflect that new have been generated after cloning, + /// when has been used. /// - public static class AssetCloningHelper + /// The type of content in the metadata. + /// The metadata to update. + /// A dictionary representing the mapping between initial ids and their corresponding id in the cloned object. + /// If not null, this method will apply the remapping only for paths that are contained in the given base path. + public static void RemapIdentifiablePaths(YamlAssetMetadata metadata, Dictionary idRemapping, YamlAssetPath? basePath = null) { - /// - /// Updates the paths in the given instance to reflect that new have been generated after cloning, - /// when has been used. - /// - /// The type of content in the metadata. - /// The metadata to update. - /// A dictionary representing the mapping between initial ids and their corresponding id in the cloned object. - /// If not null, this method will apply the remapping only for paths that are contained in the given base path. - public static void RemapIdentifiablePaths(YamlAssetMetadata metadata, Dictionary idRemapping, YamlAssetPath basePath = null) - { - // Early exit if nothing to remap - if (metadata == null || idRemapping == null) - return; + // Early exit if nothing to remap + if (metadata == null || idRemapping == null) + return; - var replacements = new List>(); - foreach (var entry in metadata) - { - // Skip paths that doesn't start with the given base path. - if (basePath != null && !entry.Key.StartsWith(basePath)) - continue; + var replacements = new List>(); + foreach (var entry in metadata) + { + // Skip paths that doesn't start with the given base path. + if (basePath is not null && !entry.Key.StartsWith(basePath)) + continue; - var newPath = new YamlAssetPath(entry.Key.Elements.Select(x => FixupIdentifier(x, idRemapping))); - replacements.Add(Tuple.Create(entry.Key, newPath, entry.Value)); - } + var newPath = new YamlAssetPath(entry.Key.Elements.Select(x => FixupIdentifier(x, idRemapping))); + replacements.Add(Tuple.Create(entry.Key, newPath, entry.Value)); + } - // First remove everything, then re-add everything, in case we have a collision between an old path and a new path - foreach (var replacement in replacements) - { - metadata.Remove(replacement.Item1); - } - foreach (var replacement in replacements) - { - metadata.Set(replacement.Item2, replacement.Item3); - } + // First remove everything, then re-add everything, in case we have a collision between an old path and a new path + foreach (var replacement in replacements) + { + metadata.Remove(replacement.Item1); } + foreach (var replacement in replacements) + { + metadata.Set(replacement.Item2, replacement.Item3); + } + } - private static YamlAssetPath.Element FixupIdentifier(YamlAssetPath.Element element, Dictionary idRemapping) + private static YamlAssetPath.Element FixupIdentifier(YamlAssetPath.Element element, Dictionary idRemapping) + { + switch (element.Type) { - switch (element.Type) - { - case YamlAssetPath.ElementType.Index: - if (element.Value is Guid && idRemapping.TryGetValue((Guid)element.Value, out var newId)) - { - return new YamlAssetPath.Element(YamlAssetPath.ElementType.Index, newId); - } - return element; - case YamlAssetPath.ElementType.Member: - case YamlAssetPath.ElementType.ItemId: - return element; - default: - throw new ArgumentOutOfRangeException(); - } + case YamlAssetPath.ElementType.Index: + if (element.Value is Guid guid && idRemapping.TryGetValue(guid, out var newId)) + { + return new YamlAssetPath.Element(YamlAssetPath.ElementType.Index, newId); + } + return element; + case YamlAssetPath.ElementType.Member: + case YamlAssetPath.ElementType.ItemId: + return element; + default: + throw new ArgumentOutOfRangeException(); } } -} \ No newline at end of file +} diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetCompositeBaseToDerivedRegistry.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetCompositeBaseToDerivedRegistry.cs index 8fc6e97280..779216986f 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetCompositeBaseToDerivedRegistry.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetCompositeBaseToDerivedRegistry.cs @@ -1,42 +1,36 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +internal class AssetCompositeBaseToDerivedRegistry : IBaseToDerivedRegistry { - internal class AssetCompositeBaseToDerivedRegistry : IBaseToDerivedRegistry - { - private readonly Dictionary baseToInstances = new Dictionary(); - private readonly AssetPropertyGraph propertyGraph; + private readonly Dictionary baseToInstances = []; + private readonly AssetPropertyGraph propertyGraph; - public AssetCompositeBaseToDerivedRegistry(AssetPropertyGraph propertyGraph) - { - this.propertyGraph = propertyGraph; - } + public AssetCompositeBaseToDerivedRegistry(AssetPropertyGraph propertyGraph) + { + this.propertyGraph = propertyGraph; + } - public void RegisterBaseToDerived(IAssetNode baseNode, IAssetNode derivedNode) - { - var ownerPart = derivedNode.GetContent(NodesToOwnerPartVisitor.OwnerPartContentName); - var instanceId = (ownerPart?.Retrieve() as IAssetPartDesign)?.Base?.InstanceId ?? Guid.Empty; - AssetBaseToDerivedRegistry derivedRegistry; - if (!baseToInstances.TryGetValue(instanceId, out derivedRegistry)) - baseToInstances[instanceId] = derivedRegistry = new AssetBaseToDerivedRegistry(propertyGraph); + public void RegisterBaseToDerived(IAssetNode? baseNode, IAssetNode derivedNode) + { + var ownerPart = derivedNode.GetContent(NodesToOwnerPartVisitor.OwnerPartContentName); + var instanceId = (ownerPart?.Retrieve() as IAssetPartDesign)?.Base?.InstanceId ?? Guid.Empty; + if (!baseToInstances.TryGetValue(instanceId, out var derivedRegistry)) + baseToInstances[instanceId] = derivedRegistry = new AssetBaseToDerivedRegistry(propertyGraph); - derivedRegistry.RegisterBaseToDerived(baseNode, derivedNode); - } + derivedRegistry.RegisterBaseToDerived(baseNode, derivedNode); + } - public IIdentifiable ResolveFromBase(object baseObjectReference, IAssetNode derivedReferencerNode) - { - if (derivedReferencerNode == null) throw new ArgumentNullException(nameof(derivedReferencerNode)); - var ownerPart = derivedReferencerNode.GetContent(NodesToOwnerPartVisitor.OwnerPartContentName); - var instanceId = (ownerPart?.Retrieve() as IAssetPartDesign)?.Base?.InstanceId ?? Guid.Empty; - AssetBaseToDerivedRegistry derivedRegistry; - if (!baseToInstances.TryGetValue(instanceId, out derivedRegistry)) - return null; + public IIdentifiable? ResolveFromBase(object? baseObjectReference, IAssetNode derivedReferencerNode) + { + ArgumentNullException.ThrowIfNull(derivedReferencerNode); + var ownerPart = derivedReferencerNode.GetContent(NodesToOwnerPartVisitor.OwnerPartContentName); + var instanceId = (ownerPart?.Retrieve() as IAssetPartDesign)?.Base?.InstanceId ?? Guid.Empty; + if (!baseToInstances.TryGetValue(instanceId, out var derivedRegistry)) + return null; - return derivedRegistry.ResolveFromBase(baseObjectReference, derivedReferencerNode); - } + return derivedRegistry.ResolveFromBase(baseObjectReference, derivedReferencerNode); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetCompositeHierarchyPropertyGraph.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetCompositeHierarchyPropertyGraph.cs index 51f3e465f0..17d8102cca 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetCompositeHierarchyPropertyGraph.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetCompositeHierarchyPropertyGraph.cs @@ -1,95 +1,93 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using Stride.Core.Assets.Quantum.Visitors; using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.Diagnostics; using Stride.Core.Extensions; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +public abstract class AssetCompositeHierarchyPropertyGraph : AssetCompositePropertyGraph + where TAssetPart : class, IIdentifiable + where TAssetPartDesign : class, IAssetPartDesign { - public abstract class AssetCompositeHierarchyPropertyGraph : AssetCompositePropertyGraph - where TAssetPart : class, IIdentifiable - where TAssetPartDesign : class, IAssetPartDesign - { - /// - /// A dictionary mapping each base asset to a collection of instance ids existing in this asset. - /// - private readonly Dictionary, HashSet> basePartAssets = new Dictionary, HashSet>(); - /// - /// A dictionary mapping a tuple of (base part id, instance id) to the corresponding asset part in this asset. - /// - /// Part stored here are preserved after being removed, in case they have to come back later, for example if a part in the base is being moved (removed + added again). - private readonly Dictionary, TAssetPartDesign> baseInstanceMapping = new Dictionary, TAssetPartDesign>(); - - /// - /// A mapping of (base part id, instance id) corresponding to deleted parts in specific instances of this asset which base part exists in the base asset. - /// - private readonly HashSet> deletedPartsInstanceMapping = new HashSet>(); - /// - /// A dictionary mapping instance ids to the common ancestor of the parts corresponding to that instance id in this asset. - /// - /// This dictionary is used to remember where the part instance was located, if during some time all its parts are removed, for example during some specific operaiton in the base asset. - private readonly Dictionary instancesCommonAncestors = new Dictionary(); - /// - /// A hashset of nodes representing the collections of children from a parent part. - /// - private readonly HashSet registeredChildParts = new HashSet(); - - protected AssetCompositeHierarchyPropertyGraph([NotNull] AssetPropertyGraphContainer container, [NotNull] AssetItem assetItem, ILogger logger) - : base(container, assetItem, logger) + /// + /// A dictionary mapping each base asset to a collection of instance ids existing in this asset. + /// + private readonly Dictionary, HashSet> basePartAssets = []; + /// + /// A dictionary mapping a tuple of (base part id, instance id) to the corresponding asset part in this asset. + /// + /// Part stored here are preserved after being removed, in case they have to come back later, for example if a part in the base is being moved (removed + added again). + private readonly Dictionary, TAssetPartDesign> baseInstanceMapping = []; + + /// + /// A mapping of (base part id, instance id) corresponding to deleted parts in specific instances of this asset which base part exists in the base asset. + /// + private readonly HashSet> deletedPartsInstanceMapping = []; + /// + /// A dictionary mapping instance ids to the common ancestor of the parts corresponding to that instance id in this asset. + /// + /// This dictionary is used to remember where the part instance was located, if during some time all its parts are removed, for example during some specific operaiton in the base asset. + private readonly Dictionary instancesCommonAncestors = []; + /// + /// A hashset of nodes representing the collections of children from a parent part. + /// + private readonly HashSet registeredChildParts = []; + + protected AssetCompositeHierarchyPropertyGraph(AssetPropertyGraphContainer container, AssetItem assetItem, ILogger logger) + : base(container, assetItem, logger) + { + HierarchyNode = RootNode[nameof(AssetCompositeHierarchy.Hierarchy)].Target!; + var rootPartsNode = HierarchyNode[nameof(AssetCompositeHierarchyData.RootParts)].Target!; + rootPartsNode.ItemChanged += RootPartsChanged; + foreach (var childPartNode in Asset.Hierarchy.Parts.Values.SelectMany(x => RetrieveChildPartNodes(x.Part))) { - HierarchyNode = RootNode[nameof(AssetCompositeHierarchy.Hierarchy)].Target; - var rootPartsNode = HierarchyNode[nameof(AssetCompositeHierarchyData.RootParts)].Target; - rootPartsNode.ItemChanged += RootPartsChanged; - foreach (var childPartNode in Asset.Hierarchy.Parts.Values.SelectMany(x => RetrieveChildPartNodes(x.Part))) - { - RegisterChildPartNode(childPartNode); - } - var partsNode = HierarchyNode[nameof(AssetCompositeHierarchyData, IIdentifiable>.Parts)].Target; - partsNode.ItemChanged += PartsChanged; + RegisterChildPartNode(childPartNode); + } + var partsNode = HierarchyNode[nameof(AssetCompositeHierarchyData, IIdentifiable>.Parts)].Target!; + partsNode.ItemChanged += PartsChanged; - foreach (var part in Asset.Hierarchy.Parts.Values) - { - LinkToOwnerPart(Container.NodeContainer.GetNode(part.Part), part); - } + foreach (var part in Asset.Hierarchy.Parts.Values) + { + LinkToOwnerPart(Container.NodeContainer.GetNode(part.Part)!, part); } + } - /// - public new AssetCompositeHierarchy Asset => (AssetCompositeHierarchy)base.Asset; + /// + public new AssetCompositeHierarchy Asset => (AssetCompositeHierarchy)base.Asset; - protected IObjectNode HierarchyNode { get; } + protected IObjectNode HierarchyNode { get; } - /// - /// Gets the name of the property targeting the part in the type. - /// - [NotNull] - protected virtual string PartName => nameof(IAssetPartDesign.Part); + /// + /// Gets the name of the property targeting the part in the type. + /// + protected virtual string PartName => nameof(IAssetPartDesign.Part); - /// - /// Raised when a part is added to this asset. - /// - public event EventHandler PartAdded; + /// + /// Raised when a part is added to this asset. + /// + public event EventHandler? PartAdded; - /// - /// Raised when a part is removed from this asset. - /// - public event EventHandler PartRemoved; + /// + /// Raised when a part is removed from this asset. + /// + public event EventHandler? PartRemoved; - public abstract bool IsChildPartReference(IGraphNode node, NodeIndex index); + public abstract bool IsChildPartReference(IGraphNode node, NodeIndex index); - /// - public override void Dispose() + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) { - base.Dispose(); - var rootPartsNode = HierarchyNode[nameof(AssetCompositeHierarchyData.RootParts)].Target; + var rootPartsNode = HierarchyNode[nameof(AssetCompositeHierarchyData.RootParts)].Target!; rootPartsNode.ItemChanged -= RootPartsChanged; registeredChildParts.ToList().ForEach(UnregisterChildPartNode); - var partsNode = HierarchyNode[nameof(AssetCompositeHierarchyData, IIdentifiable>.Parts)].Target; + var partsNode = HierarchyNode[nameof(AssetCompositeHierarchyData, IIdentifiable>.Parts)].Target!; partsNode.ItemChanged -= PartsChanged; foreach (var basePartAsset in basePartAssets.Keys) @@ -99,775 +97,771 @@ public override void Dispose() } basePartAssets.Clear(); } + } + + /// + public override void ClearReferencesToObjects(IEnumerable objectIds) + { + ArgumentNullException.ThrowIfNull(objectIds); + var visitor = new ClearObjectReferenceVisitor(Definition, objectIds, (node, index) => !IsChildPartReference(node, index)); + visitor.Visit(RootNode); + } - /// - public override void ClearReferencesToObjects(IEnumerable objectIds) + /// + /// Gets all the instance ids corresponding to an instance of the part of the given base asset. + /// + /// The property graph of the base asset for which to return instance ids. + /// A collection of instances ids corresponding to instances of parts of the given base asset. + public IReadOnlyCollection GetInstanceIds(AssetCompositeHierarchyPropertyGraph baseAssetPropertyGraph) + { + ArgumentNullException.ThrowIfNull(baseAssetPropertyGraph); + basePartAssets.TryGetValue(baseAssetPropertyGraph, out var instanceIds); + return (IReadOnlyCollection?)instanceIds ?? []; + } + + /// + /// Retrieves all the assets that contains bases of parts of this asset. + /// + /// + public IReadOnlyCollection> GetBasePartAssets() + { + return basePartAssets.Keys; + } + + public void BreakBasePartLinks(IEnumerable assetPartDesigns) + { + foreach (var part in assetPartDesigns.Where(x => x.Base is not null)) { - if (objectIds == null) throw new ArgumentNullException(nameof(objectIds)); - var visitor = new ClearObjectReferenceVisitor(Definition, objectIds, (node, index) => !IsChildPartReference(node, index)); - visitor.Visit(RootNode); + var node = Container.NodeContainer.GetNode(part)!; + node[nameof(IAssetPartDesign.Base)].Update(null); + // We must refresh the base to stop further update from the base asset to the instance parts + RefreshBase((IAssetNode)node, null); } + } - /// - /// Gets all the instance ids corresponding to an instance of the part of the given base asset. - /// - /// The property graph of the base asset for which to return instance ids. - /// A collection of instances ids corresponding to instances of parts of the given base asset. - [NotNull] - public IReadOnlyCollection GetInstanceIds([NotNull] AssetCompositeHierarchyPropertyGraph baseAssetPropertyGraph) + /// + /// Adds a part to this asset. This method updates the collection. + /// If is null, it also updates the collection. + /// Otherwise, it updates the collection containing the list of children from the parent part. + /// + /// A collection containing the part to add and all its child parts recursively, with their associated . + /// The part to add to this asset. + /// The parent part in which to add the child part. + /// The index in which to insert this part, either in the collection of root part or in the collection of child part of the parent part.. + public void AddPartToAsset(AssetPartCollection newPartCollection, TAssetPartDesign child, TAssetPart? parent, int index) + { + // This insert method does not support negative indices. + ArgumentOutOfRangeException.ThrowIfNegative(index); + // For consistency, we need to always add first to the Parts collection before adding to RootParts or as a child of an existing part + InsertPartInPartsCollection(newPartCollection, child); + if (parent is null) { - if (baseAssetPropertyGraph == null) throw new ArgumentNullException(nameof(baseAssetPropertyGraph)); - HashSet instanceIds; - basePartAssets.TryGetValue(baseAssetPropertyGraph, out instanceIds); - return (IReadOnlyCollection)instanceIds ?? new Guid[0]; + var rootPartsNode = HierarchyNode[nameof(AssetCompositeHierarchyData.RootParts)].Target!; + rootPartsNode.Add(child.Part, new NodeIndex(index)); } - - /// - /// Retrieves all the assets that contains bases of parts of this asset. - /// - /// - [NotNull] - public IReadOnlyCollection> GetBasePartAssets() + else { - return basePartAssets.Keys; + AddChildPartToParentPart(parent, child.Part, index); } + } - public void BreakBasePartLinks([NotNull] IEnumerable assetPartDesigns) + /// + /// Removes a part from this asset. This method updates the collection. + /// If the part to remove is a root part, it also updates the collection. + /// Otherwise, it updates the collection containing the list of children from the parent of this part. + /// + /// The part to remove from this asset. + public void RemovePartFromAsset(TAssetPartDesign partDesign) + { + if (!Asset.Hierarchy.RootParts.Contains(partDesign.Part)) { - foreach (var part in assetPartDesigns.Where(x => x.Base != null)) - { - var node = Container.NodeContainer.GetNode(part); - node[nameof(IAssetPartDesign.Base)].Update(null); - // We must refresh the base to stop further update from the base asset to the instance parts - RefreshBase((IAssetNode)node, null); - } + var parent = Asset.GetParent(partDesign.Part) + ?? throw new InvalidOperationException("The part has no parent but is not in the RootParts collection."); + RemoveChildPartFromParentPart(parent, partDesign.Part); } - - /// - /// Adds a part to this asset. This method updates the collection. - /// If is null, it also updates the collection. - /// Otherwise, it updates the collection containing the list of children from the parent part. - /// - /// A collection containing the part to add and all its child parts recursively, with their associated . - /// The part to add to this asset. - /// The parent part in which to add the child part. - /// The index in which to insert this part, either in the collection of root part or in the collection of child part of the parent part.. - public void AddPartToAsset(AssetPartCollection newPartCollection, [NotNull] TAssetPartDesign child, TAssetPart parent, int index) + else { - // This insert method does not support negative indices. - if (index < 0) throw new ArgumentOutOfRangeException(nameof(index)); - // For consistency, we need to always add first to the Parts collection before adding to RootParts or as a child of an existing part - InsertPartInPartsCollection(newPartCollection, child); - if (parent == null) - { - var rootPartsNode = HierarchyNode[nameof(AssetCompositeHierarchyData.RootParts)].Target; - rootPartsNode.Add(child.Part, new NodeIndex(index)); - } - else - { - AddChildPartToParentPart(parent, child.Part, index); - } + var index = new NodeIndex(Asset.Hierarchy.RootParts.IndexOf(partDesign.Part)); + var rootPartsNode = HierarchyNode[nameof(AssetCompositeHierarchyData.RootParts)].Target!; + rootPartsNode.Remove(partDesign.Part, index); } + RemovePartFromPartsCollection(partDesign); + } - /// - /// Removes a part from this asset. This method updates the collection. - /// If the part to remove is a root part, it also updates the collection. - /// Otherwise, it updates the collection containing the list of children from the parent of this part. - /// - /// The part to remove from this asset. - public void RemovePartFromAsset([NotNull] TAssetPartDesign partDesign) - { - if (!Asset.Hierarchy.RootParts.Contains(partDesign.Part)) + /// + /// Deletes the given parts and all its children, recursively, and clear all object references to it. + /// + /// The parts to delete. + /// A mapping of the base information (base part id, instance id) of the deleted parts that have a base. + public void DeleteParts(IEnumerable partDesigns, out HashSet> deletedPartsMapping) + { + ArgumentNullException.ThrowIfNull(partDesigns); + var partsToDelete = new Stack(partDesigns); + var referencesToClear = new HashSet(); + deletedPartsMapping = []; + while (partsToDelete.Count > 0) + { + // We need to remove children first to keep consistency in our data + var partToDelete = partsToDelete.Peek(); + var children = Asset.EnumerateChildPartDesigns(partToDelete, Asset.Hierarchy, false).ToList(); + if (children.Count > 0) { - var parent = Asset.GetParent(partDesign.Part); - if (parent == null) throw new InvalidOperationException("The part has no parent but is not in the RootParts collection."); - RemoveChildPartFromParentPart(parent, partDesign.Part); + // Enqueue children if there is any, and re-process the stack + children.ForEach(x => partsToDelete.Push(x)); + continue; } - else + // No children to process, we can safely remove the current part from the stack + partToDelete = partsToDelete.Pop(); + // First remove all references to the part we are deleting + // Note: we must do this first so instances of this base will be able to properly make the connection with the base part being cleared + var containedIdentifiables = IdentifiableObjectCollector.Collect(Definition, Container.NodeContainer.GetNode(partToDelete.Part)); + containedIdentifiables.Keys.ForEach(x => referencesToClear.Add(x)); + referencesToClear.Add(partToDelete.Part.Id); + // Then actually remove the part from the hierarchy + RemovePartFromAsset(partToDelete); + // Keep track of deleted part instances + if (partToDelete.Base is not null) { - var index = new NodeIndex(Asset.Hierarchy.RootParts.IndexOf(partDesign.Part)); - var rootPartsNode = HierarchyNode[nameof(AssetCompositeHierarchyData.RootParts)].Target; - rootPartsNode.Remove(partDesign.Part, index); + deletedPartsMapping.Add(Tuple.Create(partToDelete.Base.BasePartId, partToDelete.Base.InstanceId)); } - RemovePartFromPartsCollection(partDesign); } + TrackDeletedInstanceParts(deletedPartsMapping); + ClearReferencesToObjects(referencesToClear); + } - /// - /// Deletes the given parts and all its children, recursively, and clear all object references to it. - /// - /// The parts to delete. - /// A mapping of the base information (base part id, instance id) of the deleted parts that have a base. - public void DeleteParts([NotNull] IEnumerable partDesigns, [NotNull] out HashSet> deletedPartsMapping) + /// + public override IGraphNode FindTarget(IGraphNode sourceNode, IGraphNode target) + { + // TODO: try to generalize what the overrides of this implementation are doing. + // Connect the parts to their base if any. + if (sourceNode.Retrieve() is TAssetPart part && sourceNode is IObjectNode) { - if (partDesigns == null) throw new ArgumentNullException(nameof(partDesigns)); - var partsToDelete = new Stack(partDesigns); - var referencesToClear = new HashSet(); - deletedPartsMapping = new HashSet>(); - while (partsToDelete.Count > 0) + // The part might be being moved and could possibly be currently not into the Parts collection. + if (Asset.Hierarchy.Parts.TryGetValue(part.Id, out var partDesign) && partDesign.Base is not null) { - // We need to remove children first to keep consistency in our data - var partToDelete = partsToDelete.Peek(); - var children = Asset.EnumerateChildPartDesigns(partToDelete, Asset.Hierarchy, false).ToList(); - if (children.Count > 0) - { - // Enqueue children if there is any, and re-process the stack - children.ForEach(x => partsToDelete.Push(x)); - continue; - } - // No children to process, we can safely remove the current part from the stack - partToDelete = partsToDelete.Pop(); - // First remove all references to the part we are deleting - // Note: we must do this first so instances of this base will be able to properly make the connection with the base part being cleared - var containedIdentifiables = IdentifiableObjectCollector.Collect(Definition, Container.NodeContainer.GetNode(partToDelete.Part)); - containedIdentifiables.Keys.ForEach(x => referencesToClear.Add(x)); - referencesToClear.Add(partToDelete.Part.Id); - // Then actually remove the part from the hierarchy - RemovePartFromAsset(partToDelete); - // Keep track of deleted part instances - if (partToDelete.Base != null) - { - deletedPartsMapping.Add(Tuple.Create(partToDelete.Base.BasePartId, partToDelete.Base.InstanceId)); - } + var baseAssetGraph = Container.TryGetGraph(partDesign.Base.BasePartAsset.Id); + // Base asset might have been deleted + if (baseAssetGraph is null) + return base.FindTarget(sourceNode, target); + + // Part might have been deleted in base asset + ((AssetCompositeHierarchy)baseAssetGraph.RootNode.Retrieve()).Hierarchy.Parts.TryGetValue(partDesign.Base.BasePartId, out TAssetPartDesign basePart); + return basePart is not null ? Container.NodeContainer.GetOrCreateNode(basePart.Part) : base.FindTarget(sourceNode, target); } - TrackDeletedInstanceParts(deletedPartsMapping); - ClearReferencesToObjects(referencesToClear); } - /// - public override IGraphNode FindTarget([NotNull] IGraphNode sourceNode, IGraphNode target) - { - // TODO: try to generalize what the overrides of this implementation are doing. - // Connect the parts to their base if any. - if (sourceNode.Retrieve() is TAssetPart part && sourceNode is IObjectNode) - { - // The part might be being moved and could possibly be currently not into the Parts collection. - if (Asset.Hierarchy.Parts.TryGetValue(part.Id, out TAssetPartDesign partDesign) && partDesign.Base != null) - { - var baseAssetGraph = Container.TryGetGraph(partDesign.Base.BasePartAsset.Id); - // Base asset might have been deleted - if (baseAssetGraph == null) - return base.FindTarget(sourceNode, target); - - // Part might have been deleted in base asset - ((AssetCompositeHierarchy)baseAssetGraph.RootNode.Retrieve()).Hierarchy.Parts.TryGetValue(partDesign.Base.BasePartId, out TAssetPartDesign basePart); - return basePart != null ? Container.NodeContainer.GetOrCreateNode(basePart.Part) : base.FindTarget(sourceNode, target); - } - } + return base.FindTarget(sourceNode, target); + } - return base.FindTarget(sourceNode, target); - } + /// + /// Clones a sub-hierarchy of a composite hierarchical asset. + /// + /// The container in which are the nodes of the hierarchy to clone and in which to create nodes for the cloned hierarchy, used to propagate metadata (overrides, etc.) if needed. + /// The asset from which to clone sub-hierarchies. + /// The ids that are the roots of the sub-hierarchies to clone. + /// The flags customizing the cloning operation. + /// A dictionary containing the remapping of if has been passed to the cloner. + /// A corresponding to the cloned parts. + /// The parts passed to this methods must be independent in the hierarchy. + public static AssetCompositeHierarchyData CloneSubHierarchies(AssetNodeContainer nodeContainer, AssetCompositeHierarchy asset, + IEnumerable sourceRootIds, SubHierarchyCloneFlags flags, out Dictionary idRemapping) + { + return CloneSubHierarchies(nodeContainer, nodeContainer, asset, sourceRootIds, flags, out idRemapping); + } + + /// + /// Clones a sub-hierarchy of a composite hierarchical asset. + /// + /// The container in which are the nodes of the hierarchy to clone, used to extract metadata (overrides, etc.) if needed. + /// The container in which the nodes of the cloned hierarchy should be created, used to re-apply metadata (overrides, etc.) if needed. + /// The asset from which to clone sub-hierarchies. + /// The ids that are the roots of the sub-hierarchies to clone. + /// The flags customizing the cloning operation. + /// A dictionary containing the remapping of if has been passed to the cloner. + /// A corresponding to the cloned parts. + /// The parts passed to this methods must be independent in the hierarchy. + public static AssetCompositeHierarchyData CloneSubHierarchies(AssetNodeContainer sourceNodeContainer, AssetNodeContainer targetNodeContainer, + AssetCompositeHierarchy asset, IEnumerable sourceRootIds, SubHierarchyCloneFlags flags, out Dictionary idRemapping) + { + ArgumentNullException.ThrowIfNull(sourceNodeContainer); + ArgumentNullException.ThrowIfNull(targetNodeContainer); + ArgumentNullException.ThrowIfNull(asset); + ArgumentNullException.ThrowIfNull(sourceRootIds); - /// - /// Clones a sub-hierarchy of a composite hierarchical asset. - /// - /// The container in which are the nodes of the hierarchy to clone and in which to create nodes for the cloned hierarchy, used to propagate metadata (overrides, etc.) if needed. - /// The asset from which to clone sub-hierarchies. - /// The ids that are the roots of the sub-hierarchies to clone. - /// The flags customizing the cloning operation. - /// A dictionary containing the remapping of if has been passed to the cloner. - /// A corresponding to the cloned parts. - /// The parts passed to this methods must be independent in the hierarchy. - public static AssetCompositeHierarchyData CloneSubHierarchies([NotNull] AssetNodeContainer nodeContainer, [NotNull] AssetCompositeHierarchy asset, - [NotNull] IEnumerable sourceRootIds, SubHierarchyCloneFlags flags, [NotNull] out Dictionary idRemapping) + // Extract the actual sub hierarchies to clone from the asset into a new instance of AssetCompositeHierarchyData + var subTreeHierarchy = new AssetCompositeHierarchyData(); + foreach (var rootId in sourceRootIds) { - return CloneSubHierarchies(nodeContainer, nodeContainer, asset, sourceRootIds, flags, out idRemapping); + if (!asset.Hierarchy.Parts.TryGetValue(rootId, out var item)) + throw new ArgumentException("The source root parts must be parts of this asset.", nameof(sourceRootIds)); + + subTreeHierarchy.RootParts.Add(item.Part); + + subTreeHierarchy.Parts.Add(item); + foreach (var subTreePart in asset.EnumerateChildParts(item.Part, true)) + subTreeHierarchy.Parts.Add(asset.Hierarchy.Parts[subTreePart.Id]); } - /// - /// Clones a sub-hierarchy of a composite hierarchical asset. - /// - /// The container in which are the nodes of the hierarchy to clone, used to extract metadata (overrides, etc.) if needed. - /// The container in which the nodes of the cloned hierarchy should be created, used to re-apply metadata (overrides, etc.) if needed. - /// The asset from which to clone sub-hierarchies. - /// The ids that are the roots of the sub-hierarchies to clone. - /// The flags customizing the cloning operation. - /// A dictionary containing the remapping of if has been passed to the cloner. - /// A corresponding to the cloned parts. - /// The parts passed to this methods must be independent in the hierarchy. - public static AssetCompositeHierarchyData CloneSubHierarchies([NotNull] AssetNodeContainer sourceNodeContainer, [NotNull] AssetNodeContainer targetNodeContainer, - [NotNull] AssetCompositeHierarchy asset, [NotNull] IEnumerable sourceRootIds, SubHierarchyCloneFlags flags, [NotNull] out Dictionary idRemapping) - { - if (sourceNodeContainer == null) throw new ArgumentNullException(nameof(sourceNodeContainer)); - if (targetNodeContainer == null) throw new ArgumentNullException(nameof(targetNodeContainer)); - if (asset == null) throw new ArgumentNullException(nameof(asset)); - if (sourceRootIds == null) throw new ArgumentNullException(nameof(sourceRootIds)); - - // Extract the actual sub hierarchies to clone from the asset into a new instance of AssetCompositeHierarchyData - var subTreeHierarchy = new AssetCompositeHierarchyData(); - foreach (var rootId in sourceRootIds) - { - if (!asset.Hierarchy.Parts.ContainsKey(rootId)) - throw new ArgumentException(@"The source root parts must be parts of this asset.", nameof(sourceRootIds)); + var assetType = asset.GetType(); - subTreeHierarchy.RootParts.Add(asset.Hierarchy.Parts[rootId].Part); + // Create a new empty asset of the same type, and assign the sub hierachies to clone to it + var cloneAsset = (AssetCompositeHierarchy)Activator.CreateInstance(assetType)!; + cloneAsset.Hierarchy = subTreeHierarchy; + var assetDefinition = AssetQuantumRegistry.GetDefinition(assetType); - subTreeHierarchy.Parts.Add(asset.Hierarchy.Parts[rootId]); - foreach (var subTreePart in asset.EnumerateChildParts(asset.Hierarchy.Parts[rootId].Part, true)) - subTreeHierarchy.Parts.Add(asset.Hierarchy.Parts[subTreePart.Id]); - } + // We get the node corresponding to the new asset in the source NodeContainer, to be able to generate metadata (overrides, object references) needed for cloning. + var rootNode = sourceNodeContainer.GetOrCreateNode(cloneAsset); + var externalReferences = ExternalReferenceCollector.GetExternalReferences(assetDefinition, rootNode); + var overrides = (flags & SubHierarchyCloneFlags.RemoveOverrides) == 0 ? GenerateOverridesForSerialization(rootNode) : null; - var assetType = asset.GetType(); + // Now we ready to clone, let's just translate the flags and pass everything to the asset cloner. + var clonerFlags = AssetClonerFlags.None; + if ((flags & SubHierarchyCloneFlags.GenerateNewIdsForIdentifiableObjects) != 0) + clonerFlags |= AssetClonerFlags.GenerateNewIdsForIdentifiableObjects; + if ((flags & SubHierarchyCloneFlags.CleanExternalReferences) != 0) + clonerFlags |= AssetClonerFlags.ClearExternalReferences; + // We don't need to clone the asset itself, just the hierarchy. The asset itself is just useful so the property graph is in a normal context to do what we need. + var clonedHierarchy = AssetCloner.Clone(subTreeHierarchy, clonerFlags, externalReferences, out idRemapping); - // Create a new empty asset of the same type, and assign the sub hierachies to clone to it - var cloneAsset = (AssetCompositeHierarchy)Activator.CreateInstance(assetType); - cloneAsset.Hierarchy = subTreeHierarchy; - var assetDefinition = AssetQuantumRegistry.GetDefinition(assetType); + if ((flags & SubHierarchyCloneFlags.RemoveOverrides) == 0) + { + // We need to propagate the override information to the nodes of the cloned objects into the target node container. + // Let's reuse our temporary asset, and get its node in the target node container. + rootNode = targetNodeContainer.GetOrCreateNode(cloneAsset); + // Replace the initial hierarchy by the cloned one (through the Update method, in case the target container is the same as the source one). + rootNode[nameof(AssetCompositeHierarchy.Hierarchy)].Update(clonedHierarchy); + // Remap the paths to overriden properties in case we generated new ids for identifiable objects. + AssetCloningHelper.RemapIdentifiablePaths(overrides, idRemapping); + // Finally apply the overrides that come from the source parts. + ApplyOverrides((IAssetNode)rootNode, overrides); + } - // We get the node corresponding to the new asset in the source NodeContainer, to be able to generate metadata (overrides, object references) needed for cloning. - var rootNode = sourceNodeContainer.GetOrCreateNode(cloneAsset); - var externalReferences = ExternalReferenceCollector.GetExternalReferences(assetDefinition, rootNode); - var overrides = (flags & SubHierarchyCloneFlags.RemoveOverrides) == 0 ? GenerateOverridesForSerialization(rootNode) : null; + return clonedHierarchy; + } - // Now we ready to clone, let's just translate the flags and pass everything to the asset cloner. - var clonerFlags = AssetClonerFlags.None; - if ((flags & SubHierarchyCloneFlags.GenerateNewIdsForIdentifiableObjects) != 0) - clonerFlags |= AssetClonerFlags.GenerateNewIdsForIdentifiableObjects; - if ((flags & SubHierarchyCloneFlags.CleanExternalReferences) != 0) - clonerFlags |= AssetClonerFlags.ClearExternalReferences; - // We don't need to clone the asset itself, just the hierarchy. The asset itself is just useful so the property graph is in a normal context to do what we need. - var clonedHierarchy = AssetCloner.Clone(subTreeHierarchy, clonerFlags, externalReferences, out idRemapping); + /// + public override void RefreshBase() + { + base.RefreshBase(); + UpdateAssetPartBases(); + } - if ((flags & SubHierarchyCloneFlags.RemoveOverrides) == 0) - { - // We need to propagate the override information to the nodes of the cloned objects into the target node container. - // Let's reuse our temporary asset, and get its node in the target node container. - rootNode = targetNodeContainer.GetOrCreateNode(cloneAsset); - // Replace the initial hierarchy by the cloned one (through the Update method, in case the target container is the same as the source one). - rootNode[nameof(AssetCompositeHierarchy.Hierarchy)].Update(clonedHierarchy); - // Remap the paths to overriden properties in case we generated new ids for identifiable objects. - AssetCloningHelper.RemapIdentifiablePaths(overrides, idRemapping); - // Finally apply the overrides that come from the source parts. - ApplyOverrides((IAssetNode)rootNode, overrides); - } + /// + public override void RefreshBase(IAssetNode node, IAssetNode? baseNode) + { + base.RefreshBase(node, baseNode); + UpdateAssetPartBases(); + } - return clonedHierarchy; - } + /// + /// Tracks the given deleted instance parts. + /// + /// A mapping of deleted parts (base part id, instance id). + public void TrackDeletedInstanceParts(IEnumerable> deletedPartsMapping) + { + ArgumentNullException.ThrowIfNull(deletedPartsMapping); + deletedPartsInstanceMapping.UnionWith(deletedPartsMapping); + } - /// - public override void RefreshBase() - { - base.RefreshBase(); - UpdateAssetPartBases(); - } + /// + /// Untracks the given deleted instance parts. + /// + /// A mapping of deleted parts (base part id, instance id). + public void UntrackDeletedInstanceParts(IEnumerable> deletedPartsMapping) + { + ArgumentNullException.ThrowIfNull(deletedPartsMapping); + deletedPartsInstanceMapping.ExceptWith(deletedPartsMapping); + } - /// - public override void RefreshBase(IAssetNode node, IAssetNode baseNode) + /// + protected override void FinalizeInitialization() + { + // Track parts that were removed in instances by comparing to the base + foreach (var kv in basePartAssets) { - base.RefreshBase(node, baseNode); - UpdateAssetPartBases(); + var baseAsset = kv.Key.Asset; + var instanceIds = kv.Value; + var baseParts = baseAsset.Hierarchy.Parts.Keys.SelectMany(_ => instanceIds, Tuple.Create); + var existingParts = baseInstanceMapping.Keys; + var deletedParts = baseParts.Except(existingParts); + TrackDeletedInstanceParts(deletedParts); } + } - /// - /// Tracks the given deleted instance parts. - /// - /// A mapping of deleted parts (base part id, instance id). - public void TrackDeletedInstanceParts([NotNull] IEnumerable> deletedPartsMapping) + /// + /// Retrieves the Quantum instances containing the child parts. These contents can be collections or single values. + /// + /// The part instance for which to retrieve the Quantum content/ + /// A sequence containing all contents containing child parts. + // TODO: this method probably doesn't need to return an enumerable, our current use case are single content only. + protected abstract IEnumerable RetrieveChildPartNodes(TAssetPart part); + + /// + /// Retrieves the corresponding to the given part. + /// + /// The part for which to retrieve the id. + protected abstract Guid GetIdFromChildPart(object part); + + /// + /// Adds the given child part to the list of children of the given parent part. + /// + /// + /// The child part. + /// The index of the child part in the list of children of the parent part. + /// This method does not modify the contained in this asset. + protected abstract void AddChildPartToParentPart(TAssetPart parentPart, TAssetPart childPart, int index); + + /// + /// Removes the given child part from the list of children of the given parent part. + /// + /// + /// The child part. + /// This method does not modify the contained in this asset. + protected abstract void RemoveChildPartFromParentPart(TAssetPart parentPart, TAssetPart childPart); + + /// + /// When a part is added to the base asset, it could be the result of a move (remove + add). + /// In that case, remove the new and replace it with the . + /// + /// The cloned base hierarchy. + /// The cloned part to replace. + /// The existing part to restore. + /// + /// + /// Inheriting instance can override this method to perform additional operations. + /// + protected virtual void ReuseExistingPart(AssetCompositeHierarchyData baseHierarchy, TAssetPartDesign clonedPart, TAssetPartDesign existingPart) + { + // Replace the cloned part by the one to restore in the list of root if needed + if (baseHierarchy.RootParts.Remove(clonedPart.Part)) { - if (deletedPartsMapping == null) throw new ArgumentNullException(nameof(deletedPartsMapping)); - deletedPartsInstanceMapping.UnionWith(deletedPartsMapping); + baseHierarchy.RootParts.Add(existingPart.Part); } - /// - /// Untracks the given deleted instance parts. - /// - /// A mapping of deleted parts (base part id, instance id). - public void UntrackDeletedInstanceParts([NotNull] IEnumerable> deletedPartsMapping) - { - if (deletedPartsMapping == null) throw new ArgumentNullException(nameof(deletedPartsMapping)); - deletedPartsInstanceMapping.ExceptWith(deletedPartsMapping); - } + // Replace the cloned part by the one to restore in the list of parts + if (!baseHierarchy.Parts.Remove(clonedPart.Part.Id)) throw new InvalidOperationException("The new part should be in the baseHierarchy."); + baseHierarchy.Parts.Add(existingPart); + } + + /// + /// Indicates whether a new part added in a base asset should be also cloned and added to this asset. + /// + /// The property graph of the base asset. + /// The new part that has been added in the base asset. + /// The parent of the new part that has been added in the base asset. + /// The instance id for which the part might be cloned. + /// true if the part should be cloned and added to this asset; otherwise, false. + protected virtual bool ShouldAddNewPartFromBase(AssetCompositeHierarchyPropertyGraph baseAssetGraph, TAssetPartDesign newPart, TAssetPart? newPartParent, Guid instanceId) + { + return !deletedPartsInstanceMapping.Contains(Tuple.Create(newPart.Part.Id, instanceId)); + } + + protected virtual void RewriteIds(TAssetPart targetPart, TAssetPart sourcePart) + { + // TODO: this method is temporary! + targetPart.Id = sourcePart.Id; + } - /// - protected override void FinalizeInitialization() + /// + /// Finds the best index (and parent) at which to insert a new part that is propagated after being added to one of the bases of this asset. + /// + /// The base asset for the part that has been added. + /// The new part that has been added to the base. + /// The parent part of the part that has been added to the base. + /// The id of the instance for which we are looking for an index and parent. + /// The parent in which to insert the new instance part. If null, the new part will be inserted as root of the hierarchy. + /// The index at which to insert the new part in the instance, or a negative value if the part should be discarded. + protected virtual int FindBestInsertIndex(AssetCompositeHierarchy baseAsset, TAssetPartDesign newBasePart, TAssetPart? newBasePartParent, Guid instanceId, out TAssetPartDesign? instanceParent) + { + instanceParent = null; + var insertIndex = -1; + + // First, let's find out where it is the best to insert this new part + if (newBasePartParent is null) { - // Track parts that were removed in instances by comparing to the base - foreach (var kv in basePartAssets) + // The part is a root, so we must place it according to its sibling (since no parent exists). + var partIndex = baseAsset.Hierarchy.RootParts.IndexOf(x => x.Id == newBasePart.Part.Id); + // Let's try to find a sibling in the parts preceding it, in order + for (var i = partIndex - 1; i >= 0 && insertIndex < 0; --i) { - var baseAsset = kv.Key.Asset; - var instanceIds = kv.Value; - var baseParts = baseAsset.Hierarchy.Parts.Keys.SelectMany(basePartId => instanceIds, Tuple.Create); - var existingParts = baseInstanceMapping.Keys; - var deletedParts = baseParts.Except(existingParts); - TrackDeletedInstanceParts(deletedParts); + var sibling = baseAsset.Hierarchy.Parts[baseAsset.Hierarchy.RootParts[i].Id]; + var instanceSibling = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == sibling.Part.Id); + // This sibling still exists instance-side, let's get its parent. + if (instanceSibling is not null) + { + // If the sibling itself has a parent instance-side, let's use the same parent and insert after it + // Otherwise the sibling is root, let's insert after it in the root parts + var parent = Asset.GetParent(instanceSibling.Part); + instanceParent = parent is not null ? Asset.Hierarchy.Parts[parent.Id] : null; + insertIndex = Asset.IndexOf(instanceSibling.Part) + 1; + break; + } } - } - /// - /// Retrieves the Quantum instances containing the child parts. These contents can be collections or single values. - /// - /// The part instance for which to retrieve the Quantum content/ - /// A sequence containing all contents containing child parts. - // TODO: this method probably doesn't need to return an enumerable, our current use case are single content only. - protected abstract IEnumerable RetrieveChildPartNodes(TAssetPart part); - - /// - /// Retrieves the corresponding to the given part. - /// - /// The part for which to retrieve the id. - protected abstract Guid GetIdFromChildPart(object part); - - /// - /// Adds the given child part to the list of children of the given parent part. - /// - /// - /// The child part. - /// The index of the child part in the list of children of the parent part. - /// This method does not modify the contained in this asset. - protected abstract void AddChildPartToParentPart([NotNull] TAssetPart parentPart, [NotNull] TAssetPart childPart, int index); - - /// - /// Removes the given child part from the list of children of the given parent part. - /// - /// - /// The child part. - /// This method does not modify the contained in this asset. - protected abstract void RemoveChildPartFromParentPart([NotNull] TAssetPart parentPart, [NotNull] TAssetPart childPart); - - /// - /// When a part is added to the base asset, it could be the result of a move (remove + add). - /// In that case, remove the new and replace it with the . - /// - /// The cloned base hierarchy. - /// The cloned part to replace. - /// The existing part to restore. - /// - /// - /// Inheriting instance can override this method to perform additional operations. - /// - protected virtual void ReuseExistingPart([NotNull] AssetCompositeHierarchyData baseHierarchy, [NotNull] TAssetPartDesign clonedPart, [NotNull] TAssetPartDesign existingPart) - { - // Replace the cloned part by the one to restore in the list of root if needed - if (baseHierarchy.RootParts.Remove(clonedPart.Part)) + // Let's try to find a sibling in the parts following it, in order + for (var i = partIndex + 1; i < baseAsset.Hierarchy.RootParts.Count && insertIndex < 0; ++i) { - baseHierarchy.RootParts.Add(existingPart.Part); + var sibling = baseAsset.Hierarchy.Parts[baseAsset.Hierarchy.RootParts[i].Id]; + var instanceSibling = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == sibling.Part.Id); + // This sibling still exists instance-side, let's get its parent. + if (instanceSibling is not null) + { + // If the sibling itself has a parent instance-side, let's use the same parent and insert after it + // Otherwise the sibling is root, let's insert after it in the root parts + var parent = Asset.GetParent(instanceSibling.Part); + instanceParent = parent is not null ? Asset.Hierarchy.Parts[parent.Id] : null; + insertIndex = Asset.IndexOf(instanceSibling.Part); + break; + } } - - // Replace the cloned part by the one to restore in the list of parts - if (!baseHierarchy.Parts.Remove(clonedPart.Part.Id)) throw new InvalidOperationException("The new part should be in the baseHierarchy."); - baseHierarchy.Parts.Add(existingPart); - } - - /// - /// Indicates whether a new part added in a base asset should be also cloned and added to this asset. - /// - /// The property graph of the base asset. - /// The new part that has been added in the base asset. - /// The parent of the new part that has been added in the base asset. - /// The instance id for which the part might be cloned. - /// true if the part should be cloned and added to this asset; otherwise, false. - protected virtual bool ShouldAddNewPartFromBase(AssetCompositeHierarchyPropertyGraph baseAssetGraph, [NotNull] TAssetPartDesign newPart, TAssetPart newPartParent, Guid instanceId) - { - return !deletedPartsInstanceMapping.Contains(Tuple.Create(newPart.Part.Id, instanceId)); - } - - protected virtual void RewriteIds([NotNull] TAssetPart targetPart, [NotNull] TAssetPart sourcePart) - { - // TODO: this method is temporary! - targetPart.Id = sourcePart.Id; } - - /// - /// Finds the best index (and parent) at which to insert a new part that is propagated after being added to one of the bases of this asset. - /// - /// The base asset for the part that has been added. - /// The new part that has been added to the base. - /// The parent part of the part that has been added to the base. - /// The id of the instance for which we are looking for an index and parent. - /// The parent in which to insert the new instance part. If null, the new part will be inserted as root of the hierarchy. - /// The index at which to insert the new part in the instance, or a negative value if the part should be discarded. - protected virtual int FindBestInsertIndex(AssetCompositeHierarchy baseAsset, TAssetPartDesign newBasePart, TAssetPart newBasePartParent, Guid instanceId, out TAssetPartDesign instanceParent) + else { - instanceParent = null; - var insertIndex = -1; + // The new part is not root, it has a parent. + instanceParent = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == newBasePartParent.Id); - // First, let's find out where it is the best to insert this new part - if (newBasePartParent == null) + // If the parent has been removed instance side, the hierarchy to the new part does not exist anymore. We can discard it + if (instanceParent is not null) { - // The part is a root, so we must place it according to its sibling (since no parent exists). - var partIndex = baseAsset.Hierarchy.RootParts.IndexOf(x => x.Id == newBasePart.Part.Id); + var partIndex = baseAsset.IndexOf(newBasePart.Part); + // Let's try to find a sibling in the parts preceding it, in order for (var i = partIndex - 1; i >= 0 && insertIndex < 0; --i) { - var sibling = baseAsset.Hierarchy.Parts[baseAsset.Hierarchy.RootParts[i].Id]; - var instanceSibling = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == sibling.Part.Id); - // This sibling still exists instance-side, let's get its parent. - if (instanceSibling != null) - { - // If the sibling itself has a parent instance-side, let's use the same parent and insert after it - // Otherwise the sibling is root, let's insert after it in the root parts - var parent = Asset.GetParent(instanceSibling.Part); - instanceParent = parent != null ? Asset.Hierarchy.Parts[parent.Id] : null; - insertIndex = Asset.IndexOf(instanceSibling.Part) + 1; - break; - } + var sibling = baseAsset.GetChild(newBasePartParent, i); + var instanceSibling = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == sibling.Id); + // This sibling still exists instance-side, let's insert after it + if (instanceSibling is not null) + insertIndex = i + 1; } // Let's try to find a sibling in the parts following it, in order - for (var i = partIndex + 1; i < baseAsset.Hierarchy.RootParts.Count && insertIndex < 0; ++i) + for (var i = partIndex + 1; i < baseAsset.GetChildCount(newBasePartParent) && insertIndex < 0; ++i) { - var sibling = baseAsset.Hierarchy.Parts[baseAsset.Hierarchy.RootParts[i].Id]; - var instanceSibling = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == sibling.Part.Id); - // This sibling still exists instance-side, let's get its parent. - if (instanceSibling != null) - { - // If the sibling itself has a parent instance-side, let's use the same parent and insert after it - // Otherwise the sibling is root, let's insert after it in the root parts - var parent = Asset.GetParent(instanceSibling.Part); - instanceParent = parent != null ? Asset.Hierarchy.Parts[parent.Id] : null; - insertIndex = Asset.IndexOf(instanceSibling.Part); - break; - } + var sibling = baseAsset.GetChild(newBasePartParent, i); + var instanceSibling = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == sibling.Id); + // This sibling still exists instance-side, let's insert before it + if (instanceSibling is not null) + insertIndex = i - 1; } - } - else - { - // The new part is not root, it has a parent. - instanceParent = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == newBasePartParent.Id); - - // If the parent has been removed instance side, the hierarchy to the new part does not exist anymore. We can discard it - if (instanceParent != null) - { - var partIndex = baseAsset.IndexOf(newBasePart.Part); - - // Let's try to find a sibling in the parts preceding it, in order - for (var i = partIndex - 1; i >= 0 && insertIndex < 0; --i) - { - var sibling = baseAsset.GetChild(newBasePartParent, i); - var instanceSibling = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == sibling.Id); - // This sibling still exists instance-side, let's insert after it - if (instanceSibling != null) - insertIndex = i + 1; - } - - // Let's try to find a sibling in the parts following it, in order - for (var i = partIndex + 1; i < baseAsset.GetChildCount(newBasePartParent) && insertIndex < 0; ++i) - { - var sibling = baseAsset.GetChild(newBasePartParent, i); - var instanceSibling = Asset.Hierarchy.Parts.Values.FirstOrDefault(x => x.Base?.InstanceId == instanceId && x.Base?.BasePartId == sibling.Id); - // This sibling still exists instance-side, let's insert before it - if (instanceSibling != null) - insertIndex = i - 1; - } - // Default position is first index - if (insertIndex < 0) - insertIndex = 0; - } + // Default position is first index + if (insertIndex < 0) + insertIndex = 0; } + } - if (insertIndex < 0) + if (insertIndex < 0) + { + // We couldn't find any parent/sibling in the instance. Either the parent has been removed, in which case we'll discard the part, + // or the base is a single part that has been moved around, and we'll rely on the last known common ancestor of this instance to re-insert it. + var isAlone = Asset.Hierarchy.Parts.Values.All(x => x.Base?.InstanceId != instanceId); + if (isAlone) { - // We couldn't find any parent/sibling in the instance. Either the parent has been removed, in which case we'll discard the part, - // or the base is a single part that has been moved around, and we'll rely on the last known common ancestor of this instance to re-insert it. - var isAlone = Asset.Hierarchy.Parts.Values.All(x => x.Base?.InstanceId != instanceId); - if (isAlone) + if (instancesCommonAncestors.TryGetValue(instanceId, out var parentId) && parentId != Guid.Empty) { - Guid parentId; - if (instancesCommonAncestors.TryGetValue(instanceId, out parentId) && parentId != Guid.Empty) + // FIXME: instancesCommonAncestors should be synchronized with existing instances, i.e. if the instance has been compltely removed then the common ancestor should have been cleared. + if (Asset.Hierarchy.Parts.TryGetValue(parentId, out instanceParent)) { - // FIXME: instancesCommonAncestors should be synchronized with existing instances, i.e. if the instance has been compltely removed then the common ancestor should have been cleared. - if (Asset.Hierarchy.Parts.TryGetValue(parentId, out instanceParent)) - { - insertIndex = 0; - } + insertIndex = 0; } } } - return insertIndex; } + return insertIndex; + } + + /// + protected override void OnContentChanged(MemberNodeChangeEventArgs args) + { + RelinkToOwnerPart((IAssetNode)args.Member, args.NewValue); + base.OnContentChanged(args); + } - /// - protected override void OnContentChanged([NotNull] MemberNodeChangeEventArgs args) + /// + protected override void OnItemChanged(ItemChangeEventArgs args) + { + RelinkToOwnerPart((IAssetNode)args.Collection, args.NewValue); + base.OnItemChanged(args); + } + + private void RelinkToOwnerPart(IAssetNode node, object? newValue) + { + var partDesign = (TAssetPartDesign?)node.GetContent(NodesToOwnerPartVisitor.OwnerPartContentName)?.Retrieve(); + if (partDesign is not null) { - RelinkToOwnerPart((IAssetNode)args.Member, args.NewValue); - base.OnContentChanged(args); + // A property of a part has changed + LinkToOwnerPart(node, partDesign); } - - /// - protected override void OnItemChanged([NotNull] ItemChangeEventArgs args) + else if (node.Type == typeof(AssetPartCollection) && newValue is TAssetPartDesign design) { - RelinkToOwnerPart((IAssetNode)args.Collection, args.NewValue); - base.OnItemChanged(args); + // A new part has been added + partDesign = design; + LinkToOwnerPart(Container.NodeContainer.GetNode(partDesign.Part), partDesign); } + } - private void RelinkToOwnerPart([NotNull] IAssetNode node, object newValue) + private void UpdateAssetPartBases() + { + // Unregister deleted base assets + var deletedBaseParts = basePartAssets.Keys.Where(g => g.AssetItem.IsDeleted).ToList(); + foreach (var basePartAsset in deletedBaseParts) { - var partDesign = (TAssetPartDesign)node.GetContent(NodesToOwnerPartVisitor.OwnerPartContentName)?.Retrieve(); - if (partDesign != null) - { - // A property of a part has changed - LinkToOwnerPart(node, partDesign); - } - else if (node.Type == typeof(AssetPartCollection) && newValue is TAssetPartDesign) - { - // A new part has been added - partDesign = (TAssetPartDesign)newValue; - LinkToOwnerPart(Container.NodeContainer.GetNode(partDesign.Part), partDesign); - } + basePartAsset.PartAdded -= PartAddedInBaseAsset; + basePartAsset.PartRemoved -= PartRemovedInBaseAsset; + basePartAssets.Remove(basePartAsset); } - private void UpdateAssetPartBases() - { - // Unregister deleted base assets - var deletedBaseParts = basePartAssets.Keys.Where(g => g.AssetItem.IsDeleted).ToList(); - foreach (var basePartAsset in deletedBaseParts) - { - basePartAsset.PartAdded -= PartAddedInBaseAsset; - basePartAsset.PartRemoved -= PartRemovedInBaseAsset; - basePartAssets.Remove(basePartAsset); - } + // We need to subscribe to event of new base assets, but we don't want to unregister from previous one, in case the user is moving (remove + add) + // the single part of a base. In this case we wouldn't have any part linking to the base once it has been removed. + var newBasePartAsset = new HashSet>(); - // We need to subscribe to event of new base assets, but we don't want to unregister from previous one, in case the user is moving (remove + add) - // the single part of a base. In this case we wouldn't have any part linking to the base once it has been removed. - var newBasePartAsset = new HashSet>(); + // We want to enumerate parts that are actually "reachable", so we don't use Hierarchy.Parts for iteration - we iterate from the root parts instead. + // We use Hierarchy.Parts at the end just to retrieve the part design from the actual part. + var currentParts = Asset.Hierarchy.RootParts.DepthFirst(x => Asset.EnumerateChildParts(x, false)).Select(x => Asset.Hierarchy.Parts[x.Id]); + foreach (var part in currentParts) + { + if (part.Base is null) + continue; - // We want to enumerate parts that are actually "reachable", so we don't use Hierarchy.Parts for iteration - we iterate from the root parts instead. - // We use Hierarchy.Parts at the end just to retrieve the part design from the actual part. - var currentParts = Asset.Hierarchy.RootParts.DepthFirst(x => Asset.EnumerateChildParts(x, false)).Select(x => Asset.Hierarchy.Parts[x.Id]); - foreach (var part in currentParts) + if (Container.TryGetGraph(part.Base.BasePartAsset.Id) is AssetCompositeHierarchyPropertyGraph baseAssetGraph) { - if (part.Base == null) - continue; - - if (Container.TryGetGraph(part.Base.BasePartAsset.Id) is AssetCompositeHierarchyPropertyGraph baseAssetGraph) + if (!basePartAssets.TryGetValue(baseAssetGraph, out var instanceIds)) { - if (!basePartAssets.TryGetValue(baseAssetGraph, out HashSet instanceIds)) - { - instanceIds = new HashSet(); - basePartAssets.Add(baseAssetGraph, instanceIds); - newBasePartAsset.Add(baseAssetGraph); - } - instanceIds.Add(part.Base.InstanceId); + instanceIds = []; + basePartAssets.Add(baseAssetGraph, instanceIds); + newBasePartAsset.Add(baseAssetGraph); } + instanceIds.Add(part.Base.InstanceId); + } - // Update mapping - baseInstanceMapping[Tuple.Create(part.Base.BasePartId, part.Base.InstanceId)] = part; + // Update mapping + baseInstanceMapping[Tuple.Create(part.Base.BasePartId, part.Base.InstanceId)] = part; - // Update common ancestors - if (!instancesCommonAncestors.TryGetValue(part.Base.InstanceId, out Guid ancestorId)) + // Update common ancestors + if (!instancesCommonAncestors.TryGetValue(part.Base.InstanceId, out Guid ancestorId)) + { + instancesCommonAncestors[part.Base.InstanceId] = Asset.GetParent(part.Part)?.Id ?? Guid.Empty; + } + else + { + var parent = ancestorId; + var parents = new HashSet(); + while (parent != Guid.Empty) { - instancesCommonAncestors[part.Base.InstanceId] = Asset.GetParent(part.Part)?.Id ?? Guid.Empty; + parents.Add(parent); + // Note: parent could have been deleted + parent = Asset.Hierarchy.Parts.TryGetValue(parent, out var assetPartDesign) ? Asset.GetParent(assetPartDesign.Part)?.Id ?? Guid.Empty : Guid.Empty; } - else + ancestorId = Asset.GetParent(part.Part)?.Id ?? Guid.Empty; + while (ancestorId != Guid.Empty && !parents.Contains(ancestorId)) { - var parent = ancestorId; - var parents = new HashSet(); - while (parent != Guid.Empty) - { - parents.Add(parent); - // Note: parent could have been deleted - parent = Asset.Hierarchy.Parts.TryGetValue(parent, out TAssetPartDesign assetPartDesign) ? Asset.GetParent(assetPartDesign.Part)?.Id ?? Guid.Empty : Guid.Empty; - } - ancestorId = Asset.GetParent(part.Part)?.Id ?? Guid.Empty; - while (ancestorId != Guid.Empty && !parents.Contains(ancestorId)) - { - // Note: ancestor could have been deleted - ancestorId = Asset.Hierarchy.Parts.TryGetValue(ancestorId, out TAssetPartDesign assetPartDesign) ? (Asset.GetParent(assetPartDesign.Part)?.Id ?? Guid.Empty) : Guid.Empty; - } - instancesCommonAncestors[part.Base.InstanceId] = ancestorId; + // Note: ancestor could have been deleted + ancestorId = Asset.Hierarchy.Parts.TryGetValue(ancestorId, out var assetPartDesign) ? (Asset.GetParent(assetPartDesign.Part)?.Id ?? Guid.Empty) : Guid.Empty; } - } - - // Register to new base part events - foreach (var basePartAsset in newBasePartAsset) - { - basePartAsset.PartAdded += PartAddedInBaseAsset; - basePartAsset.PartRemoved += PartRemovedInBaseAsset; + instancesCommonAncestors[part.Base.InstanceId] = ancestorId; } } - private void PartAddedInBaseAsset(object sender, [NotNull] AssetPartChangeEventArgs e) + // Register to new base part events + foreach (var basePartAsset in newBasePartAsset) { - UpdatingPropertyFromBase = true; + basePartAsset.PartAdded += PartAddedInBaseAsset; + basePartAsset.PartRemoved += PartRemovedInBaseAsset; + } + } - var baseAsset = (AssetCompositeHierarchy)e.Asset; - var newPart = baseAsset.Hierarchy.Parts[e.PartId]; - var newPartParent = baseAsset.GetParent(newPart.Part); - var baseAssetGraph = Container.TryGetGraph(baseAsset.Id) as AssetCompositeHierarchyPropertyGraph; - if (baseAssetGraph == null) throw new InvalidOperationException("Unable to find the graph corresponding to the base part"); + private void PartAddedInBaseAsset(object? sender, AssetPartChangeEventArgs e) + { + UpdatingPropertyFromBase = true; + var baseAsset = (AssetCompositeHierarchy)e.Asset; + var newPart = baseAsset.Hierarchy.Parts[e.PartId]; + var newPartParent = baseAsset.GetParent(newPart.Part); + if (Container.TryGetGraph(baseAsset.Id) is not AssetCompositeHierarchyPropertyGraph baseAssetGraph) throw new InvalidOperationException("Unable to find the graph corresponding to the base part"); - foreach (var instanceId in basePartAssets[baseAssetGraph]) + foreach (var instanceId in basePartAssets[baseAssetGraph]) + { + // Discard the part if this asset don't want it + if (!ShouldAddNewPartFromBase(baseAssetGraph, newPart, newPartParent, instanceId)) + continue; + + var insertIndex = FindBestInsertIndex(baseAsset, newPart, newPartParent, instanceId, out var instanceParent); + if (insertIndex < 0) + continue; + + // Now we know where to insert, let's clone the new part. + const SubHierarchyCloneFlags flags = SubHierarchyCloneFlags.GenerateNewIdsForIdentifiableObjects | SubHierarchyCloneFlags.RemoveOverrides; + var baseHierarchy = CloneSubHierarchies(baseAssetGraph.Container.NodeContainer, baseAssetGraph.Asset, newPart.Part.Id.Yield(), flags, out Dictionary mapping); + foreach (var ids in mapping) { - // Discard the part if this asset don't want it - if (!ShouldAddNewPartFromBase(baseAssetGraph, newPart, newPartParent, instanceId)) + // Process only ids that correspond to parts + if (!baseHierarchy.Parts.TryGetValue(ids.Value, out var clone)) continue; - var insertIndex = FindBestInsertIndex(baseAsset, newPart, newPartParent, instanceId, out TAssetPartDesign instanceParent); - if (insertIndex < 0) - continue; + clone.Base = new BasePart(new AssetReference(e.AssetItem.Id, e.AssetItem.Location), ids.Key, instanceId); - // Now we know where to insert, let's clone the new part. - var flags = SubHierarchyCloneFlags.GenerateNewIdsForIdentifiableObjects | SubHierarchyCloneFlags.RemoveOverrides; - var baseHierarchy = CloneSubHierarchies(baseAssetGraph.Container.NodeContainer, baseAssetGraph.Asset, newPart.Part.Id.Yield(), flags, out Dictionary mapping); - foreach (var ids in mapping) + // This add could actually be a move (remove + add). So we compare to the existing baseInstanceMapping and reuse the existing part if necessary + var mappingKey = Tuple.Create(ids.Key, instanceId); + if (!deletedPartsInstanceMapping.Contains(mappingKey) && baseInstanceMapping.TryGetValue(mappingKey, out var existingPart)) { - // Process only ids that correspond to parts - if (!baseHierarchy.Parts.TryGetValue(ids.Value, out TAssetPartDesign clone)) - continue; - - clone.Base = new BasePart(new AssetReference(e.AssetItem.Id, e.AssetItem.Location), ids.Key, instanceId); - - // This add could actually be a move (remove + add). So we compare to the existing baseInstanceMapping and reuse the existing part if necessary - var mappingKey = Tuple.Create(ids.Key, instanceId); - if (!deletedPartsInstanceMapping.Contains(mappingKey) && baseInstanceMapping.TryGetValue(mappingKey, out TAssetPartDesign existingPart)) - { - ReuseExistingPart(baseHierarchy, clone, existingPart); - } + ReuseExistingPart(baseHierarchy, clone, existingPart); } - - // We might have changed some ids, let's refresh the collection - baseHierarchy.Parts.RefreshKeys(); - - // Then actually add the new part - var rootClone = baseHierarchy.Parts[baseHierarchy.RootParts.Single().Id]; - AddPartToAsset(baseHierarchy.Parts, rootClone, instanceParent?.Part, insertIndex); } - // Reconcile with base - RefreshBase(); - ReconcileWithBase(); + // We might have changed some ids, let's refresh the collection + baseHierarchy.Parts.RefreshKeys(); - UpdatingPropertyFromBase = false; + // Then actually add the new part + var rootClone = baseHierarchy.Parts[baseHierarchy.RootParts.Single().Id]; + AddPartToAsset(baseHierarchy.Parts, rootClone, instanceParent?.Part, insertIndex); } - private void PartRemovedInBaseAsset(object sender, AssetPartChangeEventArgs e) + // Reconcile with base + RefreshBase(); + ReconcileWithBase(); + + UpdatingPropertyFromBase = false; + } + + private void PartRemovedInBaseAsset(object? sender, AssetPartChangeEventArgs e) + { + UpdatingPropertyFromBase = true; + foreach (var part in Asset.Hierarchy.Parts.Values.Where(x => x.Base?.BasePartId == e.PartId).ToList()) { - UpdatingPropertyFromBase = true; - foreach (var part in Asset.Hierarchy.Parts.Values.Where(x => x.Base?.BasePartId == e.PartId).ToList()) - { - RemovePartFromAsset(part); - } - UpdatingPropertyFromBase = false; + RemovePartFromAsset(part); } + UpdatingPropertyFromBase = false; + } - private void InsertPartInPartsCollection(AssetPartCollection newPartCollection, [NotNull] TAssetPartDesign rootPart) + private void InsertPartInPartsCollection(AssetPartCollection newPartCollection, TAssetPartDesign rootPart) + { + var node = HierarchyNode[nameof(AssetCompositeHierarchyData.Parts)].Target; + node?.Add(rootPart, new NodeIndex(rootPart.Part.Id)); + foreach (var childPart in Asset.EnumerateChildParts(rootPart.Part, false)) { - var node = HierarchyNode[nameof(AssetCompositeHierarchyData.Parts)].Target; - node.Add(rootPart, new NodeIndex(rootPart.Part.Id)); - foreach (var childPart in Asset.EnumerateChildParts(rootPart.Part, false)) - { - var partDesign = newPartCollection[childPart.Id]; - InsertPartInPartsCollection(newPartCollection, partDesign); - } + var partDesign = newPartCollection[childPart.Id]; + InsertPartInPartsCollection(newPartCollection, partDesign); } + } - private void RemovePartFromPartsCollection([NotNull] TAssetPartDesign rootPart) + private void RemovePartFromPartsCollection(TAssetPartDesign rootPart) + { + foreach (var childPart in Asset.EnumerateChildParts(rootPart.Part, false)) { - foreach (var childPart in Asset.EnumerateChildParts(rootPart.Part, false)) - { - var partDesign = Asset.Hierarchy.Parts[childPart.Id]; - RemovePartFromPartsCollection(partDesign); - } - var node = HierarchyNode[nameof(AssetCompositeHierarchyData.Parts)].Target; - var index = new NodeIndex(rootPart.Part.Id); - node.Remove(rootPart, index); + var partDesign = Asset.Hierarchy.Parts[childPart.Id]; + RemovePartFromPartsCollection(partDesign); } + var node = HierarchyNode[nameof(AssetCompositeHierarchyData.Parts)].Target; + var index = new NodeIndex(rootPart.Part.Id); + node?.Remove(rootPart, index); + } + + private void NotifyPartAdded(Guid partId) + { + UpdateAssetPartBases(); + PartAdded?.Invoke(this, new AssetPartChangeEventArgs(AssetItem, partId)); + } + + private void NotifyPartRemoved(Guid partId) + { + UpdateAssetPartBases(); + PartRemoved?.Invoke(this, new AssetPartChangeEventArgs(AssetItem, partId)); + } - private void NotifyPartAdded(Guid partId) + private void RootPartsChanged(object? sender, INodeChangeEventArgs e) + { + switch (e.ChangeType) { - UpdateAssetPartBases(); - PartAdded?.Invoke(this, new AssetPartChangeEventArgs(AssetItem, partId)); + case ContentChangeType.CollectionAdd: + { + if (e.NewValue is TAssetPart part) + NotifyPartAdded(part.Id); + } + break; + case ContentChangeType.CollectionRemove: + { + if (e.OldValue is TAssetPart part) + NotifyPartRemoved(part.Id); + } + break; } + } - private void NotifyPartRemoved(Guid partId) + private void ChildPartChanged(object? sender, INodeChangeEventArgs e) + { + switch (e.ChangeType) { - UpdateAssetPartBases(); - PartRemoved?.Invoke(this, new AssetPartChangeEventArgs(AssetItem, partId)); + case ContentChangeType.CollectionUpdate: + case ContentChangeType.ValueChange: + if (e.OldValue is not null) + { + NotifyPartRemoved(GetIdFromChildPart(e.OldValue)); + } + if (e.NewValue is not null) + { + NotifyPartAdded(GetIdFromChildPart(e.NewValue)); + } + break; + case ContentChangeType.CollectionAdd: + NotifyPartAdded(GetIdFromChildPart(e.NewValue!)); + break; + case ContentChangeType.CollectionRemove: + NotifyPartRemoved(GetIdFromChildPart(e.OldValue!)); + break; } + } - private void RootPartsChanged(object sender, [NotNull] INodeChangeEventArgs e) + private void PartsChanged(object? sender, ItemChangeEventArgs e) + { + TAssetPart? part; + switch (e.ChangeType) { - switch (e.ChangeType) - { - case ContentChangeType.CollectionAdd: - NotifyPartAdded(((TAssetPart)e.NewValue).Id); - break; - case ContentChangeType.CollectionRemove: - NotifyPartRemoved(((TAssetPart)e.OldValue).Id); - break; - } + case ContentChangeType.CollectionAdd: + // Ensure that we track children added later to any new part + part = ((TAssetPartDesign?)e.NewValue)?.Part; + foreach (var childPartNode in RetrieveChildPartNodes(part)) + { + RegisterChildPartNode(childPartNode); + } + break; + case ContentChangeType.CollectionRemove: + // And untrack removed parts + part = ((TAssetPartDesign?)e.OldValue)?.Part; + foreach (var childPartNode in RetrieveChildPartNodes(part)) + { + UnregisterChildPartNode(childPartNode); + } + break; } + } - private void ChildPartChanged(object sender, [NotNull] INodeChangeEventArgs e) + private void RegisterChildPartNode(IGraphNode node) + { + if (registeredChildParts.Add(node)) { - switch (e.ChangeType) + if (node is IMemberNode memberNode) { - case ContentChangeType.CollectionUpdate: - case ContentChangeType.ValueChange: - if (e.OldValue != null) - { - NotifyPartRemoved(GetIdFromChildPart(e.OldValue)); - } - if (e.NewValue != null) - { - NotifyPartAdded(GetIdFromChildPart(e.NewValue)); - } - break; - case ContentChangeType.CollectionAdd: - NotifyPartAdded(GetIdFromChildPart(e.NewValue)); - break; - case ContentChangeType.CollectionRemove: - NotifyPartRemoved(GetIdFromChildPart(e.OldValue)); - break; + memberNode.ValueChanged += ChildPartChanged; } - } - - private void PartsChanged(object sender, [NotNull] ItemChangeEventArgs e) - { - TAssetPart part; - switch (e.ChangeType) + if (node is IObjectNode objectNode) { - case ContentChangeType.CollectionAdd: - // Ensure that we track children added later to any new part - part = ((TAssetPartDesign)e.NewValue).Part; - foreach (var childPartNode in RetrieveChildPartNodes(part)) - { - RegisterChildPartNode(childPartNode); - } - break; - case ContentChangeType.CollectionRemove: - // And untrack removed parts - part = ((TAssetPartDesign)e.OldValue).Part; - foreach (var childPartNode in RetrieveChildPartNodes(part)) - { - UnregisterChildPartNode(childPartNode); - } - break; + objectNode.ItemChanged += ChildPartChanged; } } + } - private void RegisterChildPartNode(IGraphNode node) + private void UnregisterChildPartNode(IGraphNode node) + { + if (registeredChildParts.Remove(node)) { - if (registeredChildParts.Add(node)) + if (node is IMemberNode memberNode) { - var memberNode = node as IMemberNode; - if (memberNode != null) - { - memberNode.ValueChanged += ChildPartChanged; - } - var objectNode = node as IObjectNode; - if (objectNode != null) - { - objectNode.ItemChanged += ChildPartChanged; - } + memberNode.ValueChanged -= ChildPartChanged; } - } - - private void UnregisterChildPartNode(IGraphNode node) - { - if (registeredChildParts.Remove(node)) + if (node is IObjectNode objectNode) { - var memberNode = node as IMemberNode; - if (memberNode != null) - { - memberNode.ValueChanged -= ChildPartChanged; - } - var objectNode = node as IObjectNode; - if (objectNode != null) - { - objectNode.ItemChanged -= ChildPartChanged; - } + objectNode.ItemChanged -= ChildPartChanged; } } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetCompositeHierarchyPropertyGraphDefinition.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetCompositeHierarchyPropertyGraphDefinition.cs index 879e403cca..e123f66f5f 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetCompositeHierarchyPropertyGraphDefinition.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetCompositeHierarchyPropertyGraphDefinition.cs @@ -1,27 +1,24 @@ -using System; -using Stride.Core; -using Stride.Core.Quantum; +using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +[AssetPropertyGraphDefinition(typeof(AssetCompositeHierarchy<,>))] +public class AssetCompositeHierarchyPropertyGraphDefinition : AssetPropertyGraphDefinition + where TAssetPart : class, IIdentifiable + where TAssetPartDesign : class, IAssetPartDesign { - [AssetPropertyGraphDefinition(typeof(AssetCompositeHierarchy<,>))] - public class AssetCompositeHierarchyPropertyGraphDefinition : AssetPropertyGraphDefinition - where TAssetPart : class, IIdentifiable - where TAssetPartDesign : class, IAssetPartDesign + public override bool IsMemberTargetObjectReference(IMemberNode member, object? value) { - public override bool IsMemberTargetObjectReference(IMemberNode member, object value) + if (value is TAssetPart) { - if (value is TAssetPart) - { - // Check if we're the part referenced by a part design - other cases are references - return member.Parent.Type != typeof(TAssetPartDesign); - } - return base.IsMemberTargetObjectReference(member, value); + // Check if we're the part referenced by a part design - other cases are references + return member.Parent.Type != typeof(TAssetPartDesign); } + return base.IsMemberTargetObjectReference(member, value); + } - public override bool IsTargetItemObjectReference(IObjectNode collection, NodeIndex itemIndex, object value) - { - return value is TAssetPart || base.IsTargetItemObjectReference(collection, itemIndex, value); - } + public override bool IsTargetItemObjectReference(IObjectNode collection, NodeIndex itemIndex, object? value) + { + return value is TAssetPart || base.IsTargetItemObjectReference(collection, itemIndex, value); } } \ No newline at end of file diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetCompositePropertyGraph.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetCompositePropertyGraph.cs index 1e310ac237..7e80134743 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetCompositePropertyGraph.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetCompositePropertyGraph.cs @@ -1,30 +1,28 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; + using Stride.Core.Diagnostics; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +[AssetPropertyGraph(typeof(AssetComposite))] +public class AssetCompositePropertyGraph : AssetPropertyGraph { - [AssetPropertyGraph(typeof(AssetComposite))] - public class AssetCompositePropertyGraph : AssetPropertyGraph + public AssetCompositePropertyGraph(AssetPropertyGraphContainer container, AssetItem assetItem, ILogger logger) + : base(container, assetItem, logger) { - public AssetCompositePropertyGraph([NotNull] AssetPropertyGraphContainer container, [NotNull] AssetItem assetItem, ILogger logger) - : base(container, assetItem, logger) - { - } + } - protected void LinkToOwnerPart([NotNull] IGraphNode node, object part) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - var visitor = new NodesToOwnerPartVisitor(Definition, Container.NodeContainer, part); - visitor.Visit(node); - } + protected void LinkToOwnerPart(IGraphNode node, object part) + { + ArgumentNullException.ThrowIfNull(node); + var visitor = new NodesToOwnerPartVisitor(Definition, Container.NodeContainer, part); + visitor.Visit(node); + } - protected sealed override IBaseToDerivedRegistry CreateBaseToDerivedRegistry() - { - return new AssetCompositeBaseToDerivedRegistry(this); - } + protected sealed override IBaseToDerivedRegistry CreateBaseToDerivedRegistry() + { + return new AssetCompositeBaseToDerivedRegistry(this); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetGraphNodeChangeListener.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetGraphNodeChangeListener.cs index d811de8567..9a2e6607a5 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetGraphNodeChangeListener.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetGraphNodeChangeListener.cs @@ -1,35 +1,32 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using Stride.Core.Assets.Quantum.Visitors; -using Stride.Core.Annotations; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +/// +/// An implementation of that uses an to prevent visiting object references. +/// +public class AssetGraphNodeChangeListener : GraphNodeChangeListener { + private readonly AssetPropertyGraphDefinition propertyGraphDefinition; + /// - /// An implementation of that uses an to prevent visiting object references. + /// Initializes a new instance of the class. /// - public class AssetGraphNodeChangeListener : GraphNodeChangeListener + /// The root node of the extended graph to listen to. + /// The that describes which nodes represent an object reference. + public AssetGraphNodeChangeListener(IGraphNode rootNode, AssetPropertyGraphDefinition propertyGraphDefinition) + : base(rootNode) { - [NotNull] private readonly AssetPropertyGraphDefinition propertyGraphDefinition; - - /// - /// Initializes a new instance of the class. - /// - /// The root node of the extended graph to listen to. - /// The that describes which nodes represent an object reference. - public AssetGraphNodeChangeListener(IGraphNode rootNode, [NotNull] AssetPropertyGraphDefinition propertyGraphDefinition) - : base(rootNode) - { - this.propertyGraphDefinition = propertyGraphDefinition ?? throw new ArgumentNullException(nameof(propertyGraphDefinition)); - } + this.propertyGraphDefinition = propertyGraphDefinition ?? throw new ArgumentNullException(nameof(propertyGraphDefinition)); + } - /// - protected override GraphVisitorBase CreateVisitor() - { - return new AssetGraphVisitorBase(propertyGraphDefinition); - } + /// + protected override GraphVisitorBase CreateVisitor() + { + return new AssetGraphVisitorBase(propertyGraphDefinition); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetGraphNodeLinker.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetGraphNodeLinker.cs index 07f45e514d..85c2fbc2cb 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetGraphNodeLinker.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetGraphNodeLinker.cs @@ -1,29 +1,26 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +public class AssetGraphNodeLinker : GraphNodeLinker { - public class AssetGraphNodeLinker : GraphNodeLinker - { - private readonly AssetPropertyGraphDefinition propertyGraphDefinition; + private readonly AssetPropertyGraphDefinition propertyGraphDefinition; - public AssetGraphNodeLinker(AssetPropertyGraphDefinition propertyGraphDefinition) - { - this.propertyGraphDefinition = propertyGraphDefinition; - } + public AssetGraphNodeLinker(AssetPropertyGraphDefinition propertyGraphDefinition) + { + this.propertyGraphDefinition = propertyGraphDefinition; + } - protected override bool ShouldVisitMemberTarget([NotNull] IMemberNode member) - { - return !propertyGraphDefinition.IsMemberTargetObjectReference(member, member.Retrieve()) && base.ShouldVisitMemberTarget(member); - } + protected override bool ShouldVisitMemberTarget(IMemberNode member) + { + return !propertyGraphDefinition.IsMemberTargetObjectReference(member, member.Retrieve()) && base.ShouldVisitMemberTarget(member); + } - protected override bool ShouldVisitTargetItem([NotNull] IObjectNode collectionNode, NodeIndex index) - { - return !propertyGraphDefinition.IsTargetItemObjectReference(collectionNode, index, collectionNode.Retrieve(index)) && base.ShouldVisitTargetItem(collectionNode, index); - } + protected override bool ShouldVisitTargetItem(IObjectNode collectionNode, NodeIndex index) + { + return !propertyGraphDefinition.IsTargetItemObjectReference(collectionNode, index, collectionNode.Retrieve(index)) && base.ShouldVisitTargetItem(collectionNode, index); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetMemberNodeChangeEventArgs.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetMemberNodeChangeEventArgs.cs index 4b43d5d77f..7d2badec86 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetMemberNodeChangeEventArgs.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetMemberNodeChangeEventArgs.cs @@ -1,51 +1,50 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Annotations; + using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +public interface IAssetNodeChangeEventArgs : INodeChangeEventArgs { - public interface IAssetNodeChangeEventArgs : INodeChangeEventArgs - { - OverrideType PreviousOverride { get; } + OverrideType PreviousOverride { get; } - OverrideType NewOverride { get; } + OverrideType NewOverride { get; } - ItemId ItemId { get; } - } + ItemId ItemId { get; } +} - public class AssetMemberNodeChangeEventArgs : MemberNodeChangeEventArgs, IAssetNodeChangeEventArgs +public class AssetMemberNodeChangeEventArgs : MemberNodeChangeEventArgs, IAssetNodeChangeEventArgs +{ + public AssetMemberNodeChangeEventArgs(MemberNodeChangeEventArgs e, OverrideType previousOverride, OverrideType newOverride, ItemId itemId) + : base(e.Member, e.OldValue, e.NewValue) { - public AssetMemberNodeChangeEventArgs([NotNull] MemberNodeChangeEventArgs e, OverrideType previousOverride, OverrideType newOverride, ItemId itemId) - : base(e.Member, e.OldValue, e.NewValue) - { - PreviousOverride = previousOverride; - NewOverride = newOverride; - ItemId = itemId; - } + PreviousOverride = previousOverride; + NewOverride = newOverride; + ItemId = itemId; + } - public OverrideType PreviousOverride { get; } + public OverrideType PreviousOverride { get; } - public OverrideType NewOverride { get; } + public OverrideType NewOverride { get; } - public ItemId ItemId { get; } - } + public ItemId ItemId { get; } +} - public class AssetItemNodeChangeEventArgs : ItemChangeEventArgs, IAssetNodeChangeEventArgs +public class AssetItemNodeChangeEventArgs : ItemChangeEventArgs, IAssetNodeChangeEventArgs +{ + public AssetItemNodeChangeEventArgs(ItemChangeEventArgs e, OverrideType previousOverride, OverrideType newOverride, ItemId itemId) + : base(e.Collection, e.Index, e.ChangeType, e.OldValue, e.NewValue) { - public AssetItemNodeChangeEventArgs([NotNull] ItemChangeEventArgs e, OverrideType previousOverride, OverrideType newOverride, ItemId itemId) - : base(e.Collection, e.Index, e.ChangeType, e.OldValue, e.NewValue) - { - PreviousOverride = previousOverride; - NewOverride = newOverride; - ItemId = itemId; - } + PreviousOverride = previousOverride; + NewOverride = newOverride; + ItemId = itemId; + } - public OverrideType PreviousOverride { get; } + public OverrideType PreviousOverride { get; } - public OverrideType NewOverride { get; } + public OverrideType NewOverride { get; } - public ItemId ItemId { get; } - } + public ItemId ItemId { get; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetNodeContainer.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetNodeContainer.cs index 032b3686c2..ba362b520a 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetNodeContainer.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetNodeContainer.cs @@ -1,44 +1,42 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; + using Stride.Core.IO; using Stride.Core.Mathematics; using Stride.Core.Quantum; using IReference = Stride.Core.Serialization.Contents.IReference; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +public class AssetNodeContainer : NodeContainer { - public class AssetNodeContainer : NodeContainer + public AssetNodeContainer() { - public AssetNodeContainer() + NodeBuilder.RegisterPrimitiveType(typeof(IReference)); + NodeBuilder.RegisterPrimitiveType(typeof(PropertyKey)); + NodeBuilder.RegisterPrimitiveType(typeof(TimeSpan)); + NodeBuilder.RegisterPrimitiveType(typeof(DateTime)); + NodeBuilder.RegisterPrimitiveType(typeof(Guid)); + NodeBuilder.RegisterPrimitiveType(typeof(AssetId)); + NodeBuilder.RegisterPrimitiveType(typeof(Color)); + NodeBuilder.RegisterPrimitiveType(typeof(Color3)); + NodeBuilder.RegisterPrimitiveType(typeof(Color4)); + NodeBuilder.RegisterPrimitiveType(typeof(Vector2)); + NodeBuilder.RegisterPrimitiveType(typeof(Vector3)); + NodeBuilder.RegisterPrimitiveType(typeof(Vector4)); + NodeBuilder.RegisterPrimitiveType(typeof(Int2)); + NodeBuilder.RegisterPrimitiveType(typeof(Int3)); + NodeBuilder.RegisterPrimitiveType(typeof(Int4)); + NodeBuilder.RegisterPrimitiveType(typeof(Quaternion)); + NodeBuilder.RegisterPrimitiveType(typeof(RectangleF)); + NodeBuilder.RegisterPrimitiveType(typeof(Rectangle)); + NodeBuilder.RegisterPrimitiveType(typeof(Matrix)); + NodeBuilder.RegisterPrimitiveType(typeof(UPath)); + NodeBuilder.RegisterPrimitiveType(typeof(AngleSingle)); + // Register content types as primitive so they are not processed by Quantum + foreach (var contentType in AssetRegistry.GetContentTypes()) { - NodeBuilder.RegisterPrimitiveType(typeof(IReference)); - NodeBuilder.RegisterPrimitiveType(typeof(PropertyKey)); - NodeBuilder.RegisterPrimitiveType(typeof(TimeSpan)); - NodeBuilder.RegisterPrimitiveType(typeof(DateTime)); - NodeBuilder.RegisterPrimitiveType(typeof(Guid)); - NodeBuilder.RegisterPrimitiveType(typeof(AssetId)); - NodeBuilder.RegisterPrimitiveType(typeof(Color)); - NodeBuilder.RegisterPrimitiveType(typeof(Color3)); - NodeBuilder.RegisterPrimitiveType(typeof(Color4)); - NodeBuilder.RegisterPrimitiveType(typeof(Vector2)); - NodeBuilder.RegisterPrimitiveType(typeof(Vector3)); - NodeBuilder.RegisterPrimitiveType(typeof(Vector4)); - NodeBuilder.RegisterPrimitiveType(typeof(Int2)); - NodeBuilder.RegisterPrimitiveType(typeof(Int3)); - NodeBuilder.RegisterPrimitiveType(typeof(Int4)); - NodeBuilder.RegisterPrimitiveType(typeof(Quaternion)); - NodeBuilder.RegisterPrimitiveType(typeof(RectangleF)); - NodeBuilder.RegisterPrimitiveType(typeof(Rectangle)); - NodeBuilder.RegisterPrimitiveType(typeof(Matrix)); - NodeBuilder.RegisterPrimitiveType(typeof(UPath)); - NodeBuilder.RegisterPrimitiveType(typeof(AngleSingle)); - // Register content types as primitive so they are not processed by Quantum - foreach (var contentType in AssetRegistry.GetContentTypes()) - { - NodeBuilder.RegisterPrimitiveType(contentType); - } + NodeBuilder.RegisterPrimitiveType(contentType); } } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetNodeFactory.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetNodeFactory.cs index aa39ba8ba5..59172e588e 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetNodeFactory.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetNodeFactory.cs @@ -1,46 +1,45 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Assets.Quantum.Internal; using Stride.Core.Reflection; using Stride.Core.Quantum; using Stride.Core.Quantum.References; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +/// +/// An implementation of that creates node capable of storing additional metadata, such as override information, connection +/// to a base node or any other node, etc. +/// +public class AssetNodeFactory : INodeFactory { - /// - /// An implementation of that creates node capable of storing additional metadata, such as override information, connection - /// to a base node or any other node, etc. - /// - public class AssetNodeFactory : INodeFactory + /// + public IObjectNode CreateObjectNode(INodeBuilder nodeBuilder, Guid guid, object obj, ITypeDescriptor descriptor) { - /// - public IObjectNode CreateObjectNode(INodeBuilder nodeBuilder, Guid guid, object obj, ITypeDescriptor descriptor) - { - if (nodeBuilder == null) throw new ArgumentNullException(nameof(nodeBuilder)); - if (obj == null) throw new ArgumentNullException(nameof(obj)); - if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); - var reference = nodeBuilder.CreateReferenceForNode(descriptor.Type, obj, false) as ReferenceEnumerable; - return new AssetObjectNode(nodeBuilder, obj, guid, descriptor, reference); - } + ArgumentNullException.ThrowIfNull(nodeBuilder); + ArgumentNullException.ThrowIfNull(obj); + ArgumentNullException.ThrowIfNull(descriptor); + var reference = nodeBuilder.CreateReferenceForNode(descriptor.Type, obj, false) as ReferenceEnumerable; + return new AssetObjectNode(nodeBuilder, obj, guid, descriptor, reference); + } - /// - public IObjectNode CreateBoxedNode(INodeBuilder nodeBuilder, Guid guid, object structure, ITypeDescriptor descriptor) - { - if (nodeBuilder == null) throw new ArgumentNullException(nameof(nodeBuilder)); - if (structure == null) throw new ArgumentNullException(nameof(structure)); - if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); - return new AssetBoxedNode(nodeBuilder, structure, guid, descriptor); - } + /// + public IObjectNode CreateBoxedNode(INodeBuilder nodeBuilder, Guid guid, object structure, ITypeDescriptor descriptor) + { + ArgumentNullException.ThrowIfNull(nodeBuilder); + ArgumentNullException.ThrowIfNull(structure); + ArgumentNullException.ThrowIfNull(descriptor); + return new AssetBoxedNode(nodeBuilder, structure, guid, descriptor); + } - /// - public IMemberNode CreateMemberNode(INodeBuilder nodeBuilder, Guid guid, IObjectNode parent, IMemberDescriptor member, object value) - { - if (nodeBuilder == null) throw new ArgumentNullException(nameof(nodeBuilder)); - if (parent == null) throw new ArgumentNullException(nameof(parent)); - if (member == null) throw new ArgumentNullException(nameof(member)); - var reference = nodeBuilder.CreateReferenceForNode(member.Type, value, true); - return new AssetMemberNode(nodeBuilder, guid, parent, member, reference); - } + /// + public IMemberNode CreateMemberNode(INodeBuilder nodeBuilder, Guid guid, IObjectNode parent, IMemberDescriptor member, object? value) + { + ArgumentNullException.ThrowIfNull(nodeBuilder); + ArgumentNullException.ThrowIfNull(parent); + ArgumentNullException.ThrowIfNull(member); + var reference = nodeBuilder.CreateReferenceForNode(member.Type, value, true); + return new AssetMemberNode(nodeBuilder, guid, parent, member, reference); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetPartChangeEventArgs.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetPartChangeEventArgs.cs index 0bd9f182d5..b3d0349803 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetPartChangeEventArgs.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetPartChangeEventArgs.cs @@ -1,42 +1,38 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +/// +/// Arguments of the and events. +/// +public class AssetPartChangeEventArgs : EventArgs { /// - /// Arguments of the and events. + /// Initializes a new instance of the class. /// - public class AssetPartChangeEventArgs : EventArgs + /// The asset that was modified. + /// The id of the part that has been added or removed. + public AssetPartChangeEventArgs(AssetItem assetItem, Guid partId) { - /// - /// Initializes a new instance of the class. - /// - /// The asset that was modified. - /// The id of the part that has been added or removed. - public AssetPartChangeEventArgs([NotNull] AssetItem assetItem, Guid partId) - { - if (!(assetItem.Asset is AssetComposite)) - throw new ArgumentException($@"The given assetItem does not hold an {nameof(AssetComposite)}.", nameof(assetItem)); - AssetItem = assetItem; - PartId = partId; - } + if (assetItem.Asset is not AssetComposite) + throw new ArgumentException($@"The given assetItem does not hold an {nameof(AssetComposite)}.", nameof(assetItem)); + AssetItem = assetItem; + PartId = partId; + } - /// - /// Gets the asset item of the asset that was modified. - /// - public AssetItem AssetItem { get; } + /// + /// Gets the asset item of the asset that was modified. + /// + public AssetItem AssetItem { get; } - /// - /// Gets the asset that was modified. - /// - [NotNull] - public AssetComposite Asset => (AssetComposite)AssetItem.Asset; + /// + /// Gets the asset that was modified. + /// + public AssetComposite Asset => (AssetComposite)AssetItem.Asset; - /// - /// Gets the id of the part that has been added or removed. - /// - public Guid PartId { get; } - } + /// + /// Gets the id of the part that has been added or removed. + /// + public Guid PartId { get; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraph.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraph.cs index a1cfc5ba7b..e4fc3d4c94 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraph.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraph.cs @@ -1,1056 +1,1042 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; using Stride.Core.Assets.Analysis; using Stride.Core.Assets.Quantum.Internal; using Stride.Core.Assets.Quantum.Visitors; using Stride.Core.Assets.Yaml; -using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.Diagnostics; using Stride.Core.Reflection; using Stride.Core.Serialization; using Stride.Core.Yaml; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +[AssetPropertyGraph(typeof(Asset))] +// ReSharper disable once RequiredBaseTypesIsNotInherited - due to a limitation on how ReSharper checks this requirement (see https://youtrack.jetbrains.com/issue/RSRP-462598) +public class AssetPropertyGraph : IDisposable { - [AssetPropertyGraph(typeof(Asset))] - // ReSharper disable once RequiredBaseTypesIsNotInherited - due to a limitation on how ReSharper checks this requirement (see https://youtrack.jetbrains.com/issue/RSRP-462598) - public class AssetPropertyGraph : IDisposable - { - protected readonly AssetItem AssetItem; + protected readonly AssetItem AssetItem; - public struct NodeOverride + public readonly struct NodeOverride + { + public NodeOverride(IAssetNode overriddenNode, NodeIndex overriddenIndex, OverrideTarget target) { - public NodeOverride(IAssetNode overriddenNode, NodeIndex overriddenIndex, OverrideTarget target) - { - Node = overriddenNode; - Index = overriddenIndex; - Target = target; - } - public readonly IAssetNode Node; - public readonly NodeIndex Index; - public readonly OverrideTarget Target; + Node = overriddenNode; + Index = overriddenIndex; + Target = target; } + public readonly IAssetNode Node; + public readonly NodeIndex Index; + public readonly OverrideTarget Target; + } - private struct NodeChangeHandlers + private readonly struct NodeChangeHandlers + { + public readonly EventHandler? ValueChange; + public readonly EventHandler? ItemChange; + public NodeChangeHandlers(EventHandler? valueChange, EventHandler? itemChange) { - public readonly EventHandler ValueChange; - public readonly EventHandler ItemChange; - public NodeChangeHandlers(EventHandler valueChange, EventHandler itemChange) - { - ValueChange = valueChange; - ItemChange = itemChange; - } + ValueChange = valueChange; + ItemChange = itemChange; } + } - private readonly Dictionary previousOverrides = new Dictionary(); - private readonly Dictionary removedItemIds = new Dictionary(); - - /// - /// The asset this property graph represents. - /// - protected readonly Asset Asset; - // TODO: turn back private - internal readonly AssetToBaseNodeLinker baseLinker; - private readonly AssetToBaseNodeLinker baseUnlinker; - private readonly GraphNodeChangeListener nodeListener; - private readonly Dictionary baseLinkedNodes = new Dictionary(); - private IBaseToDerivedRegistry baseToDerivedRegistry; - private bool isDisposed; - - public AssetPropertyGraph([NotNull] AssetPropertyGraphContainer container, [NotNull] AssetItem assetItem, ILogger logger) + private readonly Dictionary previousOverrides = []; + private readonly Dictionary removedItemIds = []; + + /// + /// The asset this property graph represents. + /// + protected readonly Asset Asset; + // TODO: turn back private + internal readonly AssetToBaseNodeLinker baseLinker; + private readonly AssetToBaseNodeLinker baseUnlinker; + private readonly GraphNodeChangeListener nodeListener; + private readonly Dictionary baseLinkedNodes = []; + private IBaseToDerivedRegistry baseToDerivedRegistry; + private bool isDisposed; + + public AssetPropertyGraph(AssetPropertyGraphContainer container, AssetItem assetItem, ILogger? logger) + { + AssetItem = assetItem ?? throw new ArgumentNullException(nameof(assetItem)); + Container = container ?? throw new ArgumentNullException(nameof(container)); + Definition = AssetQuantumRegistry.GetDefinition(AssetItem.Asset.GetType()); + AssetCollectionItemIdHelper.GenerateMissingItemIds(assetItem.Asset); + CollectionItemIdsAnalysis.FixupItemIds(assetItem, logger); + Asset = assetItem.Asset; + RootNode = (AssetObjectNode)Container.NodeContainer.GetOrCreateNode(assetItem.Asset); + var overrides = assetItem.YamlMetadata?.RetrieveMetadata(AssetObjectSerializerBackend.OverrideDictionaryKey); + ApplyOverrides(RootNode, overrides); + nodeListener = new AssetGraphNodeChangeListener(RootNode, Definition); + nodeListener.Initialize(); + nodeListener.ValueChanging += AssetContentChanging; + nodeListener.ValueChanged += AssetContentChanged; + nodeListener.ItemChanging += AssetItemChanging; + nodeListener.ItemChanged += AssetItemChanged; + + baseLinker = new AssetToBaseNodeLinker(this) { LinkAction = LinkBaseNode }; + baseUnlinker = new AssetToBaseNodeLinker(this) { LinkAction = UnlinkBaseNode }; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (isDisposed) return; + + if (disposing) { - AssetItem = assetItem ?? throw new ArgumentNullException(nameof(assetItem)); - Container = container ?? throw new ArgumentNullException(nameof(container)); - Definition = AssetQuantumRegistry.GetDefinition(AssetItem.Asset.GetType()); - AssetCollectionItemIdHelper.GenerateMissingItemIds(assetItem.Asset); - CollectionItemIdsAnalysis.FixupItemIds(assetItem, logger); - Asset = assetItem.Asset; - RootNode = (AssetObjectNode)Container.NodeContainer.GetOrCreateNode(assetItem.Asset); - var overrides = assetItem.YamlMetadata?.RetrieveMetadata(AssetObjectSerializerBackend.OverrideDictionaryKey); - ApplyOverrides(RootNode, overrides); - nodeListener = new AssetGraphNodeChangeListener(RootNode, Definition); - nodeListener.Initialize(); - nodeListener.ValueChanging += AssetContentChanging; - nodeListener.ValueChanged += AssetContentChanged; - nodeListener.ItemChanging += AssetItemChanging; - nodeListener.ItemChanged += AssetItemChanged; - - baseLinker = new AssetToBaseNodeLinker(this) { LinkAction = LinkBaseNode }; - baseUnlinker = new AssetToBaseNodeLinker(this) { LinkAction = UnlinkBaseNode }; + nodeListener.ValueChanging -= AssetContentChanging; + nodeListener.ValueChanged -= AssetContentChanged; + nodeListener.ItemChanging -= AssetItemChanging; + nodeListener.ItemChanged -= AssetItemChanged; + nodeListener.Dispose(); + ClearAllBaseLinks(); } + isDisposed = true; + } - public virtual void Dispose() + /// + /// The identifier of this asset. + /// + public AssetId Id => AssetItem.Id; + + /// + /// The root node of this asset. + /// + public IAssetObjectNode RootNode { get; } + + /// + /// The container containing all asset property graphs. + /// + public AssetPropertyGraphContainer Container { get; } + + /// + /// The associated to the type of the asset. + /// + public AssetPropertyGraphDefinition Definition { get; } + + /// + /// The property graph of the archetype asset, if it has one. + /// + public AssetPropertyGraph Archetype { get; set; } + + /// + /// Indicates whether a property is currently being updated from a change in the base of this asset. + /// + public bool UpdatingPropertyFromBase { get; protected set; } + + /// + /// Raised after one of the node referenced by the related root node has changed. + /// + public event EventHandler Changing { add => nodeListener.ValueChanging += value; remove => nodeListener.ValueChanging -= value; } + + /// + /// Raised after one of the node referenced by the related root node has changed. + /// + /// In addition to the usual generated by this event also gives information about override changes. + /// . + public event EventHandler Changed; + + public event EventHandler ItemChanging { add => nodeListener.ItemChanging += value; remove => nodeListener.ItemChanging -= value; } + + public event EventHandler ItemChanged; + + /// + /// Raised when a base content has changed, after updating the related content of this graph. + /// + public Action BaseContentChanged; + + /// + /// Indicates whether this property graph has been initialized. + /// + protected bool IsInitialized { get; private set; } + + /// + /// Indicates whether this property graph is currently being initialized. + /// + protected bool IsInitializing { get; private set; } + + private IBaseToDerivedRegistry BaseToDerivedRegistry => baseToDerivedRegistry ??= CreateBaseToDerivedRegistry(); + + /// + /// Initializes this property graph. + /// + public void Initialize() + { + if (IsInitialized) + throw new InvalidOperationException("This property graph has already been initialized."); + + try { - if (!isDisposed) - { - nodeListener.ValueChanging -= AssetContentChanging; - nodeListener.ValueChanged -= AssetContentChanged; - nodeListener.ItemChanging -= AssetItemChanging; - nodeListener.ItemChanged -= AssetItemChanged; - nodeListener.Dispose(); - ClearAllBaseLinks(); - isDisposed = true; - } + IsInitializing = true; + RefreshBase(); + ReconcileWithBase(); + FinalizeInitialization(); } - - /// - /// The identifier of this asset. - /// - public AssetId Id => AssetItem.Id; - - /// - /// The root node of this asset. - /// - public IAssetObjectNode RootNode { get; } - - /// - /// The container containing all asset property graphs. - /// - public AssetPropertyGraphContainer Container { get; } - - /// - /// The associated to the type of the asset. - /// - public AssetPropertyGraphDefinition Definition { get; } - - /// - /// The property graph of the archetype asset, if it has one. - /// - public AssetPropertyGraph Archetype { get; set; } - - /// - /// Indicates whether a property is currently being updated from a change in the base of this asset. - /// - public bool UpdatingPropertyFromBase { get; protected set; } - - /// - /// Raised after one of the node referenced by the related root node has changed. - /// - public event EventHandler Changing { add => nodeListener.ValueChanging += value; remove => nodeListener.ValueChanging -= value; } - - /// - /// Raised after one of the node referenced by the related root node has changed. - /// - /// In addition to the usual generated by this event also gives information about override changes. - /// . - public event EventHandler Changed; - - public event EventHandler ItemChanging { add => nodeListener.ItemChanging += value; remove => nodeListener.ItemChanging -= value; } - - public event EventHandler ItemChanged; - - /// - /// Raised when a base content has changed, after updating the related content of this graph. - /// - public Action BaseContentChanged; - - /// - /// Indicates whether this property graph has been initialized. - /// - protected bool IsInitialized { get; private set; } - - /// - /// Indicates whether this property graph is currently being initialized. - /// - protected bool IsInitializing { get; private set; } - - [NotNull] - private IBaseToDerivedRegistry BaseToDerivedRegistry => baseToDerivedRegistry ?? (baseToDerivedRegistry = CreateBaseToDerivedRegistry()); - - /// - /// Initializes this property graph. - /// - public void Initialize() + finally { - if (IsInitialized) - throw new InvalidOperationException("This property graph has already been initialized."); - - try - { - IsInitializing = true; - RefreshBase(); - ReconcileWithBase(); - FinalizeInitialization(); - } - finally - { - IsInitializing = false; - } - IsInitialized = true; + IsInitializing = false; } + IsInitialized = true; + } - public virtual void RefreshBase() + public virtual void RefreshBase() + { + if (AssetItem.Asset.Archetype is not null) { - if (AssetItem.Asset.Archetype != null) - { - Archetype = Container.TryGetGraph(AssetItem.Asset.Archetype.Id); - if (Archetype == null) - throw new InvalidOperationException($"Unable to find the base [{AssetItem.Asset.Archetype.Location}] of asset [{AssetItem.Location}]."); - } + Archetype = Container.TryGetGraph(AssetItem.Asset.Archetype.Id) + ?? throw new InvalidOperationException($"Unable to find the base [{AssetItem.Asset.Archetype.Location}] of asset [{AssetItem.Location}]."); + } - // Unlink previously linked nodes - ClearAllBaseLinks(); + // Unlink previously linked nodes + ClearAllBaseLinks(); - // Link nodes to the new base. - // Note: in case of composition (prefabs, etc.), even if baseAssetGraph is null, each part (entities, etc.) will discover - // its own base by itself via the FindTarget method. - LinkToBase(RootNode, Archetype?.RootNode); - } + // Link nodes to the new base. + // Note: in case of composition (prefabs, etc.), even if baseAssetGraph is null, each part (entities, etc.) will discover + // its own base by itself via the FindTarget method. + LinkToBase(RootNode, Archetype?.RootNode); + } - public virtual void RefreshBase([NotNull] IAssetNode node, IAssetNode baseNode) - { - UnlinkFromBase(node); - LinkToBase(node, baseNode); - } + public virtual void RefreshBase(IAssetNode node, IAssetNode? baseNode) + { + UnlinkFromBase(node); + LinkToBase(node, baseNode); + } - public void ReconcileWithBase() - { - ReconcileWithBase(RootNode); - } + public void ReconcileWithBase() + { + ReconcileWithBase(RootNode); + } + + private void ReconcileWithBase(IAssetNode rootNode, Dictionary? nodesToReset = null) + { + // Two passes: first pass will reconcile almost everything, but skip object reference. + // The reason is that the target of the reference might not exist yet (might need to be reconciled) + var visitor = CreateReconcilerVisitor(); + visitor.Visiting += (node, path) => ReconcileWithBaseNode((IAssetNode)node, path, reconcileObjectReference: false, nodesToReset); + visitor.Visit(rootNode); + // Second pass: this one should only reconcile remaining object reference. + // TODO: these two passes could be improved! + visitor = CreateReconcilerVisitor(); + visitor.Visiting += (node, path) => ReconcileWithBaseNode((IAssetNode)node, path, reconcileObjectReference: true, nodesToReset); + visitor.Visit(rootNode); + } - private void ReconcileWithBase([NotNull] IAssetNode rootNode, Dictionary nodesToReset = null) + /// + /// Resets the overrides attached to the given node and its descendants, recursively. + /// + /// The node for which to reset overrides. + /// The index of the override to reset in this node, if relevant. + internal void ResetAllOverridesRecursively(IAssetNode rootNode, NodeIndex indexToReset) + { + if (rootNode is IAssetMemberNode && indexToReset != NodeIndex.Empty) throw new ArgumentException(@"The index must be empty when invoking this method on a member node.", nameof(indexToReset)); + + // We first use a visitor to reset recursively all overrides + var nodesToReset = new Dictionary(); + + IGraphNode? visitRoot = null; + var memberNode = rootNode as AssetMemberNode; + if (memberNode is not null) { - // Two passes: first pass will reconcile almost everything, but skip object reference. - // The reason is that the target of the reference might not exist yet (might need to be reconciled) - var visitor = CreateReconcilerVisitor(); - visitor.Visiting += (node, path) => ReconcileWithBaseNode((IAssetNode)node, path, reconcileObjectReference: false, nodesToReset); - visitor.Visit(rootNode); - // Second pass: this one should only reconcile remaining object reference. - // TODO: these two passes could be improved! - visitor = CreateReconcilerVisitor(); - visitor.Visiting += (node, path) => ReconcileWithBaseNode((IAssetNode)node, path, reconcileObjectReference: true, nodesToReset); - visitor.Visit(rootNode); + if (indexToReset != NodeIndex.Empty) throw new InvalidOperationException("Expecting empty index when resetting a member node."); + visitRoot = memberNode.Target; + nodesToReset.Add(rootNode, indexToReset); } - /// - /// Resets the overrides attached to the given node and its descendants, recursively. - /// - /// The node for which to reset overrides. - /// The index of the override to reset in this node, if relevant. - internal void ResetAllOverridesRecursively([NotNull] IAssetNode rootNode, NodeIndex indexToReset) + var objectNode = rootNode as AssetObjectNode; + if (objectNode is not null) { - if (rootNode is IAssetMemberNode && indexToReset != NodeIndex.Empty) throw new ArgumentException(@"The index must be empty when invoking this method on a member node.", nameof(indexToReset)); - - // We first use a visitor to reset recursively all overrides - var nodesToReset = new Dictionary(); - - IGraphNode visitRoot = null; - var memberNode = rootNode as AssetMemberNode; - if (memberNode != null) + if (indexToReset != NodeIndex.Empty) { - if (indexToReset != NodeIndex.Empty) throw new InvalidOperationException("Expecting empty index when resetting a member node."); - visitRoot = memberNode.Target; nodesToReset.Add(rootNode, indexToReset); + visitRoot = objectNode.IsReference ? objectNode.IndexedTarget(indexToReset) : null; + objectNode.OverrideItem(false, indexToReset); } - - var objectNode = rootNode as AssetObjectNode; - if (objectNode != null) + else { - if (indexToReset != NodeIndex.Empty) - { - nodesToReset.Add(rootNode, indexToReset); - visitRoot = objectNode.IsReference ? objectNode.IndexedTarget(indexToReset) : null; - objectNode.OverrideItem(false, indexToReset); - } - else - { - // Otherwise reset everything - visitRoot = objectNode; - } - } - if (visitRoot != null) - { - var visitor = new AssetGraphVisitorBase(Definition); - // If we're in scenario where rootNode is an object node and index is not empty, we might already have the node in the dictionary so let's check this in Visiting - visitor.Visiting += (node, path) => { nodesToReset.TryAdd(node, NodeIndex.Empty); }; - visitor.Visit(rootNode); + // Otherwise reset everything + visitRoot = objectNode; } - // Then we reconcile (recursively) with the base. - ReconcileWithBase(rootNode, nodesToReset); } - - /// - /// Clears all object references targeting the objects corresponding to the given identifiers. - /// - /// The identifiers of the objects for which to clear targeting references. - public virtual void ClearReferencesToObjects([NotNull] IEnumerable objectIds) + if (visitRoot is not null) { - if (objectIds == null) throw new ArgumentNullException(nameof(objectIds)); - var visitor = new ClearObjectReferenceVisitor(Definition, objectIds); - visitor.Visit(RootNode); + var visitor = new AssetGraphVisitorBase(Definition); + // If we're in scenario where rootNode is an object node and index is not empty, we might already have the node in the dictionary so let's check this in Visiting + visitor.Visiting += (node, _) => nodesToReset.TryAdd(node, NodeIndex.Empty); + visitor.Visit(rootNode); } + // Then we reconcile (recursively) with the base. + ReconcileWithBase(rootNode, nodesToReset); + } - /// - /// Creates an instance of that is suited to reconcile properties with the base. - /// - /// A new instance of for reconciliation. - [NotNull] - [Obsolete($"To be removed in future releases. Use {nameof(CreateReconcilerVisitor)} instead.")] - public virtual GraphVisitorBase CreateReconcilierVisitor() => CreateReconcilerVisitor(); + /// + /// Clears all object references targeting the objects corresponding to the given identifiers. + /// + /// The identifiers of the objects for which to clear targeting references. + public virtual void ClearReferencesToObjects(IEnumerable objectIds) + { + ArgumentNullException.ThrowIfNull(objectIds); + var visitor = new ClearObjectReferenceVisitor(Definition, objectIds); + visitor.Visit(RootNode); + } - [NotNull] - public virtual GraphVisitorBase CreateReconcilerVisitor() - { - return new AssetGraphVisitorBase(Definition); - } + /// + /// Creates an instance of that is suited to reconcile properties with the base. + /// + /// A new instance of for reconciliation. + [Obsolete($"To be removed in future releases. Use {nameof(CreateReconcilerVisitor)} instead.")] + public virtual GraphVisitorBase CreateReconcilierVisitor() => CreateReconcilerVisitor(); - public virtual IGraphNode FindTarget(IGraphNode sourceNode, IGraphNode target) - { - return target; - } + public virtual GraphVisitorBase CreateReconcilerVisitor() + { + return new AssetGraphVisitorBase(Definition); + } - public void PrepareForSave(ILogger logger, [NotNull] AssetItem assetItem) - { - if (assetItem.Asset != Asset) throw new ArgumentException($@"The given {nameof(Assets.AssetItem)} does not match the asset associated with this instance", nameof(assetItem)); - AssetCollectionItemIdHelper.GenerateMissingItemIds(assetItem.Asset); - CollectionItemIdsAnalysis.FixupItemIds(assetItem, logger); - assetItem.YamlMetadata.AttachMetadata(AssetObjectSerializerBackend.OverrideDictionaryKey, GenerateOverridesForSerialization(RootNode)); - assetItem.YamlMetadata.AttachMetadata(AssetObjectSerializerBackend.ObjectReferencesKey, GenerateObjectReferencesForSerialization(RootNode)); - } + public virtual IGraphNode FindTarget(IGraphNode sourceNode, IGraphNode target) + { + return target; + } - /// - /// When overridden in a derived class, initializes the -derived class. - /// - /// - /// Override to implement custom initialization behavior for your property graph. - /// - protected virtual void FinalizeInitialization() - { - // Default implementation does nothing - } + public void PrepareForSave(ILogger logger, AssetItem assetItem) + { + if (assetItem.Asset != Asset) throw new ArgumentException($@"The given {nameof(Assets.AssetItem)} does not match the asset associated with this instance", nameof(assetItem)); + AssetCollectionItemIdHelper.GenerateMissingItemIds(assetItem.Asset); + CollectionItemIdsAnalysis.FixupItemIds(assetItem, logger); + assetItem.YamlMetadata.AttachMetadata(AssetObjectSerializerBackend.OverrideDictionaryKey, GenerateOverridesForSerialization(RootNode)); + assetItem.YamlMetadata.AttachMetadata(AssetObjectSerializerBackend.ObjectReferencesKey, GenerateObjectReferencesForSerialization(RootNode)); + } - protected virtual void OnContentChanged(MemberNodeChangeEventArgs args) - { - // Do nothing by default - } + /// + /// When overridden in a derived class, initializes the -derived class. + /// + /// + /// Override to implement custom initialization behavior for your property graph. + /// + protected virtual void FinalizeInitialization() + { + // Default implementation does nothing + } - protected virtual void OnItemChanged(ItemChangeEventArgs args) - { - // Do nothing by default - } + protected virtual void OnContentChanged(MemberNodeChangeEventArgs args) + { + // Do nothing by default + } + + protected virtual void OnItemChanged(ItemChangeEventArgs args) + { + // Do nothing by default + } - [CanBeNull] - private static IAssetNode ResolveObjectPath([NotNull] IAssetNode rootNode, [NotNull] YamlAssetPath path, out NodeIndex index, out bool resolveOnIndex) + private static IAssetNode? ResolveObjectPath(IAssetNode rootNode, YamlAssetPath path, out NodeIndex index, out bool resolveOnIndex) + { + var currentNode = rootNode; + index = NodeIndex.Empty; + resolveOnIndex = false; + for (var i = 0; i < path.Elements.Count; i++) { - var currentNode = rootNode; - index = NodeIndex.Empty; - resolveOnIndex = false; - for (var i = 0; i < path.Elements.Count; i++) + var item = path.Elements[i]; + switch (item.Type) { - var item = path.Elements[i]; - switch (item.Type) - { - case YamlAssetPath.ElementType.Member: + case YamlAssetPath.ElementType.Member: { index = NodeIndex.Empty; resolveOnIndex = false; if (currentNode.IsReference) { - var memberNode = currentNode as IMemberNode; - if (memberNode == null) throw new InvalidOperationException($"An IMemberNode was expected when processing the path [{path}]"); - currentNode = (IAssetNode)memberNode.Target; + if (currentNode is not IMemberNode memberNode) throw new InvalidOperationException($"An IMemberNode was expected when processing the path [{path}]"); + currentNode = (IAssetNode?)memberNode.Target; } - var objectNode = currentNode as IObjectNode; - if (objectNode == null) throw new InvalidOperationException($"An IObjectNode was expected when processing the path [{path}]"); + if (currentNode is not IObjectNode objectNode) throw new InvalidOperationException($"An IObjectNode was expected when processing the path [{path}]"); var name = item.AsMember(); - currentNode = (IAssetNode)objectNode.TryGetChild(name); + currentNode = (IAssetNode?)objectNode.TryGetChild(name); break; } - case YamlAssetPath.ElementType.Index: + case YamlAssetPath.ElementType.Index: { index = new NodeIndex(item.Value); resolveOnIndex = true; - var memberNode = currentNode as IMemberNode; - if (memberNode == null) throw new InvalidOperationException($"An IMemberNode was expected when processing the path [{path}]"); - currentNode = (IAssetNode)memberNode.Target; + if (currentNode is not IMemberNode memberNode) throw new InvalidOperationException($"An IMemberNode was expected when processing the path [{path}]"); + currentNode = (IAssetNode?)memberNode.Target; if (currentNode.IsReference && i < path.Elements.Count - 1) { - var objNode = currentNode as IObjectNode; - if (objNode == null) throw new InvalidOperationException($"An IObjectNode was expected when processing the path [{path}]"); - currentNode = (IAssetNode)objNode.IndexedTarget(new NodeIndex(item.Value)); + if (currentNode is not IObjectNode objNode) throw new InvalidOperationException($"An IObjectNode was expected when processing the path [{path}]"); + currentNode = (IAssetNode?)objNode.IndexedTarget(new NodeIndex(item.Value)); } break; } - case YamlAssetPath.ElementType.ItemId: + case YamlAssetPath.ElementType.ItemId: { var ids = CollectionItemIdHelper.GetCollectionItemIds(currentNode.Retrieve()); var key = ids.GetKey(item.AsItemId()); index = new NodeIndex(key); resolveOnIndex = false; - var memberNode = currentNode as IMemberNode; - if (memberNode == null) throw new InvalidOperationException($"An IMemberNode was expected when processing the path [{path}]"); - currentNode = (IAssetNode)memberNode.Target; + if (currentNode is not IMemberNode memberNode) throw new InvalidOperationException($"An IMemberNode was expected when processing the path [{path}]"); + currentNode = (IAssetNode?)memberNode.Target; if (currentNode.IsReference && i < path.Elements.Count - 1) { - var objNode = currentNode as IObjectNode; - if (objNode == null) throw new InvalidOperationException($"An IObjectNode was expected when processing the path [{path}]"); - currentNode = (IAssetNode)objNode.IndexedTarget(new NodeIndex(key)); + if (currentNode is not IObjectNode objNode) throw new InvalidOperationException($"An IObjectNode was expected when processing the path [{path}]"); + currentNode = (IAssetNode?)objNode.IndexedTarget(new NodeIndex(key)); } break; } - default: - throw new ArgumentOutOfRangeException(); - } - - // Something wrong happen, the node is unreachable. - if (currentNode == null) - return null; + default: + throw new ArgumentOutOfRangeException(); } - return currentNode; + // Something wrong happen, the node is unreachable. + if (currentNode is null) + return null; } - [NotNull] - public static YamlAssetMetadata GenerateOverridesForSerialization([NotNull] IGraphNode rootNode) - { - if (rootNode == null) throw new ArgumentNullException(nameof(rootNode)); + return currentNode; + } - var visitor = new OverrideTypePathGenerator(); - visitor.Visit(rootNode); - return visitor.Result; - } + public static YamlAssetMetadata GenerateOverridesForSerialization(IGraphNode rootNode) + { + ArgumentNullException.ThrowIfNull(rootNode); - public YamlAssetMetadata GenerateObjectReferencesForSerialization([NotNull] IGraphNode rootNode) - { - if (rootNode == null) throw new ArgumentNullException(nameof(rootNode)); + var visitor = new OverrideTypePathGenerator(); + visitor.Visit(rootNode); + return visitor.Result; + } - var visitor = new ObjectReferencePathGenerator(Definition); - visitor.Visit(rootNode); - return visitor.Result; - } + public YamlAssetMetadata GenerateObjectReferencesForSerialization(IGraphNode rootNode) + { + ArgumentNullException.ThrowIfNull(rootNode); - public static void ApplyOverrides([NotNull] IAssetNode rootNode, YamlAssetMetadata overrides) - { - if (rootNode == null) throw new ArgumentNullException(nameof(rootNode)); + var visitor = new ObjectReferencePathGenerator(Definition); + visitor.Visit(rootNode); + return visitor.Result; + } - if (overrides == null) - return; + public static void ApplyOverrides(IAssetNode rootNode, YamlAssetMetadata overrides) + { + ArgumentNullException.ThrowIfNull(rootNode); - foreach (var overrideInfo in overrides) - { - var node = ResolveObjectPath(rootNode, overrideInfo.Key, out NodeIndex index, out bool overrideOnKey); - // The node is unreachable, skip this override. - if (node == null) - continue; + if (overrides is null) + return; - var memberNode = node as AssetMemberNode; - memberNode?.SetContentOverride(overrideInfo.Value); + foreach (var overrideInfo in overrides) + { + var node = ResolveObjectPath(rootNode, overrideInfo.Key, out NodeIndex index, out bool overrideOnKey); + // The node is unreachable, skip this override. + if (node is null) + continue; + + var memberNode = node as AssetMemberNode; + memberNode?.SetContentOverride(overrideInfo.Value); - var objectNode = node as IAssetObjectNode; - if (objectNode != null) + var objectNode = node as IAssetObjectNode; + if (objectNode is not null) + { + if (!overrideOnKey) { - if (!overrideOnKey) - { - objectNode.OverrideItem(true, index); - } - else - { - objectNode.OverrideKey(true, index); - } + objectNode.OverrideItem(true, index); + } + else + { + objectNode.OverrideKey(true, index); } } } + } - [NotNull] - public List ClearAllOverrides() - { - // Unregister handlers - must be done first! - ClearAllBaseLinks(); + public List ClearAllOverrides() + { + // Unregister handlers - must be done first! + ClearAllBaseLinks(); - var clearedOverrides = new List(); - // Clear override and base from node - if (RootNode != null) + var clearedOverrides = new List(); + // Clear override and base from node + if (RootNode is not null) + { + var visitor = new GraphVisitorBase { SkipRootNode = true }; + visitor.Visiting += (node, _) => { - var visitor = new GraphVisitorBase { SkipRootNode = true }; - visitor.Visiting += (node, path) => - { - var memberNode = node as AssetMemberNode; - var objectNode = node as AssetObjectNode; + var memberNode = node as AssetMemberNode; + var objectNode = node as AssetObjectNode; - if (memberNode != null && memberNode.IsContentOverridden()) + if (memberNode?.IsContentOverridden() == true) + { + memberNode.OverrideContent(false); + clearedOverrides.Add(new NodeOverride(memberNode, NodeIndex.Empty, OverrideTarget.Content)); + } + if (objectNode is not null) + { + foreach (var index in objectNode.GetOverriddenItemIndices().ToList()) { - memberNode.OverrideContent(false); - clearedOverrides.Add(new NodeOverride(memberNode, NodeIndex.Empty, OverrideTarget.Content)); + objectNode.OverrideItem(false, index); + clearedOverrides.Add(new NodeOverride(objectNode, index, OverrideTarget.Item)); } - if (objectNode != null) + foreach (var index in objectNode.GetOverriddenKeyIndices().ToList()) { - foreach (var index in objectNode.GetOverriddenItemIndices().ToList()) - { - objectNode.OverrideItem(false, index); - clearedOverrides.Add(new NodeOverride(objectNode, index, OverrideTarget.Item)); - } - foreach (var index in objectNode.GetOverriddenKeyIndices().ToList()) - { - objectNode.OverrideKey(false, index); - clearedOverrides.Add(new NodeOverride(objectNode, index, OverrideTarget.Key)); - } + objectNode.OverrideKey(false, index); + clearedOverrides.Add(new NodeOverride(objectNode, index, OverrideTarget.Key)); } - }; - visitor.Visit(RootNode); - } + } + }; + visitor.Visit(RootNode); + } - RefreshBase(); + RefreshBase(); - return clearedOverrides; - } + return clearedOverrides; + } - public void RestoreOverrides([NotNull] List overridesToRestore, AssetPropertyGraph archetypeBase) + public void RestoreOverrides(List overridesToRestore, AssetPropertyGraph archetypeBase) + { + foreach (var clearedOverride in overridesToRestore) { - foreach (var clearedOverride in overridesToRestore) + // TODO: this will need improvement when adding support for Seal + switch (clearedOverride.Target) { - // TODO: this will need improvement when adding support for Seal - switch (clearedOverride.Target) - { - case OverrideTarget.Content: - ((AssetMemberNode)clearedOverride.Node).OverrideContent(true); - break; - case OverrideTarget.Item: - ((AssetObjectNode)clearedOverride.Node).OverrideItem(true, clearedOverride.Index); - break; - case OverrideTarget.Key: - ((AssetObjectNode)clearedOverride.Node).OverrideKey(true, clearedOverride.Index); - break; - default: - throw new ArgumentOutOfRangeException(); - } + case OverrideTarget.Content: + ((AssetMemberNode)clearedOverride.Node).OverrideContent(true); + break; + case OverrideTarget.Item: + ((AssetObjectNode)clearedOverride.Node).OverrideItem(true, clearedOverride.Index); + break; + case OverrideTarget.Key: + ((AssetObjectNode)clearedOverride.Node).OverrideKey(true, clearedOverride.Index); + break; + default: + throw new ArgumentOutOfRangeException(); } } + } - private void LinkToBase([NotNull] IAssetNode sourceRootNode, IAssetNode targetRootNode) - { - baseLinker.LinkGraph(sourceRootNode, targetRootNode); - } - - private void UnlinkFromBase([NotNull] IAssetNode sourceRootNode) - { - baseUnlinker.LinkGraph(sourceRootNode, sourceRootNode.BaseNode); - } - - [NotNull] - protected virtual IBaseToDerivedRegistry CreateBaseToDerivedRegistry() - { - return new AssetBaseToDerivedRegistry(this); - } + private void LinkToBase(IAssetNode sourceRootNode, IAssetNode? targetRootNode) + { + baseLinker.LinkGraph(sourceRootNode, targetRootNode); + } - // TODO: this method is should be called in every scenario of ReconcileWithBase, it is not the case yet. - protected virtual bool CanUpdate(IAssetNode node, ContentChangeType changeType, NodeIndex index, object value) - { - return true; - } + private void UnlinkFromBase(IAssetNode sourceRootNode) + { + baseUnlinker.LinkGraph(sourceRootNode, sourceRootNode.BaseNode); + } - protected virtual object CloneValueFromBase(object value, IAssetNode node) - { - return CloneFromBase(value); - } + protected virtual IBaseToDerivedRegistry CreateBaseToDerivedRegistry() + { + return new AssetBaseToDerivedRegistry(this); + } - /// - /// Clones the given object, remove any override information on it, and propagate its id (from ) to the cloned object. - /// - /// The object to clone. - /// A clone of the given object. - /// If the given object is null, this method returns null. - /// If the given object is a content reference, the given object won't be cloned but directly returned. - private static object CloneFromBase(object value) - { - if (value == null) - return null; + // TODO: this method is should be called in every scenario of ReconcileWithBase, it is not the case yet. + protected virtual bool CanUpdate(IAssetNode node, ContentChangeType changeType, NodeIndex index, object value) + { + return true; + } - // TODO: check if the cloner is aware of the content type (attached reference) and does not already avoid cloning them. + protected virtual object? CloneValueFromBase(object value, IAssetNode node) + { + return CloneFromBase(value); + } - // TODO FIXME - //if (SessionViewModel.Instance.ContentReferenceService.IsContentType(value.GetType())) - // return value; + /// + /// Clones the given object, remove any override information on it, and propagate its id (from ) to the cloned object. + /// + /// The object to clone. + /// A clone of the given object. + /// If the given object is null, this method returns null. + /// If the given object is a content reference, the given object won't be cloned but directly returned. + private static object? CloneFromBase(object value) + { + if (value is null) + return null; - var result = AssetCloner.Clone(value, AssetClonerFlags.GenerateNewIdsForIdentifiableObjects, out Dictionary remapping); - return result; - } + // TODO: check if the cloner is aware of the content type (attached reference) and does not already avoid cloning them. - private void LinkBaseNode([NotNull] IGraphNode currentNode, IGraphNode baseNode) - { - var assetNode = (IAssetNodeInternal)currentNode; - assetNode.SetPropertyGraph(this); - assetNode.SetBaseNode(baseNode); + // TODO FIXME + //if (SessionViewModel.Instance.ContentReferenceService.IsContentType(value.GetType())) + // return value; - BaseToDerivedRegistry.RegisterBaseToDerived((IAssetNode)baseNode, assetNode); + return AssetCloner.Clone(value, AssetClonerFlags.GenerateNewIdsForIdentifiableObjects, out _); + } - if (!baseLinkedNodes.ContainsKey(assetNode)) - { - EventHandler valueChange = null; - EventHandler itemChange = null; - if (baseNode != null) - { - if (assetNode.BaseNode is IMemberNode member) - { - valueChange = (s, e) => OnBaseContentChanged(e, currentNode); - member.ValueChanged += valueChange; - } + private void LinkBaseNode(IGraphNode currentNode, IGraphNode baseNode) + { + var assetNode = (IAssetNodeInternal)currentNode; + assetNode.SetPropertyGraph(this); + assetNode.SetBaseNode(baseNode); - if (assetNode.BaseNode is IObjectNode objectNode) - { - itemChange = (s, e) => OnBaseContentChanged(e, currentNode); - objectNode.ItemChanged += itemChange; - } - } - baseLinkedNodes.Add(assetNode, new NodeChangeHandlers(valueChange, itemChange)); - } - } + BaseToDerivedRegistry.RegisterBaseToDerived((IAssetNode)baseNode, assetNode); - private void UnlinkBaseNode(IGraphNode currentNode, IGraphNode baseNode) + if (!baseLinkedNodes.ContainsKey(assetNode)) { - var assetNode = (IAssetNode)currentNode; - - if (baseLinkedNodes.TryGetValue(assetNode, out NodeChangeHandlers linkedNode)) + EventHandler? valueChange = null; + EventHandler? itemChange = null; + if (baseNode is not null) { if (assetNode.BaseNode is IMemberNode member) { - member.ValueChanged -= linkedNode.ValueChange; + valueChange = (s, e) => OnBaseContentChanged(e, currentNode); + member.ValueChanged += valueChange; } if (assetNode.BaseNode is IObjectNode objectNode) { - objectNode.ItemChanged -= linkedNode.ItemChange; + itemChange = (s, e) => OnBaseContentChanged(e, currentNode); + objectNode.ItemChanged += itemChange; } } - baseLinkedNodes.Remove(assetNode); + baseLinkedNodes.Add(assetNode, new NodeChangeHandlers(valueChange, itemChange)); } + } - private void ClearAllBaseLinks() + private void UnlinkBaseNode(IGraphNode currentNode, IGraphNode baseNode) + { + var assetNode = (IAssetNode)currentNode; + + if (baseLinkedNodes.TryGetValue(assetNode, out NodeChangeHandlers linkedNode)) { - foreach (var linkedNode in baseLinkedNodes) + if (assetNode.BaseNode is IMemberNode member) { - if (linkedNode.Key.BaseNode is IMemberNode member) - { - member.ValueChanged -= linkedNode.Value.ValueChange; - } - - if (linkedNode.Key.BaseNode is IObjectNode objectNode) - { - objectNode.ItemChanged -= linkedNode.Value.ItemChange; - } + member.ValueChanged -= linkedNode.ValueChange; + } + if (assetNode.BaseNode is IObjectNode objectNode) + { + objectNode.ItemChanged -= linkedNode.ItemChange; } - baseLinkedNodes.Clear(); } + baseLinkedNodes.Remove(assetNode); + } - private void AssetContentChanging(object sender, [NotNull] MemberNodeChangeEventArgs e) + private void ClearAllBaseLinks() + { + foreach (var linkedNode in baseLinkedNodes) { - var node = (AssetMemberNode)e.Member; - var overrideValue = node.GetContentOverride(); - previousOverrides[e.Member] = overrideValue; - } + if (linkedNode.Key.BaseNode is IMemberNode member) + { + member.ValueChanged -= linkedNode.Value.ValueChange; + } - private void AssetContentChanged(object sender, [NotNull] MemberNodeChangeEventArgs e) - { - var previousOverride = previousOverrides[e.Member]; - previousOverrides.Remove(e.Member); - var node = (AssetMemberNode)e.Member; - var overrideValue = node.GetContentOverride(); - if (node.ResettingOverride) - overrideValue &= ~OverrideType.New; - - // Link the node that has changed to its base. - LinkToBase(node, (IAssetNode)node.BaseNode); - OnContentChanged(e); - Changed?.Invoke(sender, new AssetMemberNodeChangeEventArgs(e, previousOverride, overrideValue, ItemId.Empty)); + if (linkedNode.Key.BaseNode is IObjectNode objectNode) + { + objectNode.ItemChanged -= linkedNode.Value.ItemChange; + } } + baseLinkedNodes.Clear(); + } + + private void AssetContentChanging(object? sender, MemberNodeChangeEventArgs e) + { + var node = (AssetMemberNode)e.Member; + var overrideValue = node.GetContentOverride(); + previousOverrides[e.Member] = overrideValue; + } - private void AssetItemChanging(object sender, [NotNull] ItemChangeEventArgs e) + private void AssetContentChanged(object? sender, MemberNodeChangeEventArgs e) + { + var previousOverride = previousOverrides[e.Member]; + previousOverrides.Remove(e.Member); + var node = (AssetMemberNode)e.Member; + var overrideValue = node.GetContentOverride(); + if (node.ResettingOverride) + overrideValue &= ~OverrideType.New; + + // Link the node that has changed to its base. + LinkToBase(node, (IAssetNode)node.BaseNode); + OnContentChanged(e); + Changed?.Invoke(sender, new AssetMemberNodeChangeEventArgs(e, previousOverride, overrideValue, ItemId.Empty)); + } + + private void AssetItemChanging(object? sender, ItemChangeEventArgs e) + { + var overrideValue = OverrideType.Base; + var node = (AssetObjectNode)e.Collection; + var collection = node.Retrieve(); + // For value change and remove, we store the current override state. + if (CollectionItemIdHelper.HasCollectionItemIds(collection)) { - var overrideValue = OverrideType.Base; - var node = (AssetObjectNode)e.Collection; - var collection = node.Retrieve(); - // For value change and remove, we store the current override state. - if (CollectionItemIdHelper.HasCollectionItemIds(collection)) + overrideValue = node.GetItemOverride(e.Index); + if (e.ChangeType == ContentChangeType.CollectionRemove) { - overrideValue = node.GetItemOverride(e.Index); - if (e.ChangeType == ContentChangeType.CollectionRemove) - { - // For remove, we also collect the id of the item that will be removed, so we can pass it to the Changed event. - var ids = CollectionItemIdHelper.GetCollectionItemIds(collection); - ids.TryGet(e.Index.Value, out ItemId itemId); - removedItemIds[e.Collection] = itemId; - } + // For remove, we also collect the id of the item that will be removed, so we can pass it to the Changed event. + var ids = CollectionItemIdHelper.GetCollectionItemIds(collection); + ids.TryGet(e.Index.Value, out ItemId itemId); + removedItemIds[e.Collection] = itemId; } - previousOverrides[e.Collection] = overrideValue; } + previousOverrides[e.Collection] = overrideValue; + } - private void AssetItemChanged(object sender, [NotNull] ItemChangeEventArgs e) + private void AssetItemChanged(object? sender, ItemChangeEventArgs e) + { + var previousOverride = previousOverrides[e.Collection]; + previousOverrides.Remove(e.Collection); + + var itemId = ItemId.Empty; + var overrideValue = OverrideType.Base; + var node = (IAssetObjectNodeInternal)e.Collection; + var collection = node.Retrieve(); + if (e.ChangeType == ContentChangeType.CollectionUpdate || e.ChangeType == ContentChangeType.CollectionAdd) { - var previousOverride = previousOverrides[e.Collection]; - previousOverrides.Remove(e.Collection); - - var itemId = ItemId.Empty; - var overrideValue = OverrideType.Base; - var node = (IAssetObjectNodeInternal)e.Collection; - var collection = node.Retrieve(); - if (e.ChangeType == ContentChangeType.CollectionUpdate || e.ChangeType == ContentChangeType.CollectionAdd) + // We're changing an item of a collection. If the collection has identifiable items, retrieve the override status of the item. + if (CollectionItemIdHelper.HasCollectionItemIds(collection)) { - // We're changing an item of a collection. If the collection has identifiable items, retrieve the override status of the item. - if (CollectionItemIdHelper.HasCollectionItemIds(collection)) - { - overrideValue = node.GetItemOverride(e.Index); + overrideValue = node.GetItemOverride(e.Index); - // Also retrieve the id of the modified item (this should fail only if the collection doesn't have identifiable items) - var ids = CollectionItemIdHelper.GetCollectionItemIds(collection); - ids.TryGet(e.Index.Value, out itemId); - } + // Also retrieve the id of the modified item (this should fail only if the collection doesn't have identifiable items) + var ids = CollectionItemIdHelper.GetCollectionItemIds(collection); + ids.TryGet(e.Index.Value, out itemId); } - else if (e.ChangeType == ContentChangeType.CollectionRemove) + } + else if (e.ChangeType == ContentChangeType.CollectionRemove) + { + // When deleting we are always overriding (unless there is no base or non-identifiable items) + if (CollectionItemIdHelper.HasCollectionItemIds(collection)) { - // When deleting we are always overriding (unless there is no base or non-identifiable items) - if (CollectionItemIdHelper.HasCollectionItemIds(collection)) - { - overrideValue = node.BaseNode != null && !UpdatingPropertyFromBase ? OverrideType.New : OverrideType.Base; - itemId = removedItemIds[e.Collection]; - removedItemIds.Remove(e.Collection); - } + overrideValue = node.BaseNode is not null && !UpdatingPropertyFromBase ? OverrideType.New : OverrideType.Base; + itemId = removedItemIds[e.Collection]; + removedItemIds.Remove(e.Collection); } + } - // Link the node that has changed to its base. - // TODO: can link only the changed item instead of the whole collection - LinkToBase(node, (IAssetNode)node.BaseNode); - OnItemChanged(e); + // Link the node that has changed to its base. + // TODO: can link only the changed item instead of the whole collection + LinkToBase(node, (IAssetNode?)node.BaseNode); + OnItemChanged(e); - if (node.ResettingOverride) - overrideValue &= ~OverrideType.New; + if (node.ResettingOverride) + overrideValue &= ~OverrideType.New; - ItemChanged?.Invoke(sender, new AssetItemNodeChangeEventArgs(e, previousOverride, overrideValue, itemId)); - } + ItemChanged?.Invoke(sender, new AssetItemNodeChangeEventArgs(e, previousOverride, overrideValue, itemId)); + } - private void OnBaseContentChanged(INodeChangeEventArgs e, IGraphNode node) - { - var assetNode = (IAssetNode)node; + private void OnBaseContentChanged(INodeChangeEventArgs e, IGraphNode node) + { + var assetNode = (IAssetNode)node; - // Ignore base change if propagation is disabled. - if (!Container.PropagateChangesFromBase) - return; + // Ignore base change if propagation is disabled. + if (!Container.PropagateChangesFromBase) + return; - if (node.IsReference && e.OldValue != null && !e.OldValue.GetType().IsValueType) - { - var oldNode = (IAssetNode)Container.NodeContainer.GetNode(e.OldValue); - UnlinkFromBase(oldNode); - } + if (node.IsReference && e.OldValue?.GetType().IsValueType == false) + { + var oldNode = (IAssetNode?)Container.NodeContainer.GetNode(e.OldValue); + UnlinkFromBase(oldNode); + } - UpdatingPropertyFromBase = true; - var baseNode = assetNode.BaseNode; - if (assetNode.BaseNode == null) - throw new InvalidOperationException("The base is unset for the current node."); - RefreshBase(assetNode, (IAssetNode)baseNode); - ReconcileWithBase((IAssetNode)node); - UpdatingPropertyFromBase = false; + UpdatingPropertyFromBase = true; + var baseNode = assetNode.BaseNode; + if (assetNode.BaseNode is null) + throw new InvalidOperationException("The base is unset for the current node."); + RefreshBase(assetNode, (IAssetNode)baseNode); + ReconcileWithBase((IAssetNode)node); + UpdatingPropertyFromBase = false; - BaseContentChanged?.Invoke(e, node); - } + BaseContentChanged?.Invoke(e, node); + } - private void ReconcileWithBaseNode(IAssetNode assetNode, GraphNodePath currentPath, bool reconcileObjectReference, Dictionary nodesToReset) - { - var memberNode = assetNode as AssetMemberNode; - var objectNode = assetNode as IAssetObjectNodeInternal; - // Non-overridable members should not be reconciled. - if (assetNode?.BaseNode == null || !memberNode?.CanOverride == true) - return; + private void ReconcileWithBaseNode(IAssetNode assetNode, GraphNodePath currentPath, bool reconcileObjectReference, Dictionary? nodesToReset) + { + var memberNode = assetNode as AssetMemberNode; + var objectNode = assetNode as IAssetObjectNodeInternal; + // Non-overridable members should not be reconciled. + if (assetNode?.BaseNode is null || memberNode?.CanOverride == false) + return; - var localValue = assetNode.Retrieve(); - var baseValue = assetNode.BaseNode.Retrieve(); + var localValue = assetNode.Retrieve(); + var baseValue = assetNode.BaseNode.Retrieve(); - // Reconcile occurs only when the node is not overridden. - if (memberNode != null) + // Reconcile occurs only when the node is not overridden. + if (memberNode is not null) + { + if (ShouldReconcileMember(memberNode, currentPath, reconcileObjectReference, nodesToReset)) { - if (ShouldReconcileMember(memberNode, currentPath, reconcileObjectReference, nodesToReset)) + // If we have no setter, we cannot reconcile this property. Usually it means that the value is already correct (eg. it's an instance of the correct type, + // or it's a value that cannot change), so we'll just keep going and try to reconcile the children of this member. + if (memberNode.MemberDescriptor.HasSet) { - // If we have no setter, we cannot reconcile this property. Usually it means that the value is already correct (eg. it's an instance of the correct type, - // or it's a value that cannot change), so we'll just keep going and try to reconcile the children of this member. - if (memberNode.MemberDescriptor.HasSet) - { - memberNode.ResettingOverride = true; + memberNode.ResettingOverride = true; - object clonedValue; - // Object references - if (baseValue is IIdentifiable && Definition.IsMemberTargetObjectReference((IMemberNode)memberNode.BaseNode, memberNode.BaseNode.Retrieve())) - clonedValue = BaseToDerivedRegistry.ResolveFromBase(baseValue, memberNode); - else - clonedValue = CloneValueFromBase(baseValue, assetNode); + object clonedValue; + // Object references + if (baseValue is IIdentifiable && Definition.IsMemberTargetObjectReference((IMemberNode)memberNode.BaseNode, memberNode.BaseNode.Retrieve())) + clonedValue = BaseToDerivedRegistry.ResolveFromBase(baseValue, memberNode); + else + clonedValue = CloneValueFromBase(baseValue, assetNode); - // Clear override, in case we are resetting it during this reconciliation. - memberNode.Update(clonedValue); - memberNode.OverrideContent(false); + // Clear override, in case we are resetting it during this reconciliation. + memberNode.Update(clonedValue); + memberNode.OverrideContent(false); - memberNode.ResettingOverride = false; - } + memberNode.ResettingOverride = false; } } - if (objectNode != null) + } + if (objectNode is not null) + { + var baseNode = (IAssetObjectNodeInternal)assetNode.BaseNode; + objectNode.ResettingOverride = true; + // Handle collection and dictionary cases + if ((assetNode.Descriptor is CollectionDescriptor || assetNode.Descriptor is DictionaryDescriptor) && CollectionItemIdHelper.HasCollectionItemIds(objectNode.Retrieve())) { - var baseNode = (IAssetObjectNodeInternal)assetNode.BaseNode; - objectNode.ResettingOverride = true; - // Handle collection and dictionary cases - if ((assetNode.Descriptor is CollectionDescriptor || assetNode.Descriptor is DictionaryDescriptor) && CollectionItemIdHelper.HasCollectionItemIds(objectNode.Retrieve())) + // Items to add and to remove are stored in local collections and processed later, since they might affect indices + var itemsToRemove = new List(); + var itemsToAdd = new SortedList(new DefaultKeyComparer()); + + // Check for item present in the instance and absent from the base. + foreach (var index in objectNode.Indices) { - // Items to add and to remove are stored in local collections and processed later, since they might affect indices - var itemsToRemove = new List(); - var itemsToAdd = new SortedList(new DefaultKeyComparer()); + // Skip overridden items, if they are not marked to be reset. + if (objectNode.IsItemOverridden(index) || (nodesToReset is not null && nodesToReset.TryGetValue(objectNode, out NodeIndex indexToReset) && indexToReset != index)) + continue; - // Check for item present in the instance and absent from the base. - foreach (var index in objectNode.Indices) + var itemId = objectNode.IndexToId(index); + if (itemId != ItemId.Empty) { - // Skip overridden items, if they are not marked to be reset. - if (objectNode.IsItemOverridden(index) || (nodesToReset != null && nodesToReset.TryGetValue(objectNode, out NodeIndex indexToReset) && indexToReset != index)) - continue; - - var itemId = objectNode.IndexToId(index); - if (itemId != ItemId.Empty) - { - // Look if an item with the same id exists in the base. - if (!baseNode.HasId(itemId)) - { - // If not, remove this item from the instance. - itemsToRemove.Add(itemId); - } - } - else + // Look if an item with the same id exists in the base. + if (!baseNode.HasId(itemId)) { - // This case should not happen, but if we have an empty id due to corrupted data let's just remove the item. + // If not, remove this item from the instance. itemsToRemove.Add(itemId); } } + else + { + // This case should not happen, but if we have an empty id due to corrupted data let's just remove the item. + itemsToRemove.Add(itemId); + } + } - var ids = CollectionItemIdHelper.GetCollectionItemIds(localValue); - // Clean items marked as "override-deleted" that are absent from the base. - foreach (var deletedId in ids.DeletedItems.ToList()) + var ids = CollectionItemIdHelper.GetCollectionItemIds(localValue); + // Clean items marked as "override-deleted" that are absent from the base. + foreach (var deletedId in ids.DeletedItems.ToList()) + { + if (baseNode.Indices.All(x => baseNode.IndexToId(x) != deletedId)) { - if (baseNode.Indices.All(x => baseNode.IndexToId(x) != deletedId)) - { - // We "disconnect" it instead of purely remove it, so it can still be restored by undo/redo - objectNode.DisconnectOverriddenDeletedItem(deletedId); - } + // We "disconnect" it instead of purely remove it, so it can still be restored by undo/redo + objectNode.DisconnectOverriddenDeletedItem(deletedId); } + } - // Add item present in the base and missing here, and also update items that have different values between base and instance - foreach (var index in baseNode.Indices) + // Add item present in the base and missing here, and also update items that have different values between base and instance + foreach (var index in baseNode.Indices) + { + var itemId = baseNode.IndexToId(index); + // TODO: What should we do if it's empty? It can happen only from corrupted data + + // Skip items marked as "override-deleted" + if (itemId == ItemId.Empty || objectNode.IsItemDeleted(itemId)) { - var itemId = baseNode.IndexToId(index); - // TODO: What should we do if it's empty? It can happen only from corrupted data + // We force-write the item to be deleted, in case it was just "disconnected" + objectNode.OverrideDeletedItem(true, itemId); + continue; + } - // Skip items marked as "override-deleted" - if (itemId == ItemId.Empty || objectNode.IsItemDeleted(itemId)) + if (!objectNode.TryIdToIndex(itemId, out NodeIndex localIndex)) + { + // For dictionary, we might have a key collision, if so, we consider that the new value from the base is deleted in the instance. + var keyCollision = assetNode.Descriptor is DictionaryDescriptor && (objectNode.ItemReferences?.HasIndex(index) == true || objectNode.Indices.Any(x => index.Equals(x))); + // For specific collections (eg. EntityComponentCollection) it might not be possible to add due to other kinds of collisions or invalid value. + var itemRejected = !CanUpdate(assetNode, ContentChangeType.CollectionAdd, localIndex, baseNode.Retrieve(index)); + + // We cannot add the item, let's mark it as deleted. + if (keyCollision || itemRejected) { - // We force-write the item to be deleted, in case it was just "disconnected" objectNode.OverrideDeletedItem(true, itemId); - continue; } - - if (!objectNode.TryIdToIndex(itemId, out NodeIndex localIndex)) + else { - // For dictionary, we might have a key collision, if so, we consider that the new value from the base is deleted in the instance. - var keyCollision = assetNode.Descriptor is DictionaryDescriptor && (objectNode.ItemReferences?.HasIndex(index) == true || objectNode.Indices.Any(x => index.Equals(x))); - // For specific collections (eg. EntityComponentCollection) it might not be possible to add due to other kinds of collisions or invalid value. - var itemRejected = !CanUpdate(assetNode, ContentChangeType.CollectionAdd, localIndex, baseNode.Retrieve(index)); - - // We cannot add the item, let's mark it as deleted. - if (keyCollision || itemRejected) - { - objectNode.OverrideDeletedItem(true, itemId); - } + // Add it if the key is available for add + itemsToAdd.Add(index.Value, itemId); + } + } + else + { + // If the item is present in both the instance and the base, check if we need to reconcile the value + if (ShouldReconcileItem(objectNode, localIndex, index, reconcileObjectReference, nodesToReset)) + { + object clonedValue; + var baseItemValue = objectNode.BaseNode.Retrieve(index); + // Object references + if (baseItemValue is IIdentifiable && Definition.IsTargetItemObjectReference((IObjectNode)objectNode.BaseNode, index, objectNode.BaseNode.Retrieve(index))) + clonedValue = BaseToDerivedRegistry.ResolveFromBase(baseItemValue, objectNode); else - { - // Add it if the key is available for add - itemsToAdd.Add(index.Value, itemId); - } + clonedValue = CloneValueFromBase(baseItemValue, assetNode); + + objectNode.Update(clonedValue, localIndex); + objectNode.OverrideItem(false, localIndex); } - else + // In dictionaries, the keys might be different between the instance and the base. We need to reconcile them too + if (objectNode.Descriptor is DictionaryDescriptor && !objectNode.IsKeyOverridden(localIndex)) { - // If the item is present in both the instance and the base, check if we need to reconcile the value - if (ShouldReconcileItem(objectNode, localIndex, index, reconcileObjectReference, nodesToReset)) + if (ShouldReconcileIndex(localIndex, index)) { - object clonedValue; - var baseItemValue = objectNode.BaseNode.Retrieve(index); - // Object references - if (baseItemValue is IIdentifiable && Definition.IsTargetItemObjectReference((IObjectNode)objectNode.BaseNode, index, objectNode.BaseNode.Retrieve(index))) - clonedValue = BaseToDerivedRegistry.ResolveFromBase(baseItemValue, objectNode); - else - clonedValue = CloneValueFromBase(baseItemValue, assetNode); - - objectNode.Update(clonedValue, localIndex); - objectNode.OverrideItem(false, localIndex); - } - // In dictionaries, the keys might be different between the instance and the base. We need to reconcile them too - if (objectNode.Descriptor is DictionaryDescriptor && !objectNode.IsKeyOverridden(localIndex)) - { - if (ShouldReconcileIndex(localIndex, index)) - { - // Reconcile using a move (Remove + Add) of the key-value pair - var clonedIndex = new NodeIndex(CloneValueFromBase(index.Value, assetNode)); - var localItemValue = assetNode.Retrieve(localIndex); - objectNode.Remove(localItemValue, localIndex); - objectNode.Add(localItemValue, clonedIndex); - ids[clonedIndex.Value] = itemId; - } + // Reconcile using a move (Remove + Add) of the key-value pair + var clonedIndex = new NodeIndex(CloneValueFromBase(index.Value, assetNode)); + var localItemValue = assetNode.Retrieve(localIndex); + objectNode.Remove(localItemValue, localIndex); + objectNode.Add(localItemValue, clonedIndex); + ids[clonedIndex.Value] = itemId; } } } + } - // Process items marked to be removed - foreach (var item in itemsToRemove) - { - var index = objectNode.IdToIndex(item); - var value = assetNode.Retrieve(index); - objectNode.Remove(value, index); - // We're reconciling, so let's hack the normal behavior of marking the removed item as deleted. - objectNode.OverrideDeletedItem(false, item); - } + // Process items marked to be removed + foreach (var item in itemsToRemove) + { + var index = objectNode.IdToIndex(item); + var value = assetNode.Retrieve(index); + objectNode.Remove(value, index); + // We're reconciling, so let's hack the normal behavior of marking the removed item as deleted. + objectNode.OverrideDeletedItem(false, item); + } - // Process items marked to be added - foreach (var item in itemsToAdd) + // Process items marked to be added + foreach (var item in itemsToAdd) + { + var baseIndex = baseNode.IdToIndex(item.Value); + var baseItemValue = baseNode.Retrieve(baseIndex); + var clonedValue = CloneValueFromBase(baseItemValue, assetNode); + if (assetNode.Descriptor is CollectionDescriptor) { - var baseIndex = baseNode.IdToIndex(item.Value); - var baseItemValue = baseNode.Retrieve(baseIndex); - var clonedValue = CloneValueFromBase(baseItemValue, assetNode); - if (assetNode.Descriptor is CollectionDescriptor) - { - // In a collection, we need to find an index that matches the index on the base to maintain order. - // To do so, we iterate from the index in the base to zero. - var currentBaseIndex = baseIndex.Int - 1; + // In a collection, we need to find an index that matches the index on the base to maintain order. + // To do so, we iterate from the index in the base to zero. + var currentBaseIndex = baseIndex.Int - 1; - // Initialize the target index to zero, in case we don't find any better index. - var localIndex = new NodeIndex(0); + // Initialize the target index to zero, in case we don't find any better index. + var localIndex = new NodeIndex(0); + + // Find the first item of the base that also exists (in term of id) in the local node, iterating backward (from baseIndex to 0) + while (currentBaseIndex >= 0) + { + // This should not happen since the currentBaseIndex comes from the base. + if (!baseNode.TryIndexToId(new NodeIndex(currentBaseIndex), out ItemId baseId)) + throw new InvalidOperationException("Cannot find an identifier matching the index in the base collection"); - // Find the first item of the base that also exists (in term of id) in the local node, iterating backward (from baseIndex to 0) - while (currentBaseIndex >= 0) + // If we have an matching item, we want to insert right after it + if (objectNode.TryIdToIndex(baseId, out NodeIndex sameIndexInInstance)) { - // This should not happen since the currentBaseIndex comes from the base. - if (!baseNode.TryIndexToId(new NodeIndex(currentBaseIndex), out ItemId baseId)) - throw new InvalidOperationException("Cannot find an identifier matching the index in the base collection"); - - // If we have an matching item, we want to insert right after it - if (objectNode.TryIdToIndex(baseId, out NodeIndex sameIndexInInstance)) - { - localIndex = new NodeIndex(sameIndexInInstance.Int + 1); - break; - } - currentBaseIndex--; + localIndex = new NodeIndex(sameIndexInInstance.Int + 1); + break; } - - objectNode.Restore(clonedValue, localIndex, item.Value); - } - else - { - // This case is for dictionary. Key collisions have already been handle at that point so we can directly do the add without further checks. - objectNode.Restore(clonedValue, baseIndex, item.Value); + currentBaseIndex--; } + + objectNode.Restore(clonedValue, localIndex, item.Value); + } + else + { + // This case is for dictionary. Key collisions have already been handle at that point so we can directly do the add without further checks. + objectNode.Restore(clonedValue, baseIndex, item.Value); } } - - objectNode.ResettingOverride = false; } + + objectNode.ResettingOverride = false; } + } - private bool ShouldReconcileMember([NotNull] IAssetMemberNode memberNode, GraphNodePath currentPath, bool reconcileObjectReference, Dictionary nodesToReset) + private bool ShouldReconcileMember(AssetMemberNode memberNode, GraphNodePath currentPath, bool reconcileObjectReference, Dictionary? nodesToReset) + { + var localValue = memberNode.Retrieve(); + var baseValue = memberNode.BaseNode.Retrieve(); + + // First rule: if the node is to be reset, we should reconcile. + var index = NodeIndex.Empty; + if (nodesToReset?.TryGetValue(memberNode, out index) ?? false) { - var localValue = memberNode.Retrieve(); - var baseValue = memberNode.BaseNode.Retrieve(); + return index == NodeIndex.Empty; + } - // First rule: if the node is to be reset, we should reconcile. - var index = NodeIndex.Empty; - if (nodesToReset?.TryGetValue(memberNode, out index) ?? false) - { - return index == NodeIndex.Empty; - } + // Second rule: if the node is overridden, we shouldn't reconcile. + if (memberNode.IsContentOverridden()) + return false; - // Second rule: if the node is overridden, we shouldn't reconcile. - if (memberNode.IsContentOverridden()) + // Object references + if (baseValue is IIdentifiable && Definition.IsMemberTargetObjectReference((IMemberNode)memberNode.BaseNode, baseValue)) + { + if (!reconcileObjectReference) return false; - // Object references - if (baseValue is IIdentifiable && Definition.IsMemberTargetObjectReference((IMemberNode)memberNode.BaseNode, baseValue)) - { - if (!reconcileObjectReference) - return false; + var derivedTarget = BaseToDerivedRegistry.ResolveFromBase(baseValue, memberNode); + return !Equals(localValue, derivedTarget); + } - var derivedTarget = BaseToDerivedRegistry.ResolveFromBase(baseValue, memberNode); - return !Equals(localValue, derivedTarget); - } + // Non value type and non primitive types + if (memberNode.IsReference || memberNode.BaseNode.IsReference) + { + return localValue?.GetType() != baseValue?.GetType(); + } - // Non value type and non primitive types - if (memberNode.IsReference || memberNode.BaseNode.IsReference) - { - return localValue?.GetType() != baseValue?.GetType(); - } + // Content reference (note: they are not treated as reference but as primitive type) + if (AssetRegistry.IsExactContentType(localValue?.GetType()) || AssetRegistry.IsExactContentType(baseValue?.GetType())) + { + var localRef = AttachedReferenceManager.GetAttachedReference(localValue); + var baseRef = AttachedReferenceManager.GetAttachedReference(baseValue); + return localRef?.Id != baseRef?.Id || localRef?.Url != baseRef?.Url; + } - // Content reference (note: they are not treated as reference but as primitive type) - if (AssetRegistry.IsExactContentType(localValue?.GetType()) || AssetRegistry.IsExactContentType(baseValue?.GetType())) + // Value type, check if it is a child of a value type that has been overridden + foreach (var pathNode in currentPath) + { + if (pathNode is IAssetMemberNode assetMemberNode && assetMemberNode.IsContentOverridden()) { - var localRef = AttachedReferenceManager.GetAttachedReference(localValue); - var baseRef = AttachedReferenceManager.GetAttachedReference(baseValue); - return localRef?.Id != baseRef?.Id || localRef?.Url != baseRef?.Url; + // We are inside a struct that was overridden, so we are actually considered overridden + return false; } + } - // Value type, check if it is a child of a value type that has been overridden - foreach (var pathNode in currentPath) - { - if (pathNode is IAssetMemberNode assetMemberNode && assetMemberNode.IsContentOverridden()) - { - // We are inside a struct that was overridden, so we are actually considered overridden - return false; - } - } + // Value type, we check for equality + return !Equals(localValue, baseValue); + } - // Value type, we check for equality - return !Equals(localValue, baseValue); - } + private bool ShouldReconcileItem(IAssetObjectNode node, NodeIndex localIndex, NodeIndex baseIndex, bool reconcileObjectReference, Dictionary nodesToReset) + { + var localValue = node.Retrieve(localIndex); + var baseValue = node.BaseNode.Retrieve(baseIndex); - private bool ShouldReconcileItem([NotNull] IAssetObjectNode node, NodeIndex localIndex, NodeIndex baseIndex, bool reconcileObjectReference, Dictionary nodesToReset) + // First rule: if the node is to be reset, we should reconcile. + var index = NodeIndex.Empty; + if (nodesToReset?.TryGetValue(node, out index) ?? false) { - var localValue = node.Retrieve(localIndex); - var baseValue = node.BaseNode.Retrieve(baseIndex); + return index == NodeIndex.Empty || index == localIndex; + } - // First rule: if the node is to be reset, we should reconcile. - var index = NodeIndex.Empty; - if (nodesToReset?.TryGetValue(node, out index) ?? false) - { - return index == NodeIndex.Empty || index == localIndex; - } + // Second rule: if the node is overridden, we shouldn't reconcile. + if (node.IsItemOverridden(localIndex)) + return false; - // Second rule: if the node is overridden, we shouldn't reconcile. - if (node.IsItemOverridden(localIndex)) + // Object references + if (baseValue is IIdentifiable && Definition.IsTargetItemObjectReference((IObjectNode)node.BaseNode, baseIndex, node.BaseNode.Retrieve(baseIndex))) + { + if (!reconcileObjectReference) return false; - // Object references - if (baseValue is IIdentifiable && Definition.IsTargetItemObjectReference((IObjectNode)node.BaseNode, baseIndex, node.BaseNode.Retrieve(baseIndex))) - { - if (!reconcileObjectReference) - return false; - - var derivedTarget = BaseToDerivedRegistry.ResolveFromBase(baseValue, node); - return !Equals(localValue, derivedTarget); - } - - // Non value type and non primitive types - if (node.IsReference || node.BaseNode.IsReference) - { - return localValue?.GetType() != baseValue?.GetType(); - } - - // Content reference (note: they are not treated as reference but as primitive type) - if (AssetRegistry.IsExactContentType(localValue?.GetType()) || AssetRegistry.IsExactContentType(baseValue?.GetType())) - { - var localRef = AttachedReferenceManager.GetAttachedReference(localValue); - var baseRef = AttachedReferenceManager.GetAttachedReference(baseValue); - return localRef?.Id != baseRef?.Id || localRef?.Url != baseRef?.Url; - } + var derivedTarget = BaseToDerivedRegistry.ResolveFromBase(baseValue, node); + return !Equals(localValue, derivedTarget); + } - // Value type, we check for equality - return !Equals(localValue, baseValue); + // Non value type and non primitive types + if (node.IsReference || node.BaseNode.IsReference) + { + return localValue?.GetType() != baseValue?.GetType(); } - private static bool ShouldReconcileIndex(NodeIndex localIndex, NodeIndex baseIndex) + // Content reference (note: they are not treated as reference but as primitive type) + if (AssetRegistry.IsExactContentType(localValue?.GetType()) || AssetRegistry.IsExactContentType(baseValue?.GetType())) { - return !Equals(localIndex, baseIndex); + var localRef = AttachedReferenceManager.GetAttachedReference(localValue); + var baseRef = AttachedReferenceManager.GetAttachedReference(baseValue); + return localRef?.Id != baseRef?.Id || localRef?.Url != baseRef?.Url; } + + // Value type, we check for equality + return !Equals(localValue, baseValue); + } + + private static bool ShouldReconcileIndex(NodeIndex localIndex, NodeIndex baseIndex) + { + return !Equals(localIndex, baseIndex); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphAttribute.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphAttribute.cs index 45faf73bc5..678be97f3c 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphAttribute.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphAttribute.cs @@ -1,21 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Annotations; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +[BaseTypeRequired(typeof(AssetPropertyGraph))] +public class AssetPropertyGraphAttribute : Attribute { - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - [BaseTypeRequired(typeof(AssetPropertyGraph))] - public class AssetPropertyGraphAttribute : Attribute + public AssetPropertyGraphAttribute(Type assetType) { - public AssetPropertyGraphAttribute([NotNull] Type assetType) - { - if (assetType == null) throw new ArgumentNullException(nameof(assetType)); - if (!typeof(Asset).IsAssignableFrom(assetType)) throw new ArgumentException($"The given type must be assignable to the {nameof(Asset)} type."); - AssetType = assetType; - } - - public Type AssetType { get; } + ArgumentNullException.ThrowIfNull(assetType); + if (!typeof(Asset).IsAssignableFrom(assetType)) throw new ArgumentException($"The given type must be assignable to the {nameof(Asset)} type."); + AssetType = assetType; } + + public Type AssetType { get; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphContainer.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphContainer.cs index fb98d8c578..1a8c517206 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphContainer.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphContainer.cs @@ -1,55 +1,48 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Core.Annotations; + using Stride.Core.Diagnostics; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +public class AssetPropertyGraphContainer { - public class AssetPropertyGraphContainer + private readonly Dictionary registeredGraphs = []; + + public AssetPropertyGraphContainer(AssetNodeContainer nodeContainer) + { + NodeContainer = nodeContainer ?? throw new ArgumentNullException(nameof(nodeContainer)); + } + + public AssetNodeContainer NodeContainer { get; } + + public bool PropagateChangesFromBase { get; set; } = true; + + public AssetPropertyGraph? InitializeAsset(AssetItem assetItem, ILogger logger) + { + // SourceCodeAssets have no property + if (assetItem.Asset is SourceCodeAsset) + return null; + + var graph = AssetQuantumRegistry.ConstructPropertyGraph(this, assetItem, logger); + RegisterGraph(graph); + return graph; + } + + public AssetPropertyGraph? TryGetGraph(AssetId assetId) + { + registeredGraphs.TryGetValue(assetId, out var graph); + return graph; + } + + public void RegisterGraph(AssetPropertyGraph graph) + { + ArgumentNullException.ThrowIfNull(graph); + registeredGraphs.Add(graph.Id, graph); + } + + public bool UnregisterGraph(AssetId assetId) { - private readonly Dictionary registeredGraphs = new Dictionary(); - - public AssetPropertyGraphContainer([NotNull] AssetNodeContainer nodeContainer) - { - NodeContainer = nodeContainer ?? throw new ArgumentNullException(nameof(nodeContainer)); - } - - [NotNull] - public AssetNodeContainer NodeContainer { get; } - - public bool PropagateChangesFromBase { get; set; } = true; - - [CanBeNull] - public AssetPropertyGraph InitializeAsset([NotNull] AssetItem assetItem, ILogger logger) - { - // SourceCodeAssets have no property - if (assetItem.Asset is SourceCodeAsset) - return null; - - var graph = AssetQuantumRegistry.ConstructPropertyGraph(this, assetItem, logger); - RegisterGraph(graph); - return graph; - } - - [CanBeNull] - public AssetPropertyGraph TryGetGraph(AssetId assetId) - { - registeredGraphs.TryGetValue(assetId, out var graph); - return graph; - } - - public void RegisterGraph([NotNull] AssetPropertyGraph graph) - { - if (graph == null) throw new ArgumentNullException(nameof(graph)); - registeredGraphs.Add(graph.Id, graph); - } - - public bool UnregisterGraph(AssetId assetId) - { - return registeredGraphs.Remove(assetId); - } + return registeredGraphs.Remove(assetId); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphDefinition.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphDefinition.cs index d3131eeb57..3dc6840a5c 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphDefinition.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphDefinition.cs @@ -1,37 +1,34 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +/// +/// A class defining how an should behave for a given type. +/// +[AssetPropertyGraphDefinition(typeof(Asset))] +// ReSharper disable once RequiredBaseTypesIsNotInherited - due to a limitation on how ReSharper checks this requirement (see https://youtrack.jetbrains.com/issue/RSRP-462598) +public class AssetPropertyGraphDefinition { - /// - /// A class defining how an should behave for a given type. - /// - [AssetPropertyGraphDefinition(typeof(Asset))] - // ReSharper disable once RequiredBaseTypesIsNotInherited - due to a limitation on how ReSharper checks this requirement (see https://youtrack.jetbrains.com/issue/RSRP-462598) - public class AssetPropertyGraphDefinition + public bool IsObjectReference(NodeAccessor nodeAccessor, object value) { - public bool IsObjectReference(NodeAccessor nodeAccessor, object value) - { - if (nodeAccessor.IsMember) - return IsMemberTargetObjectReference((IMemberNode)nodeAccessor.Node, value); - if (nodeAccessor.IsItem) - return IsTargetItemObjectReference((IObjectNode)nodeAccessor.Node, nodeAccessor.Index, value); + if (nodeAccessor.IsMember) + return IsMemberTargetObjectReference((IMemberNode)nodeAccessor.Node, value); + if (nodeAccessor.IsItem) + return IsTargetItemObjectReference((IObjectNode)nodeAccessor.Node, nodeAccessor.Index, value); - return false; - } + return false; + } - public virtual bool IsMemberTargetObjectReference(IMemberNode member, object value) - { - return false; - } + public virtual bool IsMemberTargetObjectReference(IMemberNode member, object? value) + { + return false; + } - public virtual bool IsTargetItemObjectReference(IObjectNode collection, NodeIndex itemIndex, object value) - { - return false; - } + public virtual bool IsTargetItemObjectReference(IObjectNode collection, NodeIndex itemIndex, object? value) + { + return false; } } \ No newline at end of file diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphDefinitionAttribute.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphDefinitionAttribute.cs index 730fd90ee0..ae3902e9f9 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphDefinitionAttribute.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetPropertyGraphDefinitionAttribute.cs @@ -1,19 +1,18 @@ -using System; + using Stride.Core.Annotations; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +[BaseTypeRequired(typeof(AssetPropertyGraphDefinition))] +public class AssetPropertyGraphDefinitionAttribute : Attribute { - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - [BaseTypeRequired(typeof(AssetPropertyGraphDefinition))] - public class AssetPropertyGraphDefinitionAttribute : Attribute + public AssetPropertyGraphDefinitionAttribute(Type assetType) { - public AssetPropertyGraphDefinitionAttribute([NotNull] Type assetType) - { - if (assetType == null) throw new ArgumentNullException(nameof(assetType)); - if (!typeof(Asset).IsAssignableFrom(assetType)) throw new ArgumentException($"The given type must be assignable to the {nameof(Asset)} type."); - AssetType = assetType; - } - - public Type AssetType { get; } + ArgumentNullException.ThrowIfNull(assetType); + if (!typeof(Asset).IsAssignableFrom(assetType)) throw new ArgumentException($"The given type must be assignable to the {nameof(Asset)} type."); + AssetType = assetType; } + + public Type AssetType { get; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetQuantumRegistry.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetQuantumRegistry.cs index f1b47d1538..a420fd4ba8 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetQuantumRegistry.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetQuantumRegistry.cs @@ -1,128 +1,123 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.Reflection; -using Stride.Core.Annotations; using Stride.Core.Diagnostics; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +public static class AssetQuantumRegistry { - public static class AssetQuantumRegistry - { - private static readonly Type[] AssetPropertyNodeGraphConstructorSignature = { typeof(AssetPropertyGraphContainer), typeof(AssetItem), typeof(ILogger) }; - private static readonly Dictionary NodeGraphTypes = new Dictionary(); - private static readonly Dictionary NodeGraphDefinitions = new Dictionary(); - private static readonly Dictionary GenericNodeGraphDefinitionTypes = new Dictionary(); + private static readonly Type[] AssetPropertyNodeGraphConstructorSignature = [typeof(AssetPropertyGraphContainer), typeof(AssetItem), typeof(ILogger)]; + private static readonly Dictionary NodeGraphTypes = []; + private static readonly Dictionary NodeGraphDefinitions = []; + private static readonly Dictionary GenericNodeGraphDefinitionTypes = []; - public static void RegisterAssembly([NotNull] Assembly assembly) + public static void RegisterAssembly(Assembly assembly) + { + foreach (var type in assembly.GetTypes()) { - foreach (var type in assembly.GetTypes()) + if (typeof(AssetPropertyGraph).IsAssignableFrom(type)) { - if (typeof(AssetPropertyGraph).IsAssignableFrom(type)) - { - var attribute = type.GetCustomAttribute(); - if (attribute == null) - continue; + var attribute = type.GetCustomAttribute(); + if (attribute is null) + continue; - if (type.GetConstructor(AssetPropertyNodeGraphConstructorSignature) == null) - throw new InvalidOperationException($"The type {type.Name} does not have a public constructor matching the expected signature: ({string.Join(", ", (IEnumerable)AssetPropertyNodeGraphConstructorSignature)})"); + if (type.GetConstructor(AssetPropertyNodeGraphConstructorSignature) is null) + throw new InvalidOperationException($"The type {type.Name} does not have a public constructor matching the expected signature: ({string.Join(", ", (IEnumerable)AssetPropertyNodeGraphConstructorSignature)})"); - if (NodeGraphTypes.ContainsKey(attribute.AssetType)) - throw new ArgumentException($"The type {attribute.AssetType.Name} already has an associated property node graph type."); + if (NodeGraphTypes.ContainsKey(attribute.AssetType)) + throw new ArgumentException($"The type {attribute.AssetType.Name} already has an associated property node graph type."); - NodeGraphTypes.Add(attribute.AssetType, type); - } + NodeGraphTypes.Add(attribute.AssetType, type); + } - if (typeof(AssetPropertyGraphDefinition).IsAssignableFrom(type)) - { - var attribute = type.GetCustomAttribute(); - if (attribute == null) - continue; + if (typeof(AssetPropertyGraphDefinition).IsAssignableFrom(type)) + { + var attribute = type.GetCustomAttribute(); + if (attribute is null) + continue; - if (type.GetConstructor(Type.EmptyTypes) == null) - throw new InvalidOperationException($"The type {type.Name} does not have a public parameterless constructor.)"); + if (type.GetConstructor(Type.EmptyTypes) is null) + throw new InvalidOperationException($"The type {type.Name} does not have a public parameterless constructor.)"); - if (NodeGraphDefinitions.ContainsKey(attribute.AssetType)) - throw new ArgumentException($"The type {attribute.AssetType.Name} already has an associated property node graph type."); + if (NodeGraphDefinitions.ContainsKey(attribute.AssetType)) + throw new ArgumentException($"The type {attribute.AssetType.Name} already has an associated property node graph type."); - if (attribute.AssetType.IsGenericTypeDefinition && type.IsGenericType) - { - // If the asset type is generic (usually a base class of other asset types), we cannot create instances yet. - // So we just store the generic type definition in another dictionary. - GenericNodeGraphDefinitionTypes.Add(attribute.AssetType, type.GetGenericTypeDefinition()); - } - else - { - // Normal case, we create an instance of the definition immediately. - var definition = (AssetPropertyGraphDefinition)Activator.CreateInstance(type); - NodeGraphDefinitions.Add(attribute.AssetType, definition); - } + if (attribute.AssetType.IsGenericTypeDefinition && type.IsGenericType) + { + // If the asset type is generic (usually a base class of other asset types), we cannot create instances yet. + // So we just store the generic type definition in another dictionary. + GenericNodeGraphDefinitionTypes.Add(attribute.AssetType, type.GetGenericTypeDefinition()); + } + else + { + // Normal case, we create an instance of the definition immediately. + var definition = (AssetPropertyGraphDefinition)Activator.CreateInstance(type)!; + NodeGraphDefinitions.Add(attribute.AssetType, definition); } } } + } - [NotNull] - public static AssetPropertyGraph ConstructPropertyGraph(AssetPropertyGraphContainer container, [NotNull] AssetItem assetItem, ILogger logger) + public static AssetPropertyGraph ConstructPropertyGraph(AssetPropertyGraphContainer container, AssetItem assetItem, ILogger? logger) + { + var assetType = assetItem.Asset.GetType(); + while (assetType is not null) { - var assetType = assetItem.Asset.GetType(); - while (assetType != null) + var typeToTest = assetType.IsGenericType ? assetType.GetGenericTypeDefinition() : assetType; + if (NodeGraphTypes.TryGetValue(typeToTest, out var propertyGraphType)) { - var typeToTest = assetType.IsGenericType ? assetType.GetGenericTypeDefinition() : assetType; - if (NodeGraphTypes.TryGetValue(typeToTest, out Type propertyGraphType)) - { - return (AssetPropertyGraph)Activator.CreateInstance(propertyGraphType, container, assetItem, logger); - } - assetType = assetType.BaseType; + return (AssetPropertyGraph)Activator.CreateInstance(propertyGraphType, container, assetItem, logger)!; } - throw new InvalidOperationException("No AssetPropertyGraph type matching the given asset type has been found"); + assetType = assetType.BaseType; } + throw new InvalidOperationException("No AssetPropertyGraph type matching the given asset type has been found"); + } + + public static AssetPropertyGraphDefinition GetDefinition(Type assetType) + { + if (!typeof(Asset).IsAssignableFrom(assetType)) + throw new ArgumentException($"The type {assetType.Name} is not an asset type"); - public static AssetPropertyGraphDefinition GetDefinition(Type assetType) + var currentType = assetType; + while (currentType is not null && currentType != typeof(Asset)) { - if (!typeof(Asset).IsAssignableFrom(assetType)) - throw new ArgumentException($"The type {assetType.Name} is not an asset type"); + // ReSharper disable once AssignNullToNotNullAttribute - cannot happen + if (NodeGraphDefinitions.TryGetValue(currentType, out var definition)) + { + // Register the instance for this specific type so we don't have to do this again next time. + if (currentType != assetType) + { + NodeGraphDefinitions.Add(assetType, definition); + } + return definition; + } - var currentType = assetType; - while (currentType != typeof(Asset)) + if (currentType.IsGenericType) { - AssetPropertyGraphDefinition definition; - // ReSharper disable once AssignNullToNotNullAttribute - cannot happen - if (NodeGraphDefinitions.TryGetValue(currentType, out definition)) + // If we reach a generic type, we must check if we have a matching generic definition and if so, create a proper instance of this generic type. + var assetGenericDefinitionType = currentType.GetGenericTypeDefinition(); + if (GenericNodeGraphDefinitionTypes.TryGetValue(assetGenericDefinitionType, out var definitionGenericDefinitionType)) { - // Register the instance for this specific type so we don't have to do this again next time. - if (currentType != assetType) + try { + var definitionType = definitionGenericDefinitionType.MakeGenericType(currentType.GetGenericArguments()); + definition = (AssetPropertyGraphDefinition)Activator.CreateInstance(definitionType)!; + // Register the (new) instance for this specific type so we don't have to do this again next time. NodeGraphDefinitions.Add(assetType, definition); + return definition; } - return definition; - } - - if (currentType.IsGenericType) - { - // If we reach a generic type, we must check if we have a matching generic definition and if so, create a proper instance of this generic type. - var assetGenericDefinitionType = currentType.GetGenericTypeDefinition(); - if (GenericNodeGraphDefinitionTypes.TryGetValue(assetGenericDefinitionType, out var definitionGenericDefinitionType)) + catch (Exception) { - try - { - var definitionType = definitionGenericDefinitionType.MakeGenericType(currentType.GetGenericArguments()); - definition = (AssetPropertyGraphDefinition)Activator.CreateInstance(definitionType); - // Register the (new) instance for this specific type so we don't have to do this again next time. - NodeGraphDefinitions.Add(assetType, definition); - return definition; - } - catch (Exception) - { - throw new InvalidOperationException($"Unable to create an instance of definition type {definitionGenericDefinitionType.Name} for asset type {currentType.Name}."); - } + throw new InvalidOperationException($"Unable to create an instance of definition type {definitionGenericDefinitionType.Name} for asset type {currentType.Name}."); } } - - currentType = currentType.BaseType; } - return NodeGraphDefinitions[typeof(Asset)]; + currentType = currentType.BaseType; } + + return NodeGraphDefinitions[typeof(Asset)]; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/AssetToBaseNodeLinker.cs b/sources/assets/Stride.Core.Assets.Quantum/AssetToBaseNodeLinker.cs index 345c6ab6b4..f4c056f59c 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/AssetToBaseNodeLinker.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/AssetToBaseNodeLinker.cs @@ -1,60 +1,57 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Linq; + using Stride.Core.Assets.Quantum.Internal; -using Stride.Core.Annotations; using Stride.Core.Reflection; using Stride.Core.Quantum; using Stride.Core.Quantum.References; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +/// +/// A that can link nodes of an asset to the corresponding nodes in their base. +/// +/// This method will invoke when linking, to allow custom links for cases such as . +public class AssetToBaseNodeLinker : AssetGraphNodeLinker { - /// - /// A that can link nodes of an asset to the corresponding nodes in their base. - /// - /// This method will invoke when linking, to allow custom links for cases such as . - public class AssetToBaseNodeLinker : AssetGraphNodeLinker - { - private readonly AssetPropertyGraph propertyGraph; + private readonly AssetPropertyGraph propertyGraph; - public AssetToBaseNodeLinker([NotNull] AssetPropertyGraph propertyGraph) - : base(propertyGraph.Definition) - { - this.propertyGraph = propertyGraph; - } + public AssetToBaseNodeLinker(AssetPropertyGraph propertyGraph) + : base(propertyGraph.Definition) + { + this.propertyGraph = propertyGraph; + } - protected override IGraphNode FindTarget(IGraphNode sourceNode) - { - var defaultTarget = base.FindTarget(sourceNode); - return propertyGraph.FindTarget(sourceNode, defaultTarget); - } + protected override IGraphNode FindTarget(IGraphNode sourceNode) + { + var defaultTarget = base.FindTarget(sourceNode); + return propertyGraph.FindTarget(sourceNode, defaultTarget); + } - public override ObjectReference FindTargetReference(IGraphNode sourceNode, IGraphNode targetNode, ObjectReference sourceReference) - { - // Not identifiable - default applies - if (sourceReference.Index.IsEmpty || sourceReference.ObjectValue == null) - return base.FindTargetReference(sourceNode, targetNode, sourceReference); - - // Special case for objects that are identifiable: the object must be linked to the base only if it has the same id - var sourceAssetNode = (AssetObjectNode)sourceNode; - var targetAssetNode = (AssetObjectNode)targetNode; - if (!CollectionItemIdHelper.HasCollectionItemIds(sourceAssetNode.Retrieve())) - return null; - - // Enumerable reference: we look for an object with the same id - var targetReference = targetAssetNode.ItemReferences; - var sourceIds = CollectionItemIdHelper.GetCollectionItemIds(sourceNode.Retrieve()); - var targetIds = CollectionItemIdHelper.GetCollectionItemIds(targetNode.Retrieve()); - var itemId = sourceIds[sourceReference.Index.Value]; - var targetKey = targetIds.GetKey(itemId); - foreach (var targetRef in targetReference) - { - if (Equals(targetRef.Index.Value, targetKey)) - return targetRef; - } + public override ObjectReference? FindTargetReference(IGraphNode sourceNode, IGraphNode targetNode, ObjectReference sourceReference) + { + // Not identifiable - default applies + if (sourceReference.Index.IsEmpty || sourceReference.ObjectValue is null) + return base.FindTargetReference(sourceNode, targetNode, sourceReference); + // Special case for objects that are identifiable: the object must be linked to the base only if it has the same id + var sourceAssetNode = (AssetObjectNode)sourceNode; + var targetAssetNode = (AssetObjectNode)targetNode; + if (!CollectionItemIdHelper.HasCollectionItemIds(sourceAssetNode.Retrieve())) return null; + + // Enumerable reference: we look for an object with the same id + var targetReference = targetAssetNode.ItemReferences; + var sourceIds = CollectionItemIdHelper.GetCollectionItemIds(sourceNode.Retrieve()); + var targetIds = CollectionItemIdHelper.GetCollectionItemIds(targetNode.Retrieve()); + var itemId = sourceIds[sourceReference.Index.Value]; + var targetKey = targetIds.GetKey(itemId); + foreach (var targetRef in targetReference) + { + if (Equals(targetRef.Index.Value, targetKey)) + return targetRef; } + + return null; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/IAssetMemberNode.cs b/sources/assets/Stride.Core.Assets.Quantum/IAssetMemberNode.cs index 46d6f5af7f..87dbece221 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/IAssetMemberNode.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/IAssetMemberNode.cs @@ -1,28 +1,26 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Annotations; + using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +public interface IAssetMemberNode : IAssetNode, IMemberNode { - public interface IAssetMemberNode : IAssetNode, IMemberNode - { - bool IsNonIdentifiableCollectionContent { get; } + bool IsNonIdentifiableCollectionContent { get; } - bool CanOverride { get; } + bool CanOverride { get; } - [NotNull] - new IAssetObjectNode Parent { get; } + new IAssetObjectNode Parent { get; } - new IAssetObjectNode Target { get; } + new IAssetObjectNode? Target { get; } - void OverrideContent(bool isOverridden); + void OverrideContent(bool isOverridden); - OverrideType GetContentOverride(); + OverrideType GetContentOverride(); - bool IsContentOverridden(); + bool IsContentOverridden(); - bool IsContentInherited(); - } + bool IsContentInherited(); } diff --git a/sources/assets/Stride.Core.Assets.Quantum/IAssetNode.cs b/sources/assets/Stride.Core.Assets.Quantum/IAssetNode.cs index e78f54b8f2..6c4e425e7b 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/IAssetNode.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/IAssetNode.cs @@ -1,46 +1,45 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +/// +/// Base interface for that represents asset properties. +/// +public interface IAssetNode : IGraphNode { /// - /// Base interface for that represents asset properties. + /// Gets the of the related asset. + /// + AssetPropertyGraph PropertyGraph { get; } + + /// + /// Gets the base node from which the value inherits, in case it inherits from an Archetype or from part composition. + /// + IGraphNode BaseNode { get; } + + /// + /// Attaches a specific node to this node. + /// + /// The key representing the type of the node to attach. + /// The node to attach. + void SetContent(string key, IGraphNode node); + + /// + /// Retrieves an attached node. + /// + /// The key representing the type of attached node. + /// The attached node corresponding to the given key if available, null otherwise. + IGraphNode? GetContent(string key); + + event EventHandler? OverrideChanging; + + event EventHandler? OverrideChanged; + + /// + /// Resets all the overrides attached to this node and its descendants, recursively. /// - public interface IAssetNode : IGraphNode - { - /// - /// Gets the of the related asset. - /// - AssetPropertyGraph PropertyGraph { get; } - - /// - /// Gets the base node from which the value inherits, in case it inherits from an Archetype or from part composition. - /// - IGraphNode BaseNode { get; } - - /// - /// Attaches a specific node to this node. - /// - /// The key representing the type of the node to attach. - /// The node to attach. - void SetContent(string key, IGraphNode node); - - /// - /// Retrieves an attached node. - /// - /// The key representing the type of attached node. - /// The attached node corresponding to the given key if available, null otherwise. - IGraphNode GetContent(string key); - - event EventHandler OverrideChanging; - - event EventHandler OverrideChanged; - - /// - /// Resets all the overrides attached to this node and its descendants, recursively. - /// - void ResetOverrideRecursively(); - } + void ResetOverrideRecursively(); } diff --git a/sources/assets/Stride.Core.Assets.Quantum/IAssetObjectNode.cs b/sources/assets/Stride.Core.Assets.Quantum/IAssetObjectNode.cs index 788ba097ff..467ac91e07 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/IAssetObjectNode.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/IAssetObjectNode.cs @@ -1,61 +1,58 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +public interface IAssetObjectNode : IAssetNode, IObjectNode { - public interface IAssetObjectNode : IAssetNode, IObjectNode - { - [NotNull] - new IAssetMemberNode this[string name] { get; } + new IAssetMemberNode this[string name] { get; } - new IAssetObjectNode IndexedTarget(NodeIndex index); + new IAssetObjectNode? IndexedTarget(NodeIndex index); - void OverrideItem(bool isOverridden, NodeIndex index); + void OverrideItem(bool isOverridden, NodeIndex index); - void OverrideKey(bool isOverridden, NodeIndex index); + void OverrideKey(bool isOverridden, NodeIndex index); - void OverrideDeletedItem(bool isOverridden, ItemId deletedId); + void OverrideDeletedItem(bool isOverridden, ItemId deletedId); - bool IsItemDeleted(ItemId itemId); + bool IsItemDeleted(ItemId itemId); - void Restore(object restoredItem, ItemId id); + void Restore(object restoredItem, ItemId id); - void Restore(object restoredItem, NodeIndex index, ItemId id); + void Restore(object restoredItem, NodeIndex index, ItemId id); - void RemoveAndDiscard(object item, NodeIndex itemIndex, ItemId id); + void RemoveAndDiscard(object item, NodeIndex itemIndex, ItemId id); - bool IsItemInherited(NodeIndex index); + bool IsItemInherited(NodeIndex index); - bool IsKeyInherited(NodeIndex index); + bool IsKeyInherited(NodeIndex index); - bool IsItemOverridden(NodeIndex index); + bool IsItemOverridden(NodeIndex index); - bool IsItemOverriddenDeleted(ItemId id); + bool IsItemOverriddenDeleted(ItemId id); - bool IsKeyOverridden(NodeIndex index); + bool IsKeyOverridden(NodeIndex index); - ItemId IndexToId(NodeIndex index); + ItemId IndexToId(NodeIndex index); - bool TryIndexToId(NodeIndex index, out ItemId id); + bool TryIndexToId(NodeIndex index, out ItemId id); - bool HasId(ItemId id); + bool HasId(ItemId id); - NodeIndex IdToIndex(ItemId id); + NodeIndex IdToIndex(ItemId id); - bool TryIdToIndex(ItemId id, out NodeIndex index); + bool TryIdToIndex(ItemId id, out NodeIndex index); - /// - /// Resets the overrides attached to this node at a specific index and to its descendants, recursively. - /// - /// The index of the override to reset in this node. - void ResetOverrideRecursively(NodeIndex indexToReset); + /// + /// Resets the overrides attached to this node at a specific index and to its descendants, recursively. + /// + /// The index of the override to reset in this node. + void ResetOverrideRecursively(NodeIndex indexToReset); - IEnumerable GetOverriddenItemIndices(); + IEnumerable GetOverriddenItemIndices(); - IEnumerable GetOverriddenKeyIndices(); - } + IEnumerable GetOverriddenKeyIndices(); } diff --git a/sources/assets/Stride.Core.Assets.Quantum/IBaseToDerivedRegistry.cs b/sources/assets/Stride.Core.Assets.Quantum/IBaseToDerivedRegistry.cs index c1ddddef77..ef2257c431 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/IBaseToDerivedRegistry.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/IBaseToDerivedRegistry.cs @@ -1,15 +1,11 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; -using Stride.Core.Annotations; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +public interface IBaseToDerivedRegistry { - public interface IBaseToDerivedRegistry - { - void RegisterBaseToDerived([CanBeNull] IAssetNode baseNode, [NotNull] IAssetNode derivedNode); + void RegisterBaseToDerived(IAssetNode? baseNode, IAssetNode derivedNode); - [CanBeNull] - IIdentifiable ResolveFromBase([CanBeNull] object baseObjectReference, [NotNull] IAssetNode derivedReferencerNode); - } + IIdentifiable? ResolveFromBase(object? baseObjectReference, IAssetNode derivedReferencerNode); } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetBoxedNode.cs b/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetBoxedNode.cs index eae7dfb6e4..d84a96d300 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetBoxedNode.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetBoxedNode.cs @@ -1,97 +1,93 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Internal +namespace Stride.Core.Assets.Quantum.Internal; + +internal class AssetBoxedNode : BoxedNode, IAssetObjectNodeInternal { - internal class AssetBoxedNode : BoxedNode, IAssetObjectNodeInternal - { - private AssetObjectNodeExtended ex; + private AssetObjectNodeExtended ex; - public AssetBoxedNode([NotNull] INodeBuilder nodeBuilder, object value, Guid guid, [NotNull] ITypeDescriptor descriptor) - : base(nodeBuilder, value, guid, descriptor) - { - ex = new AssetObjectNodeExtended(this); - ItemChanged += (sender, e) => ex.OnItemChanged(sender, e); - } + public AssetBoxedNode(INodeBuilder nodeBuilder, object value, Guid guid, ITypeDescriptor descriptor) + : base(nodeBuilder, value, guid, descriptor) + { + ex = new AssetObjectNodeExtended(this); + ItemChanged += (sender, e) => ex.OnItemChanged(sender, e); + } - public AssetPropertyGraph PropertyGraph => ex.PropertyGraph; + public AssetPropertyGraph PropertyGraph => ex.PropertyGraph; - public IGraphNode BaseNode => ex.BaseNode; + public IGraphNode BaseNode => ex.BaseNode; - public new IAssetMemberNode this[[NotNull] string name] => (IAssetMemberNode)base[name]; + public new IAssetMemberNode this[string name] => (IAssetMemberNode)base[name]; - public event EventHandler OverrideChanging; + public event EventHandler? OverrideChanging; - public event EventHandler OverrideChanged; + public event EventHandler? OverrideChanged; - public void SetContent([NotNull] string key, IGraphNode node) => ex.SetContent(key, node); + public void SetContent(string key, IGraphNode node) => ex.SetContent(key, node); - [CanBeNull] - public IGraphNode GetContent([NotNull] string key) => ex.GetContent(key); + public IGraphNode? GetContent(string key) => ex.GetContent(key); - public void ResetOverrideRecursively() => ex.ResetOverrideRecursively(NodeIndex.Empty); + public void ResetOverrideRecursively() => ex.ResetOverrideRecursively(NodeIndex.Empty); - public void ResetOverrideRecursively(NodeIndex indexToReset) => ex.ResetOverrideRecursively(indexToReset); + public void ResetOverrideRecursively(NodeIndex indexToReset) => ex.ResetOverrideRecursively(indexToReset); - public void OverrideItem(bool isOverridden, NodeIndex index) => ex.OverrideItem(isOverridden, index); + public void OverrideItem(bool isOverridden, NodeIndex index) => ex.OverrideItem(isOverridden, index); - public void OverrideKey(bool isOverridden, NodeIndex index) => ex.OverrideKey(isOverridden, index); + public void OverrideKey(bool isOverridden, NodeIndex index) => ex.OverrideKey(isOverridden, index); - public void OverrideDeletedItem(bool isOverridden, ItemId deletedId) => ex.OverrideDeletedItem(isOverridden, deletedId); + public void OverrideDeletedItem(bool isOverridden, ItemId deletedId) => ex.OverrideDeletedItem(isOverridden, deletedId); - public bool IsItemDeleted(ItemId itemId) => ex.IsItemDeleted(itemId); + public bool IsItemDeleted(ItemId itemId) => ex.IsItemDeleted(itemId); - public void Restore(object restoredItem, ItemId id) => ex.Restore(restoredItem, id); + public void Restore(object restoredItem, ItemId id) => ex.Restore(restoredItem, id); - public void Restore(object restoredItem, NodeIndex index, ItemId id) => ex.Restore(restoredItem, index, id); + public void Restore(object restoredItem, NodeIndex index, ItemId id) => ex.Restore(restoredItem, index, id); - public void RemoveAndDiscard(object item, NodeIndex itemIndex, ItemId id) => ex.RemoveAndDiscard(item, itemIndex, id); + public void RemoveAndDiscard(object item, NodeIndex itemIndex, ItemId id) => ex.RemoveAndDiscard(item, itemIndex, id); - public OverrideType GetItemOverride(NodeIndex index) => ex.GetItemOverride(index); + public OverrideType GetItemOverride(NodeIndex index) => ex.GetItemOverride(index); - public OverrideType GetKeyOverride(NodeIndex index) => ex.GetKeyOverride(index); + public OverrideType GetKeyOverride(NodeIndex index) => ex.GetKeyOverride(index); - public bool IsItemInherited(NodeIndex index) => ex.IsItemInherited(index); + public bool IsItemInherited(NodeIndex index) => ex.IsItemInherited(index); - public bool IsKeyInherited(NodeIndex index) => ex.IsKeyInherited(index); + public bool IsKeyInherited(NodeIndex index) => ex.IsKeyInherited(index); - public bool IsItemOverridden(NodeIndex index) => ex.IsItemOverridden(index); + public bool IsItemOverridden(NodeIndex index) => ex.IsItemOverridden(index); - public bool IsItemOverriddenDeleted(ItemId id) => ex.IsItemOverriddenDeleted(id); + public bool IsItemOverriddenDeleted(ItemId id) => ex.IsItemOverriddenDeleted(id); - public bool IsKeyOverridden(NodeIndex index) => ex.IsKeyOverridden(index); + public bool IsKeyOverridden(NodeIndex index) => ex.IsKeyOverridden(index); - public IEnumerable GetOverriddenItemIndices() => ex.GetOverriddenItemIndices(); + public IEnumerable GetOverriddenItemIndices() => ex.GetOverriddenItemIndices(); - public IEnumerable GetOverriddenKeyIndices() => ex.GetOverriddenKeyIndices(); + public IEnumerable GetOverriddenKeyIndices() => ex.GetOverriddenKeyIndices(); - public ItemId IndexToId(NodeIndex index) => ex.IndexToId(index); + public ItemId IndexToId(NodeIndex index) => ex.IndexToId(index); - public bool TryIndexToId(NodeIndex index, out ItemId id) => ex.TryIndexToId(index, out id); + public bool TryIndexToId(NodeIndex index, out ItemId id) => ex.TryIndexToId(index, out id); - public bool HasId(ItemId id) => ex.HasId(id); + public bool HasId(ItemId id) => ex.HasId(id); - public NodeIndex IdToIndex(ItemId id) => ex.IdToIndex(id); + public NodeIndex IdToIndex(ItemId id) => ex.IdToIndex(id); - public bool TryIdToIndex(ItemId id, out NodeIndex index) => ex.TryIdToIndex(id, out index); + public bool TryIdToIndex(ItemId id, out NodeIndex index) => ex.TryIdToIndex(id, out index); - IAssetObjectNode IAssetObjectNode.IndexedTarget(NodeIndex index) => (IAssetObjectNode)IndexedTarget(index); + IAssetObjectNode? IAssetObjectNode.IndexedTarget(NodeIndex index) => (IAssetObjectNode?)IndexedTarget(index); - void IAssetObjectNodeInternal.DisconnectOverriddenDeletedItem(ItemId deletedId) => ex.DisconnectOverriddenDeletedItem(deletedId); + void IAssetObjectNodeInternal.DisconnectOverriddenDeletedItem(ItemId deletedId) => ex.DisconnectOverriddenDeletedItem(deletedId); - void IAssetObjectNodeInternal.NotifyOverrideChanging() => OverrideChanging?.Invoke(this, EventArgs.Empty); + void IAssetObjectNodeInternal.NotifyOverrideChanging() => OverrideChanging?.Invoke(this, EventArgs.Empty); - void IAssetObjectNodeInternal.NotifyOverrideChanged() => OverrideChanged?.Invoke(this, EventArgs.Empty); + void IAssetObjectNodeInternal.NotifyOverrideChanged() => OverrideChanged?.Invoke(this, EventArgs.Empty); - bool IAssetNodeInternal.ResettingOverride { get => ex.ResettingOverride; set => ex.ResettingOverride = value; } + bool IAssetNodeInternal.ResettingOverride { get => ex.ResettingOverride; set => ex.ResettingOverride = value; } - void IAssetNodeInternal.SetPropertyGraph(AssetPropertyGraph assetPropertyGraph) => ex.SetPropertyGraph(assetPropertyGraph); + void IAssetNodeInternal.SetPropertyGraph(AssetPropertyGraph assetPropertyGraph) => ex.SetPropertyGraph(assetPropertyGraph); - void IAssetNodeInternal.SetBaseNode(IGraphNode node) => ex.SetBaseContent(node); - } + void IAssetNodeInternal.SetBaseNode(IGraphNode node) => ex.SetBaseContent(node); } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetMemberNode.cs b/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetMemberNode.cs index d614ddcb78..2f99878a0b 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetMemberNode.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetMemberNode.cs @@ -1,131 +1,126 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using Stride.Core.Annotations; using Stride.Core.Reflection; using Stride.Core.Quantum; using Stride.Core.Quantum.References; -namespace Stride.Core.Assets.Quantum.Internal +namespace Stride.Core.Assets.Quantum.Internal; + +internal class AssetMemberNode : MemberNode, IAssetMemberNode, IAssetNodeInternal { - internal class AssetMemberNode : MemberNode, IAssetMemberNode, IAssetNodeInternal + private AssetPropertyGraph propertyGraph; + private readonly Dictionary contents = []; + + private OverrideType contentOverride; + + public AssetMemberNode(INodeBuilder nodeBuilder, Guid guid, IObjectNode parent, IMemberDescriptor memberDescriptor, IReference? reference) + : base(nodeBuilder, guid, parent, memberDescriptor, reference) { - private AssetPropertyGraph propertyGraph; - private readonly Dictionary contents = new Dictionary(); + ValueChanged += ContentChanged; + IsNonIdentifiableCollectionContent = MemberDescriptor.GetCustomAttributes(true)?.Any() ?? false; + CanOverride = MemberDescriptor.GetCustomAttributes(true)?.Any() != true; + } - private OverrideType contentOverride; + public bool IsNonIdentifiableCollectionContent { get; } - public AssetMemberNode([NotNull] INodeBuilder nodeBuilder, Guid guid, [NotNull] IObjectNode parent, [NotNull] IMemberDescriptor memberDescriptor, IReference reference) - : base(nodeBuilder, guid, parent, memberDescriptor, reference) - { - ValueChanged += ContentChanged; - IsNonIdentifiableCollectionContent = MemberDescriptor.GetCustomAttributes(true)?.Any() ?? false; - CanOverride = MemberDescriptor.GetCustomAttributes(true)?.Any() != true; - } + public bool CanOverride { get; } - public bool IsNonIdentifiableCollectionContent { get; } + internal bool ResettingOverride { get; set; } - public bool CanOverride { get; } + public event EventHandler? OverrideChanging; - internal bool ResettingOverride { get; set; } + public event EventHandler? OverrideChanged; - public event EventHandler OverrideChanging; + public AssetPropertyGraph PropertyGraph { get => propertyGraph; internal set => propertyGraph = value ?? throw new ArgumentNullException(nameof(value)); } - public event EventHandler OverrideChanged; + public IGraphNode BaseNode { get; private set; } - public AssetPropertyGraph PropertyGraph { get => propertyGraph; internal set => propertyGraph = value ?? throw new ArgumentNullException(nameof(value)); } + public new IAssetObjectNode Parent => (IAssetObjectNode)base.Parent; - public IGraphNode BaseNode { get; private set; } + public new IAssetObjectNode? Target => (IAssetObjectNode?)base.Target; - public new IAssetObjectNode Parent => (IAssetObjectNode)base.Parent; + public void SetContent(string key, IGraphNode node) + { + contents[key] = node; + } - [CanBeNull] - public new IAssetObjectNode Target => (IAssetObjectNode)base.Target; + public IGraphNode? GetContent(string key) + { + contents.TryGetValue(key, out var node); + return node; + } - public void SetContent([NotNull] string key, IGraphNode node) + public void OverrideContent(bool isOverridden) + { + if (CanOverride) { - contents[key] = node; + OverrideChanging?.Invoke(this, EventArgs.Empty); + contentOverride = isOverridden ? OverrideType.New : OverrideType.Base; + OverrideChanged?.Invoke(this, EventArgs.Empty); } + } - [CanBeNull] - public IGraphNode GetContent([NotNull] string key) - { - contents.TryGetValue(key, out IGraphNode node); - return node; - } + /// + public void ResetOverrideRecursively() + { + PropertyGraph.ResetAllOverridesRecursively(this, NodeIndex.Empty); + } - public void OverrideContent(bool isOverridden) - { - if (CanOverride) - { - OverrideChanging?.Invoke(this, EventArgs.Empty); - contentOverride = isOverridden ? OverrideType.New : OverrideType.Base; - OverrideChanged?.Invoke(this, EventArgs.Empty); - } - } + private void ContentChanged(object? sender, MemberNodeChangeEventArgs e) + { + var node = (AssetMemberNode)e.Member; + if (node.IsNonIdentifiableCollectionContent) + return; - /// - public void ResetOverrideRecursively() - { - PropertyGraph.ResetAllOverridesRecursively(this, NodeIndex.Empty); - } + // Make sure that we have item ids everywhere we're supposed to. + AssetCollectionItemIdHelper.GenerateMissingItemIds(e.Member.Retrieve()); - private void ContentChanged(object sender, [NotNull] MemberNodeChangeEventArgs e) - { - var node = (AssetMemberNode)e.Member; - if (node.IsNonIdentifiableCollectionContent) - return; - - // Make sure that we have item ids everywhere we're supposed to. - AssetCollectionItemIdHelper.GenerateMissingItemIds(e.Member.Retrieve()); - - // Don't update override if propagation from base is disabled. - if (PropertyGraph?.Container == null || PropertyGraph?.Container?.PropagateChangesFromBase == false) - return; - - // Mark it as New if it does not come from the base - if (BaseNode != null && !PropertyGraph.UpdatingPropertyFromBase && !ResettingOverride) - { - OverrideContent(!ResettingOverride); - } - } + // Don't update override if propagation from base is disabled. + if (PropertyGraph?.Container is null || !PropertyGraph.Container.PropagateChangesFromBase) + return; - internal void SetContentOverride(OverrideType overrideType) + // Mark it as New if it does not come from the base + if (BaseNode is not null && !PropertyGraph.UpdatingPropertyFromBase && !ResettingOverride) { - if (CanOverride) - { - contentOverride = overrideType; - } + OverrideContent(!ResettingOverride); } + } - public OverrideType GetContentOverride() + internal void SetContentOverride(OverrideType overrideType) + { + if (CanOverride) { - return contentOverride; + contentOverride = overrideType; } + } - public bool IsContentOverridden() - { - return (contentOverride & OverrideType.New) == OverrideType.New; - } + public OverrideType GetContentOverride() + { + return contentOverride; + } - public bool IsContentInherited() - { - return BaseNode != null && !IsContentOverridden(); - } + public bool IsContentOverridden() + { + return (contentOverride & OverrideType.New) == OverrideType.New; + } - bool IAssetNodeInternal.ResettingOverride { get; set; } + public bool IsContentInherited() + { + return BaseNode is not null && !IsContentOverridden(); + } - void IAssetNodeInternal.SetPropertyGraph(AssetPropertyGraph assetPropertyGraph) - { - if (assetPropertyGraph == null) throw new ArgumentNullException(nameof(assetPropertyGraph)); - PropertyGraph = assetPropertyGraph; - } + bool IAssetNodeInternal.ResettingOverride { get; set; } - void IAssetNodeInternal.SetBaseNode(IGraphNode node) - { - BaseNode = node; - } + void IAssetNodeInternal.SetPropertyGraph(AssetPropertyGraph assetPropertyGraph) + { + ArgumentNullException.ThrowIfNull(assetPropertyGraph); + PropertyGraph = assetPropertyGraph; + } + + void IAssetNodeInternal.SetBaseNode(IGraphNode node) + { + BaseNode = node; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetObjectNode.cs b/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetObjectNode.cs index a09307abad..765c91bd1a 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetObjectNode.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetObjectNode.cs @@ -1,98 +1,94 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Reflection; using Stride.Core.Quantum; using Stride.Core.Quantum.References; -namespace Stride.Core.Assets.Quantum.Internal +namespace Stride.Core.Assets.Quantum.Internal; + +internal class AssetObjectNode : ObjectNode, IAssetObjectNodeInternal { - internal class AssetObjectNode : ObjectNode, IAssetObjectNodeInternal - { - private AssetObjectNodeExtended ex; + private AssetObjectNodeExtended ex; - public AssetObjectNode([NotNull] INodeBuilder nodeBuilder, object value, Guid guid, [NotNull] ITypeDescriptor descriptor, IReference reference) - : base(nodeBuilder, value, guid, descriptor, reference) - { - ex = new AssetObjectNodeExtended(this); - ItemChanged += (sender, e) => ex.OnItemChanged(sender, e); - } + public AssetObjectNode(INodeBuilder nodeBuilder, object value, Guid guid, ITypeDescriptor descriptor, IReference? reference) + : base(nodeBuilder, value, guid, descriptor, reference) + { + ex = new AssetObjectNodeExtended(this); + ItemChanged += (sender, e) => ex.OnItemChanged(sender, e); + } - public AssetPropertyGraph PropertyGraph => ex.PropertyGraph; + public AssetPropertyGraph PropertyGraph => ex.PropertyGraph; - public IGraphNode BaseNode => ex.BaseNode; + public IGraphNode BaseNode => ex.BaseNode; - public new IAssetMemberNode this[[NotNull] string name] => (IAssetMemberNode)base[name]; + public new IAssetMemberNode this[string name] => (IAssetMemberNode)base[name]; - public event EventHandler OverrideChanging; + public event EventHandler? OverrideChanging; - public event EventHandler OverrideChanged; + public event EventHandler? OverrideChanged; - public void SetContent([NotNull] string key, IGraphNode node) => ex.SetContent(key, node); + public void SetContent(string key, IGraphNode node) => ex.SetContent(key, node); - [CanBeNull] - public IGraphNode GetContent([NotNull] string key) => ex.GetContent(key); + public IGraphNode? GetContent(string key) => ex.GetContent(key); - public void ResetOverrideRecursively() => ex.ResetOverrideRecursively(NodeIndex.Empty); + public void ResetOverrideRecursively() => ex.ResetOverrideRecursively(NodeIndex.Empty); - public void ResetOverrideRecursively(NodeIndex indexToReset) => ex.ResetOverrideRecursively(indexToReset); + public void ResetOverrideRecursively(NodeIndex indexToReset) => ex.ResetOverrideRecursively(indexToReset); - public void OverrideItem(bool isOverridden, NodeIndex index) => ex.OverrideItem(isOverridden, index); + public void OverrideItem(bool isOverridden, NodeIndex index) => ex.OverrideItem(isOverridden, index); - public void OverrideKey(bool isOverridden, NodeIndex index) => ex.OverrideKey(isOverridden, index); + public void OverrideKey(bool isOverridden, NodeIndex index) => ex.OverrideKey(isOverridden, index); - public void OverrideDeletedItem(bool isOverridden, ItemId deletedId) => ex.OverrideDeletedItem(isOverridden, deletedId); + public void OverrideDeletedItem(bool isOverridden, ItemId deletedId) => ex.OverrideDeletedItem(isOverridden, deletedId); - public bool IsItemDeleted(ItemId itemId) => ex.IsItemDeleted(itemId); + public bool IsItemDeleted(ItemId itemId) => ex.IsItemDeleted(itemId); - public void Restore(object restoredItem, ItemId id) => ex.Restore(restoredItem, id); + public void Restore(object restoredItem, ItemId id) => ex.Restore(restoredItem, id); - public void Restore(object restoredItem, NodeIndex index, ItemId id) => ex.Restore(restoredItem, index, id); + public void Restore(object restoredItem, NodeIndex index, ItemId id) => ex.Restore(restoredItem, index, id); - public void RemoveAndDiscard(object item, NodeIndex itemIndex, ItemId id) => ex.RemoveAndDiscard(item, itemIndex, id); + public void RemoveAndDiscard(object item, NodeIndex itemIndex, ItemId id) => ex.RemoveAndDiscard(item, itemIndex, id); - public OverrideType GetItemOverride(NodeIndex index) => ex.GetItemOverride(index); + public OverrideType GetItemOverride(NodeIndex index) => ex.GetItemOverride(index); - public OverrideType GetKeyOverride(NodeIndex index) => ex.GetKeyOverride(index); + public OverrideType GetKeyOverride(NodeIndex index) => ex.GetKeyOverride(index); - public bool IsItemInherited(NodeIndex index) => ex.IsItemInherited(index); + public bool IsItemInherited(NodeIndex index) => ex.IsItemInherited(index); - public bool IsKeyInherited(NodeIndex index) => ex.IsKeyInherited(index); + public bool IsKeyInherited(NodeIndex index) => ex.IsKeyInherited(index); - public bool IsItemOverridden(NodeIndex index) => ex.IsItemOverridden(index); + public bool IsItemOverridden(NodeIndex index) => ex.IsItemOverridden(index); - public bool IsItemOverriddenDeleted(ItemId id) => ex.IsItemOverriddenDeleted(id); + public bool IsItemOverriddenDeleted(ItemId id) => ex.IsItemOverriddenDeleted(id); - public bool IsKeyOverridden(NodeIndex index) => ex.IsKeyOverridden(index); + public bool IsKeyOverridden(NodeIndex index) => ex.IsKeyOverridden(index); - public IEnumerable GetOverriddenItemIndices() => ex.GetOverriddenItemIndices(); + public IEnumerable GetOverriddenItemIndices() => ex.GetOverriddenItemIndices(); - public IEnumerable GetOverriddenKeyIndices() => ex.GetOverriddenKeyIndices(); + public IEnumerable GetOverriddenKeyIndices() => ex.GetOverriddenKeyIndices(); - public ItemId IndexToId(NodeIndex index) => ex.IndexToId(index); + public ItemId IndexToId(NodeIndex index) => ex.IndexToId(index); - public bool TryIndexToId(NodeIndex index, out ItemId id) => ex.TryIndexToId(index, out id); + public bool TryIndexToId(NodeIndex index, out ItemId id) => ex.TryIndexToId(index, out id); - public bool HasId(ItemId id) => ex.HasId(id); + public bool HasId(ItemId id) => ex.HasId(id); - public NodeIndex IdToIndex(ItemId id) => ex.IdToIndex(id); + public NodeIndex IdToIndex(ItemId id) => ex.IdToIndex(id); - public bool TryIdToIndex(ItemId id, out NodeIndex index) => ex.TryIdToIndex(id, out index); + public bool TryIdToIndex(ItemId id, out NodeIndex index) => ex.TryIdToIndex(id, out index); - IAssetObjectNode IAssetObjectNode.IndexedTarget(NodeIndex index) => (IAssetObjectNode)IndexedTarget(index); + IAssetObjectNode? IAssetObjectNode.IndexedTarget(NodeIndex index) => (IAssetObjectNode?)IndexedTarget(index); - void IAssetObjectNodeInternal.DisconnectOverriddenDeletedItem(ItemId deletedId) => ex.DisconnectOverriddenDeletedItem(deletedId); + void IAssetObjectNodeInternal.DisconnectOverriddenDeletedItem(ItemId deletedId) => ex.DisconnectOverriddenDeletedItem(deletedId); - void IAssetObjectNodeInternal.NotifyOverrideChanging() => OverrideChanging?.Invoke(this, EventArgs.Empty); + void IAssetObjectNodeInternal.NotifyOverrideChanging() => OverrideChanging?.Invoke(this, EventArgs.Empty); - void IAssetObjectNodeInternal.NotifyOverrideChanged() => OverrideChanged?.Invoke(this, EventArgs.Empty); + void IAssetObjectNodeInternal.NotifyOverrideChanged() => OverrideChanged?.Invoke(this, EventArgs.Empty); - bool IAssetNodeInternal.ResettingOverride { get => ex.ResettingOverride; set => ex.ResettingOverride = value; } + bool IAssetNodeInternal.ResettingOverride { get => ex.ResettingOverride; set => ex.ResettingOverride = value; } - void IAssetNodeInternal.SetPropertyGraph(AssetPropertyGraph assetPropertyGraph) => ex.SetPropertyGraph(assetPropertyGraph); + void IAssetNodeInternal.SetPropertyGraph(AssetPropertyGraph assetPropertyGraph) => ex.SetPropertyGraph(assetPropertyGraph); - void IAssetNodeInternal.SetBaseNode(IGraphNode node) => ex.SetBaseContent(node); - } + void IAssetNodeInternal.SetBaseNode(IGraphNode node) => ex.SetBaseContent(node); } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetObjectNodeExtended.cs b/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetObjectNodeExtended.cs index 15be493444..6142c916e3 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetObjectNodeExtended.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetObjectNodeExtended.cs @@ -1,438 +1,414 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Reflection; using Stride.Core.Quantum; +using System.Diagnostics.CodeAnalysis; + +namespace Stride.Core.Assets.Quantum.Internal; -namespace Stride.Core.Assets.Quantum.Internal +internal struct AssetObjectNodeExtended { - internal struct AssetObjectNodeExtended + private readonly IAssetObjectNodeInternal node; + private readonly Dictionary contents; + private readonly Dictionary itemOverrides; + private readonly Dictionary keyOverrides; + private readonly HashSet disconnectedDeletedIds; + private CollectionItemIdentifiers? collectionItemIdentifiers; + private ItemId restoringId; + + public AssetObjectNodeExtended(IAssetObjectNodeInternal node) { - [NotNull] private readonly IAssetObjectNodeInternal node; - private readonly Dictionary contents; - private readonly Dictionary itemOverrides; - private readonly Dictionary keyOverrides; - private readonly HashSet disconnectedDeletedIds; - private CollectionItemIdentifiers collectionItemIdentifiers; - private ItemId restoringId; - - public AssetObjectNodeExtended([NotNull] IAssetObjectNodeInternal node) - { - this.node = node; - contents = new Dictionary(); - itemOverrides = new Dictionary(); - keyOverrides = new Dictionary(); - disconnectedDeletedIds = new HashSet(); - collectionItemIdentifiers = null; - restoringId = ItemId.Empty; - PropertyGraph = null; - BaseNode = null; - ResettingOverride = false; - } + this.node = node; + contents = []; + itemOverrides = []; + keyOverrides = []; + disconnectedDeletedIds = []; + collectionItemIdentifiers = null; + restoringId = ItemId.Empty; + PropertyGraph = null; + BaseNode = null; + ResettingOverride = false; + } - public AssetPropertyGraph PropertyGraph { get; private set; } + public AssetPropertyGraph? PropertyGraph { get; private set; } - public IGraphNode BaseNode { get; private set; } + public IGraphNode? BaseNode { get; private set; } - internal bool ResettingOverride { get; set; } + internal bool ResettingOverride { get; set; } - public void SetContent([NotNull] string key, IGraphNode node) - { - contents[key] = node; - } + public readonly void SetContent(string key, IGraphNode node) + { + contents[key] = node; + } - [CanBeNull] - public IGraphNode GetContent([NotNull] string key) - { - contents.TryGetValue(key, out IGraphNode node); - return node; - } + public readonly IGraphNode? GetContent(string key) + { + contents.TryGetValue(key, out var node); + return node; + } - /// - public void ResetOverrideRecursively(NodeIndex indexToReset) - { - OverrideItem(false, indexToReset); - PropertyGraph.ResetAllOverridesRecursively(node, indexToReset); - } + /// + public void ResetOverrideRecursively(NodeIndex indexToReset) + { + OverrideItem(false, indexToReset); + PropertyGraph?.ResetAllOverridesRecursively(node, indexToReset); + } - public void OverrideItem(bool isOverridden, NodeIndex index) - { - node.NotifyOverrideChanging(); - var id = IndexToId(index); - SetOverride(isOverridden ? OverrideType.New : OverrideType.Base, id, itemOverrides); - node.NotifyOverrideChanged(); - } + public void OverrideItem(bool isOverridden, NodeIndex index) + { + node.NotifyOverrideChanging(); + var id = IndexToId(index); + SetOverride(isOverridden ? OverrideType.New : OverrideType.Base, id, itemOverrides); + node.NotifyOverrideChanged(); + } - public void OverrideKey(bool isOverridden, NodeIndex index) - { - node.NotifyOverrideChanging(); - var id = IndexToId(index); - SetOverride(isOverridden ? OverrideType.New : OverrideType.Base, id, keyOverrides); - node.NotifyOverrideChanged(); - } + public void OverrideKey(bool isOverridden, NodeIndex index) + { + node.NotifyOverrideChanging(); + var id = IndexToId(index); + SetOverride(isOverridden ? OverrideType.New : OverrideType.Base, id, keyOverrides); + node.NotifyOverrideChanged(); + } - public void OverrideDeletedItem(bool isOverridden, ItemId deletedId) + public void OverrideDeletedItem(bool isOverridden, ItemId deletedId) + { + if (TryGetCollectionItemIds(node.Retrieve(), out var ids)) { - CollectionItemIdentifiers ids; - if (TryGetCollectionItemIds(node.Retrieve(), out ids)) + node.NotifyOverrideChanging(); + SetOverride(isOverridden ? OverrideType.New : OverrideType.Base, deletedId, itemOverrides); + if (isOverridden) { - node.NotifyOverrideChanging(); - SetOverride(isOverridden ? OverrideType.New : OverrideType.Base, deletedId, itemOverrides); - if (isOverridden) - { - ids.MarkAsDeleted(deletedId); - disconnectedDeletedIds.Remove(deletedId); - } - else - { - ids.UnmarkAsDeleted(deletedId); - } - node.NotifyOverrideChanged(); + ids.MarkAsDeleted(deletedId); + disconnectedDeletedIds.Remove(deletedId); + } + else + { + ids.UnmarkAsDeleted(deletedId); } + node.NotifyOverrideChanged(); } + } - public void DisconnectOverriddenDeletedItem(ItemId deletedId) - { - disconnectedDeletedIds.Add(deletedId); - OverrideDeletedItem(false, deletedId); - } + public void DisconnectOverriddenDeletedItem(ItemId deletedId) + { + disconnectedDeletedIds.Add(deletedId); + OverrideDeletedItem(false, deletedId); + } - public bool IsItemDeleted(ItemId itemId) - { - if (disconnectedDeletedIds.Contains(itemId)) - return true; - - var collection = node.Retrieve(); - CollectionItemIdentifiers ids; - if (!TryGetCollectionItemIds(collection, out ids)) - throw new InvalidOperationException("No Collection item identifier associated to the given collection."); - return ids.IsDeleted(itemId); - } + public bool IsItemDeleted(ItemId itemId) + { + if (disconnectedDeletedIds.Contains(itemId)) + return true; - private bool TryGetCollectionItemIds(object instance, out CollectionItemIdentifiers itemIds) - { - if (collectionItemIdentifiers != null) - { - itemIds = collectionItemIdentifiers; - return true; - } + var collection = node.Retrieve(); + if (!TryGetCollectionItemIds(collection, out var ids)) + throw new InvalidOperationException("No Collection item identifier associated to the given collection."); + return ids.IsDeleted(itemId); + } - var result = CollectionItemIdHelper.TryGetCollectionItemIds(instance, out collectionItemIdentifiers); + private bool TryGetCollectionItemIds(object? instance, [MaybeNullWhen(false)] out CollectionItemIdentifiers itemIds) + { + if (collectionItemIdentifiers is not null) + { itemIds = collectionItemIdentifiers; - return result; + return true; } - public void Restore(object restoredItem, ItemId id) - { - CollectionItemIdentifiers ids; - if (TryGetCollectionItemIds(node.Retrieve(), out ids)) - { - // Remove the item from deleted ids if it was here. - ids.UnmarkAsDeleted(id); - } - // Actually restore the item. - node.Add(restoredItem); - } + var result = CollectionItemIdHelper.TryGetCollectionItemIds(instance, out collectionItemIdentifiers); + itemIds = collectionItemIdentifiers; + return result; + } - public void Restore(object restoredItem, NodeIndex index, ItemId id) + public void Restore(object restoredItem, ItemId id) + { + if (TryGetCollectionItemIds(node.Retrieve(), out var ids)) { - restoringId = id; - node.Add(restoredItem, index); - restoringId = ItemId.Empty; - CollectionItemIdentifiers ids; - if (TryGetCollectionItemIds(node.Retrieve(), out ids)) - { - // Remove the item from deleted ids if it was here. - ids.UnmarkAsDeleted(id); - } + // Remove the item from deleted ids if it was here. + ids.UnmarkAsDeleted(id); } + // Actually restore the item. + node.Add(restoredItem); + } - public void RemoveAndDiscard(object item, NodeIndex itemIndex, ItemId id) + public void Restore(object restoredItem, NodeIndex index, ItemId id) + { + restoringId = id; + node.Add(restoredItem, index); + restoringId = ItemId.Empty; + if (TryGetCollectionItemIds(node.Retrieve(), out var ids)) { - node.Remove(item, itemIndex); - CollectionItemIdentifiers ids; - if (TryGetCollectionItemIds(node.Retrieve(), out ids)) - { - // Remove the item from deleted ids if it was here. - ids.UnmarkAsDeleted(id); - } + // Remove the item from deleted ids if it was here. + ids.UnmarkAsDeleted(id); } + } - public OverrideType GetItemOverride(NodeIndex index) + public void RemoveAndDiscard(object item, NodeIndex itemIndex, ItemId id) + { + node.Remove(item, itemIndex); + if (TryGetCollectionItemIds(node.Retrieve(), out var ids)) { - var result = OverrideType.Base; - ItemId id; - if (!TryIndexToId(index, out id)) - return result; - return itemOverrides.TryGetValue(id, out result) ? result : OverrideType.Base; + // Remove the item from deleted ids if it was here. + ids.UnmarkAsDeleted(id); } + } - public OverrideType GetKeyOverride(NodeIndex index) - { - var result = OverrideType.Base; - ItemId id; - if (!TryIndexToId(index, out id)) - return result; - return keyOverrides.TryGetValue(id, out result) ? result : OverrideType.Base; - } + public OverrideType GetItemOverride(NodeIndex index) + { + var result = OverrideType.Base; + if (!TryIndexToId(index, out var id)) + return result; + return itemOverrides.TryGetValue(id, out result) ? result : OverrideType.Base; + } - public bool IsItemInherited(NodeIndex index) - { - return BaseNode != null && !IsItemOverridden(index); - } + public OverrideType GetKeyOverride(NodeIndex index) + { + var result = OverrideType.Base; + if (!TryIndexToId(index, out var id)) + return result; + return keyOverrides.TryGetValue(id, out result) ? result : OverrideType.Base; + } - public bool IsKeyInherited(NodeIndex index) - { - return BaseNode != null && !IsKeyOverridden(index); - } + public bool IsItemInherited(NodeIndex index) + { + return BaseNode is not null && !IsItemOverridden(index); + } - public bool IsItemOverridden(NodeIndex index) - { - OverrideType result; - ItemId id; - if (!TryIndexToId(index, out id)) - return false; - return itemOverrides.TryGetValue(id, out result) && (result & OverrideType.New) == OverrideType.New; - } + public bool IsKeyInherited(NodeIndex index) + { + return BaseNode is not null && !IsKeyOverridden(index); + } - public bool IsItemOverriddenDeleted(ItemId id) - { - OverrideType result; - return IsItemDeleted(id) && itemOverrides.TryGetValue(id, out result) && (result & OverrideType.New) == OverrideType.New; - } + public bool IsItemOverridden(NodeIndex index) + { + if (!TryIndexToId(index, out var id)) + return false; + return itemOverrides.TryGetValue(id, out var result) && (result & OverrideType.New) == OverrideType.New; + } - public bool IsKeyOverridden(NodeIndex index) - { - OverrideType result; - ItemId id; - if (!TryIndexToId(index, out id)) - return false; - return keyOverrides.TryGetValue(id, out result) && (result & OverrideType.New) == OverrideType.New; - } + public bool IsItemOverriddenDeleted(ItemId id) + { + return IsItemDeleted(id) && itemOverrides.TryGetValue(id, out var result) && (result & OverrideType.New) == OverrideType.New; + } - public IEnumerable GetOverriddenItemIndices() - { - if (BaseNode == null) - yield break; + public bool IsKeyOverridden(NodeIndex index) + { + if (!TryIndexToId(index, out var id)) + return false; + return keyOverrides.TryGetValue(id, out var result) && (result & OverrideType.New) == OverrideType.New; + } - CollectionItemIdentifiers ids; - var collection = node.Retrieve(); - if (!TryGetCollectionItemIds(collection, out ids)) - yield break; + public IEnumerable GetOverriddenItemIndices() + { + if (BaseNode is null) + yield break; + + var collection = node.Retrieve(); + if (!TryGetCollectionItemIds(collection, out var ids)) + yield break; - foreach (var flags in itemOverrides) + foreach (var flags in itemOverrides) + { + if ((flags.Value & OverrideType.New) == OverrideType.New) { - if ((flags.Value & OverrideType.New) == OverrideType.New) - { - // If the override is a deleted item, there's no matching index to return. - if (ids.IsDeleted(flags.Key)) - continue; + // If the override is a deleted item, there's no matching index to return. + if (ids.IsDeleted(flags.Key)) + continue; - yield return IdToIndex(flags.Key); - } + yield return IdToIndex(flags.Key); } } + } - public IEnumerable GetOverriddenKeyIndices() - { - if (BaseNode == null) - yield break; + public IEnumerable GetOverriddenKeyIndices() + { + if (BaseNode is null) + yield break; - CollectionItemIdentifiers ids; - var collection = node.Retrieve(); - if (!TryGetCollectionItemIds(collection, out ids)) - yield break; + var collection = node.Retrieve(); + if (!TryGetCollectionItemIds(collection, out var ids)) + yield break; - foreach (var flags in keyOverrides) + foreach (var flags in keyOverrides) + { + if ((flags.Value & OverrideType.New) == OverrideType.New) { - if ((flags.Value & OverrideType.New) == OverrideType.New) - { - // If the override is a deleted item, there's no matching index to return. - if (ids.IsDeleted(flags.Key)) - continue; + // If the override is a deleted item, there's no matching index to return. + if (ids.IsDeleted(flags.Key)) + continue; - yield return IdToIndex(flags.Key); - } + yield return IdToIndex(flags.Key); } } + } - public bool HasId(ItemId id) - { - NodeIndex index; - return TryIdToIndex(id, out index); - } + public bool HasId(ItemId id) + { + return TryIdToIndex(id, out var _); + } - public NodeIndex IdToIndex(ItemId id) - { - NodeIndex index; - if (!TryIdToIndex(id, out index)) throw new InvalidOperationException("No Collection item identifier associated to the given collection."); - return index; - } + public NodeIndex IdToIndex(ItemId id) + { + if (!TryIdToIndex(id, out var index)) throw new InvalidOperationException("No Collection item identifier associated to the given collection."); + return index; + } - public bool TryIdToIndex(ItemId id, out NodeIndex index) + public bool TryIdToIndex(ItemId id, out NodeIndex index) + { + if (id == ItemId.Empty) { - if (id == ItemId.Empty) - { - index = NodeIndex.Empty; - return true; - } - - var collection = node.Retrieve(); - CollectionItemIdentifiers ids; - if (TryGetCollectionItemIds(collection, out ids)) - { - index = new NodeIndex(ids.GetKey(id)); - return !index.IsEmpty; - } index = NodeIndex.Empty; - return false; - + return true; } - public ItemId IndexToId(NodeIndex index) + var collection = node.Retrieve(); + if (TryGetCollectionItemIds(collection, out var ids)) { - ItemId id; - if (!TryIndexToId(index, out id)) throw new InvalidOperationException("No Collection item identifier associated to the given collection."); - return id; + index = new NodeIndex(ids.GetKey(id)); + return !index.IsEmpty; } + index = NodeIndex.Empty; + return false; + } - public bool TryIndexToId(NodeIndex index, out ItemId id) - { - if (index == NodeIndex.Empty) - { - id = ItemId.Empty; - return true; - } + public ItemId IndexToId(NodeIndex index) + { + if (!TryIndexToId(index, out var id)) throw new InvalidOperationException("No Collection item identifier associated to the given collection."); + return id; + } - var collection = node.Retrieve(); - CollectionItemIdentifiers ids; - if (TryGetCollectionItemIds(collection, out ids)) - { - return ids.TryGet(index.Value, out id); - } + public bool TryIndexToId(NodeIndex index, out ItemId id) + { + if (index == NodeIndex.Empty) + { id = ItemId.Empty; - return false; + return true; } - internal void OnItemChanged(object sender, ItemChangeEventArgs e) + var collection = node.Retrieve(); + if (TryGetCollectionItemIds(collection, out var ids)) { - var value = node.Retrieve(); + return ids.TryGet(index.Value, out id); + } + id = ItemId.Empty; + return false; + } + + internal void OnItemChanged(object? sender, ItemChangeEventArgs e) + { + var value = node.Retrieve(); - if (!CollectionItemIdHelper.HasCollectionItemIds(value)) - return; + if (!CollectionItemIdHelper.HasCollectionItemIds(value)) + return; - // Make sure that we have item ids everywhere we're supposed to. - AssetCollectionItemIdHelper.GenerateMissingItemIds(e.Collection.Retrieve()); + // Make sure that we have item ids everywhere we're supposed to. + AssetCollectionItemIdHelper.GenerateMissingItemIds(e.Collection.Retrieve()); - // Clear the cached item identifier collection. - collectionItemIdentifiers = null; + // Clear the cached item identifier collection. + collectionItemIdentifiers = null; - // Create new ids for collection items - var baseNode = (AssetObjectNode)BaseNode; - var removedId = ItemId.Empty; - var isOverriding = baseNode != null && !PropertyGraph.UpdatingPropertyFromBase; - var itemIds = CollectionItemIdHelper.GetCollectionItemIds(value); - var collectionDescriptor = node.Descriptor as CollectionDescriptor; - switch (e.ChangeType) + // Create new ids for collection items + var baseNode = (AssetObjectNode?)BaseNode; + var removedId = ItemId.Empty; + var isOverriding = baseNode is not null && !PropertyGraph.UpdatingPropertyFromBase; + var itemIds = CollectionItemIdHelper.GetCollectionItemIds(value); + var collectionDescriptor = node.Descriptor as CollectionDescriptor; + switch (e.ChangeType) + { + case ContentChangeType.CollectionUpdate: { - case ContentChangeType.CollectionUpdate: + if (collectionDescriptor is SetDescriptor setDescriptor) { - if (collectionDescriptor is SetDescriptor setDescriptor) + itemIds.Delete(e.OldValue); + object? newValue = e.NewValue; + if (newValue is null && !setDescriptor.ElementType.IsNullable()) + { + newValue = setDescriptor.ElementType.Default(); + } + if (!itemIds.ContainsKey(newValue)) { - itemIds.Delete(e.OldValue); - object newValue = e.NewValue; - if (newValue == null && !setDescriptor.ElementType.IsNullable()) - { - newValue = setDescriptor.ElementType.Default(); - } - if (!itemIds.ContainsKey(newValue)) - { - var itemId = restoringId != ItemId.Empty ? restoringId : ItemId.New(); - itemIds[newValue] = itemId; - } + var itemId = restoringId != ItemId.Empty ? restoringId : ItemId.New(); + itemIds[newValue] = itemId; } - break; } - case ContentChangeType.CollectionAdd: + break; + } + case ContentChangeType.CollectionAdd: + { + // Compute the id we will add for this item + var itemId = restoringId != ItemId.Empty ? restoringId : ItemId.New(); + // Add the id to the proper location (insert or add) + if (collectionDescriptor is not null && collectionDescriptor.Category != DescriptorCategory.Set) { - // Compute the id we will add for this item - var itemId = restoringId != ItemId.Empty ? restoringId : ItemId.New(); - // Add the id to the proper location (insert or add) - if (collectionDescriptor != null && collectionDescriptor.Category != DescriptorCategory.Set) - { - if (e.Index == NodeIndex.Empty) - throw new InvalidOperationException("An item has been added to a collection that does not have a predictable Add. Consider using NonIdentifiableCollectionItemsAttribute on this collection."); + if (e.Index == NodeIndex.Empty) + throw new InvalidOperationException("An item has been added to a collection that does not have a predictable Add. Consider using NonIdentifiableCollectionItemsAttribute on this collection."); - itemIds.Insert(e.Index.Int, itemId); - } - else - { - itemIds[e.Index.Value] = itemId; - } - break; + itemIds.Insert(e.Index.Int, itemId); } - case ContentChangeType.CollectionRemove: + else { - var itemId = itemIds[e.Index.Value]; - // update isOverriding, it should be true only if the item being removed exist in the base. - isOverriding = isOverriding && baseNode.HasId(itemId); - if (collectionDescriptor != null && collectionDescriptor.Category != DescriptorCategory.Set) - { - removedId = itemIds.DeleteAndShift(e.Index.Int, isOverriding); - } - else - { - removedId = itemIds.Delete(e.Index.Value, isOverriding); - } - break; + itemIds[e.Index.Value] = itemId; } - default: - throw new ArgumentOutOfRangeException(); + break; } - - - // Don't update override if propagation from base is disabled. - if (PropertyGraph?.Container?.PropagateChangesFromBase == false) - return; - - // Mark it as New if it does not come from the base - if (!ResettingOverride) + case ContentChangeType.CollectionRemove: { - if (e.ChangeType != ContentChangeType.CollectionRemove && isOverriding) + var itemId = itemIds[e.Index.Value]; + // update isOverriding, it should be true only if the item being removed exist in the base. + isOverriding = isOverriding && (baseNode?.HasId(itemId) ?? false); + if (collectionDescriptor is not null && collectionDescriptor.Category != DescriptorCategory.Set) { - // If it's an add or an updating, there is no scenario where we can be "un-overriding" without ResettingOverride, so we pass true. - OverrideItem(true, e.Index); + removedId = itemIds.DeleteAndShift(e.Index.Int, isOverriding); } - else if (e.ChangeType == ContentChangeType.CollectionRemove) + else { - // If it's a delete, it could be an item that was previously added as an override, and that should not be marked as "deleted-overridden", so we pass isOverriding - OverrideDeletedItem(isOverriding, removedId); + removedId = itemIds.Delete(e.Index.Value, isOverriding); } + break; } + default: + throw new ArgumentOutOfRangeException(); } - private static void SetOverride(OverrideType overrideType, ItemId id, [NotNull] Dictionary dictionary) + // Don't update override if propagation from base is disabled. + if (PropertyGraph?.Container?.PropagateChangesFromBase == false) + return; + + // Mark it as New if it does not come from the base + if (!ResettingOverride) { - if (overrideType == OverrideType.Base) + if (e.ChangeType != ContentChangeType.CollectionRemove && isOverriding) { - dictionary.Remove(id); + // If it's an add or an updating, there is no scenario where we can be "un-overriding" without ResettingOverride, so we pass true. + OverrideItem(true, e.Index); } - else + else if (e.ChangeType == ContentChangeType.CollectionRemove) { - dictionary[id] = overrideType; + // If it's a delete, it could be an item that was previously added as an override, and that should not be marked as "deleted-overridden", so we pass isOverriding + OverrideDeletedItem(isOverriding, removedId); } } + } - public void SetPropertyGraph([NotNull] AssetPropertyGraph assetPropertyGraph) + private static void SetOverride(OverrideType overrideType, ItemId id, Dictionary dictionary) + { + if (overrideType == OverrideType.Base) { - PropertyGraph = assetPropertyGraph ?? throw new ArgumentNullException(nameof(assetPropertyGraph)); + dictionary.Remove(id); } - - public void SetBaseContent(IGraphNode baseNode) + else { - BaseNode = baseNode; + dictionary[id] = overrideType; } } + + public void SetPropertyGraph(AssetPropertyGraph assetPropertyGraph) + { + PropertyGraph = assetPropertyGraph ?? throw new ArgumentNullException(nameof(assetPropertyGraph)); + } + + public void SetBaseContent(IGraphNode baseNode) + { + BaseNode = baseNode; + } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Internal/IAssetNodeInternal.cs b/sources/assets/Stride.Core.Assets.Quantum/Internal/IAssetNodeInternal.cs index 45cf265a8e..47b41c0ae2 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Internal/IAssetNodeInternal.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Internal/IAssetNodeInternal.cs @@ -1,30 +1,29 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Annotations; + using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Internal +namespace Stride.Core.Assets.Quantum.Internal; + +/// +/// An interface exposing internal methods of +/// +internal interface IAssetNodeInternal : IAssetNode { /// - /// An interface exposing internal methods of + /// Gets or sets whether the override properties of this node are currently being reset. /// - internal interface IAssetNodeInternal : IAssetNode - { - /// - /// Gets or sets whether the override properties of this node are currently being reset. - /// - bool ResettingOverride { get; set; } + bool ResettingOverride { get; set; } - /// - /// Sets the of the asset related to this node. - /// - /// - void SetPropertyGraph([NotNull] AssetPropertyGraph assetPropertyGraph); + /// + /// Sets the of the asset related to this node. + /// + /// + void SetPropertyGraph(AssetPropertyGraph assetPropertyGraph); - /// - /// Gets the base of this node. - /// - /// The base node to set. - void SetBaseNode(IGraphNode node); - } + /// + /// Gets the base of this node. + /// + /// The base node to set. + void SetBaseNode(IGraphNode node); } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Internal/IAssetObjectNodeInternal.cs b/sources/assets/Stride.Core.Assets.Quantum/Internal/IAssetObjectNodeInternal.cs index e000f2b5aa..a2c8c46331 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Internal/IAssetObjectNodeInternal.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Internal/IAssetObjectNodeInternal.cs @@ -1,30 +1,29 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; + using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Internal +namespace Stride.Core.Assets.Quantum.Internal; + +/// +/// An interface exposing internal methods of +/// +internal interface IAssetObjectNodeInternal : IAssetObjectNode, IAssetNodeInternal { - /// - /// An interface exposing internal methods of - /// - internal interface IAssetObjectNodeInternal : IAssetObjectNode, IAssetNodeInternal - { - OverrideType GetItemOverride(NodeIndex index); + OverrideType GetItemOverride(NodeIndex index); - OverrideType GetKeyOverride(NodeIndex index); + OverrideType GetKeyOverride(NodeIndex index); - /// - /// Removes the given from the list of overridden deleted items in the underlying , but keep - /// track of it if this node is requested whether this id is overridden-deleted. - /// - /// The id to disconnect. - /// The purpose of this method is to unmark as deleted the given id, but keep track of it for undo-redo. - void DisconnectOverriddenDeletedItem(ItemId deletedId); + /// + /// Removes the given from the list of overridden deleted items in the underlying , but keep + /// track of it if this node is requested whether this id is overridden-deleted. + /// + /// The id to disconnect. + /// The purpose of this method is to unmark as deleted the given id, but keep track of it for undo-redo. + void DisconnectOverriddenDeletedItem(ItemId deletedId); - void NotifyOverrideChanging(); + void NotifyOverrideChanging(); - void NotifyOverrideChanged(); - } + void NotifyOverrideChanged(); } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Module.cs b/sources/assets/Stride.Core.Assets.Quantum/Module.cs index d95ec6047b..f614bd6ee9 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Module.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Module.cs @@ -1,19 +1,14 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Assets.Tracking; -using Stride.Core; -using Stride.Core.Reflection; -using Stride.Core.Yaml; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +internal class Module { - internal class Module + [ModuleInitializer] + public static void Initialize() { - [ModuleInitializer] - public static void Initialize() - { - // Make sure that this assembly is registered - AssetQuantumRegistry.RegisterAssembly(typeof(Module).Assembly); - } + // Make sure that this assembly is registered + AssetQuantumRegistry.RegisterAssembly(typeof(Module).Assembly); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/NodesToOwnerPartVisitor.cs b/sources/assets/Stride.Core.Assets.Quantum/NodesToOwnerPartVisitor.cs index f995d2d58a..e64bce724a 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/NodesToOwnerPartVisitor.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/NodesToOwnerPartVisitor.cs @@ -1,35 +1,34 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Assets.Quantum.Visitors; -using Stride.Core.Annotations; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +/// +/// A node visitor that will link nodes of a part of an to the root node of the part itself. +/// +public class NodesToOwnerPartVisitor : AssetGraphVisitorBase { /// - /// A node visitor that will link nodes of a part of an to the root node of the part itself. + /// The identifier of the link in each node. /// - public class NodesToOwnerPartVisitor : AssetGraphVisitorBase - { - /// - /// The identifier of the link in each node. - /// - public const string OwnerPartContentName = "OwnerPart"; + public const string OwnerPartContentName = "OwnerPart"; - private readonly IAssetObjectNode ownerPartNode; + private readonly IAssetObjectNode ownerPartNode; - public NodesToOwnerPartVisitor([NotNull] AssetPropertyGraphDefinition propertyGraphDefinition, [NotNull] INodeContainer nodeContainer, object ownerPart) - : base(propertyGraphDefinition) - { - ownerPartNode = (IAssetObjectNode)nodeContainer.GetOrCreateNode(ownerPart); - } + public NodesToOwnerPartVisitor(AssetPropertyGraphDefinition propertyGraphDefinition, INodeContainer nodeContainer, object ownerPart) + : base(propertyGraphDefinition) + { + ownerPartNode = (IAssetObjectNode)nodeContainer.GetOrCreateNode(ownerPart); + } - protected override void VisitNode(IGraphNode node) - { - var assetNode = node as IAssetNode; - assetNode?.SetContent(OwnerPartContentName, ownerPartNode); + protected override void VisitNode(IGraphNode node) + { + var assetNode = node as IAssetNode; + assetNode?.SetContent(OwnerPartContentName, ownerPartNode); - base.VisitNode(node); - } + base.VisitNode(node); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Properties/AssemblyInfo.cs b/sources/assets/Stride.Core.Assets.Quantum/Properties/AssemblyInfo.cs index f859552ce9..9290f4b250 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Properties/AssemblyInfo.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Properties/AssemblyInfo.cs @@ -1,9 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Reflection; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Stride.Core.Assets.Quantum.Tests")] - - diff --git a/sources/assets/Stride.Core.Assets.Quantum/Stride.Core.Assets.Quantum.csproj b/sources/assets/Stride.Core.Assets.Quantum/Stride.Core.Assets.Quantum.csproj index 516f6fc97b..c2ab893642 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Stride.Core.Assets.Quantum.csproj +++ b/sources/assets/Stride.Core.Assets.Quantum/Stride.Core.Assets.Quantum.csproj @@ -6,6 +6,9 @@ 2.0 true $(StrideXplatEditorTargetFramework) + enable + latest + enable true --auto-module-initializer --serialization true diff --git a/sources/assets/Stride.Core.Assets.Quantum/SubHierarchyCloneFlags.cs b/sources/assets/Stride.Core.Assets.Quantum/SubHierarchyCloneFlags.cs index ebed7bcd53..1a7d8714b6 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/SubHierarchyCloneFlags.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/SubHierarchyCloneFlags.cs @@ -1,28 +1,25 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; -namespace Stride.Core.Assets.Quantum +namespace Stride.Core.Assets.Quantum; + +[Flags] +public enum SubHierarchyCloneFlags { - [Flags] - public enum SubHierarchyCloneFlags - { - /// - /// No specific flag. - /// - None = 0, - /// - /// Clean any reference to an object that is external to the sub-hierarchy. - /// - CleanExternalReferences = 1, - /// - /// Generates new identifiers for any object that is internal to the sub-hierarchy. - /// - GenerateNewIdsForIdentifiableObjects = 2, - /// - /// Do not apply overrides on the cloned sub-hierarchy. - /// - RemoveOverrides = 4, - } + /// + /// No specific flag. + /// + None = 0, + /// + /// Clean any reference to an object that is external to the sub-hierarchy. + /// + CleanExternalReferences = 1, + /// + /// Generates new identifiers for any object that is internal to the sub-hierarchy. + /// + GenerateNewIdsForIdentifiableObjects = 2, + /// + /// Do not apply overrides on the cloned sub-hierarchy. + /// + RemoveOverrides = 4, } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Visitors/AssetCollector.cs b/sources/assets/Stride.Core.Assets.Quantum/Visitors/AssetCollector.cs index f839c1ca4b..02332a34a5 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Visitors/AssetCollector.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Visitors/AssetCollector.cs @@ -1,51 +1,45 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; -using Stride.Core.Reflection; + using Stride.Core.Quantum; using Stride.Core.Quantum.References; -namespace Stride.Core.Assets.Quantum.Visitors +namespace Stride.Core.Assets.Quantum.Visitors; + +/// +/// A visitor that collects all assets from an object containing multiple assets +/// +public class AssetCollector : GraphVisitorBase { + private readonly Dictionary assets = []; + + private AssetCollector() + { + } + /// - /// A visitor that collects all assets from an object containing multiple assets + /// Collects all assets referenced by the object in the given node. /// - public class AssetCollector : GraphVisitorBase + /// The root node to visit to collect assets. + /// A collection containing all assets found by visiting the given root. + public static IReadOnlyDictionary Collect(IObjectNode root) { - private readonly Dictionary assets = new Dictionary(); - - private AssetCollector() - { - - } + var visitor = new AssetCollector(); + visitor.Visit(root); + return visitor.assets; + } - /// - /// Collects all assets referenced by the object in the given node. - /// - /// The root node to visit to collect assets. - /// A collection containing all assets found by visiting the given root. - public static IReadOnlyDictionary Collect([NotNull] IObjectNode root) + /// + protected override void VisitReference(IGraphNode referencer, ObjectReference reference) + { + if (reference.TargetNode?.Retrieve() is Asset asset) { - var visitor = new AssetCollector(); - visitor.Visit(root); - return visitor.assets; + assets.Add(CurrentPath.Clone(), asset); } - - /// - protected override void VisitReference(IGraphNode referencer, ObjectReference reference) + // Don't continue the visit once we found an asset, we cannot have nested assets. + else { - var asset = reference.TargetNode.Retrieve() as Asset; - if (asset != null) - { - assets.Add(CurrentPath.Clone(), asset); - } - // Don't continue the visit once we found an asset, we cannot have nested assets. - else - { - base.VisitReference(referencer, reference); - } + base.VisitReference(referencer, reference); } } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Visitors/AssetGraphVisitorBase.cs b/sources/assets/Stride.Core.Assets.Quantum/Visitors/AssetGraphVisitorBase.cs index 65e87d020b..615360600e 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Visitors/AssetGraphVisitorBase.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Visitors/AssetGraphVisitorBase.cs @@ -1,39 +1,36 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Visitors +namespace Stride.Core.Assets.Quantum.Visitors; + +/// +/// An implementation of that will stop visiting deeper each time it reaches a node representing an object reference. +/// +/// This visitor requires a to analyze if a node represents an object reference. +public class AssetGraphVisitorBase : GraphVisitorBase { + protected readonly AssetPropertyGraphDefinition PropertyGraphDefinition; + /// - /// An implementation of that will stop visiting deeper each time it reaches a node representing an object reference. + /// Initializes a new instance of the class. /// - /// This visitor requires a to analyze if a node represents an object reference. - public class AssetGraphVisitorBase : GraphVisitorBase + /// The used to analyze object references. + public AssetGraphVisitorBase(AssetPropertyGraphDefinition propertyGraphDefinition) { - protected readonly AssetPropertyGraphDefinition PropertyGraphDefinition; - - /// - /// Initializes a new instance of the class. - /// - /// The used to analyze object references. - public AssetGraphVisitorBase([NotNull] AssetPropertyGraphDefinition propertyGraphDefinition) - { - PropertyGraphDefinition = propertyGraphDefinition ?? throw new ArgumentNullException(nameof(propertyGraphDefinition)); - } + PropertyGraphDefinition = propertyGraphDefinition ?? throw new ArgumentNullException(nameof(propertyGraphDefinition)); + } - /// - protected override bool ShouldVisitMemberTarget(IMemberNode member) - { - return base.ShouldVisitMemberTarget(member) && !PropertyGraphDefinition.IsMemberTargetObjectReference(member, member.Retrieve()); - } + /// + protected override bool ShouldVisitMemberTarget(IMemberNode member) + { + return base.ShouldVisitMemberTarget(member) && !PropertyGraphDefinition.IsMemberTargetObjectReference(member, member.Retrieve()); + } - /// - protected override bool ShouldVisitTargetItem(IObjectNode collectionNode, NodeIndex index) - { - return base.ShouldVisitTargetItem(collectionNode, index) && !PropertyGraphDefinition.IsTargetItemObjectReference(collectionNode, index, collectionNode.Retrieve(index)); - } + /// + protected override bool ShouldVisitTargetItem(IObjectNode collectionNode, NodeIndex index) + { + return base.ShouldVisitTargetItem(collectionNode, index) && !PropertyGraphDefinition.IsTargetItemObjectReference(collectionNode, index, collectionNode.Retrieve(index)); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Visitors/AssetNodeMetadataCollectorBase.cs b/sources/assets/Stride.Core.Assets.Quantum/Visitors/AssetNodeMetadataCollectorBase.cs index 19e71d1ce3..7599d52057 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Visitors/AssetNodeMetadataCollectorBase.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Visitors/AssetNodeMetadataCollectorBase.cs @@ -1,130 +1,121 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Linq; + using Stride.Core.Assets.Quantum.Internal; using Stride.Core.Assets.Yaml; using Stride.Core.Annotations; using Stride.Core.Reflection; -using Stride.Core.Yaml; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Visitors +namespace Stride.Core.Assets.Quantum.Visitors; + +/// +/// A visitor that collects metadata to pass to YAML serialization. +/// +public abstract class AssetNodeMetadataCollectorBase : GraphVisitorBase { - /// - /// A visitor that collects metadata to pass to YAML serialization. - /// - public abstract class AssetNodeMetadataCollectorBase : GraphVisitorBase + private int inNonIdentifiableType; + + /// + protected override void VisitNode(IGraphNode node) { - private int inNonIdentifiableType; + var assetNode = (IAssetNode)node; - /// - protected override void VisitNode(IGraphNode node) + var localInNonIdentifiableType = false; + if ((node.Descriptor as ObjectDescriptor)?.Attributes.OfType().Any() ?? false) { - var assetNode = (IAssetNode)node; - - var localInNonIdentifiableType = false; - if ((node.Descriptor as ObjectDescriptor)?.Attributes.OfType().Any() ?? false) - { - localInNonIdentifiableType = true; - inNonIdentifiableType++; - } - - var memberNode = assetNode as IAssetMemberNode; - if (memberNode != null) - { - VisitMemberNode(memberNode, inNonIdentifiableType); - } - var objectNode = assetNode as IAssetObjectNode; - if (objectNode != null) - { - VisitObjectNode(objectNode, inNonIdentifiableType); - } - base.VisitNode(node); + localInNonIdentifiableType = true; + inNonIdentifiableType++; + } - if (localInNonIdentifiableType) - inNonIdentifiableType--; + if (assetNode is IAssetMemberNode memberNode) + { + VisitMemberNode(memberNode, inNonIdentifiableType); + } + if (assetNode is IAssetObjectNode objectNode) + { + VisitObjectNode(objectNode, inNonIdentifiableType); } + base.VisitNode(node); - /// - /// Visits a node that is an . - /// - /// The node to visit. - /// - protected abstract void VisitMemberNode(IAssetMemberNode memberNode, int inNonIdentifiableType); + if (localInNonIdentifiableType) + inNonIdentifiableType--; + } - /// - /// Visits a node that is an . - /// - /// The node to visit. - /// - protected abstract void VisitObjectNode(IAssetObjectNode objectNode, int inNonIdentifiableType); + /// + /// Visits a node that is an . + /// + /// The node to visit. + /// + protected abstract void VisitMemberNode(IAssetMemberNode memberNode, int inNonIdentifiableType); - /// - /// Converts the given to a that can be processed by YAML serialization. - /// - /// The path to convert. - /// If greater than zero, will ignore collection item ids and write indices instead. - /// An instance of corresponding to the given . - [NotNull] - public static YamlAssetPath ConvertPath([NotNull] GraphNodePath path, int inNonIdentifiableType = 0) + /// + /// Visits a node that is an . + /// + /// The node to visit. + /// + protected abstract void VisitObjectNode(IAssetObjectNode objectNode, int inNonIdentifiableType); + + /// + /// Converts the given to a that can be processed by YAML serialization. + /// + /// The path to convert. + /// If greater than zero, will ignore collection item ids and write indices instead. + /// An instance of corresponding to the given . + public static YamlAssetPath ConvertPath(GraphNodePath path, int inNonIdentifiableType = 0) + { + ArgumentNullException.ThrowIfNull(path); + var currentNode = (IAssetNode)path.RootNode; + var result = new YamlAssetPath(); + var i = 0; + foreach (var item in path.Path) { - if (path == null) throw new ArgumentNullException(nameof(path)); - var currentNode = (IAssetNode)path.RootNode; - var result = new YamlAssetPath(); - var i = 0; - foreach (var item in path.Path) + switch (item.Type) { - switch (item.Type) + case GraphNodePath.ElementType.Member: { - case GraphNodePath.ElementType.Member: + var member = item.Name; + result.PushMember(member); + if (currentNode is not IObjectNode objectNode) throw new InvalidOperationException($"An IObjectNode was expected when processing the path [{path}]"); + currentNode = (IAssetNode?)objectNode.TryGetChild(member); + break; + } + case GraphNodePath.ElementType.Target: + { + if (i < path.Path.Count - 1) + { + if (currentNode is not IMemberNode targetingMemberNode) throw new InvalidOperationException($"An IMemberNode was expected when processing the path [{path}]"); + currentNode = (IAssetNode?)targetingMemberNode.Target; + } + break; + } + case GraphNodePath.ElementType.Index: + { + var index = item.Index; + if (currentNode is not AssetObjectNode objectNode) throw new InvalidOperationException($"An IObjectNode was expected when processing the path [{path}]"); + if (inNonIdentifiableType > 0 || !CollectionItemIdHelper.HasCollectionItemIds(objectNode.Retrieve())) { - var member = item.Name; - result.PushMember(member); - var objectNode = currentNode as IObjectNode; - if (objectNode == null) throw new InvalidOperationException($"An IObjectNode was expected when processing the path [{path}]"); - currentNode = (IAssetNode)objectNode.TryGetChild(member); - break; + result.PushIndex(index.Value); } - case GraphNodePath.ElementType.Target: + else { - if (i < path.Path.Count - 1) - { - var targetingMemberNode = currentNode as IMemberNode; - if (targetingMemberNode == null) throw new InvalidOperationException($"An IMemberNode was expected when processing the path [{path}]"); - currentNode = (IAssetNode)targetingMemberNode.Target; - } - break; + var id = objectNode.IndexToId(index); + // Create a new id if we don't have any so far + if (id == ItemId.Empty) + id = ItemId.New(); + result.PushItemId(id); } - case GraphNodePath.ElementType.Index: + if (i < path.Path.Count - 1) { - var index = item.Index; - var objectNode = currentNode as AssetObjectNode; - if (objectNode == null) throw new InvalidOperationException($"An IObjectNode was expected when processing the path [{path}]"); - if (inNonIdentifiableType > 0 || !CollectionItemIdHelper.HasCollectionItemIds(objectNode.Retrieve())) - { - result.PushIndex(index.Value); - } - else - { - var id = objectNode.IndexToId(index); - // Create a new id if we don't have any so far - if (id == ItemId.Empty) - id = ItemId.New(); - result.PushItemId(id); - } - if (i < path.Path.Count - 1) - { - currentNode = (IAssetNode)objectNode.IndexedTarget(index); - } - break; + currentNode = (IAssetNode?)objectNode.IndexedTarget(index); } - default: - throw new ArgumentOutOfRangeException(); + break; } - ++i; + default: + throw new ArgumentOutOfRangeException(); } - return result; + ++i; } + return result; } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Visitors/ClearObjectReferenceVisitor.cs b/sources/assets/Stride.Core.Assets.Quantum/Visitors/ClearObjectReferenceVisitor.cs index 15228540c2..19b58a9ac0 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Visitors/ClearObjectReferenceVisitor.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Visitors/ClearObjectReferenceVisitor.cs @@ -1,64 +1,59 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; -using Stride.Core.Annotations; + using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Visitors +namespace Stride.Core.Assets.Quantum.Visitors; + +/// +/// A visitor that clear object references to a specific identifiable object. +/// +public class ClearObjectReferenceVisitor : IdentifiableObjectVisitorBase { + private readonly HashSet targetIds; + private readonly Func? shouldClearReference; + /// - /// A visitor that clear object references to a specific identifiable object. + /// Initializes a new instance of the class. /// - public class ClearObjectReferenceVisitor : IdentifiableObjectVisitorBase + /// The used to analyze object references. + /// The identifiers of the objects for which to clear references. + /// A method allowing to select which object reference to clear. If null, all object references to the given id will be cleared. + public ClearObjectReferenceVisitor(AssetPropertyGraphDefinition propertyGraphDefinition, IEnumerable targetIds, Func? shouldClearReference = null) + : base(propertyGraphDefinition) { - private readonly HashSet targetIds; - private readonly Func shouldClearReference; + ArgumentNullException.ThrowIfNull(propertyGraphDefinition); + ArgumentNullException.ThrowIfNull(targetIds); + this.targetIds = new HashSet(targetIds); + this.shouldClearReference = shouldClearReference; + } - /// - /// Initializes a new instance of the class. - /// - /// The used to analyze object references. - /// The identifiers of the objects for which to clear references. - /// A method allowing to select which object reference to clear. If null, all object references to the given id will be cleared. - public ClearObjectReferenceVisitor([NotNull] AssetPropertyGraphDefinition propertyGraphDefinition, [NotNull] IEnumerable targetIds, [CanBeNull] Func shouldClearReference = null) - : base(propertyGraphDefinition) - { - if (propertyGraphDefinition == null) throw new ArgumentNullException(nameof(propertyGraphDefinition)); - if (targetIds == null) throw new ArgumentNullException(nameof(targetIds)); - this.targetIds = new HashSet(targetIds); - this.shouldClearReference = shouldClearReference; - } + /// + protected override void ProcessIdentifiableMembers(IIdentifiable identifiable, IMemberNode member) + { + if (!targetIds.Contains(identifiable.Id)) + return; - /// - protected override void ProcessIdentifiableMembers(IIdentifiable identifiable, IMemberNode member) + if (PropertyGraphDefinition.IsMemberTargetObjectReference(member, identifiable)) { - if (!targetIds.Contains(identifiable.Id)) - return; - - if (PropertyGraphDefinition.IsMemberTargetObjectReference(member, identifiable)) + if (shouldClearReference?.Invoke(member, NodeIndex.Empty) ?? true) { - if (shouldClearReference?.Invoke(member, NodeIndex.Empty) ?? true) - { - member.Update(null); - } + member.Update(null); } - } + } - /// - protected override void ProcessIdentifiableItems(IIdentifiable identifiable, IObjectNode collection, NodeIndex index) - { - if (!targetIds.Contains(identifiable.Id)) - return; + /// + protected override void ProcessIdentifiableItems(IIdentifiable identifiable, IObjectNode collection, NodeIndex index) + { + if (!targetIds.Contains(identifiable.Id)) + return; - if (PropertyGraphDefinition.IsTargetItemObjectReference(collection, index, identifiable)) + if (PropertyGraphDefinition.IsTargetItemObjectReference(collection, index, identifiable)) + { + if (shouldClearReference?.Invoke(collection, index) ?? true) { - if (shouldClearReference?.Invoke(collection, index) ?? true) - { - collection.Update(null, index); - } + collection.Update(null, index); } } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Visitors/ExternalReferenceCollector.cs b/sources/assets/Stride.Core.Assets.Quantum/Visitors/ExternalReferenceCollector.cs index d05452c847..cd446e57be 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Visitors/ExternalReferenceCollector.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Visitors/ExternalReferenceCollector.cs @@ -1,92 +1,91 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; -using Stride.Core.Annotations; + using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Visitors +namespace Stride.Core.Assets.Quantum.Visitors; + +/// +/// A visitor that will collect all object references that target objects that are not included in the visited object. +/// +public class ExternalReferenceCollector : IdentifiableObjectVisitorBase { + private readonly AssetPropertyGraphDefinition propertyGraphDefinition; + + private readonly HashSet internalReferences = []; + private readonly HashSet externalReferences = []; + private readonly Dictionary> externalReferenceAccessors = []; + + private ExternalReferenceCollector(AssetPropertyGraphDefinition propertyGraphDefinition) + : base(propertyGraphDefinition) + { + this.propertyGraphDefinition = propertyGraphDefinition; + } + /// - /// A visitor that will collect all object references that target objects that are not included in the visited object. + /// Computes the external references to the given root node. /// - public class ExternalReferenceCollector : IdentifiableObjectVisitorBase + /// The property graph definition to use to analyze the graph. + /// The root node to analyze. + /// A set containing all external references to identifiable objects. + public static HashSet GetExternalReferences(AssetPropertyGraphDefinition propertyGraphDefinition, IGraphNode root) { - private readonly AssetPropertyGraphDefinition propertyGraphDefinition; - - private readonly HashSet internalReferences = new HashSet(); - private readonly HashSet externalReferences = new HashSet(); - private readonly Dictionary> externalReferenceAccessors = new Dictionary>(); - - private ExternalReferenceCollector([NotNull] AssetPropertyGraphDefinition propertyGraphDefinition) - : base(propertyGraphDefinition) - { - this.propertyGraphDefinition = propertyGraphDefinition; - } + var visitor = new ExternalReferenceCollector(propertyGraphDefinition); + visitor.Visit(root); + // An IIdentifiable can have been recorded both as internal and external reference. In this case we still want to clone it so let's remove it from external references + visitor.externalReferences.ExceptWith(visitor.internalReferences); + return visitor.externalReferences; + } - /// - /// Computes the external references to the given root node. - /// - /// The property graph definition to use to analyze the graph. - /// The root node to analyze. - /// A set containing all external references to identifiable objects. - [NotNull] - public static HashSet GetExternalReferences([NotNull] AssetPropertyGraphDefinition propertyGraphDefinition, [NotNull] IGraphNode root) + /// + /// Computes the external references to the given root node and their accessors. + /// + /// The property graph definition to use to analyze the graph. + /// The root node to analyze. + /// A set containing all external references to identifiable objects. + public static Dictionary> GetExternalReferenceAccessors(AssetPropertyGraphDefinition propertyGraphDefinition, IGraphNode root) + { + var visitor = new ExternalReferenceCollector(propertyGraphDefinition); + visitor.Visit(root); + // An IIdentifiable can have been recorded both as internal and external reference. In this case we still want to clone it so let's remove it from external references + foreach (var internalReference in visitor.internalReferences) { - var visitor = new ExternalReferenceCollector(propertyGraphDefinition); - visitor.Visit(root); - // An IIdentifiable can have been recorded both as internal and external reference. In this case we still want to clone it so let's remove it from external references - visitor.externalReferences.ExceptWith(visitor.internalReferences); - return visitor.externalReferences; + visitor.externalReferenceAccessors.Remove(internalReference); } + return visitor.externalReferenceAccessors; + } - /// - /// Computes the external references to the given root node and their accessors. - /// - /// The property graph definition to use to analyze the graph. - /// The root node to analyze. - /// A set containing all external references to identifiable objects. - public static Dictionary> GetExternalReferenceAccessors([NotNull] AssetPropertyGraphDefinition propertyGraphDefinition, [NotNull] IGraphNode root) + protected override void ProcessIdentifiableMembers(IIdentifiable identifiable, IMemberNode member) + { + if (propertyGraphDefinition.IsMemberTargetObjectReference(member, identifiable)) { - var visitor = new ExternalReferenceCollector(propertyGraphDefinition); - visitor.Visit(root); - // An IIdentifiable can have been recorded both as internal and external reference. In this case we still want to clone it so let's remove it from external references - foreach (var internalReference in visitor.internalReferences) + externalReferences.Add(identifiable); + if (!externalReferenceAccessors.TryGetValue(identifiable, out var accessors)) { - visitor.externalReferenceAccessors.Remove(internalReference); + externalReferenceAccessors.Add(identifiable, accessors = []); } - return visitor.externalReferenceAccessors; + accessors.Add(CurrentPath.GetAccessor()); } - - protected override void ProcessIdentifiableMembers(IIdentifiable identifiable, IMemberNode member) + else { - if (propertyGraphDefinition.IsMemberTargetObjectReference(member, identifiable)) - { - externalReferences.Add(identifiable); - if (!externalReferenceAccessors.TryGetValue(identifiable, out var accessors)) - { - externalReferenceAccessors.Add(identifiable, accessors = new List()); - } - accessors.Add(CurrentPath.GetAccessor()); - } - else - internalReferences.Add(identifiable); + internalReferences.Add(identifiable); } + } - protected override void ProcessIdentifiableItems(IIdentifiable identifiable, IObjectNode collection, NodeIndex index) + protected override void ProcessIdentifiableItems(IIdentifiable identifiable, IObjectNode collection, NodeIndex index) + { + if (propertyGraphDefinition.IsTargetItemObjectReference(collection, index, identifiable)) { - if (propertyGraphDefinition.IsTargetItemObjectReference(collection, index, identifiable)) + externalReferences.Add(identifiable); + if (!externalReferenceAccessors.TryGetValue(identifiable, out var accessors)) { - externalReferences.Add(identifiable); - if (!externalReferenceAccessors.TryGetValue(identifiable, out var accessors)) - { - externalReferenceAccessors.Add(identifiable, accessors = new List()); - } - accessors.Add(CurrentPath.GetAccessor()); + externalReferenceAccessors.Add(identifiable, accessors = []); } - else - internalReferences.Add(identifiable); + accessors.Add(CurrentPath.GetAccessor()); + } + else + { + internalReferences.Add(identifiable); } } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Visitors/IdentifiableObjectCollector.cs b/sources/assets/Stride.Core.Assets.Quantum/Visitors/IdentifiableObjectCollector.cs index 3475bcc375..ee147bff43 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Visitors/IdentifiableObjectCollector.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Visitors/IdentifiableObjectCollector.cs @@ -1,56 +1,50 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; -using Stride.Core.Annotations; + using Stride.Core.Quantum; using Stride.Core.Quantum.References; -namespace Stride.Core.Assets.Quantum.Visitors +namespace Stride.Core.Assets.Quantum.Visitors; + +/// +/// A visitor that collects all objects that are visited through nodes that are not representing object references. +/// +public class IdentifiableObjectCollector : AssetGraphVisitorBase { + private readonly Dictionary result = []; + /// - /// A visitor that collects all objects that are visited through nodes that are not representing object references. + /// Initializes a new instance of the class. /// - public class IdentifiableObjectCollector : AssetGraphVisitorBase + /// The used to analyze object references. + private IdentifiableObjectCollector(AssetPropertyGraphDefinition propertyGraphDefinition) + : base(propertyGraphDefinition) { - private readonly Dictionary result = new Dictionary(); - - /// - /// Initializes a new instance of the class. - /// - /// The used to analyze object references. - private IdentifiableObjectCollector([NotNull] AssetPropertyGraphDefinition propertyGraphDefinition) - : base(propertyGraphDefinition) - { - } + } - /// - /// Collects the objects that are visited through nodes that are not representing object references. - /// - /// The used to analyze object references. - /// The root object from which to collect. If null, will be used. - /// A dictionary mapping object by their identifier. - [NotNull] - public static Dictionary Collect([NotNull] AssetPropertyGraphDefinition propertyGraphDefinition, [NotNull] IGraphNode rootNode) - { - if (propertyGraphDefinition == null) throw new ArgumentNullException(nameof(propertyGraphDefinition)); - if (rootNode == null) throw new ArgumentNullException(nameof(rootNode)); - var visitor = new IdentifiableObjectCollector(propertyGraphDefinition); - visitor.Visit(rootNode); - return visitor.result; - } + /// + /// Collects the objects that are visited through nodes that are not representing object references. + /// + /// The used to analyze object references. + /// The root object from which to collect. If null, will be used. + /// A dictionary mapping object by their identifier. + public static Dictionary Collect(AssetPropertyGraphDefinition propertyGraphDefinition, IGraphNode rootNode) + { + ArgumentNullException.ThrowIfNull(propertyGraphDefinition); + ArgumentNullException.ThrowIfNull(rootNode); + var visitor = new IdentifiableObjectCollector(propertyGraphDefinition); + visitor.Visit(rootNode); + return visitor.result; + } - /// - protected override void VisitReference(IGraphNode referencer, ObjectReference reference) + /// + protected override void VisitReference(IGraphNode referencer, ObjectReference reference) + { + // Remark: VisitReference is invoked only when IsObjectReference returned false, therefore we will visit only 'real' object here, not references to them. + if (reference.ObjectValue is IIdentifiable value) { - // Remark: VisitReference is invoked only when IsObjectReference returned false, therefore we will visit only 'real' object here, not references to them. - var value = reference.ObjectValue as IIdentifiable; - if (value != null) - { - result[value.Id] = value; - } - base.VisitReference(referencer, reference); + result[value.Id] = value; } + base.VisitReference(referencer, reference); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Visitors/IdentifiableObjectVisitorBase.cs b/sources/assets/Stride.Core.Assets.Quantum/Visitors/IdentifiableObjectVisitorBase.cs index d11490c281..206427095c 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Visitors/IdentifiableObjectVisitorBase.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Visitors/IdentifiableObjectVisitorBase.cs @@ -1,71 +1,67 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; -using Stride.Core.Annotations; + using Stride.Core.Extensions; using Stride.Core.Quantum; -namespace Stride.Core.Assets.Quantum.Visitors +namespace Stride.Core.Assets.Quantum.Visitors; + +/// +/// A visitor that allows specific handling of all instances visited, whether they are object references of not. +/// +public abstract class IdentifiableObjectVisitorBase : AssetGraphVisitorBase { /// - /// A visitor that allows specific handling of all instances visited, whether they are object references of not. + /// Initializes a new instance of hte class. /// - public abstract class IdentifiableObjectVisitorBase : AssetGraphVisitorBase + /// The used to analyze object references. + protected IdentifiableObjectVisitorBase(AssetPropertyGraphDefinition propertyGraphDefinition) + : base(propertyGraphDefinition) { - /// - /// Initializes a new instance of hte class. - /// - /// The used to analyze object references. - protected IdentifiableObjectVisitorBase([NotNull] AssetPropertyGraphDefinition propertyGraphDefinition) - : base(propertyGraphDefinition) - { - } + } - /// - protected override void VisitMemberTarget(IMemberNode node) - { - CheckAndProcessIdentifiableMember(node); - base.VisitMemberTarget(node); - } + /// + protected override void VisitMemberTarget(IMemberNode node) + { + CheckAndProcessIdentifiableMember(node); + base.VisitMemberTarget(node); + } - /// - protected override void VisitItemTargets(IObjectNode node) - { - node.ItemReferences?.ForEach(x => CheckAndProcessIdentifiableItem(node, x.Index)); - base.VisitItemTargets(node); - } + /// + protected override void VisitItemTargets(IObjectNode node) + { + node.ItemReferences?.ForEach(x => CheckAndProcessIdentifiableItem(node, x.Index)); + base.VisitItemTargets(node); + } - /// - /// Processes an instance that is a member of an object. - /// - /// The identifiable instance to process. - /// The member node referencing the identifiable instance. - protected abstract void ProcessIdentifiableMembers([NotNull] IIdentifiable identifiable, IMemberNode member); + /// + /// Processes an instance that is a member of an object. + /// + /// The identifiable instance to process. + /// The member node referencing the identifiable instance. + protected abstract void ProcessIdentifiableMembers(IIdentifiable identifiable, IMemberNode member); - /// - /// Processes an instance that is an item of a collection. - /// - /// The identifiable instance to process. - /// The object node representing the collection referencing the identifiable instance. - /// The index at which the identifiable instance is referenced. - protected abstract void ProcessIdentifiableItems([NotNull] IIdentifiable identifiable, IObjectNode collection, NodeIndex index); + /// + /// Processes an instance that is an item of a collection. + /// + /// The identifiable instance to process. + /// The object node representing the collection referencing the identifiable instance. + /// The index at which the identifiable instance is referenced. + protected abstract void ProcessIdentifiableItems(IIdentifiable identifiable, IObjectNode collection, NodeIndex index); - private void CheckAndProcessIdentifiableMember([NotNull] IMemberNode member) - { - var identifiable = member.Retrieve() as IIdentifiable; - if (identifiable == null) - return; + private void CheckAndProcessIdentifiableMember(IMemberNode member) + { + if (member.Retrieve() is not IIdentifiable identifiable) + return; - ProcessIdentifiableMembers(identifiable, member); - } + ProcessIdentifiableMembers(identifiable, member); + } - private void CheckAndProcessIdentifiableItem([NotNull] IObjectNode collection, NodeIndex index) - { - var identifiable = collection.Retrieve(index) as IIdentifiable; - if (identifiable == null) - return; + private void CheckAndProcessIdentifiableItem(IObjectNode collection, NodeIndex index) + { + if (collection.Retrieve(index) is not IIdentifiable identifiable) + return; - ProcessIdentifiableItems(identifiable, collection, index); - } + ProcessIdentifiableItems(identifiable, collection, index); } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Visitors/ObjectReferencePathGenerator.cs b/sources/assets/Stride.Core.Assets.Quantum/Visitors/ObjectReferencePathGenerator.cs index f434589367..fff50f581d 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Visitors/ObjectReferencePathGenerator.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Visitors/ObjectReferencePathGenerator.cs @@ -1,87 +1,82 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Assets.Quantum.Internal; using Stride.Core.Assets.Yaml; -using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Quantum.Visitors +namespace Stride.Core.Assets.Quantum.Visitors; + +/// +/// An implementation of that generates the path to all object references in the given asset. +/// +public class ObjectReferencePathGenerator : AssetNodeMetadataCollectorBase { + private readonly AssetPropertyGraphDefinition propertyGraphDefinition; + /// - /// An implementation of that generates the path to all object references in the given asset. + /// Initializes a new instance of the class. /// - public class ObjectReferencePathGenerator : AssetNodeMetadataCollectorBase + /// The used to analyze object references. + public ObjectReferencePathGenerator(AssetPropertyGraphDefinition propertyGraphDefinition) { - private readonly AssetPropertyGraphDefinition propertyGraphDefinition; - - /// - /// Initializes a new instance of the class. - /// - /// The used to analyze object references. - public ObjectReferencePathGenerator(AssetPropertyGraphDefinition propertyGraphDefinition) - { - this.propertyGraphDefinition = propertyGraphDefinition; - } + this.propertyGraphDefinition = propertyGraphDefinition; + } - /// - /// Gets the resulting metadata that can be passed to YAML serialization. - /// - public YamlAssetMetadata Result { get; } = new YamlAssetMetadata(); + /// + /// Gets the resulting metadata that can be passed to YAML serialization. + /// + public YamlAssetMetadata Result { get; } = new YamlAssetMetadata(); - /// - /// Gets or sets a method that indicates if a given identifier should be output to the list of object references. - /// - public Func ShouldOutputReference { get; set; } + /// + /// Gets or sets a method that indicates if a given identifier should be output to the list of object references. + /// + public Func ShouldOutputReference { get; set; } - /// - protected override void VisitMemberNode([NotNull] IAssetMemberNode memberNode, int inNonIdentifiableType) + /// + protected override void VisitMemberNode(IAssetMemberNode memberNode, int inNonIdentifiableType) + { + var value = memberNode.Retrieve(); + if (propertyGraphDefinition.IsMemberTargetObjectReference(memberNode, value)) { - var value = memberNode.Retrieve(); - if (propertyGraphDefinition.IsMemberTargetObjectReference(memberNode, value)) - { - if (value == null) - return; + if (value == null) + return; - var identifiable = value as IIdentifiable; - if (identifiable == null) - throw new InvalidOperationException("IsObjectReference returned true for an object that is not IIdentifiable"); + if (value is not IIdentifiable identifiable) + throw new InvalidOperationException("IsObjectReference returned true for an object that is not IIdentifiable"); - var id = identifiable.Id; - if (ShouldOutputReference?.Invoke(id) ?? true) - Result.Set(ConvertPath(CurrentPath, inNonIdentifiableType), id); - } + var id = identifiable.Id; + if (ShouldOutputReference?.Invoke(id) ?? true) + Result.Set(ConvertPath(CurrentPath, inNonIdentifiableType), id); } + } - /// - protected override void VisitObjectNode([NotNull] IAssetObjectNode objectNode, int inNonIdentifiableType) + /// + protected override void VisitObjectNode(IAssetObjectNode objectNode, int inNonIdentifiableType) + { + if (!objectNode.IsReference) + return; + + foreach (var index in ((IAssetObjectNodeInternal)objectNode).Indices ?? []) { - if (!objectNode.IsReference) - return; + if (!propertyGraphDefinition.IsTargetItemObjectReference(objectNode, index, objectNode.Retrieve(index))) + continue; - foreach (var index in ((IAssetObjectNodeInternal)objectNode).Indices) + var itemPath = ConvertPath(CurrentPath, inNonIdentifiableType); + if (CollectionItemIdHelper.HasCollectionItemIds(objectNode.Retrieve())) { - if (!propertyGraphDefinition.IsTargetItemObjectReference(objectNode, index, objectNode.Retrieve(index))) - continue; - - var itemPath = ConvertPath(CurrentPath, inNonIdentifiableType); - if (CollectionItemIdHelper.HasCollectionItemIds(objectNode.Retrieve())) - { - var itemId = objectNode.IndexToId(index); - itemPath.PushItemId(itemId); - } - else - { - itemPath.PushIndex(index.Value); - } - var value = objectNode.Retrieve(index) as IIdentifiable; - if (value == null) - throw new InvalidOperationException("IsObjectReference returned true for an object that is not IIdentifiable"); - var id = value.Id; - if (ShouldOutputReference?.Invoke(id) ?? true) - Result.Set(itemPath, id); + var itemId = objectNode.IndexToId(index); + itemPath.PushItemId(itemId); + } + else + { + itemPath.PushIndex(index.Value); } + if (objectNode.Retrieve(index) is not IIdentifiable value) + throw new InvalidOperationException("IsObjectReference returned true for an object that is not IIdentifiable"); + var id = value.Id; + if (ShouldOutputReference?.Invoke(id) ?? true) + Result.Set(itemPath, id); } } } diff --git a/sources/assets/Stride.Core.Assets.Quantum/Visitors/OverrideTypePathGenerator.cs b/sources/assets/Stride.Core.Assets.Quantum/Visitors/OverrideTypePathGenerator.cs index 0b76a61bf0..42b47acfb4 100644 --- a/sources/assets/Stride.Core.Assets.Quantum/Visitors/OverrideTypePathGenerator.cs +++ b/sources/assets/Stride.Core.Assets.Quantum/Visitors/OverrideTypePathGenerator.cs @@ -1,49 +1,47 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Assets.Quantum.Internal; using Stride.Core.Assets.Yaml; -using Stride.Core.Annotations; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Quantum.Visitors +namespace Stride.Core.Assets.Quantum.Visitors; + +/// +/// An implementation of that generates the path to all object references in the given asset. +/// +public class OverrideTypePathGenerator : AssetNodeMetadataCollectorBase { /// - /// An implementation of that generates the path to all object references in the given asset. + /// Gets the resulting metadata that can be passed to YAML serialization. /// - public class OverrideTypePathGenerator : AssetNodeMetadataCollectorBase - { - /// - /// Gets the resulting metadata that can be passed to YAML serialization. - /// - [NotNull] - public YamlAssetMetadata Result { get; } = new YamlAssetMetadata(); + public YamlAssetMetadata Result { get; } = new YamlAssetMetadata(); - /// - protected override void VisitMemberNode(IAssetMemberNode memberNode, int inNonIdentifiableType) + /// + protected override void VisitMemberNode(IAssetMemberNode memberNode, int inNonIdentifiableType) + { + if (memberNode?.IsContentOverridden() == true) { - if (memberNode?.IsContentOverridden() == true) - { - Result.Set(ConvertPath(CurrentPath, inNonIdentifiableType), memberNode.GetContentOverride()); - } + Result.Set(ConvertPath(CurrentPath, inNonIdentifiableType), memberNode.GetContentOverride()); } + } - /// - protected override void VisitObjectNode([NotNull] IAssetObjectNode objectNode, int inNonIdentifiableType) + /// + protected override void VisitObjectNode(IAssetObjectNode objectNode, int inNonIdentifiableType) + { + foreach (var index in objectNode.GetOverriddenItemIndices()) + { + var id = objectNode.IndexToId(index); + var itemPath = ConvertPath(CurrentPath, inNonIdentifiableType); + itemPath.PushItemId(id); + Result.Set(itemPath, ((IAssetObjectNodeInternal)objectNode).GetItemOverride(index)); + } + foreach (var index in objectNode.GetOverriddenKeyIndices()) { - foreach (var index in objectNode.GetOverriddenItemIndices()) - { - var id = objectNode.IndexToId(index); - var itemPath = ConvertPath(CurrentPath, inNonIdentifiableType); - itemPath.PushItemId(id); - Result.Set(itemPath, ((IAssetObjectNodeInternal)objectNode).GetItemOverride(index)); - } - foreach (var index in objectNode.GetOverriddenKeyIndices()) - { - var id = objectNode.IndexToId(index); - var itemPath = ConvertPath(CurrentPath, inNonIdentifiableType); - itemPath.PushIndex(id); - Result.Set(itemPath, ((IAssetObjectNodeInternal)objectNode).GetKeyOverride(index)); - } + var id = objectNode.IndexToId(index); + var itemPath = ConvertPath(CurrentPath, inNonIdentifiableType); + itemPath.PushIndex(id); + Result.Set(itemPath, ((IAssetObjectNodeInternal)objectNode).GetKeyOverride(index)); } } } diff --git a/sources/assets/Stride.Core.Assets.Tests/AssetObjectTest.cs b/sources/assets/Stride.Core.Assets.Tests/AssetObjectTest.cs index 6d094eee2e..800736948a 100644 --- a/sources/assets/Stride.Core.Assets.Tests/AssetObjectTest.cs +++ b/sources/assets/Stride.Core.Assets.Tests/AssetObjectTest.cs @@ -1,166 +1,162 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.ComponentModel; -using System.Linq; -using Stride.Core; using Stride.Core.Annotations; using Stride.Core.IO; -namespace Stride.Core.Assets.Tests +namespace Stride.Core.Assets.Tests; + +[DataContract("!AssetObjectTest")] +[AssetDescription(FileExtension)] +public class AssetObjectTest : TestAssetWithParts, IEquatable { - [DataContract("!AssetObjectTest")] - [AssetDescription(FileExtension)] - public class AssetObjectTest : TestAssetWithParts, IEquatable - { - private const string FileExtension = ".sdtest"; + private const string FileExtension = ".sdtest"; - [DefaultValue(null)] - public AssetReference Reference { get; set; } + [DefaultValue(null)] + public AssetReference Reference { get; set; } - [DefaultValue(null)] - public UFile RawAsset { get; set; } + [DefaultValue(null)] + public UFile RawAsset { get; set; } - public bool Equals(AssetObjectTest other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return string.Equals(Name, other.Name) && Equals(Reference, other.Reference) && Equals(RawAsset, other.RawAsset); - } + public bool Equals(AssetObjectTest? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Name, other.Name) && Equals(Reference, other.Reference) && Equals(RawAsset, other.RawAsset); + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((AssetObjectTest)obj); - } + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((AssetObjectTest)obj); + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - var hashCode = Name?.GetHashCode() ?? 0; - hashCode = (hashCode*397) ^ (Reference != null ? Reference.GetHashCode() : 0); - hashCode = (hashCode*397) ^ (RawAsset != null ? RawAsset.GetHashCode() : 0); - return hashCode; - } + var hashCode = Name?.GetHashCode() ?? 0; + hashCode = (hashCode*397) ^ (Reference != null ? Reference.GetHashCode() : 0); + hashCode = (hashCode*397) ^ (RawAsset != null ? RawAsset.GetHashCode() : 0); + return hashCode; } + } - public static bool operator ==(AssetObjectTest left, AssetObjectTest right) - { - return Equals(left, right); - } + public static bool operator ==(AssetObjectTest left, AssetObjectTest right) + { + return Equals(left, right); + } - public static bool operator !=(AssetObjectTest left, AssetObjectTest right) - { - return !Equals(left, right); - } + public static bool operator !=(AssetObjectTest left, AssetObjectTest right) + { + return !Equals(left, right); } +} - [DataContract("!TestAssetWithParts")] - [AssetDescription(FileExtension)] - public class TestAssetWithParts : AssetComposite +[DataContract("!TestAssetWithParts")] +[AssetDescription(FileExtension)] +public class TestAssetWithParts : AssetComposite +{ + private const string FileExtension = ".sdpart"; + + public TestAssetWithParts() { - private const string FileExtension = ".sdpart"; + Parts = []; + } - public TestAssetWithParts() - { - Parts = new List(); - } + public string Name { get; set; } - public string Name { get; set; } + public List Parts { get; set; } - public List Parts { get; set; } + [Obsolete("The AssetPart struct might be removed soon")] + public override IEnumerable CollectParts() + { + return Parts.Select(it => new AssetPart(it.Id, it.Base, x => it.Base = x)); + } - [Obsolete("The AssetPart struct might be removed soon")] - public override IEnumerable CollectParts() - { - return Parts.Select(it => new AssetPart(it.Id, it.Base, x => it.Base = x)); - } + public override IIdentifiable? FindPart(Guid partId) + { + return Parts.FirstOrDefault(x => x.Id == partId); + } - public override IIdentifiable FindPart(Guid partId) - { - return Parts.FirstOrDefault(x => x.Id == partId); - } + public override bool ContainsPart(Guid id) + { + return Parts.Any(t => t.Id == id); + } - public override bool ContainsPart(Guid id) - { - return Parts.Any(t => t.Id == id); - } + public override Asset CreateDerivedAsset(string baseLocation, out Dictionary idRemapping) + { + var asset = (TestAssetWithParts)base.CreateDerivedAsset(baseLocation, out idRemapping); - public override Asset CreateDerivedAsset(string baseLocation, out Dictionary idRemapping) + // Create asset with new base + var assetRef = new AssetReference(Id, baseLocation); + var instanceId = Guid.NewGuid(); + for (var i = 0; i < asset.Parts.Count; i++) { - var asset = (TestAssetWithParts)base.CreateDerivedAsset(baseLocation, out idRemapping); - - // Create asset with new base - var assetRef = new AssetReference(Id, baseLocation); - var instanceId = Guid.NewGuid(); - for (var i = 0; i < asset.Parts.Count; i++) - { - // Properly set the base - asset.Parts[i].Base = new BasePart(assetRef, Parts[i].Id, instanceId); - } - - return asset; + // Properly set the base + asset.Parts[i].Base = new BasePart(assetRef, Parts[i].Id, instanceId); } - public void AddParts(TestAssetWithParts assetBaseWithParts) - { - if (assetBaseWithParts == null) throw new ArgumentNullException(nameof(assetBaseWithParts)); - - // The assetPartBase must be a plain child asset - if (assetBaseWithParts.Archetype == null) throw new InvalidOperationException($"Expecting a Base for {nameof(assetBaseWithParts)}"); - - Parts.AddRange(assetBaseWithParts.Parts); - } + return asset; } - [DataContract("AssetPartTestItem")] - public class AssetPartTestItem : IIdentifiable + public void AddParts(TestAssetWithParts assetBaseWithParts) { - public AssetPartTestItem() - { - } + ArgumentNullException.ThrowIfNull(assetBaseWithParts); - public AssetPartTestItem(Guid id) - { - Id = id; - } + // The assetPartBase must be a plain child asset + if (assetBaseWithParts.Archetype == null) throw new InvalidOperationException($"Expecting a Base for {nameof(assetBaseWithParts)}"); - public AssetPartTestItem(Guid id, AssetReference baseAsset, Guid baseId, Guid basePartInstanceId) - { - Base = new BasePart(baseAsset, baseId, basePartInstanceId); - Id = id; - } + Parts.AddRange(assetBaseWithParts.Parts); + } +} - public BasePart Base { get; set; } +[DataContract("AssetPartTestItem")] +public class AssetPartTestItem : IIdentifiable +{ + public AssetPartTestItem() + { + } - [NonOverridable] - public Guid Id { get; set; } + public AssetPartTestItem(Guid id) + { + Id = id; } - [DataContract("!AssetImportObjectTest")] - [AssetDescription(".sdimptest")] - public class AssetImportObjectTest : AssetWithSource + public AssetPartTestItem(Guid id, AssetReference baseAsset, Guid baseId, Guid basePartInstanceId) { - public AssetImportObjectTest() - { - References = new Dictionary(); - } + Base = new BasePart(baseAsset, baseId, basePartInstanceId); + Id = id; + } - public string Name { get; set; } + public BasePart Base { get; set; } - [DefaultValue(null)] - public Dictionary References { get; set; } - } + [NonOverridable] + public Guid Id { get; set; } +} - [DataContract("!AssetObjectTestSub")] - [AssetDescription(".sdtestsub")] - public class AssetObjectTestSub : Asset +[DataContract("!AssetImportObjectTest")] +[AssetDescription(".sdimptest")] +public class AssetImportObjectTest : AssetWithSource +{ + public AssetImportObjectTest() { - public int Value { get; set; } + References = []; } + + public string Name { get; set; } + + [DefaultValue(null)] + public Dictionary References { get; set; } +} + +[DataContract("!AssetObjectTestSub")] +[AssetDescription(".sdtestsub")] +public class AssetObjectTestSub : Asset +{ + public int Value { get; set; } } diff --git a/sources/assets/Stride.Core.Assets.Tests/Compilers/CompilerTestBase.cs b/sources/assets/Stride.Core.Assets.Tests/Compilers/CompilerTestBase.cs index bb8813dcee..38b7072a88 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Compilers/CompilerTestBase.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Compilers/CompilerTestBase.cs @@ -1,25 +1,22 @@ -using System; -using System.Collections.Generic; -using Xunit; using Stride.Core.Serialization; -namespace Stride.Core.Assets.Tests.Compilers +namespace Stride.Core.Assets.Tests.Compilers; + +public class CompilerTestBase : IDisposable { - public class CompilerTestBase : IDisposable + public CompilerTestBase() { - public CompilerTestBase() - { - TestCompilerBase.CompiledAssets = new HashSet(); - } + TestCompilerBase.CompiledAssets = []; + } - public void Dispose() - { - TestCompilerBase.CompiledAssets = null; - } + public void Dispose() + { + TestCompilerBase.CompiledAssets = null!; + GC.SuppressFinalize(this); + } - protected static TContentType CreateRef(AssetItem assetItem) where TContentType : class, new() - { - return AttachedReferenceManager.CreateProxyObject(assetItem.Id, assetItem.Location); - } + protected static TContentType CreateRef(AssetItem assetItem) where TContentType : class, new() + { + return AttachedReferenceManager.CreateProxyObject(assetItem.Id, assetItem.Location); } } diff --git a/sources/assets/Stride.Core.Assets.Tests/Compilers/TestAssertCompiler.cs b/sources/assets/Stride.Core.Assets.Tests/Compilers/TestAssertCompiler.cs index 669e5bf3e0..4c6bb57e17 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Compilers/TestAssertCompiler.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Compilers/TestAssertCompiler.cs @@ -1,46 +1,42 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; using Stride.Core.Assets.Compiler; using Stride.Core.BuildEngine; -namespace Stride.Core.Assets.Tests.Compilers +namespace Stride.Core.Assets.Tests.Compilers; + +public class TestAssertCompiler : TestCompilerBase where T : Asset { - public class TestAssertCompiler : TestCompilerBase where T : Asset + private class AssertCommand : AssetCommand { - private class AssertCommand : AssetCommand - { - private readonly AssetItem assetItem; - private readonly Action assertFunc; - - public AssertCommand(AssetItem assetItem, T parameters, IAssetFinder assetFinder, Action assertFunc) - : base(assetItem.Location, parameters, assetFinder) - { - this.assetItem = assetItem; - this.assertFunc = assertFunc; - } + private readonly AssetItem assetItem; + private readonly Action assertFunc; - protected override Task DoCommandOverride(ICommandContext commandContext) - { - assertFunc?.Invoke(Url, Parameters, AssetFinder); - CompiledAssets.Add(assetItem); - return Task.FromResult(ResultStatus.Successful); - } + public AssertCommand(AssetItem assetItem, T parameters, IAssetFinder assetFinder, Action assertFunc) + : base(assetItem.Location, parameters, assetFinder) + { + this.assetItem = assetItem; + this.assertFunc = assertFunc; } - public override AssetCompilerResult Prepare(AssetCompilerContext context, AssetItem assetItem) + protected override Task DoCommandOverride(ICommandContext commandContext) { - var result = new AssetCompilerResult(GetType().Name) - { - BuildSteps = new AssetBuildStep(assetItem) - }; - result.BuildSteps.Add(new AssertCommand(assetItem, (T)assetItem.Asset, assetItem.Package, DoCommandAssert)); - return result; + assertFunc?.Invoke(Url, Parameters, AssetFinder); + CompiledAssets.Add(assetItem); + return Task.FromResult(ResultStatus.Successful); } + } - protected virtual void DoCommandAssert(string url, T parameters, IAssetFinder package) + public override AssetCompilerResult Prepare(AssetCompilerContext context, AssetItem assetItem) + { + var result = new AssetCompilerResult(GetType().Name) { + BuildSteps = new AssetBuildStep(assetItem) + }; + result.BuildSteps.Add(new AssertCommand(assetItem, (T)assetItem.Asset, assetItem.Package, DoCommandAssert)); + return result; + } + + protected virtual void DoCommandAssert(string url, T parameters, IAssetFinder package) + { - } } -} \ No newline at end of file +} diff --git a/sources/assets/Stride.Core.Assets.Tests/Compilers/TestBuildDependencyManager.cs b/sources/assets/Stride.Core.Assets.Tests/Compilers/TestBuildDependencyManager.cs index c4215032aa..a9add1e810 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Compilers/TestBuildDependencyManager.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Compilers/TestBuildDependencyManager.cs @@ -1,304 +1,298 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Xunit; using Stride.Core.Assets.Analysis; using Stride.Core.Assets.Compiler; using Stride.Core.BuildEngine; -using Stride.Core; using Stride.Core.Diagnostics; using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Tests.Compilers +namespace Stride.Core.Assets.Tests.Compilers; + +public class TestBuildDependencyManager : CompilerTestBase { - public class TestBuildDependencyManager : CompilerTestBase + [Fact] + public void TestCompileAsset() { - [Fact] - public void TestCompileAsset() + var package = new Package(); + // ReSharper disable once UnusedVariable - we need a package session to compile + var packageSession = new PackageSession(package); + var otherAssets = new List { - var package = new Package(); - // ReSharper disable once UnusedVariable - we need a package session to compile - var packageSession = new PackageSession(package); - var otherAssets = new List - { - new AssetItem("asset5", new MyAsset5(), package), - new AssetItem("asset6", new MyAsset6(), package), - new AssetItem("asset7", new MyAsset7(), package), - }; - otherAssets.ForEach(x => package.Assets.Add(x)); - - var compileAssetReference = new MyAsset2 - { - CompileAssetReference = CreateRef(otherAssets[0]), - CompileContentReference = CreateRef(otherAssets[1]), - CompileRuntimeReference = CreateRef(otherAssets[2]), - }; - var assetItem = new AssetItem("asset2", compileAssetReference, package); - package.Assets.Add(assetItem); - - var asset = new MyAsset1 { CompileAssetReference = CreateRef(assetItem) }; - assetItem = new AssetItem("asset1", asset, package); - package.Assets.Add(assetItem); - package.RootAssets.Add(new AssetReference(assetItem.Id, assetItem.Location)); - - // Create context - var context = new AssetCompilerContext { CompilationContext = typeof(AssetCompilationContext) }; - // Builds the project - Exception ex = null; - MyAsset1Compiler.AssertFunc = (url, ass, pkg) => - { - // Nothing must have been compiled before - AssertInThread(ref ex, () => Assert.Empty(TestCompilerBase.CompiledAssets)); - }; - - var assetBuilder = new PackageCompiler(new RootPackageAssetEnumerator(package)); - var assetBuildResult = assetBuilder.Prepare(context); - // Since MyAsset2 is a CompileAsset reference, it should not be compiled, so we should have only 1 asset (MyAsset1) to compile. - Assert.Equal(1, assetBuildResult.BuildSteps.Count); - var builder = new Builder(GlobalLogger.GetLogger("Test"), "", ""); - builder.Root.Add(assetBuildResult.BuildSteps); - builder.Run(Builder.Mode.Build, false); - RethrowAssertsFromThread(ex); - } + new("asset5", new MyAsset5(), package), + new("asset6", new MyAsset6(), package), + new("asset7", new MyAsset7(), package), + }; + otherAssets.ForEach(x => package.Assets.Add(x)); - [Fact] - public void TestCompileContent() + var compileAssetReference = new MyAsset2 { - var package = new Package(); - // ReSharper disable once UnusedVariable - we need a package session to compile - var packageSession = new PackageSession(package); - var otherAssets = new List - { - new AssetItem("asset8", new MyAsset8(), package), - new AssetItem("asset9", new MyAsset9(), package), - new AssetItem("asset10", new MyAsset10(), package), - }; - otherAssets.ForEach(x => package.Assets.Add(x)); - - var compileAssetReference = new MyAsset3 - { - CompileAssetReference = CreateRef(otherAssets[0]), - CompileContentReference = CreateRef(otherAssets[1]), - CompileRuntimeReference = CreateRef(otherAssets[2]), - }; - var asset3 = new AssetItem("asset3", compileAssetReference, package); - package.Assets.Add(asset3); - - var asset = new MyAsset1 { CompileContentReference = CreateRef(asset3) }; - var assetItem = new AssetItem("asset1", asset, package); - package.Assets.Add(assetItem); - package.RootAssets.Add(new AssetReference(assetItem.Id, assetItem.Location)); - - // Create context - var context = new AssetCompilerContext { CompilationContext = typeof(AssetCompilationContext) }; - - Exception ex = null; - MyAsset1Compiler.AssertFunc = (url, ass, pkg) => - { - AssertInThread(ref ex, () => Assert.Single(TestCompilerBase.CompiledAssets)); - AssertInThread(ref ex, () => Assert.Equal(asset3.Id, TestCompilerBase.CompiledAssets.First().Id)); - }; - - var assetBuilder = new PackageCompiler(new RootPackageAssetEnumerator(package)); - var assetBuildResult = assetBuilder.Prepare(context); - // Since MyAsset3 is a CompileContent reference, it should be compiled, so we should have only 2 asset (MyAsset1 and MyAsset3) to compile. - Assert.Equal(2, assetBuildResult.BuildSteps.Count); - var builder = new Builder(GlobalLogger.GetLogger("Test"), "", ""); - builder.Root.Add(assetBuildResult.BuildSteps); - builder.Run(Builder.Mode.Build, false); - RethrowAssertsFromThread(ex); - } + CompileAssetReference = CreateRef(otherAssets[0]), + CompileContentReference = CreateRef(otherAssets[1]), + CompileRuntimeReference = CreateRef(otherAssets[2]), + }; + var assetItem = new AssetItem("asset2", compileAssetReference, package); + package.Assets.Add(assetItem); + + var asset = new MyAsset1 { CompileAssetReference = CreateRef(assetItem) }; + assetItem = new AssetItem("asset1", asset, package); + package.Assets.Add(assetItem); + package.RootAssets.Add(new AssetReference(assetItem.Id, assetItem.Location)); - [Fact(Skip = "Either non-deterministic or broken (failing 50%)")] - public void TestRuntime() + // Create context + var context = new AssetCompilerContext { CompilationContext = typeof(AssetCompilationContext) }; + // Builds the project + Exception? ex = null; + MyAsset1Compiler.AssertFunc = (url, ass, pkg) => { - var package = new Package(); - // ReSharper disable once UnusedVariable - we need a package session to compile - var packageSession = new PackageSession(package); - var otherAssets = new List - { - new AssetItem("asset11", new MyAsset11(), package), - new AssetItem("asset12", new MyAsset12(), package), - new AssetItem("asset13", new MyAsset13(), package), - }; - otherAssets.ForEach(x => package.Assets.Add(x)); - - var compileAssetReference = new MyAsset4 - { - CompileAssetReference = CreateRef(otherAssets[0]), - CompileContentReference = CreateRef(otherAssets[1]), - CompileRuntimeReference = CreateRef(otherAssets[2]), - }; - var assetItem = new AssetItem("asset4", compileAssetReference, package); - package.Assets.Add(assetItem); - - var asset = new MyAsset1 { CompileAssetReference = CreateRef(assetItem) }; - assetItem = new AssetItem("asset1", asset, package); - package.Assets.Add(assetItem); - package.RootAssets.Add(new AssetReference(assetItem.Id, assetItem.Location)); - - // Create context - var context = new AssetCompilerContext { CompilationContext = typeof(AssetCompilationContext) }; - - // Builds the project - Exception ex = null; - MyAsset1Compiler.AssertFunc = (url, ass, pkg) => - { - AssertInThread(ref ex, () => Assert.Single(TestCompilerBase.CompiledAssets)); - AssertInThread(ref ex, () => Assert.Equal(compileAssetReference.Id, TestCompilerBase.CompiledAssets.First().Id)); - }; - - var assetBuilder = new PackageCompiler(new RootPackageAssetEnumerator(package)); - var assetBuildResult = assetBuilder.Prepare(context); - // Since MyAsset4 is a Runtime reference, it should be compiled, so we should have 2 asset (MyAsset1 and MyAsset4) to compile. - Assert.Equal(2, assetBuildResult.BuildSteps.Count); - var builder = new Builder(GlobalLogger.GetLogger("Test"), "", ""); - builder.Root.Add(assetBuildResult.BuildSteps); - builder.Run(Builder.Mode.Build, false); - RethrowAssertsFromThread(ex); - } + // Nothing must have been compiled before + AssertInThread(ref ex, () => Assert.Empty(TestCompilerBase.CompiledAssets)); + }; + var assetBuilder = new PackageCompiler(new RootPackageAssetEnumerator(package)); + var assetBuildResult = assetBuilder.Prepare(context); + // Since MyAsset2 is a CompileAsset reference, it should not be compiled, so we should have only 1 asset (MyAsset1) to compile. + Assert.Equal(1, assetBuildResult.BuildSteps.Count); + var builder = new Builder(GlobalLogger.GetLogger("Test"), "", ""); + builder.Root.Add(assetBuildResult.BuildSteps); + builder.Run(Builder.Mode.Build, false); + RethrowAssertsFromThread(ex); + } - private static void AssertInThread(ref Exception ex, Action assert) + [Fact] + public void TestCompileContent() + { + var package = new Package(); + // ReSharper disable once UnusedVariable - we need a package session to compile + var packageSession = new PackageSession(package); + var otherAssets = new List { - try - { - assert(); - } - catch (Exception e) - { - ex = e; - } - } + new("asset8", new MyAsset8(), package), + new("asset9", new MyAsset9(), package), + new("asset10", new MyAsset10(), package), + }; + otherAssets.ForEach(x => package.Assets.Add(x)); - private static void RethrowAssertsFromThread(Exception ex) + var compileAssetReference = new MyAsset3 { - if (ex != null) - { - throw ex; - } - } + CompileAssetReference = CreateRef(otherAssets[0]), + CompileContentReference = CreateRef(otherAssets[1]), + CompileRuntimeReference = CreateRef(otherAssets[2]), + }; + var asset3 = new AssetItem("asset3", compileAssetReference, package); + package.Assets.Add(asset3); + + var asset = new MyAsset1 { CompileContentReference = CreateRef(asset3) }; + var assetItem = new AssetItem("asset1", asset, package); + package.Assets.Add(assetItem); + package.RootAssets.Add(new AssetReference(assetItem.Id, assetItem.Location)); - #region Types - - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent1 { } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent2 { } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent3 { } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent4 { } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent5 { } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent6 { } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent7 { } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent8 { } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent9 { } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent10 { } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent11 { } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent12 { } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContent13 { } - - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent1))] - public class MyAsset1 : Asset + // Create context + var context = new AssetCompilerContext { CompilationContext = typeof(AssetCompilationContext) }; + + Exception? ex = null; + MyAsset1Compiler.AssertFunc = (url, ass, pkg) => { - public MyContent2 CompileAssetReference; - public MyContent3 CompileContentReference; - public MyContent4 CompileRuntimeReference; - } + AssertInThread(ref ex, () => Assert.Single(TestCompilerBase.CompiledAssets)); + AssertInThread(ref ex, () => Assert.Equal(asset3.Id, TestCompilerBase.CompiledAssets.First().Id)); + }; - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent2))] - public class MyAsset2 : Asset + var assetBuilder = new PackageCompiler(new RootPackageAssetEnumerator(package)); + var assetBuildResult = assetBuilder.Prepare(context); + // Since MyAsset3 is a CompileContent reference, it should be compiled, so we should have only 2 asset (MyAsset1 and MyAsset3) to compile. + Assert.Equal(2, assetBuildResult.BuildSteps.Count); + var builder = new Builder(GlobalLogger.GetLogger("Test"), "", ""); + builder.Root.Add(assetBuildResult.BuildSteps); + builder.Run(Builder.Mode.Build, false); + RethrowAssertsFromThread(ex); + } + + [Fact(Skip = "Either non-deterministic or broken (failing 50%)")] + public void TestRuntime() + { + var package = new Package(); + // ReSharper disable once UnusedVariable - we need a package session to compile + var packageSession = new PackageSession(package); + var otherAssets = new List { - public MyContent5 CompileAssetReference; - public MyContent6 CompileContentReference; - public MyContent7 CompileRuntimeReference; - } + new("asset11", new MyAsset11(), package), + new("asset12", new MyAsset12(), package), + new("asset13", new MyAsset13(), package), + }; + otherAssets.ForEach(x => package.Assets.Add(x)); + + var compileAssetReference = new MyAsset4 + { + CompileAssetReference = CreateRef(otherAssets[0]), + CompileContentReference = CreateRef(otherAssets[1]), + CompileRuntimeReference = CreateRef(otherAssets[2]), + }; + var assetItem = new AssetItem("asset4", compileAssetReference, package); + package.Assets.Add(assetItem); + + var asset = new MyAsset1 { CompileAssetReference = CreateRef(assetItem) }; + assetItem = new AssetItem("asset1", asset, package); + package.Assets.Add(assetItem); + package.RootAssets.Add(new AssetReference(assetItem.Id, assetItem.Location)); - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent3))] - public class MyAsset3 : Asset + // Create context + var context = new AssetCompilerContext { CompilationContext = typeof(AssetCompilationContext) }; + + // Builds the project + Exception? ex = null; + MyAsset1Compiler.AssertFunc = (url, ass, pkg) => { - public MyContent8 CompileAssetReference; - public MyContent9 CompileContentReference; - public MyContent10 CompileRuntimeReference; + AssertInThread(ref ex, () => Assert.Single(TestCompilerBase.CompiledAssets)); + AssertInThread(ref ex, () => Assert.Equal(compileAssetReference.Id, TestCompilerBase.CompiledAssets.First().Id)); + }; + + var assetBuilder = new PackageCompiler(new RootPackageAssetEnumerator(package)); + var assetBuildResult = assetBuilder.Prepare(context); + // Since MyAsset4 is a Runtime reference, it should be compiled, so we should have 2 asset (MyAsset1 and MyAsset4) to compile. + Assert.Equal(2, assetBuildResult.BuildSteps.Count); + var builder = new Builder(GlobalLogger.GetLogger("Test"), "", ""); + builder.Root.Add(assetBuildResult.BuildSteps); + builder.Run(Builder.Mode.Build, false); + RethrowAssertsFromThread(ex); + } + + + private static void AssertInThread(ref Exception? ex, Action assert) + { + try + { + assert(); + } + catch (Exception e) + { + ex = e; } + } - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent4))] - public class MyAsset4 : Asset + private static void RethrowAssertsFromThread(Exception? ex) + { + if (ex != null) { - public MyContent11 CompileAssetReference; - public MyContent12 CompileContentReference; - public MyContent13 CompileRuntimeReference; + throw ex; } + } + + #region Types + + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent1 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent2 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent3 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent4 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent5 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent6 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent7 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent8 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent9 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent10 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent11 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent12 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContent13 { } + + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent1))] + public class MyAsset1 : Asset + { + public MyContent2? CompileAssetReference; + public MyContent3? CompileContentReference; + public MyContent4? CompileRuntimeReference; + } + + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent2))] + public class MyAsset2 : Asset + { + public MyContent5? CompileAssetReference; + public MyContent6? CompileContentReference; + public MyContent7? CompileRuntimeReference; + } + + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent3))] + public class MyAsset3 : Asset + { + public MyContent8? CompileAssetReference; + public MyContent9? CompileContentReference; + public MyContent10? CompileRuntimeReference; + } + + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent4))] + public class MyAsset4 : Asset + { + public MyContent11? CompileAssetReference; + public MyContent12? CompileContentReference; + public MyContent13? CompileRuntimeReference; + } + + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent5))] + public class MyAsset5 : Asset { } + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent6))] + public class MyAsset6 : Asset { } + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent7))] + public class MyAsset7 : Asset { } + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent8))] + public class MyAsset8 : Asset { } + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent9))] + public class MyAsset9 : Asset { } + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent10))] + public class MyAsset10 : Asset { } + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent11))] + public class MyAsset11 : Asset { } + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent12))] + public class MyAsset12 : Asset { } + [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent13))] + public class MyAsset13 : Asset { } - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent5))] - public class MyAsset5 : Asset { } - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent6))] - public class MyAsset6 : Asset { } - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent7))] - public class MyAsset7 : Asset { } - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent8))] - public class MyAsset8 : Asset { } - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent9))] - public class MyAsset9 : Asset { } - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent10))] - public class MyAsset10 : Asset { } - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent11))] - public class MyAsset11 : Asset { } - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent12))] - public class MyAsset12 : Asset { } - [DataContract, AssetDescription(".sdmytest"), AssetContentType(typeof(MyContent13))] - public class MyAsset13 : Asset { } - - [AssetCompiler(typeof(MyAsset1), typeof(AssetCompilationContext))] - public class MyAsset1Compiler : TestAssertCompiler + [AssetCompiler(typeof(MyAsset1), typeof(AssetCompilationContext))] + public class MyAsset1Compiler : TestAssertCompiler + { + public override IEnumerable GetInputTypes(AssetItem assetItem) { - public override IEnumerable GetInputTypes(AssetItem assetItem) - { - yield return new BuildDependencyInfo(typeof(MyAsset2), typeof(AssetCompilationContext), BuildDependencyType.CompileAsset); - yield return new BuildDependencyInfo(typeof(MyAsset3), typeof(AssetCompilationContext), BuildDependencyType.CompileContent); - yield return new BuildDependencyInfo(typeof(MyAsset4), typeof(AssetCompilationContext), BuildDependencyType.Runtime); - } - - public static Action AssertFunc; - protected override void DoCommandAssert(string url, MyAsset1 parameters, IAssetFinder package) => AssertFunc?.Invoke(url, parameters, package); + yield return new BuildDependencyInfo(typeof(MyAsset2), typeof(AssetCompilationContext), BuildDependencyType.CompileAsset); + yield return new BuildDependencyInfo(typeof(MyAsset3), typeof(AssetCompilationContext), BuildDependencyType.CompileContent); + yield return new BuildDependencyInfo(typeof(MyAsset4), typeof(AssetCompilationContext), BuildDependencyType.Runtime); } - [AssetCompiler(typeof(MyAsset2), typeof(AssetCompilationContext))] - public class MyAsset2Compiler : TestAssertCompiler { } - [AssetCompiler(typeof(MyAsset3), typeof(AssetCompilationContext))] - public class MyAsset3Compiler : TestAssertCompiler { } - [AssetCompiler(typeof(MyAsset4), typeof(AssetCompilationContext))] - public class MyAsset4Compiler : TestAssertCompiler { } - [AssetCompiler(typeof(MyAsset5), typeof(AssetCompilationContext))] - public class MyAsset5Compiler : TestAssertCompiler { } - [AssetCompiler(typeof(MyAsset6), typeof(AssetCompilationContext))] - public class MyAsset6Compiler : TestAssertCompiler { } - [AssetCompiler(typeof(MyAsset7), typeof(AssetCompilationContext))] - public class MyAsset7Compiler : TestAssertCompiler { } - [AssetCompiler(typeof(MyAsset8), typeof(AssetCompilationContext))] - public class MyAsset8Compiler : TestAssertCompiler { } - [AssetCompiler(typeof(MyAsset9), typeof(AssetCompilationContext))] - public class MyAsset9Compiler : TestAssertCompiler { } - [AssetCompiler(typeof(MyAsset10), typeof(AssetCompilationContext))] - public class MyAsset10Compiler : TestAssertCompiler { } - [AssetCompiler(typeof(MyAsset11), typeof(AssetCompilationContext))] - public class MyAsset11Compiler : TestAssertCompiler { } - [AssetCompiler(typeof(MyAsset12), typeof(AssetCompilationContext))] - public class MyAsset12Compiler : TestAssertCompiler { } - [AssetCompiler(typeof(MyAsset13), typeof(AssetCompilationContext))] - public class MyAsset13Compiler : TestAssertCompiler { } - - #endregion Types + public static Action? AssertFunc; + protected override void DoCommandAssert(string url, MyAsset1 parameters, IAssetFinder package) => AssertFunc?.Invoke(url, parameters, package); } + + [AssetCompiler(typeof(MyAsset2), typeof(AssetCompilationContext))] + public class MyAsset2Compiler : TestAssertCompiler { } + [AssetCompiler(typeof(MyAsset3), typeof(AssetCompilationContext))] + public class MyAsset3Compiler : TestAssertCompiler { } + [AssetCompiler(typeof(MyAsset4), typeof(AssetCompilationContext))] + public class MyAsset4Compiler : TestAssertCompiler { } + [AssetCompiler(typeof(MyAsset5), typeof(AssetCompilationContext))] + public class MyAsset5Compiler : TestAssertCompiler { } + [AssetCompiler(typeof(MyAsset6), typeof(AssetCompilationContext))] + public class MyAsset6Compiler : TestAssertCompiler { } + [AssetCompiler(typeof(MyAsset7), typeof(AssetCompilationContext))] + public class MyAsset7Compiler : TestAssertCompiler { } + [AssetCompiler(typeof(MyAsset8), typeof(AssetCompilationContext))] + public class MyAsset8Compiler : TestAssertCompiler { } + [AssetCompiler(typeof(MyAsset9), typeof(AssetCompilationContext))] + public class MyAsset9Compiler : TestAssertCompiler { } + [AssetCompiler(typeof(MyAsset10), typeof(AssetCompilationContext))] + public class MyAsset10Compiler : TestAssertCompiler { } + [AssetCompiler(typeof(MyAsset11), typeof(AssetCompilationContext))] + public class MyAsset11Compiler : TestAssertCompiler { } + [AssetCompiler(typeof(MyAsset12), typeof(AssetCompilationContext))] + public class MyAsset12Compiler : TestAssertCompiler { } + [AssetCompiler(typeof(MyAsset13), typeof(AssetCompilationContext))] + public class MyAsset13Compiler : TestAssertCompiler { } + + #endregion Types } diff --git a/sources/assets/Stride.Core.Assets.Tests/Compilers/TestCompilerBase.cs b/sources/assets/Stride.Core.Assets.Tests/Compilers/TestCompilerBase.cs index ef501d5622..f6d864e911 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Compilers/TestCompilerBase.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Compilers/TestCompilerBase.cs @@ -1,25 +1,23 @@ -using System; -using System.Collections.Generic; + using Stride.Core.Assets.Analysis; using Stride.Core.Assets.Compiler; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Tests.Compilers +namespace Stride.Core.Assets.Tests.Compilers; + +public abstract class TestCompilerBase : IAssetCompiler { - public abstract class TestCompilerBase : IAssetCompiler - { - public static HashSet CompiledAssets; + public static HashSet CompiledAssets = null!; // set by CompilerTestBase - public abstract AssetCompilerResult Prepare(AssetCompilerContext context, AssetItem assetItem); + public abstract AssetCompilerResult Prepare(AssetCompilerContext context, AssetItem assetItem); - public virtual IEnumerable GetRuntimeTypes(AssetItem assetItem) { yield break; } + public virtual IEnumerable GetRuntimeTypes(AssetItem assetItem) { yield break; } - public virtual IEnumerable GetInputFiles(AssetItem assetItem) { yield break; } + public virtual IEnumerable GetInputFiles(AssetItem assetItem) { yield break; } - public virtual IEnumerable GetInputTypes(AssetItem assetItem) { yield break; } + public virtual IEnumerable GetInputTypes(AssetItem assetItem) { yield break; } - public virtual IEnumerable GetInputTypesToExclude(AssetItem assetItem) { yield break; } + public virtual IEnumerable GetInputTypesToExclude(AssetItem assetItem) { yield break; } - public virtual bool AlwaysCheckRuntimeTypes { get; } = true; - } -} \ No newline at end of file + public virtual bool AlwaysCheckRuntimeTypes { get; } = true; +} diff --git a/sources/assets/Stride.Core.Assets.Tests/Compilers/TestCompilerVisitRuntimeType.cs b/sources/assets/Stride.Core.Assets.Tests/Compilers/TestCompilerVisitRuntimeType.cs index ea4f811b76..7099353ac7 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Compilers/TestCompilerVisitRuntimeType.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Compilers/TestCompilerVisitRuntimeType.cs @@ -1,132 +1,127 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Xunit; using Stride.Core.Assets.Analysis; using Stride.Core.Assets.Compiler; -using Stride.Core.BuildEngine; -using Stride.Core; using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Tests.Compilers +namespace Stride.Core.Assets.Tests.Compilers; + +public class TestCompilerVisitRuntimeType : CompilerTestBase { - public class TestCompilerVisitRuntimeType : CompilerTestBase + [Fact] + public void CompilerVisitRuntimeType() { - [Fact] - public void CompilerVisitRuntimeType() - { - PackageSessionPublicHelper.FindAndSetMSBuildVersion(); + PackageSessionPublicHelper.FindAndSetMSBuildVersion(); - var package = new Package(); - // ReSharper disable once UnusedVariable - we need a package session to compile - var packageSession = new PackageSession(package); - var otherAssets = new List - { - new AssetItem("contentRB", new MyAssetContentType(0), package), - new AssetItem("contentRA", new MyAssetContentType(1), package), - new AssetItem("content0B", new MyAssetContentType(2), package), - new AssetItem("content0M", new MyAssetContentType(3), package), - new AssetItem("content0A", new MyAssetContentType(4), package), - new AssetItem("content1B", new MyAssetContentType(5), package), - new AssetItem("content1M", new MyAssetContentType(6), package), - new AssetItem("content1A", new MyAssetContentType(7), package), - new AssetItem("content2B", new MyAssetContentType(8), package), - new AssetItem("content2M", new MyAssetContentType(9), package), - new AssetItem("content2A", new MyAssetContentType(10), package), - new AssetItem("content3B", new MyAssetContentType(11), package), - new AssetItem("content3M", new MyAssetContentType(12), package), - new AssetItem("content3A", new MyAssetContentType(13), package), - new AssetItem("content4B", new MyAssetContentType(14), package), - new AssetItem("content4M", new MyAssetContentType(15), package), - new AssetItem("content4A", new MyAssetContentType(16), package), - }; + var package = new Package(); + // ReSharper disable once UnusedVariable - we need a package session to compile + var packageSession = new PackageSession(package); + var otherAssets = new List + { + new("contentRB", new MyAssetContentType(0), package), + new("contentRA", new MyAssetContentType(1), package), + new("content0B", new MyAssetContentType(2), package), + new("content0M", new MyAssetContentType(3), package), + new("content0A", new MyAssetContentType(4), package), + new("content1B", new MyAssetContentType(5), package), + new("content1M", new MyAssetContentType(6), package), + new("content1A", new MyAssetContentType(7), package), + new("content2B", new MyAssetContentType(8), package), + new("content2M", new MyAssetContentType(9), package), + new("content2A", new MyAssetContentType(10), package), + new("content3B", new MyAssetContentType(11), package), + new("content3M", new MyAssetContentType(12), package), + new("content3A", new MyAssetContentType(13), package), + new("content4B", new MyAssetContentType(14), package), + new("content4M", new MyAssetContentType(15), package), + new("content4A", new MyAssetContentType(16), package), + }; - var assetToVisit = new MyAsset1(); - assetToVisit.Before = AttachedReferenceManager.CreateProxyObject(otherAssets[0].Id, otherAssets[0].Location); - assetToVisit.Zafter = AttachedReferenceManager.CreateProxyObject(otherAssets[1].Id, otherAssets[1].Location); - assetToVisit.RuntimeTypes.Add(CreateRuntimeType(otherAssets[2], otherAssets[3], otherAssets[4])); - assetToVisit.RuntimeTypes.Add(CreateRuntimeType(otherAssets[5], otherAssets[6], otherAssets[7])); - assetToVisit.RuntimeTypes.Add(CreateRuntimeType(otherAssets[8], otherAssets[9], otherAssets[10])); - assetToVisit.RuntimeTypes.Add(CreateRuntimeType(otherAssets[11], otherAssets[12], otherAssets[13])); - assetToVisit.RuntimeTypes.Add(CreateRuntimeType(otherAssets[14], otherAssets[15], otherAssets[16])); - assetToVisit.RuntimeTypes[0].A = assetToVisit.RuntimeTypes[1]; - assetToVisit.RuntimeTypes[0].B = assetToVisit.RuntimeTypes[2]; - assetToVisit.RuntimeTypes[1].A = assetToVisit.RuntimeTypes[3]; - assetToVisit.RuntimeTypes[1].B = assetToVisit.RuntimeTypes[4]; + var assetToVisit = new MyAsset1 + { + Before = AttachedReferenceManager.CreateProxyObject(otherAssets[0].Id, otherAssets[0].Location), + Zafter = AttachedReferenceManager.CreateProxyObject(otherAssets[1].Id, otherAssets[1].Location) + }; + assetToVisit.RuntimeTypes.Add(CreateRuntimeType(otherAssets[2], otherAssets[3], otherAssets[4])); + assetToVisit.RuntimeTypes.Add(CreateRuntimeType(otherAssets[5], otherAssets[6], otherAssets[7])); + assetToVisit.RuntimeTypes.Add(CreateRuntimeType(otherAssets[8], otherAssets[9], otherAssets[10])); + assetToVisit.RuntimeTypes.Add(CreateRuntimeType(otherAssets[11], otherAssets[12], otherAssets[13])); + assetToVisit.RuntimeTypes.Add(CreateRuntimeType(otherAssets[14], otherAssets[15], otherAssets[16])); + assetToVisit.RuntimeTypes[0].A = assetToVisit.RuntimeTypes[1]; + assetToVisit.RuntimeTypes[0].B = assetToVisit.RuntimeTypes[2]; + assetToVisit.RuntimeTypes[1].A = assetToVisit.RuntimeTypes[3]; + assetToVisit.RuntimeTypes[1].B = assetToVisit.RuntimeTypes[4]; - otherAssets.ForEach(x => package.Assets.Add(x)); - var assetItem = new AssetItem("asset", assetToVisit, package); - package.Assets.Add(assetItem); - package.RootAssets.Add(new AssetReference(assetItem.Id, assetItem.Location)); + otherAssets.ForEach(x => package.Assets.Add(x)); + var assetItem = new AssetItem("asset", assetToVisit, package); + package.Assets.Add(assetItem); + package.RootAssets.Add(new AssetReference(assetItem.Id, assetItem.Location)); - // Create context - var context = new AssetCompilerContext { CompilationContext = typeof(AssetCompilationContext) }; + // Create context + var context = new AssetCompilerContext { CompilationContext = typeof(AssetCompilationContext) }; - // Builds the project - var assetBuilder = new PackageCompiler(new RootPackageAssetEnumerator(package)); - context.Properties.Set(BuildAssetNode.VisitRuntimeTypes, true); - var assetBuildResult = assetBuilder.Prepare(context); - Assert.Equal(16, assetBuildResult.BuildSteps.Count); - } + // Builds the project + var assetBuilder = new PackageCompiler(new RootPackageAssetEnumerator(package)); + context.Properties.Set(BuildAssetNode.VisitRuntimeTypes, true); + var assetBuildResult = assetBuilder.Prepare(context); + Assert.Equal(16, assetBuildResult.BuildSteps.Count); + } - private static MyRuntimeType CreateRuntimeType(AssetItem beforeReference, AssetItem middleReference, AssetItem afterReference) + private static MyRuntimeType CreateRuntimeType(AssetItem beforeReference, AssetItem middleReference, AssetItem afterReference) + { + var result = new MyRuntimeType { - var result = new MyRuntimeType - { - Before = CreateRef(beforeReference), - Middle = CreateRef(middleReference), - Zafter = CreateRef(afterReference), - }; - return result; - } + Before = CreateRef(beforeReference), + Middle = CreateRef(middleReference), + Zafter = CreateRef(afterReference), + }; + return result; + } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class MyContentType - { - public int Var; - } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + public class MyContentType + { + public int Var; + } - [DataContract] - public class MyRuntimeType - { - public MyContentType Before; - public MyRuntimeType A; - public MyContentType Middle; - public MyRuntimeType B; - public MyContentType Zafter; - } + [DataContract] + public class MyRuntimeType + { + public MyContentType? Before; + public MyRuntimeType? A; + public MyContentType? Middle; + public MyRuntimeType? B; + public MyContentType? Zafter; + } - [DataContract] - [AssetDescription(FileExtension)] - [AssetContentType(typeof(MyContentType))] - public class MyAssetContentType : Asset - { - public const string FileExtension = ".sdmact"; - public int Var; - public MyAssetContentType(int i) { Var = i; } - public MyAssetContentType() { } - } + [DataContract] + [AssetDescription(FileExtension)] + [AssetContentType(typeof(MyContentType))] + public class MyAssetContentType : Asset + { + public const string FileExtension = ".sdmact"; + public int Var; + public MyAssetContentType(int i) { Var = i; } + public MyAssetContentType() { } + } - [DataContract] - [AssetDescription(".sdmytest")] - public class MyAsset1 : Asset - { - public MyContentType Before; - public List RuntimeTypes = new List(); - public MyContentType Zafter; - } + [DataContract] + [AssetDescription(".sdmytest")] + public class MyAsset1 : Asset + { + public MyContentType? Before; + public List RuntimeTypes = []; + public MyContentType? Zafter; + } - [AssetCompiler(typeof(MyAsset1), typeof(AssetCompilationContext))] - public class MyAsset1Compiler : TestAssertCompiler + [AssetCompiler(typeof(MyAsset1), typeof(AssetCompilationContext))] + public class MyAsset1Compiler : TestAssertCompiler + { + public override IEnumerable GetRuntimeTypes(AssetItem assetItem) { - public override IEnumerable GetRuntimeTypes(AssetItem assetItem) - { - yield return typeof(MyRuntimeType); - } + yield return typeof(MyRuntimeType); } - - [AssetCompiler(typeof(MyAssetContentType), typeof(AssetCompilationContext))] - public class MyAssetContentTypeCompiler : TestAssertCompiler { } } + + [AssetCompiler(typeof(MyAssetContentType), typeof(AssetCompilationContext))] + public class MyAssetContentTypeCompiler : TestAssertCompiler { } } diff --git a/sources/assets/Stride.Core.Assets.Tests/Compilers/TestDependencyByIncludeTypeAnalysis.cs b/sources/assets/Stride.Core.Assets.Tests/Compilers/TestDependencyByIncludeTypeAnalysis.cs index 2efd018aa7..3b38d7e978 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Compilers/TestDependencyByIncludeTypeAnalysis.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Compilers/TestDependencyByIncludeTypeAnalysis.cs @@ -1,102 +1,98 @@ -using System; -using System.Collections.Generic; -using Xunit; using Stride.Core.Assets.Analysis; using Stride.Core.Assets.Compiler; -using Stride.Core; using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Tests.Compilers +namespace Stride.Core.Assets.Tests.Compilers; + +public class TestDependencyByIncludeTypeAnalysis : CompilerTestBase { - public class TestDependencyByIncludeTypeAnalysis : CompilerTestBase + [Fact] + public void CompilerDependencyByIncludeTypeAnalysis() { - [Fact] - public void CompilerDependencyByIncludeTypeAnalysis() - { - var package = new Package(); - // ReSharper disable once UnusedVariable - we need a package session to compile - var packageSession = new PackageSession(package); - var asset1 = new AssetItem("content1", new MyAsset1(), package); // Should be compiled (root) - var asset2 = new AssetItem("content2", new MyAsset2(), package); // Should be compiled (Runtime for Asset1) - var asset31 = new AssetItem("content3_1", new MyAsset3(), package); // Should NOT be compiled (CompileAsset for Asset1) - var asset32 = new AssetItem("content3_2", new MyAsset3(), package); // Should be compiled (Runtime for Asset2) + var package = new Package(); + // ReSharper disable once UnusedVariable - we need a package session to compile + var packageSession = new PackageSession(package); + var asset1 = new AssetItem("content1", new MyAsset1(), package); // Should be compiled (root) + var asset2 = new AssetItem("content2", new MyAsset2(), package); // Should be compiled (Runtime for Asset1) + var asset31 = new AssetItem("content3_1", new MyAsset3(), package); // Should NOT be compiled (CompileAsset for Asset1) + var asset32 = new AssetItem("content3_2", new MyAsset3(), package); // Should be compiled (Runtime for Asset2) - ((MyAsset1)asset1.Asset).MyContent2 = AttachedReferenceManager.CreateProxyObject(asset2.Id, asset2.Location); - ((MyAsset1)asset1.Asset).MyContent3 = AttachedReferenceManager.CreateProxyObject(asset31.Id, asset31.Location); - ((MyAsset2)asset2.Asset).MyContent3 = AttachedReferenceManager.CreateProxyObject(asset32.Id, asset32.Location); + ((MyAsset1)asset1.Asset).MyContent2 = AttachedReferenceManager.CreateProxyObject(asset2.Id, asset2.Location); + ((MyAsset1)asset1.Asset).MyContent3 = AttachedReferenceManager.CreateProxyObject(asset31.Id, asset31.Location); + ((MyAsset2)asset2.Asset).MyContent3 = AttachedReferenceManager.CreateProxyObject(asset32.Id, asset32.Location); - package.Assets.Add(asset1); - package.Assets.Add(asset2); - package.Assets.Add(asset31); - package.Assets.Add(asset32); - package.RootAssets.Add(new AssetReference(asset1.Id, asset1.Location)); + package.Assets.Add(asset1); + package.Assets.Add(asset2); + package.Assets.Add(asset31); + package.Assets.Add(asset32); + package.RootAssets.Add(new AssetReference(asset1.Id, asset1.Location)); - // Create context - var context = new AssetCompilerContext { CompilationContext = typeof(AssetCompilationContext) }; + // Create context + var context = new AssetCompilerContext { CompilationContext = typeof(AssetCompilationContext) }; - // Builds the project - var assetBuilder = new PackageCompiler(new RootPackageAssetEnumerator(package)); - context.Properties.Set(BuildAssetNode.VisitRuntimeTypes, true); - var assetBuildResult = assetBuilder.Prepare(context); - // Total number of asset to compile = 3 - Assert.Equal(3, assetBuildResult.BuildSteps.Count); - } + // Builds the project + var assetBuilder = new PackageCompiler(new RootPackageAssetEnumerator(package)); + context.Properties.Set(BuildAssetNode.VisitRuntimeTypes, true); + var assetBuildResult = assetBuilder.Prepare(context); + // Total number of asset to compile = 3 + Assert.Equal(3, assetBuildResult.BuildSteps.Count); + GC.KeepAlive(packageSession); + } - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - [ContentSerializer(typeof(DataContentSerializer))] - public class MyContent1 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + [ContentSerializer(typeof(DataContentSerializer))] + public class MyContent1; - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - [ContentSerializer(typeof(DataContentSerializer))] - public class MyContent2 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + [ContentSerializer(typeof(DataContentSerializer))] + public class MyContent2; - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - [ContentSerializer(typeof(DataContentSerializer))] - public class MyContent3 { } + [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] + [ContentSerializer(typeof(DataContentSerializer))] + public class MyContent3; - [DataContract] - [AssetDescription(".sdmytest")] - [AssetContentType(typeof(MyContent1))] - public class MyAsset1 : Asset - { - public MyContent2 MyContent2 { get; set; } - public MyContent3 MyContent3 { get; set; } - } + [DataContract] + [AssetDescription(".sdmytest")] + [AssetContentType(typeof(MyContent1))] + public class MyAsset1 : Asset + { + public MyContent2? MyContent2 { get; set; } + public MyContent3? MyContent3 { get; set; } + } - [DataContract] - [AssetDescription(".sdmytest")] - [AssetContentType(typeof(MyContent2))] - public class MyAsset2 : Asset - { - public MyContent3 MyContent3 { get; set; } - } + [DataContract] + [AssetDescription(".sdmytest")] + [AssetContentType(typeof(MyContent2))] + public class MyAsset2 : Asset + { + public MyContent3? MyContent3 { get; set; } + } - [DataContract] - [AssetDescription(".sdmytest")] - [AssetContentType(typeof(MyContent3))] - public class MyAsset3 : Asset { } + [DataContract] + [AssetDescription(".sdmytest")] + [AssetContentType(typeof(MyContent3))] + public class MyAsset3 : Asset; - [AssetCompiler(typeof(MyAsset1), typeof(AssetCompilationContext))] - public class MyAsset1Compiler : TestAssertCompiler + [AssetCompiler(typeof(MyAsset1), typeof(AssetCompilationContext))] + public class MyAsset1Compiler : TestAssertCompiler + { + public override IEnumerable GetInputTypes(AssetItem assetItem) { - public override IEnumerable GetInputTypes(AssetItem assetItem) - { - yield return new BuildDependencyInfo(typeof(MyAsset2), typeof(AssetCompilationContext), BuildDependencyType.Runtime); - yield return new BuildDependencyInfo(typeof(MyAsset3), typeof(AssetCompilationContext), BuildDependencyType.CompileAsset); - } + yield return new BuildDependencyInfo(typeof(MyAsset2), typeof(AssetCompilationContext), BuildDependencyType.Runtime); + yield return new BuildDependencyInfo(typeof(MyAsset3), typeof(AssetCompilationContext), BuildDependencyType.CompileAsset); } + } - [AssetCompiler(typeof(MyAsset2), typeof(AssetCompilationContext))] - public class MyAsset2Compiler : TestAssertCompiler + [AssetCompiler(typeof(MyAsset2), typeof(AssetCompilationContext))] + public class MyAsset2Compiler : TestAssertCompiler + { + public override IEnumerable GetInputTypes(AssetItem assetItem) { - public override IEnumerable GetInputTypes(AssetItem assetItem) - { - yield return new BuildDependencyInfo(typeof(MyAsset3), typeof(AssetCompilationContext), BuildDependencyType.Runtime); - } + yield return new BuildDependencyInfo(typeof(MyAsset3), typeof(AssetCompilationContext), BuildDependencyType.Runtime); } - - [AssetCompiler(typeof(MyAsset3), typeof(AssetCompilationContext))] - public class MyAsset3Compiler : TestAssertCompiler { } } -} \ No newline at end of file + + [AssetCompiler(typeof(MyAsset3), typeof(AssetCompilationContext))] + public class MyAsset3Compiler : TestAssertCompiler; +} diff --git a/sources/assets/Stride.Core.Assets.Tests/CustomParameterCollection.cs b/sources/assets/Stride.Core.Assets.Tests/CustomParameterCollection.cs index e8da4463a5..498e2afa45 100644 --- a/sources/assets/Stride.Core.Assets.Tests/CustomParameterCollection.cs +++ b/sources/assets/Stride.Core.Assets.Tests/CustomParameterCollection.cs @@ -1,20 +1,17 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using Stride.Core; -namespace Stride.Core.Assets.Tests +namespace Stride.Core.Assets.Tests; + +/// +/// Fake a ParameterCollection using a dictionary of PropertyKey, object +/// +[DataContract] +public class CustomParameterCollection : Dictionary { - /// - /// Fake a ParameterCollection using a dictionary of PropertyKey, object - /// - [DataContract] - public class CustomParameterCollection : Dictionary - { - public void Set(PropertyKey key, object value) - { - this[key] = value; - } + public void Set(PropertyKey key, object value) + { + this[key] = value; } } diff --git a/sources/assets/Stride.Core.Assets.Tests/Helpers/GuidGenerator.cs b/sources/assets/Stride.Core.Assets.Tests/Helpers/GuidGenerator.cs index 1092623ed1..deff3d0261 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Helpers/GuidGenerator.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Helpers/GuidGenerator.cs @@ -1,49 +1,47 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets.Tests.Helpers +namespace Stride.Core.Assets.Tests.Helpers; + +/// +/// A static helper to generate deterministic for unit tests. +/// +public static class GuidGenerator { /// - /// A static helper to generate deterministic for unit tests. + /// Gets a deterministic for a given integer seed. /// - public static class GuidGenerator + /// The integer seed of the . + /// A that will always be the same for a given seed. + public static Guid Get(int seed) { - /// - /// Gets a deterministic for a given integer seed. - /// - /// The integer seed of the . - /// A that will always be the same for a given seed. - public static Guid Get(int seed) - { - var bytes = ToBytes(seed); - return new Guid(bytes); - } + var bytes = ToBytes(seed); + return new Guid(bytes); + } - /// - /// Verifies that the given corresponds to the given seed value. - /// - /// The to verify. - /// The seed that should correspond to the . - /// True if the match the seed, false otherwise. - public static bool Match(Guid guid, int seed) - { - var bytes = ToBytes(seed); - var id = new Guid(bytes); - return guid == id; - } + /// + /// Verifies that the given corresponds to the given seed value. + /// + /// The to verify. + /// The seed that should correspond to the . + /// True if the match the seed, false otherwise. + public static bool Match(Guid guid, int seed) + { + var bytes = ToBytes(seed); + var id = new Guid(bytes); + return guid == id; + } - private static byte[] ToBytes(int seed) + private static byte[] ToBytes(int seed) + { + var bytes = new byte[16]; + for (int i = 0; i < 4; ++i) { - var bytes = new byte[16]; - for (int i = 0; i < 4; ++i) - { - bytes[4 * i] = (byte)seed; - bytes[4 * i + 1] = (byte)(seed >> 8); - bytes[4 * i + 2] = (byte)(seed >> 16); - bytes[4 * i + 3] = (byte)(seed >> 24); - } - return bytes; + bytes[4 * i] = (byte)seed; + bytes[4 * i + 1] = (byte)(seed >> 8); + bytes[4 * i + 2] = (byte)(seed >> 16); + bytes[4 * i + 3] = (byte)(seed >> 24); } + return bytes; } } diff --git a/sources/assets/Stride.Core.Assets.Tests/Helpers/IdentifierGenerator.cs b/sources/assets/Stride.Core.Assets.Tests/Helpers/IdentifierGenerator.cs index 765cfd04ac..3dae190b08 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Helpers/IdentifierGenerator.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Helpers/IdentifierGenerator.cs @@ -2,48 +2,47 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using Stride.Core.Reflection; -namespace Stride.Core.Assets.Tests.Helpers +namespace Stride.Core.Assets.Tests.Helpers; + +/// +/// A static helper to generate deterministic for unit tests. +/// +public static class IdentifierGenerator { /// - /// A static helper to generate deterministic for unit tests. + /// Gets a deterministic for a given integer seed. /// - public static class IdentifierGenerator + /// The integer seed of the . + /// A that will always be the same for a given seed. + public static ItemId Get(int seed) { - /// - /// Gets a deterministic for a given integer seed. - /// - /// The integer seed of the . - /// A that will always be the same for a given seed. - public static ItemId Get(int seed) - { - var bytes = ToBytes(seed); - return new ItemId(bytes); - } + var bytes = ToBytes(seed); + return new ItemId(bytes); + } - /// - /// Verifies that the given corresponds to the given seed value. - /// - /// The to verify. - /// The seed that should correspond to the . - /// True if the match the seed, false otherwise. - public static bool Match(ItemId guid, int seed) - { - var bytes = ToBytes(seed); - var id = new ItemId(bytes); - return guid == id; - } + /// + /// Verifies that the given corresponds to the given seed value. + /// + /// The to verify. + /// The seed that should correspond to the . + /// True if the match the seed, false otherwise. + public static bool Match(ItemId guid, int seed) + { + var bytes = ToBytes(seed); + var id = new ItemId(bytes); + return guid == id; + } - private static byte[] ToBytes(int seed) + private static byte[] ToBytes(int seed) + { + var bytes = new byte[16]; + for (int i = 0; i < 4; ++i) { - var bytes = new byte[16]; - for (int i = 0; i < 4; ++i) - { - bytes[4 * i] = (byte)seed; - bytes[4 * i + 1] = (byte)(seed >> 8); - bytes[4 * i + 2] = (byte)(seed >> 16); - bytes[4 * i + 3] = (byte)(seed >> 24); - } - return bytes; + bytes[4 * i] = (byte)seed; + bytes[4 * i + 1] = (byte)(seed >> 8); + bytes[4 * i + 2] = (byte)(seed >> 16); + bytes[4 * i + 3] = (byte)(seed >> 24); } + return bytes; } } diff --git a/sources/assets/Stride.Core.Assets.Tests/Module.cs b/sources/assets/Stride.Core.Assets.Tests/Module.cs index f5118eabce..aacb5b7435 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Module.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Module.cs @@ -1,23 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.IO; using System.Runtime.CompilerServices; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Tests +namespace Stride.Core.Assets.Tests; + +// Somehow it helps Resharper NUnit to run module initializer first (to determine unit test configuration). +public class Module { - // Somehow it helps Resharper NUnit to run module initializer first (to determine unit test configuration). - public class Module + [ModuleInitializer] + internal static void Initialize() { - [ModuleInitializer] - internal static void Initialize() - { - AssemblyRegistry.Register(typeof(Module).Assembly, AssemblyCommonCategories.Assets); - RuntimeHelpers.RunModuleConstructor(typeof(Asset).Module.ModuleHandle); + AssemblyRegistry.Register(typeof(Module).Assembly, AssemblyCommonCategories.Assets); + RuntimeHelpers.RunModuleConstructor(typeof(Asset).Module.ModuleHandle); - PackageSessionPublicHelper.FindAndSetMSBuildVersion(); - } + PackageSessionPublicHelper.FindAndSetMSBuildVersion(); } } diff --git a/sources/assets/Stride.Core.Assets.Tests/Properties/AssemblyInfo.cs b/sources/assets/Stride.Core.Assets.Tests/Properties/AssemblyInfo.cs index 05b3499f94..a573b5040d 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Properties/AssemblyInfo.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Properties/AssemblyInfo.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Reflection; -using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/sources/assets/Stride.Core.Assets.Tests/PropertyKeySerializerTest.cs b/sources/assets/Stride.Core.Assets.Tests/PropertyKeySerializerTest.cs index e3b082218f..f427278981 100644 --- a/sources/assets/Stride.Core.Assets.Tests/PropertyKeySerializerTest.cs +++ b/sources/assets/Stride.Core.Assets.Tests/PropertyKeySerializerTest.cs @@ -1,73 +1,61 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Reflection; using System.Text; -using Stride.Core.Assets.Serializers; -using Stride.Core; using Stride.Core.Yaml; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets.Tests +namespace Stride.Core.Assets.Tests; + +/// +/// We are copying the PropertyKey serializer from Stride.Assets assembly to here in order +/// to validate our tests. +/// +[YamlSerializerFactory(YamlSerializerFactoryAttribute.Default)] +internal class PropertyKeySerializerTest : AssetScalarSerializerBase { - /// - /// We are copying the PropertyKey serializer from Stride.Assets assembly to here in order - /// to validate our tests. - /// - [YamlSerializerFactory(YamlSerializerFactoryAttribute.Default)] - internal class PropertyKeySerializerTest : AssetScalarSerializerBase + public override bool CanVisit(Type type) { - public override bool CanVisit(Type type) - { - return (typeof(PropertyKey).IsAssignableFrom(type)); // && (!typeof(ParameterKey).IsAssignableFrom(type)); - } - - public override object ConvertFrom(ref ObjectContext objectContext, Scalar fromScalar) - { - var lastDot = fromScalar.Value.LastIndexOf('.'); - if (lastDot == -1) - return null; - - var className = fromScalar.Value.Substring(0, lastDot); - - bool alias; - var containingClass = objectContext.SerializerContext.TypeFromTag("!" + className, out alias); // Readd initial '!' - if (containingClass == null) - { - throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to find class from tag [{0}]".ToFormat(className)); - } - - var propertyName = fromScalar.Value.Substring(lastDot + 1); - var propertyField = containingClass.GetField(propertyName, BindingFlags.Public | BindingFlags.Static); - if (propertyField == null) - { - throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to find property [{0}] in class [{1}]".ToFormat(propertyName, containingClass.Name)); - } + return typeof(PropertyKey).IsAssignableFrom(type); // && (!typeof(ParameterKey).IsAssignableFrom(type)); + } - return propertyField.GetValue(null); - } + public override object? ConvertFrom(ref ObjectContext objectContext, Scalar fromScalar) + { + var lastDot = fromScalar.Value.LastIndexOf('.'); + if (lastDot == -1) + return null; + + var className = fromScalar.Value[..lastDot]; + var containingClass = objectContext.SerializerContext.TypeFromTag("!" + className, out _) + ?? throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to find class from tag [{0}]".ToFormat(className)); // Readd initial '!' + + var propertyName = fromScalar.Value[(lastDot + 1)..]; + var propertyField = containingClass.GetField(propertyName, BindingFlags.Public | BindingFlags.Static) + ?? throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to find property [{0}] in class [{1}]".ToFormat(propertyName, containingClass.Name)); + return propertyField.GetValue(null); + } - protected override void WriteScalar(ref ObjectContext objectContext, ScalarEventInfo scalar) - { - // TODO: if ParameterKey is written to an object, It will not serialized a tag - scalar.Tag = null; - scalar.IsPlainImplicit = true; - base.WriteScalar(ref objectContext, scalar); - } + protected override void WriteScalar(ref ObjectContext objectContext, ScalarEventInfo scalar) + { + // TODO: if ParameterKey is written to an object, It will not serialized a tag + scalar.Tag = null; + scalar.IsPlainImplicit = true; + base.WriteScalar(ref objectContext, scalar); + } - public override string ConvertTo(ref ObjectContext objectContext) - { - var propertyKey = (PropertyKey)objectContext.Instance; + public override string ConvertTo(ref ObjectContext objectContext) + { + var propertyKey = (PropertyKey)objectContext.Instance; - var className = objectContext.SerializerContext.TagFromType(propertyKey.OwnerType); - var sb = new StringBuilder(className.Length + 1 + propertyKey.Name.Length); + var className = objectContext.SerializerContext.TagFromType(propertyKey.OwnerType); + var sb = new StringBuilder(className.Length + 1 + propertyKey.Name.Length); - sb.Append(className, 1, className.Length - 1); // Ignore initial '!' - sb.Append('.'); - sb.Append(propertyKey.Name); + sb.Append(className, 1, className.Length - 1); // Ignore initial '!' + sb.Append('.'); + sb.Append(propertyKey.Name); - return sb.ToString(); - } + return sb.ToString(); } } diff --git a/sources/assets/Stride.Core.Assets.Tests/Stride.Core.Assets.Tests.csproj b/sources/assets/Stride.Core.Assets.Tests/Stride.Core.Assets.Tests.csproj index 72e4b4cc20..05f71ac47c 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Stride.Core.Assets.Tests.csproj +++ b/sources/assets/Stride.Core.Assets.Tests/Stride.Core.Assets.Tests.csproj @@ -5,12 +5,16 @@ linux-x64;win-x64 enable latest + enable true --auto-module-initializer --serialization xunit.runner.stride.Program + + + diff --git a/sources/assets/Stride.Core.Assets.Tests/TestAbstractInstantiation.cs b/sources/assets/Stride.Core.Assets.Tests/TestAbstractInstantiation.cs index 69675e7d7c..41013a7235 100644 --- a/sources/assets/Stride.Core.Assets.Tests/TestAbstractInstantiation.cs +++ b/sources/assets/Stride.Core.Assets.Tests/TestAbstractInstantiation.cs @@ -1,123 +1,120 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Xunit; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Tests +namespace Stride.Core.Assets.Tests; + +public class TestAbstractInstantiation { - public class TestAbstractInstantiation + public abstract class AbstractClass + { + public object? A { get; set; } + + public abstract object? B { get; set; } + + public void MethodA() { } + + public abstract void MethodB(); + } + + public interface IInterface + { + object? A { get; set; } + + object? B { get; set; } + + void MethodA(); + + void MethodB(); + } + + public abstract class InterfaceImpl : IInterface + { + public object? A { get; set; } + + object? IInterface.B { get; set; } + + public void MethodA() { } + + void IInterface.MethodB() { } + } + + [Fact] + public void TestAbstractClassInstantiation() + { + var instance = AbstractObjectInstantiator.CreateConcreteInstance(typeof(AbstractClass)); + + // Check the type + Assert.NotEqual(typeof(AbstractClass), instance.GetType()); + // Check the base type + Assert.IsAssignableFrom(instance); + + // Check read access to 'A' property + { var a = ((AbstractClass)instance).A; } + // Check write access to 'A' property + { ((AbstractClass)instance).A = null; } + // Check read access to 'B' property + Assert.Throws(() => { var b = ((AbstractClass)instance).B; }); + // Check write access to 'B' property + Assert.Throws(() => { ((AbstractClass)instance).B = null; }); + + // Check call to 'MethodA' + { ((AbstractClass)instance).MethodA(); } + // Check call to 'MethodB' + Assert.Throws(() => { ((AbstractClass)instance).MethodB(); }); + } + + [Fact] + public void TestInterfaceInstantiation() + { + var instance = AbstractObjectInstantiator.CreateConcreteInstance(typeof(IInterface)); + + // Check the type + Assert.NotEqual(typeof(IInterface), instance.GetType()); + // Check the base type + Assert.IsAssignableFrom(instance); + + // Check read access to 'A' property + Assert.Throws(() => { var a = ((IInterface)instance).A; }); + // Check write access to 'A' property + Assert.Throws(() => { ((IInterface)instance).A = null; }); + // Check read access to 'B' property + Assert.Throws(() => { var b = ((IInterface)instance).B; }); + // Check write access to 'B' property + Assert.Throws(() => { ((IInterface)instance).B = null; }); + + // Check call to 'MethodA' + Assert.Throws(() => { ((IInterface)instance).MethodA(); }); + // Check call to 'MethodB' + Assert.Throws(() => { ((IInterface)instance).MethodB(); }); + } + + [Fact] + public void TestInterfaceImplementationInstantiation() { - public abstract class AbstractClass - { - public object A { get; set; } - - public abstract object B { get; set; } - - public void MethodA() { } - - public abstract void MethodB(); - } - - public interface IInterface - { - object A { get; set; } - - object B { get; set; } - - void MethodA(); - - void MethodB(); - } - - public abstract class InterfaceImpl : IInterface - { - public object A { get; set; } - - object IInterface.B { get; set; } - - public void MethodA() { } - - void IInterface.MethodB() { } - } - - [Fact] - public void TestAbstractClassInstantiation() - { - var instance = AbstractObjectInstantiator.CreateConcreteInstance(typeof(AbstractClass)); - - // Check the type - Assert.NotEqual(typeof(AbstractClass), instance.GetType()); - // Check the base type - Assert.IsAssignableFrom(instance); - - // Check read access to 'A' property - { var a = ((AbstractClass)instance).A; } - // Check write access to 'A' property - { ((AbstractClass)instance).A = null; } - // Check read access to 'B' property - Assert.Throws(() => { var b = ((AbstractClass)instance).B; }); - // Check write access to 'B' property - Assert.Throws(() => { ((AbstractClass)instance).B = null; }); - - // Check call to 'MethodA' - { ((AbstractClass)instance).MethodA(); } - // Check call to 'MethodB' - Assert.Throws(() => { ((AbstractClass)instance).MethodB(); }); - } - - [Fact] - public void TestInterfaceInstantiation() - { - var instance = AbstractObjectInstantiator.CreateConcreteInstance(typeof(IInterface)); - - // Check the type - Assert.NotEqual(typeof(IInterface), instance.GetType()); - // Check the base type - Assert.IsAssignableFrom(instance); - - // Check read access to 'A' property - Assert.Throws(() => { var a = ((IInterface)instance).A; }); - // Check write access to 'A' property - Assert.Throws(() => { ((IInterface)instance).A = null; }); - // Check read access to 'B' property - Assert.Throws(() => { var b = ((IInterface)instance).B; }); - // Check write access to 'B' property - Assert.Throws(() => { ((IInterface)instance).B = null; }); - - // Check call to 'MethodA' - Assert.Throws(() => { ((IInterface)instance).MethodA(); }); - // Check call to 'MethodB' - Assert.Throws(() => { ((IInterface)instance).MethodB(); }); - } - - [Fact] - public void TestInterfaceImplementationInstantiation() - { - var instance = AbstractObjectInstantiator.CreateConcreteInstance(typeof(InterfaceImpl)); - - // Check the type - Assert.NotEqual(typeof(InterfaceImpl), instance.GetType()); - Assert.NotEqual(typeof(IInterface), instance.GetType()); - // Check the base type - Assert.IsAssignableFrom(instance); - // Check the base interface - Assert.IsAssignableFrom(instance); - - // Check read access to 'A' property - { var a = ((InterfaceImpl)instance).A; } - // Check write access to 'A' property - { ((InterfaceImpl)instance).A = null; } - // Check read access to 'B' property - { var b = ((IInterface)instance).B; } - // Check write access to 'B' property - { ((IInterface)instance).B = null; } - - // Check call to 'MethodA' - { ((InterfaceImpl)instance).MethodA(); } - // Check call to 'MethodB' - { ((IInterface)instance).MethodB(); } - } + var instance = AbstractObjectInstantiator.CreateConcreteInstance(typeof(InterfaceImpl)); + + // Check the type + Assert.NotEqual(typeof(InterfaceImpl), instance.GetType()); + Assert.NotEqual(typeof(IInterface), instance.GetType()); + // Check the base type + Assert.IsAssignableFrom(instance); + // Check the base interface + Assert.IsAssignableFrom(instance); + + // Check read access to 'A' property + { var _ = ((InterfaceImpl)instance).A; } + // Check write access to 'A' property + { ((InterfaceImpl)instance).A = null; } + // Check read access to 'B' property + { var _ = ((IInterface)instance).B; } + // Check write access to 'B' property + { ((IInterface)instance).B = null; } + + // Check call to 'MethodA' + { ((InterfaceImpl)instance).MethodA(); } + // Check call to 'MethodB' + { ((IInterface)instance).MethodB(); } } } diff --git a/sources/assets/Stride.Core.Assets.Tests/TestAssetCloner.cs b/sources/assets/Stride.Core.Assets.Tests/TestAssetCloner.cs index 5129aaa7a8..c179f0a978 100644 --- a/sources/assets/Stride.Core.Assets.Tests/TestAssetCloner.cs +++ b/sources/assets/Stride.Core.Assets.Tests/TestAssetCloner.cs @@ -1,140 +1,131 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Linq; -using Xunit; -using Stride.Core; using Stride.Core.Reflection; using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Tests +namespace Stride.Core.Assets.Tests; + +[DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] +public class TestContent; + +[DataContract("TestAssetClonerContent")] +public class TestAssetClonerContent { - [DataContract, ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] - public class TestContent { } + public TestContent? Content; +} - [DataContract("TestAssetClonerContent")] - public class TestAssetClonerContent - { - public TestContent Content; - } +[DataContract("TestAssetClonerObject")] +public class TestAssetClonerObject +{ + public string? Name { get; set; } - [DataContract("TestAssetClonerObject")] - public class TestAssetClonerObject - { - public string Name { get; set; } + public TestAssetClonerObject? SubObject { get; set; } - public TestAssetClonerObject SubObject { get; set; } + public TestObjectReference? ObjectWithAttachedReference { get; set; } +} - public TestObjectReference ObjectWithAttachedReference { get; set; } - } +[DataContract("TestObjectWithCollection")] +public class TestObjectWithCollection +{ + public string? Name { get; set; } - [DataContract("TestObjectWithCollection")] - public class TestObjectWithCollection - { - public string Name { get; set; } + public List Items { get; set; } = []; +} - public List Items { get; set; } = new List(); - } +[DataContract] +[ContentSerializer(typeof(DataContentSerializer))] +[ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Asset")] +public class TestObjectReference; - [DataContract] - [ContentSerializer(typeof(DataContentSerializer))] - [ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Asset")] - public class TestObjectReference - { +public class TestAssetCloner +{ + [Fact] + public void TestAssetClonerContent() + { + var obj1 = new TestAssetClonerContent { Content = new TestContent() }; + var obj2 = AssetCloner.Clone(obj1, AssetClonerFlags.KeepReferences); + Assert.Equal(obj1.Content, obj2.Content); } - public class TestAssetCloner + [Fact] + public void TestHash() { - [Fact] - public void TestAssetClonerContent() + var obj1 = new TestAssetClonerObject { - var obj1 = new TestAssetClonerContent { Content = new TestContent() }; - var obj2 = AssetCloner.Clone(obj1, AssetClonerFlags.KeepReferences); - Assert.Equal(obj1.Content, obj2.Content); - } + Name = "Test1", + SubObject = new TestAssetClonerObject() { Name = "Test2" }, + ObjectWithAttachedReference = new TestObjectReference() + }; - [Fact] - public void TestHash() - { - var obj1 = new TestAssetClonerObject - { - Name = "Test1", - SubObject = new TestAssetClonerObject() { Name = "Test2" }, - ObjectWithAttachedReference = new TestObjectReference() - }; + // Create a fake reference to make sure that the attached reference will not be serialized + var attachedReference = AttachedReferenceManager.GetOrCreateAttachedReference(obj1.ObjectWithAttachedReference); + attachedReference.Url = "just_for_test"; + attachedReference.Id = AssetId.New(); - // Create a fake reference to make sure that the attached reference will not be serialized - var attachedReference = AttachedReferenceManager.GetOrCreateAttachedReference(obj1.ObjectWithAttachedReference); - attachedReference.Url = "just_for_test"; - attachedReference.Id = AssetId.New(); + var obj2 = AssetCloner.Clone(obj1); - var obj2 = AssetCloner.Clone(obj1); + var hash1 = AssetHash.Compute(obj1); + var hash2 = AssetHash.Compute(obj2); + Assert.Equal(hash1, hash2); - var hash1 = AssetHash.Compute(obj1); - var hash2 = AssetHash.Compute(obj2); - Assert.Equal(hash1, hash2); + obj1.Name = "Yes"; + var hash11 = AssetHash.Compute(obj1); + Assert.NotEqual(hash11, hash2); + obj1.Name = "Test1"; - obj1.Name = "Yes"; - var hash11 = AssetHash.Compute(obj1); - Assert.NotEqual(hash11, hash2); - obj1.Name = "Test1"; + var hash12 = AssetHash.Compute(obj1); + Assert.Equal(hash12, hash2); - var hash12 = AssetHash.Compute(obj1); - Assert.Equal(hash12, hash2); + obj2 = AssetCloner.Clone(obj1); - obj2 = AssetCloner.Clone(obj1); + var hash1WithOverrides = AssetHash.Compute(obj1); + var hash2WithOverrides = AssetHash.Compute(obj2); + Assert.Equal(hash1WithOverrides, hash2WithOverrides); + } - var hash1WithOverrides = AssetHash.Compute(obj1); - var hash2WithOverrides = AssetHash.Compute(obj2); - Assert.Equal(hash1WithOverrides, hash2WithOverrides); - } + [Fact] + public void TestCloneCollectionIds() + { + var obj = new TestObjectWithCollection { Name = "Test", Items = { "aaa", "bbb" } }; + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Items); + ids.Add(0, new ItemId()); + ids.Add(1, new ItemId()); + ids.MarkAsDeleted(new ItemId()); + var clone = AssetCloner.Clone(obj); + var idsExist = CollectionItemIdHelper.TryGetCollectionItemIds(clone.Items, out var cloneIds); + Assert.True(idsExist); + Assert.Equal(ids.KeyCount, cloneIds.KeyCount); + Assert.Equal(ids.DeletedCount, cloneIds.DeletedCount); + Assert.Equal(ids[0], cloneIds[0]); + Assert.Equal(ids[1], cloneIds[1]); + Assert.Equal(ids.DeletedItems.Single(), cloneIds.DeletedItems.Single()); + + clone = AssetCloner.Clone(obj, AssetClonerFlags.RemoveItemIds); + idsExist = CollectionItemIdHelper.TryGetCollectionItemIds(clone.Items, out _); + Assert.False(idsExist); + } - [Fact] - public void TestCloneCollectionIds() - { - var obj = new TestObjectWithCollection { Name = "Test", Items = { "aaa", "bbb" } }; - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Items); - ids.Add(0, new ItemId()); - ids.Add(1, new ItemId()); - ids.MarkAsDeleted(new ItemId()); - var clone = AssetCloner.Clone(obj); - CollectionItemIdentifiers cloneIds; - var idsExist = CollectionItemIdHelper.TryGetCollectionItemIds(clone.Items, out cloneIds); - Assert.True(idsExist); - Assert.Equal(ids.KeyCount, cloneIds.KeyCount); - Assert.Equal(ids.DeletedCount, cloneIds.DeletedCount); - Assert.Equal(ids[0], cloneIds[0]); - Assert.Equal(ids[1], cloneIds[1]); - Assert.Equal(ids.DeletedItems.Single(), cloneIds.DeletedItems.Single()); - - clone = AssetCloner.Clone(obj, AssetClonerFlags.RemoveItemIds); - idsExist = CollectionItemIdHelper.TryGetCollectionItemIds(clone.Items, out cloneIds); - Assert.False(idsExist); - } - - [Fact] - public void TestDiscardCollectionIds() - { - var obj = new TestObjectWithCollection { Name = "Test", Items = { "aaa", "bbb" } }; - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Items); - ids.Add(0, new ItemId()); - ids.Add(1, new ItemId()); - ids.MarkAsDeleted(new ItemId()); - var clone = AssetCloner.Clone(obj); - CollectionItemIdentifiers cloneIds; - var idsExist = CollectionItemIdHelper.TryGetCollectionItemIds(clone.Items, out cloneIds); - Assert.True(idsExist); - Assert.Equal(ids.KeyCount, cloneIds.KeyCount); - Assert.Equal(ids.DeletedCount, cloneIds.DeletedCount); - Assert.Equal(ids[0], cloneIds[0]); - Assert.Equal(ids[1], cloneIds[1]); - Assert.Equal(ids.DeletedItems.Single(), cloneIds.DeletedItems.Single()); - - clone = AssetCloner.Clone(obj, AssetClonerFlags.RemoveItemIds); - idsExist = CollectionItemIdHelper.TryGetCollectionItemIds(clone.Items, out cloneIds); - Assert.False(idsExist); - } + [Fact] + public void TestDiscardCollectionIds() + { + var obj = new TestObjectWithCollection { Name = "Test", Items = { "aaa", "bbb" } }; + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Items); + ids.Add(0, new ItemId()); + ids.Add(1, new ItemId()); + ids.MarkAsDeleted(new ItemId()); + var clone = AssetCloner.Clone(obj); + var idsExist = CollectionItemIdHelper.TryGetCollectionItemIds(clone.Items, out var cloneIds); + Assert.True(idsExist); + Assert.Equal(ids.KeyCount, cloneIds.KeyCount); + Assert.Equal(ids.DeletedCount, cloneIds.DeletedCount); + Assert.Equal(ids[0], cloneIds[0]); + Assert.Equal(ids[1], cloneIds[1]); + Assert.Equal(ids.DeletedItems.Single(), cloneIds.DeletedItems.Single()); + + clone = AssetCloner.Clone(obj, AssetClonerFlags.RemoveItemIds); + idsExist = CollectionItemIdHelper.TryGetCollectionItemIds(clone.Items, out _); + Assert.False(idsExist); } } diff --git a/sources/assets/Stride.Core.Assets.Tests/TestAssetCollision.cs b/sources/assets/Stride.Core.Assets.Tests/TestAssetCollision.cs index 48c11f30c0..6087a9b89b 100644 --- a/sources/assets/Stride.Core.Assets.Tests/TestAssetCollision.cs +++ b/sources/assets/Stride.Core.Assets.Tests/TestAssetCollision.cs @@ -1,150 +1,144 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; - -using Xunit; using Stride.Core.Assets.Analysis; using Stride.Core.IO; -namespace Stride.Core.Assets.Tests +namespace Stride.Core.Assets.Tests; + +public class TestAssetCollision { - public class TestAssetCollision + [Fact] + public void TestSimple() { - [Fact] - public void TestSimple() + var inputs = new List(); + + var asset = new AssetObjectTest(); + for (int i = 0; i < 10; i++) + { + var newAsset = new AssetObjectTest() { Id = asset.Id, Reference = new AssetReference(asset.Id, "bad")}; + inputs.Add(new AssetItem("0", newAsset)); + } + + // Tries to use existing ids + var outputs = new List(); + AssetCollision.Clean(null, inputs, outputs, new AssetResolver(), true, false); + + // Make sure we are generating exactly the same number of elements + Assert.Equal(inputs.Count, outputs.Count); + + // Make sure that asset has been cloned + Assert.NotEqual(inputs[0], outputs[0]); + + // First Id should not change + Assert.Equal(inputs[0].Id, outputs[0].Id); + + // Make sure that all ids are different + var ids = new HashSet(outputs.Select(item => item.Id)); + Assert.Equal(inputs.Count, ids.Count); + + // Make sure that all locations are different + var locations = new HashSet(outputs.Select(item => item.Location)); + Assert.Equal(inputs.Count, locations.Count); + + // Reference location "bad"should be fixed to "0" + foreach (var output in outputs) { - var inputs = new List(); - - var asset = new AssetObjectTest(); - for (int i = 0; i < 10; i++) - { - var newAsset = new AssetObjectTest() { Id = asset.Id, Reference = new AssetReference(asset.Id, "bad")}; - inputs.Add(new AssetItem("0", newAsset)); - } - - // Tries to use existing ids - var outputs = new List(); - AssetCollision.Clean(null, inputs, outputs, new AssetResolver(), true, false); - - // Make sure we are generating exactly the same number of elements - Assert.Equal(inputs.Count, outputs.Count); - - // Make sure that asset has been cloned - Assert.NotEqual(inputs[0], outputs[0]); - - // First Id should not change - Assert.Equal(inputs[0].Id, outputs[0].Id); - - // Make sure that all ids are different - var ids = new HashSet(outputs.Select(item => item.Id)); - Assert.Equal(inputs.Count, ids.Count); - - // Make sure that all locations are different - var locations = new HashSet(outputs.Select(item => item.Location)); - Assert.Equal(inputs.Count, locations.Count); - - // Reference location "bad"should be fixed to "0" - foreach (var output in outputs) - { - var assetRef = ((AssetObjectTest)output.Asset).Reference; - Assert.Equal("0", assetRef.Location); - Assert.Equal(outputs[0].Id, assetRef.Id); - } + var assetRef = ((AssetObjectTest)output.Asset).Reference; + Assert.Equal("0", assetRef.Location); + Assert.Equal(outputs[0].Id, assetRef.Id); } + } + + [Fact] + public void TestSimpleNewGuids() + { + var inputs = new List(); - [Fact] - public void TestSimpleNewGuids() + var asset = new AssetObjectTest(); + for (int i = 0; i < 10; i++) { - var inputs = new List(); - - var asset = new AssetObjectTest(); - for (int i = 0; i < 10; i++) - { - var newAsset = new AssetObjectTest() { Id = asset.Id, Reference = new AssetReference(asset.Id, "bad") }; - inputs.Add(new AssetItem("0", newAsset)); - } - - // Force to use new ids - var outputs = new List(); - AssetCollision.Clean(null, inputs, outputs, new AssetResolver() { AlwaysCreateNewId = true }, true, false); - - // Make sure we are generating exactly the same number of elements - Assert.Equal(inputs.Count, outputs.Count); - - // Make sure that asset has been cloned - Assert.NotEqual(inputs[0], outputs[0]); - - // First Id should not change - Assert.NotEqual(inputs[0].Id, outputs[0].Id); - - // Make sure that all ids are different - var ids = new HashSet(outputs.Select(item => item.Id)); - Assert.Equal(inputs.Count, ids.Count); - - // Make sure that all locations are different - var locations = new HashSet(outputs.Select(item => item.Location)); - Assert.Equal(inputs.Count, locations.Count); - - // Reference location "bad"should be fixed to "0" - foreach (var output in outputs) - { - var assetRef = ((AssetObjectTest)output.Asset).Reference; - Assert.Equal("0", assetRef.Location); - Assert.Equal(outputs[0].Id, assetRef.Id); - } + var newAsset = new AssetObjectTest() { Id = asset.Id, Reference = new AssetReference(asset.Id, "bad") }; + inputs.Add(new AssetItem("0", newAsset)); } - [Fact] - public void TestWithPackage() + // Force to use new ids + var outputs = new List(); + AssetCollision.Clean(null, inputs, outputs, new AssetResolver() { AlwaysCreateNewId = true }, true, false); + + // Make sure we are generating exactly the same number of elements + Assert.Equal(inputs.Count, outputs.Count); + + // Make sure that asset has been cloned + Assert.NotEqual(inputs[0], outputs[0]); + + // First Id should not change + Assert.NotEqual(inputs[0].Id, outputs[0].Id); + + // Make sure that all ids are different + var ids = new HashSet(outputs.Select(item => item.Id)); + Assert.Equal(inputs.Count, ids.Count); + + // Make sure that all locations are different + var locations = new HashSet(outputs.Select(item => item.Location)); + Assert.Equal(inputs.Count, locations.Count); + + // Reference location "bad"should be fixed to "0" + foreach (var output in outputs) { - var inputs = new List(); + var assetRef = ((AssetObjectTest)output.Asset).Reference; + Assert.Equal("0", assetRef.Location); + Assert.Equal(outputs[0].Id, assetRef.Id); + } + } - var asset = new AssetObjectTest(); + [Fact] + public void TestWithPackage() + { + var inputs = new List(); - var package = new Package(); - package.Assets.Add(new AssetItem("0", asset)); - var session = new PackageSession(package); + var asset = new AssetObjectTest(); - for (int i = 0; i < 10; i++) - { - var newAsset = new AssetObjectTest() { Id = asset.Id, Reference = new AssetReference(asset.Id, "bad") }; - inputs.Add(new AssetItem("0", newAsset)); - } + var package = new Package(); + package.Assets.Add(new AssetItem("0", asset)); + var session = new PackageSession(package); - // Tries to use existing ids - var outputs = new List(); - AssetCollision.Clean(null, inputs, outputs, AssetResolver.FromPackage(package), true, false); + for (int i = 0; i < 10; i++) + { + var newAsset = new AssetObjectTest() { Id = asset.Id, Reference = new AssetReference(asset.Id, "bad") }; + inputs.Add(new AssetItem("0", newAsset)); + } + + // Tries to use existing ids + var outputs = new List(); + AssetCollision.Clean(null, inputs, outputs, AssetResolver.FromPackage(package), true, false); - // Make sure we are generating exactly the same number of elements - Assert.Equal(inputs.Count, outputs.Count); + // Make sure we are generating exactly the same number of elements + Assert.Equal(inputs.Count, outputs.Count); - // Make sure that asset has been cloned - Assert.NotEqual(inputs[0], outputs[0]); + // Make sure that asset has been cloned + Assert.NotEqual(inputs[0], outputs[0]); - // First Id should not change - Assert.NotEqual(inputs[0].Id, outputs[0].Id); + // First Id should not change + Assert.NotEqual(inputs[0].Id, outputs[0].Id); - // Make sure that all ids are different - var ids = new HashSet(outputs.Select(item => item.Id)); - Assert.Equal(inputs.Count, ids.Count); + // Make sure that all ids are different + var ids = new HashSet(outputs.Select(item => item.Id)); + Assert.Equal(inputs.Count, ids.Count); - // Make sure that all locations are different - var locations = new HashSet(outputs.Select(item => item.Location)); - Assert.Equal(inputs.Count, locations.Count); + // Make sure that all locations are different + var locations = new HashSet(outputs.Select(item => item.Location)); + Assert.Equal(inputs.Count, locations.Count); - // Reference location "bad"should be fixed to "0_1" pointing to the first element - foreach (var output in outputs) - { - // Make sure of none of the locations are using "0" - Assert.NotEqual((UFile)"0", output.Location); + // Reference location "bad"should be fixed to "0_1" pointing to the first element + foreach (var output in outputs) + { + // Make sure of none of the locations are using "0" + Assert.NotEqual((UFile)"0", output.Location); - var assetRef = ((AssetObjectTest)output.Asset).Reference; - Assert.Equal("0 (2)", assetRef.Location); - Assert.Equal(outputs[0].Id, assetRef.Id); - } + var assetRef = ((AssetObjectTest)output.Asset).Reference; + Assert.Equal("0 (2)", assetRef.Location); + Assert.Equal(outputs[0].Id, assetRef.Id); } } } diff --git a/sources/assets/Stride.Core.Assets.Tests/TestAssetInheritance.cs b/sources/assets/Stride.Core.Assets.Tests/TestAssetInheritance.cs index 11cc6591ce..ddfb033c0b 100644 --- a/sources/assets/Stride.Core.Assets.Tests/TestAssetInheritance.cs +++ b/sources/assets/Stride.Core.Assets.Tests/TestAssetInheritance.cs @@ -1,54 +1,49 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Xunit; +namespace Stride.Core.Assets.Tests; -namespace Stride.Core.Assets.Tests +public class TestAssetInheritance { - public class TestAssetInheritance + [Fact] + public void TestWithParts() { - [Fact] - public void TestWithParts() - { - // Create a derivative asset with asset parts - var project = new Package(); - var assets = new List(); - var assetItems = new List(); + // Create a derivative asset with asset parts + var project = new Package(); + var assets = new List(); + var assetItems = new List(); - assets.Add(new TestAssetWithParts() + assets.Add(new TestAssetWithParts() + { + Parts = { - Parts = - { - new AssetPartTestItem(Guid.NewGuid()), - new AssetPartTestItem(Guid.NewGuid()) - } - }); - assetItems.Add(new AssetItem("asset-0", assets[0])); - project.Assets.Add(assetItems[0]); + new AssetPartTestItem(Guid.NewGuid()), + new AssetPartTestItem(Guid.NewGuid()) + } + }); + assetItems.Add(new AssetItem("asset-0", assets[0])); + project.Assets.Add(assetItems[0]); - var childAsset = (TestAssetWithParts)assetItems[0].CreateDerivedAsset(); + var childAsset = (TestAssetWithParts)assetItems[0].CreateDerivedAsset(); - // Check that child asset has a base - Assert.NotNull(childAsset.Archetype); + // Check that child asset has a base + Assert.NotNull(childAsset.Archetype); - // Check base asset - Assert.Equal(assets[0].Id, childAsset.Archetype.Id); + // Check base asset + Assert.Equal(assets[0].Id, childAsset.Archetype.Id); - // Check that base is correctly setup for the part - var i = 0; - var instanceId = Guid.Empty; - foreach (var part in childAsset.Parts) - { - Assert.Equal(assets[0].Id, part.Base.BasePartAsset.Id); - Assert.Equal(assets[0].Parts[i].Id, part.Base.BasePartId); - if (instanceId == Guid.Empty) - instanceId = part.Base.InstanceId; - Assert.NotEqual(Guid.Empty, instanceId); - Assert.Equal(instanceId, part.Base.InstanceId); - ++i; - } + // Check that base is correctly setup for the part + var i = 0; + var instanceId = Guid.Empty; + foreach (var part in childAsset.Parts) + { + Assert.Equal(assets[0].Id, part.Base.BasePartAsset.Id); + Assert.Equal(assets[0].Parts[i].Id, part.Base.BasePartId); + if (instanceId == Guid.Empty) + instanceId = part.Base.InstanceId; + Assert.NotEqual(Guid.Empty, instanceId); + Assert.Equal(instanceId, part.Base.InstanceId); + ++i; } } } diff --git a/sources/assets/Stride.Core.Assets.Tests/TestAssetReferenceAnalysis.cs b/sources/assets/Stride.Core.Assets.Tests/TestAssetReferenceAnalysis.cs index ccdab2d58c..4b5033b351 100644 --- a/sources/assets/Stride.Core.Assets.Tests/TestAssetReferenceAnalysis.cs +++ b/sources/assets/Stride.Core.Assets.Tests/TestAssetReferenceAnalysis.cs @@ -1,95 +1,93 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.IO; -using Xunit; + using Stride.Core.Assets.Analysis; using Stride.Core.IO; -namespace Stride.Core.Assets.Tests +namespace Stride.Core.Assets.Tests; + +public class TestAssetReferenceAnalysis : TestBase { - public class TestAssetReferenceAnalysis : TestBase + /// + /// Tests that updating an asset reference that is pointing to an invalid GUID but a valid location is updated with the new GUID. + /// + [Fact] + public void TestUpdateAssetUrl() { - /// - /// Tests that updating an asset reference that is pointing to an invalid GUID but a valid location is updated with the new GUID. - /// - [Fact] - public void TestUpdateAssetUrl() - { - var projectDir = new UFile(Path.Combine(Environment.CurrentDirectory, "testxk")); - - // Create a project with an asset reference a raw file - var project = new Package { FullPath = projectDir }; - var assetItem = new AssetItem("test", new AssetObjectTest() { Reference = new AssetReference(AssetId.Empty, "good/location")}); - project.Assets.Add(assetItem); - var goodAsset = new AssetObjectTest(); - project.Assets.Add(new AssetItem("good/location", goodAsset)); + var projectDir = new UFile(Path.Combine(Environment.CurrentDirectory, "testxk")); + + // Create a project with an asset reference a raw file + var project = new Package { FullPath = projectDir }; + var assetItem = new AssetItem("test", new AssetObjectTest() { Reference = new AssetReference(AssetId.Empty, "good/location")}); + project.Assets.Add(assetItem); + var goodAsset = new AssetObjectTest(); + project.Assets.Add(new AssetItem("good/location", goodAsset)); - // Add the project to the session to make sure analysis will run correctly - var session = new PackageSession(project); + // Add the project to the session to make sure analysis will run correctly + var session = new PackageSession(project); - // Create a session with this project - var analysis = new PackageAnalysis(project, - new PackageAnalysisParameters() - { - IsProcessingAssetReferences = true, - ConvertUPathTo = UPathType.Absolute, - IsProcessingUPaths = true - }); - var result = analysis.Run(); - Assert.False(result.HasErrors); - Assert.Single(result.Messages); - Assert.Contains("changed", result.Messages[0].ToString()); + // Create a session with this project + var analysis = new PackageAnalysis(project, + new PackageAnalysisParameters() + { + IsProcessingAssetReferences = true, + ConvertUPathTo = UPathType.Absolute, + IsProcessingUPaths = true + }); + var result = analysis.Run(); + Assert.False(result.HasErrors); + Assert.Single(result.Messages); + Assert.Contains("changed", result.Messages[0].ToString()); - var asset = (AssetObjectTest)assetItem.Asset; - Assert.Equal(goodAsset.Id, asset.Reference.Id); - Assert.Equal("good/location", asset.Reference.Location); - } + var asset = (AssetObjectTest)assetItem.Asset; + Assert.Equal(goodAsset.Id, asset.Reference.Id); + Assert.Equal("good/location", asset.Reference.Location); + GC.KeepAlive(session); + } - [Fact] - public void TestMoveAssetWithUFile() - { - var projectDir = new UFile(Path.Combine(Environment.CurrentDirectory, "testxk")); - var rawAssetPath = new UFile("../image.png"); - var assetPath = new UFile("sub1/sub2/test"); + [Fact] + public void TestMoveAssetWithUFile() + { + var projectDir = new UFile(Path.Combine(Environment.CurrentDirectory, "testxk")); + var rawAssetPath = new UFile("../image.png"); + var assetPath = new UFile("sub1/sub2/test"); - // Create a project with an asset reference a raw file - var project = new Package { FullPath = projectDir }; - project.AssetFolders.Clear(); - project.AssetFolders.Add(new AssetFolder(".")); - var asset = new AssetObjectTest() { RawAsset = new UFile(rawAssetPath) }; - var assetItem = new AssetItem(assetPath, asset); - project.Assets.Add(assetItem); + // Create a project with an asset reference a raw file + var project = new Package { FullPath = projectDir }; + project.AssetFolders.Clear(); + project.AssetFolders.Add(new AssetFolder(".")); + var asset = new AssetObjectTest() { RawAsset = new UFile(rawAssetPath) }; + var assetItem = new AssetItem(assetPath, asset); + project.Assets.Add(assetItem); - // Run an asset reference analysis on this project - var analysis = new PackageAnalysis(project, - new PackageAnalysisParameters() - { - ConvertUPathTo = UPathType.Absolute, - IsProcessingUPaths = true - }); - var result = analysis.Run(); - Assert.False(result.HasErrors); - Assert.Equal(UPath.Combine(project.RootDirectory, new UFile("sub1/image.png")), asset.RawAsset); + // Run an asset reference analysis on this project + var analysis = new PackageAnalysis(project, + new PackageAnalysisParameters() + { + ConvertUPathTo = UPathType.Absolute, + IsProcessingUPaths = true + }); + var result = analysis.Run(); + Assert.False(result.HasErrors); + Assert.Equal(UPath.Combine(project.RootDirectory, new UFile("sub1/image.png")), asset.RawAsset); - project.Assets.Remove(assetItem); - assetItem = new AssetItem("sub1/test", asset); - project.Assets.Add(assetItem); - result = analysis.Run(); - Assert.False(result.HasErrors); - Assert.Equal(UPath.Combine(project.RootDirectory, new UFile("sub1/image.png")), asset.RawAsset); + project.Assets.Remove(assetItem); + assetItem = new AssetItem("sub1/test", asset); + project.Assets.Add(assetItem); + result = analysis.Run(); + Assert.False(result.HasErrors); + Assert.Equal(UPath.Combine(project.RootDirectory, new UFile("sub1/image.png")), asset.RawAsset); - project.Assets.Remove(assetItem); - assetItem = new AssetItem("test", asset); - project.Assets.Add(assetItem); - result = analysis.Run(); - Assert.False(result.HasErrors); - Assert.Equal(UPath.Combine(project.RootDirectory, new UFile("sub1/image.png")), asset.RawAsset); + project.Assets.Remove(assetItem); + assetItem = new AssetItem("test", asset); + project.Assets.Add(assetItem); + result = analysis.Run(); + Assert.False(result.HasErrors); + Assert.Equal(UPath.Combine(project.RootDirectory, new UFile("sub1/image.png")), asset.RawAsset); - analysis.Parameters.ConvertUPathTo = UPathType.Relative; - result = analysis.Run(); - Assert.False(result.HasErrors); - Assert.Equal(new UFile("sub1/image.png"), asset.RawAsset); - } + analysis.Parameters.ConvertUPathTo = UPathType.Relative; + result = analysis.Run(); + Assert.False(result.HasErrors); + Assert.Equal(new UFile("sub1/image.png"), asset.RawAsset); } } diff --git a/sources/assets/Stride.Core.Assets.Tests/TestAssetReferenceCollection.cs b/sources/assets/Stride.Core.Assets.Tests/TestAssetReferenceCollection.cs index 2314d66fa8..f001762f86 100644 --- a/sources/assets/Stride.Core.Assets.Tests/TestAssetReferenceCollection.cs +++ b/sources/assets/Stride.Core.Assets.Tests/TestAssetReferenceCollection.cs @@ -1,60 +1,54 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Xunit; +namespace Stride.Core.Assets.Tests; -using Stride.Core.IO; - -namespace Stride.Core.Assets.Tests +public class TestAssetReferenceCollection { - public class TestAssetReferenceCollection + [Fact] + public void TestCollectionAddRemove() { - [Fact] - public void TestCollectionAddRemove() - { - // TODO test to be modified - /* + // TODO test to be modified + /* - var assets = new AssetItemCollection(); + var assets = new AssetItemCollection(); - // Check that null are not allowed - Assert.Throws(() => assets.Add(null)); + // Check that null are not allowed + Assert.Throws(() => assets.Add(null)); - // Check that null location are not allowed - Assert.Throws(() => assets.Add(new AssetItem(null, null))); + // Check that null location are not allowed + Assert.Throws(() => assets.Add(new AssetItem(null, null))); - // Test Find - var ref1 = new AssetItem("a/test.txt", null); - assets.Add(ref1); + // Test Find + var ref1 = new AssetItem("a/test.txt", null); + assets.Add(ref1); - var ref2 = new AssetItem("b/test.txt", null); - assets.Add(ref2); + var ref2 = new AssetItem("b/test.txt", null); + assets.Add(ref2); - var findRef1 = assets.Find("a/test"); - Assert.Equal(ref1, findRef1); + var findRef1 = assets.Find("a/test"); + Assert.Equal(ref1, findRef1); - // Test Remove - assets.Remove(ref1); - Assert.Equal(assets.Count, 1); + // Test Remove + assets.Remove(ref1); + Assert.Equal(assets.Count, 1); - // Change location after adding an asset reference - //ref1.Location = "a/test2.txt"; - assets.Add(ref1); - //ref1.Location = "a/test3.txt"; + // Change location after adding an asset reference + //ref1.Location = "a/test2.txt"; + assets.Add(ref1); + //ref1.Location = "a/test3.txt"; - findRef1 = assets.Find("a/test3"); - Assert.Equal(ref1, findRef1); - Assert.Equal(assets.Count, 2); + findRef1 = assets.Find("a/test3"); + Assert.Equal(ref1, findRef1); + Assert.Equal(assets.Count, 2); - // Add a reference with the same name - Assert.Throws(() => assets.Add(new AssetItem("a/test3.png", null))); - Assert.Equal(assets.Count, 2); + // Add a reference with the same name + Assert.Throws(() => assets.Add(new AssetItem("a/test3.png", null))); + Assert.Equal(assets.Count, 2); - // Test clear - assets.Clear(); - Assert.Equal(assets.Count, 0); - * */ - } + // Test clear + assets.Clear(); + Assert.Equal(assets.Count, 0); + * */ } } diff --git a/sources/assets/Stride.Core.Assets.Tests/TestAssetUpgrade.cs b/sources/assets/Stride.Core.Assets.Tests/TestAssetUpgrade.cs index d6100177fd..892ee874c4 100644 --- a/sources/assets/Stride.Core.Assets.Tests/TestAssetUpgrade.cs +++ b/sources/assets/Stride.Core.Assets.Tests/TestAssetUpgrade.cs @@ -1,164 +1,157 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; - -using Xunit; -using Stride.Core; using Stride.Core.Diagnostics; using Stride.Core.Mathematics; using Stride.Core.Yaml; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets.Tests +namespace Stride.Core.Assets.Tests; + +public class TestAssetUpgrade : TestBase { - public class TestAssetUpgrade : TestBase + [DataContract("MyUpgradedAsset")] + [AssetDescription(".sdobj")] + [AssetFormatVersion("TestPackage", 5, 1)] + [AssetUpgrader("TestPackage", 1, 2, typeof(AssetUpgrader1))] + [AssetUpgrader("TestPackage", 2, 4, typeof(AssetUpgrader2))] + [AssetUpgrader("TestPackage", 4, 5, typeof(AssetUpgrader3))] + public class MyUpgradedAsset : Asset { - [DataContract("MyUpgradedAsset")] - [AssetDescription(".sdobj")] - [AssetFormatVersion("TestPackage", 5, 1)] - [AssetUpgrader("TestPackage", 1, 2, typeof(AssetUpgrader1))] - [AssetUpgrader("TestPackage", 2, 4, typeof(AssetUpgrader2))] - [AssetUpgrader("TestPackage", 4, 5, typeof(AssetUpgrader3))] - public class MyUpgradedAsset : Asset + public MyUpgradedAsset(int version) { - public MyUpgradedAsset(int version) - { - SerializedVersion["TestPackage"] = PackageVersion.Parse("0.0." + version); - } + SerializedVersion["TestPackage"] = PackageVersion.Parse("0.0." + version); + } - public MyUpgradedAsset() - { - } + public MyUpgradedAsset() + { + } - public Vector3 Vector { get; set; } - public List Test1 { get; set; } - public List Test2 { get; set; } - public List Test3 { get; set; } - public List Test4 { get; set; } - public List Test5 { get; set; } + public Vector3 Vector { get; set; } + public List Test1 { get; set; } + public List Test2 { get; set; } + public List Test3 { get; set; } + public List Test4 { get; set; } + public List Test5 { get; set; } - class AssetUpgrader1 : IAssetUpgrader + class AssetUpgrader1 : IAssetUpgrader + { + public void Upgrade(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, YamlMappingNode yamlAssetNode, PackageLoadingAssetFile assetFile) { - public void Upgrade(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, YamlMappingNode yamlAssetNode, PackageLoadingAssetFile assetFile) - { - dynamic asset = new DynamicYamlMapping(yamlAssetNode); + dynamic asset = new DynamicYamlMapping(yamlAssetNode); - // Note: seems little bit strange, but original test was not using targetVersion... - var serializedVersion = AssetRegistry.GetCurrentFormatVersions(typeof(MyUpgradedAsset))[dependencyName]; - AssetUpgraderBase.SetSerializableVersion(asset, dependencyName, serializedVersion); + // Note: seems little bit strange, but original test was not using targetVersion... + var serializedVersion = AssetRegistry.GetCurrentFormatVersions(typeof(MyUpgradedAsset))[dependencyName]; + AssetUpgraderBase.SetSerializableVersion(asset, dependencyName, serializedVersion); - // Move Test1 to Test2 - asset.Test2 = asset.Test1; - asset.Test1 = DynamicYamlEmpty.Default; - } + // Move Test1 to Test2 + asset.Test2 = asset.Test1; + asset.Test1 = DynamicYamlEmpty.Default; } + } - class AssetUpgrader2 : IAssetUpgrader + class AssetUpgrader2 : IAssetUpgrader + { + public void Upgrade(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, YamlMappingNode yamlAssetNode, PackageLoadingAssetFile assetFile) { - public void Upgrade(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, YamlMappingNode yamlAssetNode, PackageLoadingAssetFile assetFile) + dynamic asset = new DynamicYamlMapping(yamlAssetNode); + + AssetUpgraderBase.SetSerializableVersion(asset, dependencyName, targetVersion); + + // Move Test2 to Test4 + if (currentVersion == PackageVersion.Parse("0.0.2")) { - dynamic asset = new DynamicYamlMapping(yamlAssetNode); - - AssetUpgraderBase.SetSerializableVersion(asset, dependencyName, targetVersion); - - // Move Test2 to Test4 - if (currentVersion == PackageVersion.Parse("0.0.2")) - { - asset.Test4 = asset.Test2; - asset.Test2 = DynamicYamlEmpty.Default; - } - // Move Test3 to Test4 - else if (currentVersion == PackageVersion.Parse("0.0.3")) - { - asset.Test4 = asset.Test3; - asset.Test3 = DynamicYamlEmpty.Default; - } + asset.Test4 = asset.Test2; + asset.Test2 = DynamicYamlEmpty.Default; } - } - - class AssetUpgrader3 : IAssetUpgrader - { - public void Upgrade(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, YamlMappingNode yamlAssetNode, PackageLoadingAssetFile assetFile) + // Move Test3 to Test4 + else if (currentVersion == PackageVersion.Parse("0.0.3")) { - dynamic asset = new DynamicYamlMapping(yamlAssetNode); - - AssetUpgraderBase.SetSerializableVersion(asset, dependencyName, targetVersion); - - // Move Test4 to Test5 - asset.Test5 = asset.Test4; - asset.Test4 = DynamicYamlEmpty.Default; + asset.Test4 = asset.Test3; + asset.Test3 = DynamicYamlEmpty.Default; } } } - [Fact] - public void Version1() + class AssetUpgrader3 : IAssetUpgrader { - var asset = new MyUpgradedAsset(1) { Vector = new Vector3(12.0f, 15.0f, 17.0f), Test1 = new List { 32, 64 } }; - TestUpgrade(asset, true); - } + public void Upgrade(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, YamlMappingNode yamlAssetNode, PackageLoadingAssetFile assetFile) + { + dynamic asset = new DynamicYamlMapping(yamlAssetNode); - [Fact] - public void Version2() - { - var asset = new MyUpgradedAsset(2) { Vector = new Vector3(12.0f, 15.0f, 17.0f), Test2 = new List { 32, 64 } }; - TestUpgrade(asset, true); - } + AssetUpgraderBase.SetSerializableVersion(asset, dependencyName, targetVersion); - [Fact] - public void Version3() - { - var asset = new MyUpgradedAsset(3) { Vector = new Vector3(12.0f, 15.0f, 17.0f), Test3 = new List { 32, 64 } }; - TestUpgrade(asset, true); + // Move Test4 to Test5 + asset.Test5 = asset.Test4; + asset.Test4 = DynamicYamlEmpty.Default; + } } + } - [Fact] - public void Version4() - { - var asset = new MyUpgradedAsset(4) { Vector = new Vector3(12.0f, 15.0f, 17.0f), Test4 = new List { 32, 64 } }; - TestUpgrade(asset, true); - } + [Fact] + public void Version1() + { + var asset = new MyUpgradedAsset(1) { Vector = new Vector3(12.0f, 15.0f, 17.0f), Test1 = [32, 64] }; + TestUpgrade(asset, true); + } - [Fact] - public void Version5() - { - var asset = new MyUpgradedAsset(5) { Vector = new Vector3(12.0f, 15.0f, 17.0f), Test5 = new List { 32, 64 } }; - TestUpgrade(asset, false); - } + [Fact] + public void Version2() + { + var asset = new MyUpgradedAsset(2) { Vector = new Vector3(12.0f, 15.0f, 17.0f), Test2 = [32, 64] }; + TestUpgrade(asset, true); + } - private void TestUpgrade(MyUpgradedAsset asset, bool needMigration) - { - var loadingFilePath = new PackageLoadingAssetFile(Path.Combine(DirectoryTestBase, "TestUpgrade\\Asset1.sdobj"), DirectoryTestBase); - var outputFilePath = loadingFilePath.FilePath.FullPath; - AssetFileSerializer.Save(outputFilePath, asset, null); + [Fact] + public void Version3() + { + var asset = new MyUpgradedAsset(3) { Vector = new Vector3(12.0f, 15.0f, 17.0f), Test3 = [32, 64] }; + TestUpgrade(asset, true); + } - var logger = new LoggerResult(); - var context = new AssetMigrationContext(null, loadingFilePath.ToReference(), loadingFilePath.FilePath.ToOSPath(), logger); - Assert.Equal(AssetMigration.MigrateAssetIfNeeded(context, loadingFilePath, "TestPackage"), needMigration); + [Fact] + public void Version4() + { + var asset = new MyUpgradedAsset(4) { Vector = new Vector3(12.0f, 15.0f, 17.0f), Test4 = [32, 64] }; + TestUpgrade(asset, true); + } - if (needMigration) - { - using (var fileStream = new FileStream(outputFilePath, FileMode.Truncate)) - fileStream.Write(loadingFilePath.AssetContent, 0, loadingFilePath.AssetContent.Length); - } + [Fact] + public void Version5() + { + var asset = new MyUpgradedAsset(5) { Vector = new Vector3(12.0f, 15.0f, 17.0f), Test5 = [32, 64] }; + TestUpgrade(asset, false); + } - Console.WriteLine(File.ReadAllText(outputFilePath).Trim()); + private void TestUpgrade(MyUpgradedAsset asset, bool needMigration) + { + var loadingFilePath = new PackageLoadingAssetFile(Path.Combine(DirectoryTestBase, "TestUpgrade\\Asset1.sdobj"), DirectoryTestBase); + var outputFilePath = loadingFilePath.FilePath.FullPath; + AssetFileSerializer.Save(outputFilePath, asset, null); - var upgradedAsset = AssetFileSerializer.Load(outputFilePath).Asset; - AssertUpgrade(upgradedAsset); - } + var logger = new LoggerResult(); + var context = new AssetMigrationContext(null, loadingFilePath.ToReference(), loadingFilePath.FilePath.ToOSPath(), logger); + Assert.Equal(AssetMigration.MigrateAssetIfNeeded(context, loadingFilePath, "TestPackage"), needMigration); - private static void AssertUpgrade(MyUpgradedAsset asset) + if (needMigration) { - Assert.Equal(new PackageVersion("0.0.5"), asset.SerializedVersion["TestPackage"]); - Assert.Null(asset.Test1); - Assert.Null(asset.Test2); - Assert.Null(asset.Test3); - Assert.Null(asset.Test4); - Assert.NotNull(asset.Test5); + using var fileStream = new FileStream(outputFilePath, FileMode.Truncate); + fileStream.Write(loadingFilePath.AssetContent, 0, loadingFilePath.AssetContent.Length); } + + Console.WriteLine(File.ReadAllText(outputFilePath).Trim()); + + var upgradedAsset = AssetFileSerializer.Load(outputFilePath).Asset; + AssertUpgrade(upgradedAsset); + } + + private static void AssertUpgrade(MyUpgradedAsset asset) + { + Assert.Equal(new PackageVersion("0.0.5"), asset.SerializedVersion?["TestPackage"]); + Assert.Null(asset.Test1); + Assert.Null(asset.Test2); + Assert.Null(asset.Test3); + Assert.Null(asset.Test4); + Assert.NotNull(asset.Test5); } } diff --git a/sources/assets/Stride.Core.Assets.Tests/Yaml/TestCollectionIdsSerialization.cs b/sources/assets/Stride.Core.Assets.Tests/Yaml/TestCollectionIdsSerialization.cs index 1a48f32338..f46a2c69f6 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Yaml/TestCollectionIdsSerialization.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Yaml/TestCollectionIdsSerialization.cs @@ -1,664 +1,674 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Xunit; + using Stride.Core.Assets.Tests.Helpers; using Stride.Core.Annotations; using Stride.Core.Reflection; using Stride.Core.Yaml; -namespace Stride.Core.Assets.Tests.Yaml +namespace Stride.Core.Assets.Tests.Yaml; + +public class TestCollectionIdsSerialization { - public class TestCollectionIdsSerialization + public class ContainerCollection { - public class ContainerCollection + public ContainerCollection() { } + public ContainerCollection(string name) { - public ContainerCollection() { } - public ContainerCollection(string name) - { - Name = name; - } - public string Name { get; set; } - public List Strings { get; set; } = new List(); - public List Objects { get; set; } = new List(); + Name = name; } + public string? Name { get; set; } + public List Strings { get; set; } = []; + public List Objects { get; set; } = []; + } - public class ContainerDictionary + public class ContainerDictionary + { + public ContainerDictionary() { } + public ContainerDictionary(string name) { - public ContainerDictionary() { } - public ContainerDictionary(string name) - { - Name = name; - } - public string Name { get; set; } - public Dictionary Strings { get; set; } = new Dictionary(); - public Dictionary Objects { get; set; } = new Dictionary(); + Name = name; } + public string? Name { get; set; } + public Dictionary Strings { get; set; } = []; + public Dictionary Objects { get; set; } = []; + } - public class ContainerNonIdentifiableCollection + public class ContainerNonIdentifiableCollection + { + public ContainerNonIdentifiableCollection() { } + public ContainerNonIdentifiableCollection(string name) { - public ContainerNonIdentifiableCollection() { } - public ContainerNonIdentifiableCollection(string name) - { - Name = name; - } - public string Name { get; set; } - public List Objects { get; set; } = new List(); - - [NonIdentifiableCollectionItems] - public List NonIdentifiableObjects { get; set; } = new List(); + Name = name; } + public string? Name { get; set; } + public List Objects { get; set; } = []; + + [NonIdentifiableCollectionItems] + public List NonIdentifiableObjects { get; set; } = []; + } - public class ContainerNonIdentifiableDictionary + public class ContainerNonIdentifiableDictionary + { + public ContainerNonIdentifiableDictionary() { } + public ContainerNonIdentifiableDictionary(string name) { - public ContainerNonIdentifiableDictionary() { } - public ContainerNonIdentifiableDictionary(string name) - { - Name = name; - } - public string Name { get; set; } - public Dictionary Objects { get; set; } = new Dictionary(); - - [NonIdentifiableCollectionItems] - public Dictionary NonIdentifiableObjects { get; set; } = new Dictionary(); + Name = name; } + public string? Name { get; set; } + public Dictionary Objects { get; set; } = []; + [NonIdentifiableCollectionItems] + public Dictionary NonIdentifiableObjects { get; set; } = []; + } - private const string YamlCollection = @"!Stride.Core.Assets.Tests.Yaml.TestCollectionIdsSerialization+ContainerCollection,Stride.Core.Assets.Tests -Name: Root -Strings: - 02000000020000000200000002000000: aaa - 01000000010000000100000001000000: bbb -Objects: - 03000000030000000300000003000000: - Name: obj1 - Strings: {} - Objects: {} - 04000000040000000400000004000000: - Name: obj2 - Strings: {} - Objects: {} -"; - - private const string YamlCollectionNotIdentifiable = @"!Stride.Core.Assets.Tests.Yaml.TestCollectionIdsSerialization+ContainerNonIdentifiableCollection,Stride.Core.Assets.Tests -Name: Root -Objects: - 02000000020000000200000002000000: - Name: aaa - Strings: - 05000000050000000500000005000000: bbb - 06000000060000000600000006000000: ccc - Objects: {} - 01000000010000000100000001000000: - Name: ddd - Strings: - 07000000070000000700000007000000: eee - 08000000080000000800000008000000: fff - Objects: {} -NonIdentifiableObjects: - - Name: ggg + + private const string YamlCollection = + """ + !Stride.Core.Assets.Tests.Yaml.TestCollectionIdsSerialization+ContainerCollection,Stride.Core.Assets.Tests + Name: Root Strings: - 09000000090000000900000009000000: hhh - 0a0000000a0000000a0000000a000000: iii - Objects: {} - - Name: jjj + 02000000020000000200000002000000: aaa + 01000000010000000100000001000000: bbb + Objects: + 03000000030000000300000003000000: + Name: obj1 + Strings: {} + Objects: {} + 04000000040000000400000004000000: + Name: obj2 + Strings: {} + Objects: {} + + """; + + private const string YamlCollectionNotIdentifiable = + """ + !Stride.Core.Assets.Tests.Yaml.TestCollectionIdsSerialization+ContainerNonIdentifiableCollection,Stride.Core.Assets.Tests + Name: Root + Objects: + 02000000020000000200000002000000: + Name: aaa + Strings: + 05000000050000000500000005000000: bbb + 06000000060000000600000006000000: ccc + Objects: {} + 01000000010000000100000001000000: + Name: ddd + Strings: + 07000000070000000700000007000000: eee + 08000000080000000800000008000000: fff + Objects: {} + NonIdentifiableObjects: + - Name: ggg + Strings: + 09000000090000000900000009000000: hhh + 0a0000000a0000000a0000000a000000: iii + Objects: {} + - Name: jjj + Strings: + 0b0000000b0000000b0000000b000000: kkk + 0c0000000c0000000c0000000c000000: lll + Objects: {} + + """; + + private const string YamlDictionary = + """ + !Stride.Core.Assets.Tests.Yaml.TestCollectionIdsSerialization+ContainerDictionary,Stride.Core.Assets.Tests + Name: Root Strings: - 0b0000000b0000000b0000000b000000: kkk - 0c0000000c0000000c0000000c000000: lll - Objects: {} -"; - - private const string YamlDictionary = @"!Stride.Core.Assets.Tests.Yaml.TestCollectionIdsSerialization+ContainerDictionary,Stride.Core.Assets.Tests -Name: Root -Strings: - 02000000020000000200000002000000~000000c8-00c8-0000-c800-0000c8000000: aaa - 01000000010000000100000001000000~00000064-0064-0000-6400-000064000000: bbb -Objects: - 03000000030000000300000003000000~key3: - Name: obj1 - Strings: {} - Objects: {} - 04000000040000000400000004000000~key4: - Name: obj2 - Strings: {} - Objects: {} -"; - - private const string YamlDictionaryNonIdentifiable = @"!Stride.Core.Assets.Tests.Yaml.TestCollectionIdsSerialization+ContainerNonIdentifiableDictionary,Stride.Core.Assets.Tests -Name: Root -Objects: - 02000000020000000200000002000000~AAA: - Name: aaa + 02000000020000000200000002000000~000000c8-00c8-0000-c800-0000c8000000: aaa + 01000000010000000100000001000000~00000064-0064-0000-6400-000064000000: bbb + Objects: + 03000000030000000300000003000000~key3: + Name: obj1 + Strings: {} + Objects: {} + 04000000040000000400000004000000~key4: + Name: obj2 + Strings: {} + Objects: {} + + """; + + private const string YamlDictionaryNonIdentifiable = + """ + !Stride.Core.Assets.Tests.Yaml.TestCollectionIdsSerialization+ContainerNonIdentifiableDictionary,Stride.Core.Assets.Tests + Name: Root + Objects: + 02000000020000000200000002000000~AAA: + Name: aaa + Strings: + 05000000050000000500000005000000: bbb + 06000000060000000600000006000000: ccc + Objects: {} + 01000000010000000100000001000000~BBB: + Name: ddd + Strings: + 07000000070000000700000007000000: eee + 08000000080000000800000008000000: fff + Objects: {} + NonIdentifiableObjects: + CCC: + Name: ggg + Strings: + 09000000090000000900000009000000: hhh + 0a0000000a0000000a0000000a000000: iii + Objects: {} + DDD: + Name: jjj + Strings: + 0b0000000b0000000b0000000b000000: kkk + 0c0000000c0000000c0000000c000000: lll + Objects: {} + + """; + + private const string YamlCollectionWithDeleted = + """ + !Stride.Core.Assets.Tests.Yaml.TestCollectionIdsSerialization+ContainerCollection,Stride.Core.Assets.Tests + Name: Root Strings: + 08000000080000000800000008000000: aaa 05000000050000000500000005000000: bbb - 06000000060000000600000006000000: ccc - Objects: {} - 01000000010000000100000001000000~BBB: - Name: ddd - Strings: - 07000000070000000700000007000000: eee - 08000000080000000800000008000000: fff - Objects: {} -NonIdentifiableObjects: - CCC: - Name: ggg + 01000000010000000100000001000000: ~(Deleted) + 03000000030000000300000003000000: ~(Deleted) + Objects: + 03000000030000000300000003000000: + Name: obj1 + Strings: {} + Objects: {} + 04000000040000000400000004000000: + Name: obj2 + Strings: {} + Objects: {} + 01000000010000000100000001000000: ~(Deleted) + 06000000060000000600000006000000: ~(Deleted) + + """; + + private const string YamlDictionaryWithDeleted = + """ + !Stride.Core.Assets.Tests.Yaml.TestCollectionIdsSerialization+ContainerDictionary,Stride.Core.Assets.Tests + Name: Root Strings: - 09000000090000000900000009000000: hhh - 0a0000000a0000000a0000000a000000: iii - Objects: {} - DDD: - Name: jjj - Strings: - 0b0000000b0000000b0000000b000000: kkk - 0c0000000c0000000c0000000c000000: lll - Objects: {} -"; - - private const string YamlCollectionWithDeleted = @"!Stride.Core.Assets.Tests.Yaml.TestCollectionIdsSerialization+ContainerCollection,Stride.Core.Assets.Tests -Name: Root -Strings: - 08000000080000000800000008000000: aaa - 05000000050000000500000005000000: bbb - 01000000010000000100000001000000: ~(Deleted) - 03000000030000000300000003000000: ~(Deleted) -Objects: - 03000000030000000300000003000000: - Name: obj1 - Strings: {} - Objects: {} - 04000000040000000400000004000000: - Name: obj2 - Strings: {} - Objects: {} - 01000000010000000100000001000000: ~(Deleted) - 06000000060000000600000006000000: ~(Deleted) -"; - - private const string YamlDictionaryWithDeleted = @"!Stride.Core.Assets.Tests.Yaml.TestCollectionIdsSerialization+ContainerDictionary,Stride.Core.Assets.Tests -Name: Root -Strings: - 08000000080000000800000008000000~000000c8-00c8-0000-c800-0000c8000000: aaa - 05000000050000000500000005000000~00000064-0064-0000-6400-000064000000: bbb - 01000000010000000100000001000000~: ~(Deleted) - 03000000030000000300000003000000~: ~(Deleted) -Objects: - 03000000030000000300000003000000~key3: - Name: obj1 - Strings: {} - Objects: {} - 04000000040000000400000004000000~key4: - Name: obj2 - Strings: {} - Objects: {} - 01000000010000000100000001000000~: ~(Deleted) - 06000000060000000600000006000000~: ~(Deleted) -"; - - private static string SerializeAsString(object instance) - { - using (var stream = new MemoryStream()) - { - AssetYamlSerializer.Default.Serialize(stream, instance); - stream.Flush(); - stream.Position = 0; - return new StreamReader(stream).ReadToEnd(); - } - } + 08000000080000000800000008000000~000000c8-00c8-0000-c800-0000c8000000: aaa + 05000000050000000500000005000000~00000064-0064-0000-6400-000064000000: bbb + 01000000010000000100000001000000~: ~(Deleted) + 03000000030000000300000003000000~: ~(Deleted) + Objects: + 03000000030000000300000003000000~key3: + Name: obj1 + Strings: {} + Objects: {} + 04000000040000000400000004000000~key4: + Name: obj2 + Strings: {} + Objects: {} + 01000000010000000100000001000000~: ~(Deleted) + 06000000060000000600000006000000~: ~(Deleted) + + """; + + private static string SerializeAsString(object instance) + { + using var stream = new MemoryStream(); + AssetYamlSerializer.Default.Serialize(stream, instance); + stream.Flush(); + stream.Position = 0; + return new StreamReader(stream).ReadToEnd(); + } - [Fact] - public void TestCollectionSerialization() + [Fact] + public void TestCollectionSerialization() + { + ShadowObject.Enable = true; + var obj = new ContainerCollection("Root") { - ShadowObject.Enable = true; - var obj = new ContainerCollection("Root") - { - Strings = { "aaa", "bbb" }, - Objects = { new ContainerCollection("obj1"), new ContainerCollection("obj2") } - }; - - var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); - stringIds[0] = IdentifierGenerator.Get(2); - stringIds[1] = IdentifierGenerator.Get(1); - var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); - objectIds[0] = IdentifierGenerator.Get(3); - objectIds[1] = IdentifierGenerator.Get(4); - var yaml = SerializeAsString(obj); - Assert.Equal(YamlCollection, yaml); - } + Strings = { "aaa", "bbb" }, + Objects = { new ContainerCollection("obj1"), new ContainerCollection("obj2") } + }; + + var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); + stringIds[0] = IdentifierGenerator.Get(2); + stringIds[1] = IdentifierGenerator.Get(1); + var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); + objectIds[0] = IdentifierGenerator.Get(3); + objectIds[1] = IdentifierGenerator.Get(4); + var yaml = SerializeAsString(obj); + Assert.Equal(YamlCollection, yaml); + } - [Fact] - public void TestCollectionDeserialization() - { - ShadowObject.Enable = true; - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(YamlCollection); - writer.Flush(); - stream.Position = 0; - var instance = AssetYamlSerializer.Default.Deserialize(stream); - Assert.NotNull(instance); - Assert.Equal(typeof(ContainerCollection), instance.GetType()); - var obj = (ContainerCollection)instance; - Assert.Equal("Root", obj.Name); - Assert.Equal(2, obj.Strings.Count); - Assert.Equal("aaa", obj.Strings[0]); - Assert.Equal("bbb", obj.Strings[1]); - Assert.Equal(2, obj.Objects.Count); - Assert.Equal("obj1", obj.Objects[0].Name); - Assert.Equal("obj2", obj.Objects[1].Name); - var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); - Assert.Equal(IdentifierGenerator.Get(2), stringIds[0]); - Assert.Equal(IdentifierGenerator.Get(1), stringIds[1]); - var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); - Assert.Equal(IdentifierGenerator.Get(3), objectIds[0]); - Assert.Equal(IdentifierGenerator.Get(4), objectIds[1]); - } + [Fact] + public void TestCollectionDeserialization() + { + ShadowObject.Enable = true; + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(YamlCollection); + writer.Flush(); + stream.Position = 0; + var instance = AssetYamlSerializer.Default.Deserialize(stream); + Assert.NotNull(instance); + Assert.Equal(typeof(ContainerCollection), instance.GetType()); + var obj = (ContainerCollection)instance; + Assert.Equal("Root", obj.Name); + Assert.Equal(2, obj.Strings.Count); + Assert.Equal("aaa", obj.Strings[0]); + Assert.Equal("bbb", obj.Strings[1]); + Assert.Equal(2, obj.Objects.Count); + Assert.Equal("obj1", obj.Objects[0].Name); + Assert.Equal("obj2", obj.Objects[1].Name); + var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); + Assert.Equal(IdentifierGenerator.Get(2), stringIds[0]); + Assert.Equal(IdentifierGenerator.Get(1), stringIds[1]); + var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); + Assert.Equal(IdentifierGenerator.Get(3), objectIds[0]); + Assert.Equal(IdentifierGenerator.Get(4), objectIds[1]); + } - [Fact] - public void TestDictionarySerialization() + [Fact] + public void TestDictionarySerialization() + { + ShadowObject.Enable = true; + var obj = new ContainerDictionary("Root") { - ShadowObject.Enable = true; - var obj = new ContainerDictionary("Root") - { - Strings = { { GuidGenerator.Get(200), "aaa" }, { GuidGenerator.Get(100), "bbb" } }, - Objects = { { "key3", new ContainerCollection("obj1") }, { "key4", new ContainerCollection("obj2") } }, - }; - - var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); - stringIds[GuidGenerator.Get(200)] = IdentifierGenerator.Get(2); - stringIds[GuidGenerator.Get(100)] = IdentifierGenerator.Get(1); - var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); - objectIds["key3"] = IdentifierGenerator.Get(3); - objectIds["key4"] = IdentifierGenerator.Get(4); - var yaml = SerializeAsString(obj); - Assert.Equal(YamlDictionary, yaml); - } + Strings = { { GuidGenerator.Get(200), "aaa" }, { GuidGenerator.Get(100), "bbb" } }, + Objects = { { "key3", new ContainerCollection("obj1") }, { "key4", new ContainerCollection("obj2") } }, + }; + + var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); + stringIds[GuidGenerator.Get(200)] = IdentifierGenerator.Get(2); + stringIds[GuidGenerator.Get(100)] = IdentifierGenerator.Get(1); + var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); + objectIds["key3"] = IdentifierGenerator.Get(3); + objectIds["key4"] = IdentifierGenerator.Get(4); + var yaml = SerializeAsString(obj); + Assert.Equal(YamlDictionary, yaml); + } - [Fact] - public void TestDictionaryDeserialization() - { - ShadowObject.Enable = true; - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(YamlDictionary); - writer.Flush(); - stream.Position = 0; - var instance = AssetYamlSerializer.Default.Deserialize(stream); - Assert.NotNull(instance); - Assert.Equal(typeof(ContainerDictionary), instance.GetType()); - var obj = (ContainerDictionary)instance; - Assert.Equal("Root", obj.Name); - Assert.Equal(2, obj.Strings.Count); - Assert.Equal("aaa", obj.Strings[GuidGenerator.Get(200)]); - Assert.Equal("bbb", obj.Strings[GuidGenerator.Get(100)]); - Assert.Equal(2, obj.Objects.Count); - Assert.Equal("obj1", obj.Objects["key3"].Name); - Assert.Equal("obj2", obj.Objects["key4"].Name); - var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); - Assert.Equal(IdentifierGenerator.Get(2), stringIds[GuidGenerator.Get(200)]); - Assert.Equal(IdentifierGenerator.Get(1), stringIds[GuidGenerator.Get(100)]); - var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); - Assert.Equal(IdentifierGenerator.Get(3), objectIds["key3"]); - Assert.Equal(IdentifierGenerator.Get(4), objectIds["key4"]); - } + [Fact] + public void TestDictionaryDeserialization() + { + ShadowObject.Enable = true; + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(YamlDictionary); + writer.Flush(); + stream.Position = 0; + var instance = AssetYamlSerializer.Default.Deserialize(stream); + Assert.NotNull(instance); + Assert.Equal(typeof(ContainerDictionary), instance.GetType()); + var obj = (ContainerDictionary)instance; + Assert.Equal("Root", obj.Name); + Assert.Equal(2, obj.Strings.Count); + Assert.Equal("aaa", obj.Strings[GuidGenerator.Get(200)]); + Assert.Equal("bbb", obj.Strings[GuidGenerator.Get(100)]); + Assert.Equal(2, obj.Objects.Count); + Assert.Equal("obj1", obj.Objects["key3"].Name); + Assert.Equal("obj2", obj.Objects["key4"].Name); + var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); + Assert.Equal(IdentifierGenerator.Get(2), stringIds[GuidGenerator.Get(200)]); + Assert.Equal(IdentifierGenerator.Get(1), stringIds[GuidGenerator.Get(100)]); + var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); + Assert.Equal(IdentifierGenerator.Get(3), objectIds["key3"]); + Assert.Equal(IdentifierGenerator.Get(4), objectIds["key4"]); + } - [Fact] - public void TestCollectionDeserializationWithDeleted() - { - ShadowObject.Enable = true; - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(YamlCollectionWithDeleted); - writer.Flush(); - stream.Position = 0; - var instance = AssetYamlSerializer.Default.Deserialize(stream); - Assert.NotNull(instance); - Assert.Equal(typeof(ContainerCollection), instance.GetType()); - var obj = (ContainerCollection)instance; - Assert.Equal("Root", obj.Name); - Assert.Equal(2, obj.Strings.Count); - Assert.Equal("aaa", obj.Strings[0]); - Assert.Equal("bbb", obj.Strings[1]); - Assert.Equal(2, obj.Objects.Count); - Assert.Equal("obj1", obj.Objects[0].Name); - Assert.Equal("obj2", obj.Objects[1].Name); - var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); - Assert.Equal(IdentifierGenerator.Get(8), stringIds[0]); - Assert.Equal(IdentifierGenerator.Get(5), stringIds[1]); - var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); - Assert.Equal(IdentifierGenerator.Get(3), objectIds[0]); - Assert.Equal(IdentifierGenerator.Get(4), objectIds[1]); - var deletedItems = stringIds.DeletedItems.ToList(); - Assert.Equal(2, deletedItems.Count); - Assert.Equal(IdentifierGenerator.Get(1), deletedItems[0]); - Assert.Equal(IdentifierGenerator.Get(3), deletedItems[1]); - deletedItems = objectIds.DeletedItems.ToList(); - Assert.Equal(2, deletedItems.Count); - Assert.Equal(IdentifierGenerator.Get(1), deletedItems[0]); - Assert.Equal(IdentifierGenerator.Get(6), deletedItems[1]); - } + [Fact] + public void TestCollectionDeserializationWithDeleted() + { + ShadowObject.Enable = true; + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(YamlCollectionWithDeleted); + writer.Flush(); + stream.Position = 0; + var instance = AssetYamlSerializer.Default.Deserialize(stream); + Assert.NotNull(instance); + Assert.Equal(typeof(ContainerCollection), instance.GetType()); + var obj = (ContainerCollection)instance; + Assert.Equal("Root", obj.Name); + Assert.Equal(2, obj.Strings.Count); + Assert.Equal("aaa", obj.Strings[0]); + Assert.Equal("bbb", obj.Strings[1]); + Assert.Equal(2, obj.Objects.Count); + Assert.Equal("obj1", obj.Objects[0].Name); + Assert.Equal("obj2", obj.Objects[1].Name); + var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); + Assert.Equal(IdentifierGenerator.Get(8), stringIds[0]); + Assert.Equal(IdentifierGenerator.Get(5), stringIds[1]); + var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); + Assert.Equal(IdentifierGenerator.Get(3), objectIds[0]); + Assert.Equal(IdentifierGenerator.Get(4), objectIds[1]); + var deletedItems = stringIds.DeletedItems.ToList(); + Assert.Equal(2, deletedItems.Count); + Assert.Equal(IdentifierGenerator.Get(1), deletedItems[0]); + Assert.Equal(IdentifierGenerator.Get(3), deletedItems[1]); + deletedItems = objectIds.DeletedItems.ToList(); + Assert.Equal(2, deletedItems.Count); + Assert.Equal(IdentifierGenerator.Get(1), deletedItems[0]); + Assert.Equal(IdentifierGenerator.Get(6), deletedItems[1]); + } - [Fact] - public void TestCollectionSerializationWithDeleted() + [Fact] + public void TestCollectionSerializationWithDeleted() + { + ShadowObject.Enable = true; + var obj = new ContainerCollection("Root") { - ShadowObject.Enable = true; - var obj = new ContainerCollection("Root") - { - Strings = { "aaa", "bbb" }, - Objects = { new ContainerCollection("obj1"), new ContainerCollection("obj2") } - }; - - var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); - stringIds[0] = IdentifierGenerator.Get(8); - stringIds[1] = IdentifierGenerator.Get(5); - stringIds.MarkAsDeleted(IdentifierGenerator.Get(3)); - stringIds.MarkAsDeleted(IdentifierGenerator.Get(1)); - var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); - objectIds[0] = IdentifierGenerator.Get(3); - objectIds[1] = IdentifierGenerator.Get(4); - objectIds.MarkAsDeleted(IdentifierGenerator.Get(1)); - objectIds.MarkAsDeleted(IdentifierGenerator.Get(6)); - var yaml = SerializeAsString(obj); - Assert.Equal(YamlCollectionWithDeleted, yaml); - } + Strings = { "aaa", "bbb" }, + Objects = { new ContainerCollection("obj1"), new ContainerCollection("obj2") } + }; + + var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); + stringIds[0] = IdentifierGenerator.Get(8); + stringIds[1] = IdentifierGenerator.Get(5); + stringIds.MarkAsDeleted(IdentifierGenerator.Get(3)); + stringIds.MarkAsDeleted(IdentifierGenerator.Get(1)); + var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); + objectIds[0] = IdentifierGenerator.Get(3); + objectIds[1] = IdentifierGenerator.Get(4); + objectIds.MarkAsDeleted(IdentifierGenerator.Get(1)); + objectIds.MarkAsDeleted(IdentifierGenerator.Get(6)); + var yaml = SerializeAsString(obj); + Assert.Equal(YamlCollectionWithDeleted, yaml); + } - [Fact] - public void TestDictionaryDeserializationWithDeleted() - { - ShadowObject.Enable = true; - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(YamlDictionaryWithDeleted); - writer.Flush(); - stream.Position = 0; - var instance = AssetYamlSerializer.Default.Deserialize(stream); - Assert.NotNull(instance); - Assert.Equal(typeof(ContainerDictionary), instance.GetType()); - var obj = (ContainerDictionary)instance; - Assert.Equal("Root", obj.Name); - Assert.Equal(2, obj.Strings.Count); - Assert.Equal("aaa", obj.Strings[GuidGenerator.Get(200)]); - Assert.Equal("bbb", obj.Strings[GuidGenerator.Get(100)]); - Assert.Equal(2, obj.Objects.Count); - Assert.Equal("obj1", obj.Objects["key3"].Name); - Assert.Equal("obj2", obj.Objects["key4"].Name); - var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); - Assert.Equal(IdentifierGenerator.Get(8), stringIds[GuidGenerator.Get(200)]); - Assert.Equal(IdentifierGenerator.Get(5), stringIds[GuidGenerator.Get(100)]); - var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); - Assert.Equal(IdentifierGenerator.Get(3), objectIds["key3"]); - Assert.Equal(IdentifierGenerator.Get(4), objectIds["key4"]); - var deletedItems = stringIds.DeletedItems.ToList(); - Assert.Equal(2, deletedItems.Count); - Assert.Equal(IdentifierGenerator.Get(1), deletedItems[0]); - Assert.Equal(IdentifierGenerator.Get(3), deletedItems[1]); - deletedItems = objectIds.DeletedItems.ToList(); - Assert.Equal(2, deletedItems.Count); - Assert.Equal(IdentifierGenerator.Get(1), deletedItems[0]); - Assert.Equal(IdentifierGenerator.Get(6), deletedItems[1]); - } + [Fact] + public void TestDictionaryDeserializationWithDeleted() + { + ShadowObject.Enable = true; + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(YamlDictionaryWithDeleted); + writer.Flush(); + stream.Position = 0; + var instance = AssetYamlSerializer.Default.Deserialize(stream); + Assert.NotNull(instance); + Assert.Equal(typeof(ContainerDictionary), instance.GetType()); + var obj = (ContainerDictionary)instance; + Assert.Equal("Root", obj.Name); + Assert.Equal(2, obj.Strings.Count); + Assert.Equal("aaa", obj.Strings[GuidGenerator.Get(200)]); + Assert.Equal("bbb", obj.Strings[GuidGenerator.Get(100)]); + Assert.Equal(2, obj.Objects.Count); + Assert.Equal("obj1", obj.Objects["key3"].Name); + Assert.Equal("obj2", obj.Objects["key4"].Name); + var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); + Assert.Equal(IdentifierGenerator.Get(8), stringIds[GuidGenerator.Get(200)]); + Assert.Equal(IdentifierGenerator.Get(5), stringIds[GuidGenerator.Get(100)]); + var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); + Assert.Equal(IdentifierGenerator.Get(3), objectIds["key3"]); + Assert.Equal(IdentifierGenerator.Get(4), objectIds["key4"]); + var deletedItems = stringIds.DeletedItems.ToList(); + Assert.Equal(2, deletedItems.Count); + Assert.Equal(IdentifierGenerator.Get(1), deletedItems[0]); + Assert.Equal(IdentifierGenerator.Get(3), deletedItems[1]); + deletedItems = objectIds.DeletedItems.ToList(); + Assert.Equal(2, deletedItems.Count); + Assert.Equal(IdentifierGenerator.Get(1), deletedItems[0]); + Assert.Equal(IdentifierGenerator.Get(6), deletedItems[1]); + } - [Fact] - public void TestDictionarySerializationWithDeleted() + [Fact] + public void TestDictionarySerializationWithDeleted() + { + ShadowObject.Enable = true; + var obj = new ContainerDictionary("Root") { - ShadowObject.Enable = true; - var obj = new ContainerDictionary("Root") - { - Strings = { { GuidGenerator.Get(200), "aaa" }, { GuidGenerator.Get(100), "bbb" } }, - Objects = { { "key3", new ContainerCollection("obj1") }, { "key4", new ContainerCollection("obj2") } }, - }; - - var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); - stringIds[GuidGenerator.Get(200)] = IdentifierGenerator.Get(8); - stringIds[GuidGenerator.Get(100)] = IdentifierGenerator.Get(5); - stringIds.MarkAsDeleted(IdentifierGenerator.Get(3)); - stringIds.MarkAsDeleted(IdentifierGenerator.Get(1)); - var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); - objectIds["key3"] = IdentifierGenerator.Get(3); - objectIds["key4"] = IdentifierGenerator.Get(4); - objectIds.MarkAsDeleted(IdentifierGenerator.Get(1)); - objectIds.MarkAsDeleted(IdentifierGenerator.Get(6)); - var yaml = SerializeAsString(obj); - Assert.Equal(YamlDictionaryWithDeleted, yaml); - } + Strings = { { GuidGenerator.Get(200), "aaa" }, { GuidGenerator.Get(100), "bbb" } }, + Objects = { { "key3", new ContainerCollection("obj1") }, { "key4", new ContainerCollection("obj2") } }, + }; + + var stringIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Strings); + stringIds[GuidGenerator.Get(200)] = IdentifierGenerator.Get(8); + stringIds[GuidGenerator.Get(100)] = IdentifierGenerator.Get(5); + stringIds.MarkAsDeleted(IdentifierGenerator.Get(3)); + stringIds.MarkAsDeleted(IdentifierGenerator.Get(1)); + var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); + objectIds["key3"] = IdentifierGenerator.Get(3); + objectIds["key4"] = IdentifierGenerator.Get(4); + objectIds.MarkAsDeleted(IdentifierGenerator.Get(1)); + objectIds.MarkAsDeleted(IdentifierGenerator.Get(6)); + var yaml = SerializeAsString(obj); + Assert.Equal(YamlDictionaryWithDeleted, yaml); + } - [Fact] - public void TestCollectionNonIdentifiableItemsSerialization() + [Fact] + public void TestCollectionNonIdentifiableItemsSerialization() + { + ShadowObject.Enable = true; + var obj = new ContainerNonIdentifiableCollection("Root") { - ShadowObject.Enable = true; - var obj = new ContainerNonIdentifiableCollection("Root") - { - Objects = { new ContainerCollection { Name = "aaa", Strings = { "bbb", "ccc" } }, new ContainerCollection { Name = "ddd", Strings = { "eee", "fff" } } }, - NonIdentifiableObjects = { new ContainerCollection { Name = "ggg", Strings = { "hhh", "iii" } }, new ContainerCollection { Name = "jjj", Strings = { "kkk", "lll" } } }, - }; - - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); - ids[0] = IdentifierGenerator.Get(2); - ids[1] = IdentifierGenerator.Get(1); - ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects[0].Strings); - ids[0] = IdentifierGenerator.Get(5); - ids[1] = IdentifierGenerator.Get(6); - ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects[1].Strings); - ids[0] = IdentifierGenerator.Get(7); - ids[1] = IdentifierGenerator.Get(8); - ids = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects); - ids[0] = IdentifierGenerator.Get(3); - ids[1] = IdentifierGenerator.Get(4); - ids = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects[0].Strings); - ids[0] = IdentifierGenerator.Get(9); - ids[1] = IdentifierGenerator.Get(10); - ids = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects[1].Strings); - ids[0] = IdentifierGenerator.Get(11); - ids[1] = IdentifierGenerator.Get(12); - var yaml = SerializeAsString(obj); - Assert.Equal(YamlCollectionNotIdentifiable, yaml); - } + Objects = { new ContainerCollection { Name = "aaa", Strings = { "bbb", "ccc" } }, new ContainerCollection { Name = "ddd", Strings = { "eee", "fff" } } }, + NonIdentifiableObjects = { new ContainerCollection { Name = "ggg", Strings = { "hhh", "iii" } }, new ContainerCollection { Name = "jjj", Strings = { "kkk", "lll" } } }, + }; + + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); + ids[0] = IdentifierGenerator.Get(2); + ids[1] = IdentifierGenerator.Get(1); + ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects[0].Strings); + ids[0] = IdentifierGenerator.Get(5); + ids[1] = IdentifierGenerator.Get(6); + ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects[1].Strings); + ids[0] = IdentifierGenerator.Get(7); + ids[1] = IdentifierGenerator.Get(8); + ids = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects); + ids[0] = IdentifierGenerator.Get(3); + ids[1] = IdentifierGenerator.Get(4); + ids = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects[0].Strings); + ids[0] = IdentifierGenerator.Get(9); + ids[1] = IdentifierGenerator.Get(10); + ids = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects[1].Strings); + ids[0] = IdentifierGenerator.Get(11); + ids[1] = IdentifierGenerator.Get(12); + var yaml = SerializeAsString(obj); + Assert.Equal(YamlCollectionNotIdentifiable, yaml); + } - [Fact] - public void TestCollectionNonIdentifiableItemsDeserialization() - { - ShadowObject.Enable = true; - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(YamlCollectionNotIdentifiable); - writer.Flush(); - stream.Position = 0; - var instance = AssetYamlSerializer.Default.Deserialize(stream); - Assert.NotNull(instance); - Assert.Equal(typeof(ContainerNonIdentifiableCollection), instance.GetType()); - var obj = (ContainerNonIdentifiableCollection)instance; - Assert.Equal("Root", obj.Name); - Assert.Equal(2, obj.Objects.Count); - Assert.Equal("aaa", obj.Objects[0].Name); - Assert.Equal(2, obj.Objects[0].Strings.Count); - Assert.Equal("bbb", obj.Objects[0].Strings[0]); - Assert.Equal("ccc", obj.Objects[0].Strings[1]); - Assert.Equal("ddd", obj.Objects[1].Name); - Assert.Equal(2, obj.Objects[1].Strings.Count); - Assert.Equal("eee", obj.Objects[1].Strings[0]); - Assert.Equal("fff", obj.Objects[1].Strings[1]); - var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); - Assert.Equal(IdentifierGenerator.Get(2), objectIds[0]); - Assert.Equal(IdentifierGenerator.Get(1), objectIds[1]); - objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects[0].Strings); - Assert.Equal(IdentifierGenerator.Get(5), objectIds[0]); - Assert.Equal(IdentifierGenerator.Get(6), objectIds[1]); - objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects[1].Strings); - Assert.Equal(IdentifierGenerator.Get(7), objectIds[0]); - Assert.Equal(IdentifierGenerator.Get(8), objectIds[1]); - - Assert.Equal(2, obj.NonIdentifiableObjects.Count); - Assert.Equal("ggg", obj.NonIdentifiableObjects[0].Name); - Assert.Equal(2, obj.NonIdentifiableObjects[0].Strings.Count); - Assert.Equal("hhh", obj.NonIdentifiableObjects[0].Strings[0]); - Assert.Equal("iii", obj.NonIdentifiableObjects[0].Strings[1]); - Assert.Equal("jjj", obj.NonIdentifiableObjects[1].Name); - Assert.Equal(2, obj.NonIdentifiableObjects[1].Strings.Count); - Assert.Equal("kkk", obj.NonIdentifiableObjects[1].Strings[0]); - Assert.Equal("lll", obj.NonIdentifiableObjects[1].Strings[1]); - Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj.NonIdentifiableObjects, out objectIds)); - objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects); - Assert.Equal(0, objectIds.KeyCount); - Assert.Equal(0, objectIds.DeletedCount); - objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects[0].Strings); - Assert.Equal(IdentifierGenerator.Get(9), objectIds[0]); - Assert.Equal(IdentifierGenerator.Get(10), objectIds[1]); - objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects[1].Strings); - Assert.Equal(IdentifierGenerator.Get(11), objectIds[0]); - Assert.Equal(IdentifierGenerator.Get(12), objectIds[1]); - } + [Fact] + public void TestCollectionNonIdentifiableItemsDeserialization() + { + ShadowObject.Enable = true; + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(YamlCollectionNotIdentifiable); + writer.Flush(); + stream.Position = 0; + var instance = AssetYamlSerializer.Default.Deserialize(stream); + Assert.NotNull(instance); + Assert.Equal(typeof(ContainerNonIdentifiableCollection), instance.GetType()); + var obj = (ContainerNonIdentifiableCollection)instance; + Assert.Equal("Root", obj.Name); + Assert.Equal(2, obj.Objects.Count); + Assert.Equal("aaa", obj.Objects[0].Name); + Assert.Equal(2, obj.Objects[0].Strings.Count); + Assert.Equal("bbb", obj.Objects[0].Strings[0]); + Assert.Equal("ccc", obj.Objects[0].Strings[1]); + Assert.Equal("ddd", obj.Objects[1].Name); + Assert.Equal(2, obj.Objects[1].Strings.Count); + Assert.Equal("eee", obj.Objects[1].Strings[0]); + Assert.Equal("fff", obj.Objects[1].Strings[1]); + var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); + Assert.Equal(IdentifierGenerator.Get(2), objectIds[0]); + Assert.Equal(IdentifierGenerator.Get(1), objectIds[1]); + objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects[0].Strings); + Assert.Equal(IdentifierGenerator.Get(5), objectIds[0]); + Assert.Equal(IdentifierGenerator.Get(6), objectIds[1]); + objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects[1].Strings); + Assert.Equal(IdentifierGenerator.Get(7), objectIds[0]); + Assert.Equal(IdentifierGenerator.Get(8), objectIds[1]); + + Assert.Equal(2, obj.NonIdentifiableObjects.Count); + Assert.Equal("ggg", obj.NonIdentifiableObjects[0].Name); + Assert.Equal(2, obj.NonIdentifiableObjects[0].Strings.Count); + Assert.Equal("hhh", obj.NonIdentifiableObjects[0].Strings[0]); + Assert.Equal("iii", obj.NonIdentifiableObjects[0].Strings[1]); + Assert.Equal("jjj", obj.NonIdentifiableObjects[1].Name); + Assert.Equal(2, obj.NonIdentifiableObjects[1].Strings.Count); + Assert.Equal("kkk", obj.NonIdentifiableObjects[1].Strings[0]); + Assert.Equal("lll", obj.NonIdentifiableObjects[1].Strings[1]); + Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj.NonIdentifiableObjects, out _)); + objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects); + Assert.Equal(0, objectIds.KeyCount); + Assert.Equal(0, objectIds.DeletedCount); + objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects[0].Strings); + Assert.Equal(IdentifierGenerator.Get(9), objectIds[0]); + Assert.Equal(IdentifierGenerator.Get(10), objectIds[1]); + objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects[1].Strings); + Assert.Equal(IdentifierGenerator.Get(11), objectIds[0]); + Assert.Equal(IdentifierGenerator.Get(12), objectIds[1]); + } - [Fact] - public void TestDictionaryNonIdentifiableItemsSerialization() + [Fact] + public void TestDictionaryNonIdentifiableItemsSerialization() + { + ShadowObject.Enable = true; + var obj = new ContainerNonIdentifiableDictionary("Root") { - ShadowObject.Enable = true; - var obj = new ContainerNonIdentifiableDictionary("Root") - { - Objects = { { "AAA", new ContainerCollection { Name = "aaa", Strings = { "bbb", "ccc" } } }, { "BBB", new ContainerCollection { Name = "ddd", Strings = { "eee", "fff" } } } }, - NonIdentifiableObjects = { { "CCC", new ContainerCollection { Name = "ggg", Strings = { "hhh", "iii" } } }, { "DDD", new ContainerCollection { Name = "jjj", Strings = { "kkk", "lll" } } } }, - }; - - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); - ids["AAA"] = IdentifierGenerator.Get(2); - ids["BBB"] = IdentifierGenerator.Get(1); - ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects["AAA"].Strings); - ids[0] = IdentifierGenerator.Get(5); - ids[1] = IdentifierGenerator.Get(6); - ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects["BBB"].Strings); - ids[0] = IdentifierGenerator.Get(7); - ids[1] = IdentifierGenerator.Get(8); - ids = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects); - ids["CCC"] = IdentifierGenerator.Get(3); - ids["DDD"] = IdentifierGenerator.Get(4); - ids = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects["CCC"].Strings); - ids[0] = IdentifierGenerator.Get(9); - ids[1] = IdentifierGenerator.Get(10); - ids = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects["DDD"].Strings); - ids[0] = IdentifierGenerator.Get(11); - ids[1] = IdentifierGenerator.Get(12); - var yaml = SerializeAsString(obj); - Assert.Equal(YamlDictionaryNonIdentifiable, yaml); - } + Objects = { { "AAA", new ContainerCollection { Name = "aaa", Strings = { "bbb", "ccc" } } }, { "BBB", new ContainerCollection { Name = "ddd", Strings = { "eee", "fff" } } } }, + NonIdentifiableObjects = { { "CCC", new ContainerCollection { Name = "ggg", Strings = { "hhh", "iii" } } }, { "DDD", new ContainerCollection { Name = "jjj", Strings = { "kkk", "lll" } } } }, + }; + + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); + ids["AAA"] = IdentifierGenerator.Get(2); + ids["BBB"] = IdentifierGenerator.Get(1); + ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects["AAA"].Strings); + ids[0] = IdentifierGenerator.Get(5); + ids[1] = IdentifierGenerator.Get(6); + ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects["BBB"].Strings); + ids[0] = IdentifierGenerator.Get(7); + ids[1] = IdentifierGenerator.Get(8); + ids = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects); + ids["CCC"] = IdentifierGenerator.Get(3); + ids["DDD"] = IdentifierGenerator.Get(4); + ids = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects["CCC"].Strings); + ids[0] = IdentifierGenerator.Get(9); + ids[1] = IdentifierGenerator.Get(10); + ids = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects["DDD"].Strings); + ids[0] = IdentifierGenerator.Get(11); + ids[1] = IdentifierGenerator.Get(12); + var yaml = SerializeAsString(obj); + Assert.Equal(YamlDictionaryNonIdentifiable, yaml); + } - [Fact] - public void TestDictionaryNonIdentifiableItemsDeserialization() - { - ShadowObject.Enable = true; - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(YamlDictionaryNonIdentifiable); - writer.Flush(); - stream.Position = 0; - var instance = AssetYamlSerializer.Default.Deserialize(stream); - Assert.NotNull(instance); - Assert.Equal(typeof(ContainerNonIdentifiableDictionary), instance.GetType()); - var obj = (ContainerNonIdentifiableDictionary)instance; - Assert.Equal("Root", obj.Name); - Assert.Equal(2, obj.Objects.Count); - Assert.Equal("aaa", obj.Objects["AAA"].Name); - Assert.Equal(2, obj.Objects["AAA"].Strings.Count); - Assert.Equal("bbb", obj.Objects["AAA"].Strings[0]); - Assert.Equal("ccc", obj.Objects["AAA"].Strings[1]); - Assert.Equal("ddd", obj.Objects["BBB"].Name); - Assert.Equal(2, obj.Objects["BBB"].Strings.Count); - Assert.Equal("eee", obj.Objects["BBB"].Strings[0]); - Assert.Equal("fff", obj.Objects["BBB"].Strings[1]); - var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); - Assert.Equal(IdentifierGenerator.Get(2), objectIds["AAA"]); - Assert.Equal(IdentifierGenerator.Get(1), objectIds["BBB"]); - objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects["AAA"].Strings); - Assert.Equal(IdentifierGenerator.Get(5), objectIds[0]); - Assert.Equal(IdentifierGenerator.Get(6), objectIds[1]); - objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects["BBB"].Strings); - Assert.Equal(IdentifierGenerator.Get(7), objectIds[0]); - Assert.Equal(IdentifierGenerator.Get(8), objectIds[1]); - - Assert.Equal(2, obj.NonIdentifiableObjects.Count); - Assert.Equal("ggg", obj.NonIdentifiableObjects["CCC"].Name); - Assert.Equal(2, obj.NonIdentifiableObjects["CCC"].Strings.Count); - Assert.Equal("hhh", obj.NonIdentifiableObjects["CCC"].Strings[0]); - Assert.Equal("iii", obj.NonIdentifiableObjects["CCC"].Strings[1]); - Assert.Equal("jjj", obj.NonIdentifiableObjects["DDD"].Name); - Assert.Equal(2, obj.NonIdentifiableObjects["DDD"].Strings.Count); - Assert.Equal("kkk", obj.NonIdentifiableObjects["DDD"].Strings[0]); - Assert.Equal("lll", obj.NonIdentifiableObjects["DDD"].Strings[1]); - Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj.NonIdentifiableObjects, out objectIds)); - objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects); - Assert.Equal(0, objectIds.KeyCount); - Assert.Equal(0, objectIds.DeletedCount); - objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects["CCC"].Strings); - Assert.Equal(IdentifierGenerator.Get(9), objectIds[0]); - Assert.Equal(IdentifierGenerator.Get(10), objectIds[1]); - objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects["DDD"].Strings); - Assert.Equal(IdentifierGenerator.Get(11), objectIds[0]); - Assert.Equal(IdentifierGenerator.Get(12), objectIds[1]); - } + [Fact] + public void TestDictionaryNonIdentifiableItemsDeserialization() + { + ShadowObject.Enable = true; + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(YamlDictionaryNonIdentifiable); + writer.Flush(); + stream.Position = 0; + var instance = AssetYamlSerializer.Default.Deserialize(stream); + Assert.NotNull(instance); + Assert.Equal(typeof(ContainerNonIdentifiableDictionary), instance.GetType()); + var obj = (ContainerNonIdentifiableDictionary)instance; + Assert.Equal("Root", obj.Name); + Assert.Equal(2, obj.Objects.Count); + Assert.Equal("aaa", obj.Objects["AAA"].Name); + Assert.Equal(2, obj.Objects["AAA"].Strings.Count); + Assert.Equal("bbb", obj.Objects["AAA"].Strings[0]); + Assert.Equal("ccc", obj.Objects["AAA"].Strings[1]); + Assert.Equal("ddd", obj.Objects["BBB"].Name); + Assert.Equal(2, obj.Objects["BBB"].Strings.Count); + Assert.Equal("eee", obj.Objects["BBB"].Strings[0]); + Assert.Equal("fff", obj.Objects["BBB"].Strings[1]); + var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); + Assert.Equal(IdentifierGenerator.Get(2), objectIds["AAA"]); + Assert.Equal(IdentifierGenerator.Get(1), objectIds["BBB"]); + objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects["AAA"].Strings); + Assert.Equal(IdentifierGenerator.Get(5), objectIds[0]); + Assert.Equal(IdentifierGenerator.Get(6), objectIds[1]); + objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects["BBB"].Strings); + Assert.Equal(IdentifierGenerator.Get(7), objectIds[0]); + Assert.Equal(IdentifierGenerator.Get(8), objectIds[1]); + + Assert.Equal(2, obj.NonIdentifiableObjects.Count); + Assert.Equal("ggg", obj.NonIdentifiableObjects["CCC"].Name); + Assert.Equal(2, obj.NonIdentifiableObjects["CCC"].Strings.Count); + Assert.Equal("hhh", obj.NonIdentifiableObjects["CCC"].Strings[0]); + Assert.Equal("iii", obj.NonIdentifiableObjects["CCC"].Strings[1]); + Assert.Equal("jjj", obj.NonIdentifiableObjects["DDD"].Name); + Assert.Equal(2, obj.NonIdentifiableObjects["DDD"].Strings.Count); + Assert.Equal("kkk", obj.NonIdentifiableObjects["DDD"].Strings[0]); + Assert.Equal("lll", obj.NonIdentifiableObjects["DDD"].Strings[1]); + Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj.NonIdentifiableObjects, out _)); + objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects); + Assert.Equal(0, objectIds.KeyCount); + Assert.Equal(0, objectIds.DeletedCount); + objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects["CCC"].Strings); + Assert.Equal(IdentifierGenerator.Get(9), objectIds[0]); + Assert.Equal(IdentifierGenerator.Get(10), objectIds[1]); + objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects["DDD"].Strings); + Assert.Equal(IdentifierGenerator.Get(11), objectIds[0]); + Assert.Equal(IdentifierGenerator.Get(12), objectIds[1]); + } - [Fact] - public void TestIdsGeneration() + [Fact] + public void TestIdsGeneration() + { + ShadowObject.Enable = true; + + var obj1 = new ContainerCollection("Root") { - ShadowObject.Enable = true; - CollectionItemIdentifiers ids; - - var obj1 = new ContainerCollection("Root") - { - Strings = { "aaa", "bbb", "ccc" }, - Objects = { new ContainerCollection("obj1"), new ContainerCollection("obj2") } - }; - var hashSet = new HashSet(); - Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Strings, out ids)); - Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Strings, out ids)); - Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Objects, out ids)); - Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Objects, out ids)); - AssetCollectionItemIdHelper.GenerateMissingItemIds(obj1); - Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Strings, out ids)); - Assert.Equal(3, ids.KeyCount); - Assert.Equal(0, ids.DeletedCount); - Assert.True(ids.ContainsKey(0)); - Assert.True(ids.ContainsKey(1)); - Assert.True(ids.ContainsKey(2)); - hashSet.Add(ids[0]); - hashSet.Add(ids[1]); - hashSet.Add(ids[2]); - Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Objects, out ids)); - Assert.Equal(2, ids.KeyCount); - Assert.Equal(0, ids.DeletedCount); - Assert.True(ids.ContainsKey(0)); - Assert.True(ids.ContainsKey(1)); - hashSet.Add(ids[0]); - hashSet.Add(ids[1]); - Assert.Equal(5, hashSet.Count); - - var obj2 = new ContainerDictionary("Root") - { - Strings = { { GuidGenerator.Get(200), "aaa" }, { GuidGenerator.Get(100), "bbb" }, { GuidGenerator.Get(300), "ccc" } }, - Objects = { { "key3", new ContainerCollection("obj1") }, { "key4", new ContainerCollection("obj2") } }, - }; - hashSet.Clear(); - Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Strings, out ids)); - Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Strings, out ids)); - Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Objects, out ids)); - Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Objects, out ids)); - AssetCollectionItemIdHelper.GenerateMissingItemIds(obj2); - Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Strings, out ids)); - Assert.Equal(3, ids.KeyCount); - Assert.Equal(0, ids.DeletedCount); - Assert.True(ids.ContainsKey(GuidGenerator.Get(200))); - Assert.True(ids.ContainsKey(GuidGenerator.Get(100))); - Assert.True(ids.ContainsKey(GuidGenerator.Get(300))); - hashSet.Add(ids[GuidGenerator.Get(200)]); - hashSet.Add(ids[GuidGenerator.Get(100)]); - hashSet.Add(ids[GuidGenerator.Get(300)]); - Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Objects, out ids)); - Assert.Equal(2, ids.KeyCount); - Assert.Equal(0, ids.DeletedCount); - Assert.True(ids.ContainsKey("key3")); - Assert.True(ids.ContainsKey("key4")); - hashSet.Add(ids["key3"]); - hashSet.Add(ids["key4"]); - Assert.Equal(5, hashSet.Count); - } + Strings = { "aaa", "bbb", "ccc" }, + Objects = { new ContainerCollection("obj1"), new ContainerCollection("obj2") } + }; + var hashSet = new HashSet(); + Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Strings, out _)); + Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Strings, out _)); + Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Objects, out _)); + Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Objects, out _)); + AssetCollectionItemIdHelper.GenerateMissingItemIds(obj1); + Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Strings, out var ids)); + Assert.Equal(3, ids.KeyCount); + Assert.Equal(0, ids.DeletedCount); + Assert.True(ids.ContainsKey(0)); + Assert.True(ids.ContainsKey(1)); + Assert.True(ids.ContainsKey(2)); + hashSet.Add(ids[0]); + hashSet.Add(ids[1]); + hashSet.Add(ids[2]); + Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Objects, out ids)); + Assert.Equal(2, ids.KeyCount); + Assert.Equal(0, ids.DeletedCount); + Assert.True(ids.ContainsKey(0)); + Assert.True(ids.ContainsKey(1)); + hashSet.Add(ids[0]); + hashSet.Add(ids[1]); + Assert.Equal(5, hashSet.Count); + + var obj2 = new ContainerDictionary("Root") + { + Strings = { { GuidGenerator.Get(200), "aaa" }, { GuidGenerator.Get(100), "bbb" }, { GuidGenerator.Get(300), "ccc" } }, + Objects = { { "key3", new ContainerCollection("obj1") }, { "key4", new ContainerCollection("obj2") } }, + }; + hashSet.Clear(); + Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Strings, out _)); + Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Strings, out _)); + Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Objects, out _)); + Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Objects, out _)); + AssetCollectionItemIdHelper.GenerateMissingItemIds(obj2); + Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Strings, out ids)); + Assert.Equal(3, ids.KeyCount); + Assert.Equal(0, ids.DeletedCount); + Assert.True(ids.ContainsKey(GuidGenerator.Get(200))); + Assert.True(ids.ContainsKey(GuidGenerator.Get(100))); + Assert.True(ids.ContainsKey(GuidGenerator.Get(300))); + hashSet.Add(ids[GuidGenerator.Get(200)]); + hashSet.Add(ids[GuidGenerator.Get(100)]); + hashSet.Add(ids[GuidGenerator.Get(300)]); + Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Objects, out ids)); + Assert.Equal(2, ids.KeyCount); + Assert.Equal(0, ids.DeletedCount); + Assert.True(ids.ContainsKey("key3")); + Assert.True(ids.ContainsKey("key4")); + hashSet.Add(ids["key3"]); + hashSet.Add(ids["key4"]); + Assert.Equal(5, hashSet.Count); } } diff --git a/sources/assets/Stride.Core.Assets.Tests/Yaml/TestObjectReferenceSerialization.cs b/sources/assets/Stride.Core.Assets.Tests/Yaml/TestObjectReferenceSerialization.cs index b6b23e1ca1..435b071d91 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Yaml/TestObjectReferenceSerialization.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Yaml/TestObjectReferenceSerialization.cs @@ -1,592 +1,621 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Xunit; + using Stride.Core.Assets.Serializers; using Stride.Core.Assets.Tests.Helpers; using Stride.Core.Assets.Yaml; -using Stride.Core; using Stride.Core.Annotations; using Stride.Core.Reflection; using Stride.Core.Yaml; -namespace Stride.Core.Assets.Tests.Yaml +namespace Stride.Core.Assets.Tests.Yaml; + +public class TestObjectReferenceSerialization { - public class TestObjectReferenceSerialization + public interface IReferenceable : IIdentifiable { - public interface IReferenceable : IIdentifiable - { - string Value { get; set; } - } + string? Value { get; set; } + } - public class Referenceable : IReferenceable - { - [NonOverridable] - public Guid Id { get; set; } + public class Referenceable : IReferenceable + { + [NonOverridable] + public Guid Id { get; set; } - public string Value { get; set; } - } + public string? Value { get; set; } + } - public class CollectionContainer - { - public List ConcreteRefList { get; set; } = new List(); - public List AbstractRefList { get; set; } = new List(); - public Dictionary ConcreteRefDictionary { get; set; } = new Dictionary(); - public Dictionary AbstractRefDictionary { get; set; } = new Dictionary(); - } + public class CollectionContainer + { + public List ConcreteRefList { get; set; } = []; + public List AbstractRefList { get; set; } = []; + public Dictionary ConcreteRefDictionary { get; set; } = []; + public Dictionary AbstractRefDictionary { get; set; } = []; + } - public class NonIdentifiableCollectionContainer - { - [NonIdentifiableCollectionItems] - public List ConcreteRefList { get; set; } = new List(); - [NonIdentifiableCollectionItems] - public List AbstractRefList { get; set; } = new List(); - [NonIdentifiableCollectionItems] - public Dictionary ConcreteRefDictionary { get; set; } = new Dictionary(); - [NonIdentifiableCollectionItems] - public Dictionary AbstractRefDictionary { get; set; } = new Dictionary(); - } + public class NonIdentifiableCollectionContainer + { + [NonIdentifiableCollectionItems] + public List ConcreteRefList { get; set; } = []; + [NonIdentifiableCollectionItems] + public List AbstractRefList { get; set; } = []; + [NonIdentifiableCollectionItems] + public Dictionary ConcreteRefDictionary { get; set; } = []; + [NonIdentifiableCollectionItems] + public Dictionary AbstractRefDictionary { get; set; } = []; + } - public class Container - { - public Referenceable Referenceable1 { get; set; } - public Referenceable Referenceable2 { get; set; } - public IReferenceable Referenceable3 { get; set; } - public IReferenceable Referenceable4 { get; set; } - } + public class Container + { + public Referenceable? Referenceable1 { get; set; } + public Referenceable? Referenceable2 { get; set; } + public IReferenceable? Referenceable3 { get; set; } + public IReferenceable? Referenceable4 { get; set; } + } - private const string ExpandedObjectYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Container,Stride.Core.Assets.Tests -Referenceable1: - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -Referenceable2: null -Referenceable3: null -Referenceable4: null -"; - - private const string ConcreteReferenceConcreteObjectYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Container,Stride.Core.Assets.Tests -Referenceable1: - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -Referenceable2: ref!! 00000001-0001-0000-0100-000001000000 -Referenceable3: null -Referenceable4: null -"; - - private const string AbstractReferenceConcreteObjectYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Container,Stride.Core.Assets.Tests -Referenceable1: - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -Referenceable2: null -Referenceable3: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests ref!! 00000001-0001-0000-0100-000001000000 -Referenceable4: null -"; - - private const string ConcreteReferenceAbstractObjectYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Container,Stride.Core.Assets.Tests -Referenceable1: null -Referenceable2: ref!! 00000001-0001-0000-0100-000001000000 -Referenceable3: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -Referenceable4: null -"; - - private const string AbstractReferenceAbstractObjectYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Container,Stride.Core.Assets.Tests -Referenceable1: null -Referenceable2: null -Referenceable3: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -Referenceable4: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests ref!! 00000001-0001-0000-0100-000001000000 -"; - - private const string ConcreteReferenceableListYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+CollectionContainer,Stride.Core.Assets.Tests -ConcreteRefList: - 01000000010000000100000001000000: ref!! 00000001-0001-0000-0100-000001000000 - 02000000020000000200000002000000: - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -AbstractRefList: {} -ConcreteRefDictionary: {} -AbstractRefDictionary: {} -"; - - private const string AbstractReferenceableListYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+CollectionContainer,Stride.Core.Assets.Tests -ConcreteRefList: {} -AbstractRefList: - 01000000010000000100000001000000: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests ref!! 00000001-0001-0000-0100-000001000000 - 02000000020000000200000002000000: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -ConcreteRefDictionary: {} -AbstractRefDictionary: {} -"; - - private const string ConcreteReferenceableDictionaryYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+CollectionContainer,Stride.Core.Assets.Tests -ConcreteRefList: {} -AbstractRefList: {} -ConcreteRefDictionary: - 01000000010000000100000001000000~Item1: ref!! 00000001-0001-0000-0100-000001000000 - 02000000020000000200000002000000~Item2: - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -AbstractRefDictionary: {} -"; - - private const string AbstractReferenceableDictionaryYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+CollectionContainer,Stride.Core.Assets.Tests -ConcreteRefList: {} -AbstractRefList: {} -ConcreteRefDictionary: {} -AbstractRefDictionary: - 01000000010000000100000001000000~Item1: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests ref!! 00000001-0001-0000-0100-000001000000 - 02000000020000000200000002000000~Item2: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -"; - - private const string ConcreteNonIdentifiableReferenceableListYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+NonIdentifiableCollectionContainer,Stride.Core.Assets.Tests -ConcreteRefList: - - ref!! 00000001-0001-0000-0100-000001000000 - - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -AbstractRefList: [] -ConcreteRefDictionary: {} -AbstractRefDictionary: {} -"; - - private const string AbstractNonIdentifiableReferenceableListYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+NonIdentifiableCollectionContainer,Stride.Core.Assets.Tests -ConcreteRefList: [] -AbstractRefList: - - !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests ref!! 00000001-0001-0000-0100-000001000000 - - !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -ConcreteRefDictionary: {} -AbstractRefDictionary: {} -"; - - private const string ConcreteNonIdentifiableReferenceableDictionaryYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+NonIdentifiableCollectionContainer,Stride.Core.Assets.Tests -ConcreteRefList: [] -AbstractRefList: [] -ConcreteRefDictionary: - Item1: ref!! 00000001-0001-0000-0100-000001000000 - Item2: - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -AbstractRefDictionary: {} -"; - - private const string AbstractNonIdentifiableReferenceableDictionaryYaml = @"!Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+NonIdentifiableCollectionContainer,Stride.Core.Assets.Tests -ConcreteRefList: [] -AbstractRefList: [] -ConcreteRefDictionary: {} -AbstractRefDictionary: - Item1: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests ref!! 00000001-0001-0000-0100-000001000000 - Item2: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests - Id: 00000001-0001-0000-0100-000001000000 - Value: Test -"; - - [Fact] - public void TestExpandObjectSerialization() - { - var obj = new Container { Referenceable1 = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" } }; - var yaml = SerializeAsString(obj, null); - Assert.Equal(ExpandedObjectYaml, yaml); - yaml = SerializeAsString(obj, new YamlAssetMetadata()); - Assert.Equal(ExpandedObjectYaml, yaml); - } + private const string ExpandedObjectYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Container,Stride.Core.Assets.Tests + Referenceable1: + Id: 00000001-0001-0000-0100-000001000000 + Value: Test + Referenceable2: null + Referenceable3: null + Referenceable4: null + + """; + + private const string ConcreteReferenceConcreteObjectYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Container,Stride.Core.Assets.Tests + Referenceable1: + Id: 00000001-0001-0000-0100-000001000000 + Value: Test + Referenceable2: ref!! 00000001-0001-0000-0100-000001000000 + Referenceable3: null + Referenceable4: null + + """; + + private const string AbstractReferenceConcreteObjectYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Container,Stride.Core.Assets.Tests + Referenceable1: + Id: 00000001-0001-0000-0100-000001000000 + Value: Test + Referenceable2: null + Referenceable3: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests ref!! 00000001-0001-0000-0100-000001000000 + Referenceable4: null + + """; + + private const string ConcreteReferenceAbstractObjectYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Container,Stride.Core.Assets.Tests + Referenceable1: null + Referenceable2: ref!! 00000001-0001-0000-0100-000001000000 + Referenceable3: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests + Id: 00000001-0001-0000-0100-000001000000 + Value: Test + Referenceable4: null + + """; + + private const string AbstractReferenceAbstractObjectYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Container,Stride.Core.Assets.Tests + Referenceable1: null + Referenceable2: null + Referenceable3: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests + Id: 00000001-0001-0000-0100-000001000000 + Value: Test + Referenceable4: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests ref!! 00000001-0001-0000-0100-000001000000 + + """; + + private const string ConcreteReferenceableListYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+CollectionContainer,Stride.Core.Assets.Tests + ConcreteRefList: + 01000000010000000100000001000000: ref!! 00000001-0001-0000-0100-000001000000 + 02000000020000000200000002000000: + Id: 00000001-0001-0000-0100-000001000000 + Value: Test + AbstractRefList: {} + ConcreteRefDictionary: {} + AbstractRefDictionary: {} + + """; + + private const string AbstractReferenceableListYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+CollectionContainer,Stride.Core.Assets.Tests + ConcreteRefList: {} + AbstractRefList: + 01000000010000000100000001000000: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests ref!! 00000001-0001-0000-0100-000001000000 + 02000000020000000200000002000000: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests + Id: 00000001-0001-0000-0100-000001000000 + Value: Test + ConcreteRefDictionary: {} + AbstractRefDictionary: {} + + """; + + private const string ConcreteReferenceableDictionaryYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+CollectionContainer,Stride.Core.Assets.Tests + ConcreteRefList: {} + AbstractRefList: {} + ConcreteRefDictionary: + 01000000010000000100000001000000~Item1: ref!! 00000001-0001-0000-0100-000001000000 + 02000000020000000200000002000000~Item2: + Id: 00000001-0001-0000-0100-000001000000 + Value: Test + AbstractRefDictionary: {} + + """; + + private const string AbstractReferenceableDictionaryYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+CollectionContainer,Stride.Core.Assets.Tests + ConcreteRefList: {} + AbstractRefList: {} + ConcreteRefDictionary: {} + AbstractRefDictionary: + 01000000010000000100000001000000~Item1: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests ref!! 00000001-0001-0000-0100-000001000000 + 02000000020000000200000002000000~Item2: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests + Id: 00000001-0001-0000-0100-000001000000 + Value: Test + + """; + + private const string ConcreteNonIdentifiableReferenceableListYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+NonIdentifiableCollectionContainer,Stride.Core.Assets.Tests + ConcreteRefList: + - ref!! 00000001-0001-0000-0100-000001000000 + - Id: 00000001-0001-0000-0100-000001000000 + Value: Test + AbstractRefList: [] + ConcreteRefDictionary: {} + AbstractRefDictionary: {} + + """; + + private const string AbstractNonIdentifiableReferenceableListYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+NonIdentifiableCollectionContainer,Stride.Core.Assets.Tests + ConcreteRefList: [] + AbstractRefList: + - !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests ref!! 00000001-0001-0000-0100-000001000000 + - !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests + Id: 00000001-0001-0000-0100-000001000000 + Value: Test + ConcreteRefDictionary: {} + AbstractRefDictionary: {} + + """; + + private const string ConcreteNonIdentifiableReferenceableDictionaryYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+NonIdentifiableCollectionContainer,Stride.Core.Assets.Tests + ConcreteRefList: [] + AbstractRefList: [] + ConcreteRefDictionary: + Item1: ref!! 00000001-0001-0000-0100-000001000000 + Item2: + Id: 00000001-0001-0000-0100-000001000000 + Value: Test + AbstractRefDictionary: {} + + """; + + private const string AbstractNonIdentifiableReferenceableDictionaryYaml = + """ + !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+NonIdentifiableCollectionContainer,Stride.Core.Assets.Tests + ConcreteRefList: [] + AbstractRefList: [] + ConcreteRefDictionary: {} + AbstractRefDictionary: + Item1: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests ref!! 00000001-0001-0000-0100-000001000000 + Item2: !Stride.Core.Assets.Tests.Yaml.TestObjectReferenceSerialization+Referenceable,Stride.Core.Assets.Tests + Id: 00000001-0001-0000-0100-000001000000 + Value: Test + + """; + + [Fact] + public void TestExpandObjectSerialization() + { + var obj = new Container { Referenceable1 = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" } }; + var yaml = SerializeAsString(obj, null); + Assert.Equal(ExpandedObjectYaml, yaml); + yaml = SerializeAsString(obj, new YamlAssetMetadata()); + Assert.Equal(ExpandedObjectYaml, yaml); + } - [Fact] - public void TestExpandObjectDeserialization() - { - var obj = (Container)Deserialize(ExpandedObjectYaml); - Assert.NotNull(obj.Referenceable1); - Assert.Null(obj.Referenceable2); - Assert.Null(obj.Referenceable3); - Assert.Null(obj.Referenceable4); - Assert.Equal(GuidGenerator.Get(1), obj.Referenceable1.Id); - Assert.Equal("Test", obj.Referenceable1.Value); - } + [Fact] + public void TestExpandObjectDeserialization() + { + var obj = (Container)Deserialize(ExpandedObjectYaml); + Assert.NotNull(obj.Referenceable1); + Assert.Null(obj.Referenceable2); + Assert.Null(obj.Referenceable3); + Assert.Null(obj.Referenceable4); + Assert.Equal(GuidGenerator.Get(1), obj.Referenceable1.Id); + Assert.Equal("Test", obj.Referenceable1.Value); + } - [Fact] - public void TestConcreteReferenceConcreteObjectSerialization() - { - var obj = new Container { Referenceable1 = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" } }; - obj.Referenceable2 = obj.Referenceable1; - var objectReferences = new YamlAssetMetadata(); - var path = new YamlAssetPath(); - path.PushMember(nameof(Container.Referenceable2)); - objectReferences.Set(path, obj.Referenceable2.Id); - var yaml = SerializeAsString(obj, objectReferences); - Assert.Equal(ConcreteReferenceConcreteObjectYaml, yaml); - } + [Fact] + public void TestConcreteReferenceConcreteObjectSerialization() + { + var obj = new Container { Referenceable1 = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" } }; + obj.Referenceable2 = obj.Referenceable1; + var objectReferences = new YamlAssetMetadata(); + var path = new YamlAssetPath(); + path.PushMember(nameof(Container.Referenceable2)); + objectReferences.Set(path, obj.Referenceable2.Id); + var yaml = SerializeAsString(obj, objectReferences); + Assert.Equal(ConcreteReferenceConcreteObjectYaml, yaml); + } - [Fact] - public void TestConcreteReferenceConcreteObjectDeserialization() - { - var obj = (Container)Deserialize(ConcreteReferenceConcreteObjectYaml); - Assert.NotNull(obj.Referenceable1); - Assert.NotNull(obj.Referenceable2); - Assert.Equal(obj.Referenceable1, obj.Referenceable2); - Assert.Null(obj.Referenceable3); - Assert.Null(obj.Referenceable4); - Assert.Equal(GuidGenerator.Get(1), obj.Referenceable1.Id); - Assert.Equal("Test", obj.Referenceable1.Value); - } + [Fact] + public void TestConcreteReferenceConcreteObjectDeserialization() + { + var obj = (Container)Deserialize(ConcreteReferenceConcreteObjectYaml); + Assert.NotNull(obj.Referenceable1); + Assert.NotNull(obj.Referenceable2); + Assert.Equal(obj.Referenceable1, obj.Referenceable2); + Assert.Null(obj.Referenceable3); + Assert.Null(obj.Referenceable4); + Assert.Equal(GuidGenerator.Get(1), obj.Referenceable1.Id); + Assert.Equal("Test", obj.Referenceable1.Value); + } - [Fact] - public void TestAbstractReferenceConcreteObjectSerialization() - { - var obj = new Container { Referenceable1 = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" } }; - obj.Referenceable3 = obj.Referenceable1; - var objectReferences = new YamlAssetMetadata(); - var path = new YamlAssetPath(); - path.PushMember(nameof(Container.Referenceable3)); - objectReferences.Set(path, obj.Referenceable3.Id); - var yaml = SerializeAsString(obj, objectReferences); - Assert.Equal(AbstractReferenceConcreteObjectYaml, yaml); - } + [Fact] + public void TestAbstractReferenceConcreteObjectSerialization() + { + var obj = new Container { Referenceable1 = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" } }; + obj.Referenceable3 = obj.Referenceable1; + var objectReferences = new YamlAssetMetadata(); + var path = new YamlAssetPath(); + path.PushMember(nameof(Container.Referenceable3)); + objectReferences.Set(path, obj.Referenceable3.Id); + var yaml = SerializeAsString(obj, objectReferences); + Assert.Equal(AbstractReferenceConcreteObjectYaml, yaml); + } - [Fact] - public void TestAbstractReferenceConcreteObjectDeserialization() - { - var obj = (Container)Deserialize(AbstractReferenceConcreteObjectYaml); - Assert.NotNull(obj.Referenceable1); - Assert.NotNull(obj.Referenceable3); - Assert.Equal(obj.Referenceable1, obj.Referenceable3); - Assert.Null(obj.Referenceable2); - Assert.Null(obj.Referenceable4); - Assert.Equal(GuidGenerator.Get(1), obj.Referenceable1.Id); - Assert.Equal("Test", obj.Referenceable1.Value); - } + [Fact] + public void TestAbstractReferenceConcreteObjectDeserialization() + { + var obj = (Container)Deserialize(AbstractReferenceConcreteObjectYaml); + Assert.NotNull(obj.Referenceable1); + Assert.NotNull(obj.Referenceable3); + Assert.Equal(obj.Referenceable1, obj.Referenceable3); + Assert.Null(obj.Referenceable2); + Assert.Null(obj.Referenceable4); + Assert.Equal(GuidGenerator.Get(1), obj.Referenceable1.Id); + Assert.Equal("Test", obj.Referenceable1.Value); + } - [Fact] - public void TestConcreteReferenceObjectAbstractSerialization() - { - var obj = new Container { Referenceable3 = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" } }; - obj.Referenceable2 = (Referenceable)obj.Referenceable3; - var objectReferences = new YamlAssetMetadata(); - var path = new YamlAssetPath(); - path.PushMember(nameof(Container.Referenceable2)); - objectReferences.Set(path, obj.Referenceable2.Id); - var yaml = SerializeAsString(obj, objectReferences); - Assert.Equal(ConcreteReferenceAbstractObjectYaml, yaml); - } + [Fact] + public void TestConcreteReferenceObjectAbstractSerialization() + { + var obj = new Container { Referenceable3 = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" } }; + obj.Referenceable2 = (Referenceable)obj.Referenceable3; + var objectReferences = new YamlAssetMetadata(); + var path = new YamlAssetPath(); + path.PushMember(nameof(Container.Referenceable2)); + objectReferences.Set(path, obj.Referenceable2.Id); + var yaml = SerializeAsString(obj, objectReferences); + Assert.Equal(ConcreteReferenceAbstractObjectYaml, yaml); + } - [Fact] - public void TestConcreteReferenceObjectAbstractDeserialization() - { - var obj = (Container)Deserialize(ConcreteReferenceAbstractObjectYaml); - Assert.NotNull(obj.Referenceable2); - Assert.NotNull(obj.Referenceable3); - Assert.Equal(obj.Referenceable2, obj.Referenceable3); - Assert.Null(obj.Referenceable1); - Assert.Null(obj.Referenceable4); - Assert.Equal(GuidGenerator.Get(1), obj.Referenceable2.Id); - Assert.Equal("Test", obj.Referenceable2.Value); - } + [Fact] + public void TestConcreteReferenceObjectAbstractDeserialization() + { + var obj = (Container)Deserialize(ConcreteReferenceAbstractObjectYaml); + Assert.NotNull(obj.Referenceable2); + Assert.NotNull(obj.Referenceable3); + Assert.Equal(obj.Referenceable2, obj.Referenceable3); + Assert.Null(obj.Referenceable1); + Assert.Null(obj.Referenceable4); + Assert.Equal(GuidGenerator.Get(1), obj.Referenceable2.Id); + Assert.Equal("Test", obj.Referenceable2.Value); + } - [Fact] - public void TestAbstracteferenceObjectAbstractSerialization() - { - var obj = new Container { Referenceable3 = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" } }; - obj.Referenceable4 = (Referenceable)obj.Referenceable3; - var objectReferences = new YamlAssetMetadata(); - var path = new YamlAssetPath(); - path.PushMember(nameof(Container.Referenceable4)); - objectReferences.Set(path, obj.Referenceable4.Id); - var yaml = SerializeAsString(obj, objectReferences); - Assert.Equal(AbstractReferenceAbstractObjectYaml, yaml); - } + [Fact] + public void TestAbstracteferenceObjectAbstractSerialization() + { + var obj = new Container { Referenceable3 = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" } }; + obj.Referenceable4 = (Referenceable)obj.Referenceable3; + var objectReferences = new YamlAssetMetadata(); + var path = new YamlAssetPath(); + path.PushMember(nameof(Container.Referenceable4)); + objectReferences.Set(path, obj.Referenceable4.Id); + var yaml = SerializeAsString(obj, objectReferences); + Assert.Equal(AbstractReferenceAbstractObjectYaml, yaml); + } - [Fact] - public void TestAbstracteferenceObjectAbstractDeserialization() - { - var obj = (Container)Deserialize(AbstractReferenceAbstractObjectYaml); - Assert.NotNull(obj.Referenceable3); - Assert.NotNull(obj.Referenceable4); - Assert.Equal(obj.Referenceable3, obj.Referenceable4); - Assert.Null(obj.Referenceable1); - Assert.Null(obj.Referenceable2); - Assert.Equal(GuidGenerator.Get(1), obj.Referenceable3.Id); - Assert.Equal("Test", obj.Referenceable3.Value); - } + [Fact] + public void TestAbstracteferenceObjectAbstractDeserialization() + { + var obj = (Container)Deserialize(AbstractReferenceAbstractObjectYaml); + Assert.NotNull(obj.Referenceable3); + Assert.NotNull(obj.Referenceable4); + Assert.Equal(obj.Referenceable3, obj.Referenceable4); + Assert.Null(obj.Referenceable1); + Assert.Null(obj.Referenceable2); + Assert.Equal(GuidGenerator.Get(1), obj.Referenceable3.Id); + Assert.Equal("Test", obj.Referenceable3.Value); + } - [Fact] - public void TestConcreteReferenceableListSerialization() - { - var obj = new CollectionContainer(); - var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; - obj.ConcreteRefList.Add(item); - obj.ConcreteRefList.Add(item); - var objectReferences = new YamlAssetMetadata(); - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.ConcreteRefList); - ids[0] = IdentifierGenerator.Get(1); - ids[1] = IdentifierGenerator.Get(2); - var path = new YamlAssetPath(); - path.PushMember(nameof(CollectionContainer.ConcreteRefList)); - path.PushItemId(IdentifierGenerator.Get(1)); - objectReferences.Set(path, GuidGenerator.Get(1)); - var yaml = SerializeAsString(obj, objectReferences); - Assert.Equal(ConcreteReferenceableListYaml, yaml); - } + [Fact] + public void TestConcreteReferenceableListSerialization() + { + var obj = new CollectionContainer(); + var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; + obj.ConcreteRefList.Add(item); + obj.ConcreteRefList.Add(item); + var objectReferences = new YamlAssetMetadata(); + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.ConcreteRefList); + ids[0] = IdentifierGenerator.Get(1); + ids[1] = IdentifierGenerator.Get(2); + var path = new YamlAssetPath(); + path.PushMember(nameof(CollectionContainer.ConcreteRefList)); + path.PushItemId(IdentifierGenerator.Get(1)); + objectReferences.Set(path, GuidGenerator.Get(1)); + var yaml = SerializeAsString(obj, objectReferences); + Assert.Equal(ConcreteReferenceableListYaml, yaml); + } - [Fact] - public void TestConcreteReferenceableListDeserialization() - { - var obj = (CollectionContainer)Deserialize(ConcreteReferenceableListYaml); - Assert.NotNull(obj.ConcreteRefList); - Assert.Equal(2, obj.ConcreteRefList.Count); - Assert.Equal(obj.ConcreteRefList[0], obj.ConcreteRefList[1]); - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.ConcreteRefList); - Assert.Equal(IdentifierGenerator.Get(1), ids[0]); - Assert.Equal(IdentifierGenerator.Get(2), ids[1]); - Assert.Equal(GuidGenerator.Get(1), obj.ConcreteRefList[0].Id); - Assert.Equal("Test", obj.ConcreteRefList[0].Value); - } + [Fact] + public void TestConcreteReferenceableListDeserialization() + { + var obj = (CollectionContainer)Deserialize(ConcreteReferenceableListYaml); + Assert.NotNull(obj.ConcreteRefList); + Assert.Equal(2, obj.ConcreteRefList.Count); + Assert.Equal(obj.ConcreteRefList[0], obj.ConcreteRefList[1]); + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.ConcreteRefList); + Assert.Equal(IdentifierGenerator.Get(1), ids[0]); + Assert.Equal(IdentifierGenerator.Get(2), ids[1]); + Assert.Equal(GuidGenerator.Get(1), obj.ConcreteRefList[0].Id); + Assert.Equal("Test", obj.ConcreteRefList[0].Value); + } - [Fact] - public void TestAbstractReferenceableListSerialization() - { - var obj = new CollectionContainer(); - var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; - obj.AbstractRefList.Add(item); - obj.AbstractRefList.Add(item); - var objectReferences = new YamlAssetMetadata(); - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.AbstractRefList); - ids[0] = IdentifierGenerator.Get(1); - ids[1] = IdentifierGenerator.Get(2); - var path = new YamlAssetPath(); - path.PushMember(nameof(CollectionContainer.AbstractRefList)); - path.PushItemId(IdentifierGenerator.Get(1)); - objectReferences.Set(path, GuidGenerator.Get(1)); - var yaml = SerializeAsString(obj, objectReferences); - Assert.Equal(AbstractReferenceableListYaml, yaml); - } + [Fact] + public void TestAbstractReferenceableListSerialization() + { + var obj = new CollectionContainer(); + var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; + obj.AbstractRefList.Add(item); + obj.AbstractRefList.Add(item); + var objectReferences = new YamlAssetMetadata(); + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.AbstractRefList); + ids[0] = IdentifierGenerator.Get(1); + ids[1] = IdentifierGenerator.Get(2); + var path = new YamlAssetPath(); + path.PushMember(nameof(CollectionContainer.AbstractRefList)); + path.PushItemId(IdentifierGenerator.Get(1)); + objectReferences.Set(path, GuidGenerator.Get(1)); + var yaml = SerializeAsString(obj, objectReferences); + Assert.Equal(AbstractReferenceableListYaml, yaml); + } - [Fact] - public void TestAbstractReferenceableListDeserialization() - { - var obj = (CollectionContainer)Deserialize(AbstractReferenceableListYaml); - Assert.NotNull(obj.AbstractRefList); - Assert.Equal(2, obj.AbstractRefList.Count); - Assert.Equal(obj.AbstractRefList[0], obj.AbstractRefList[1]); - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.AbstractRefList); - Assert.Equal(IdentifierGenerator.Get(1), ids[0]); - Assert.Equal(IdentifierGenerator.Get(2), ids[1]); - Assert.Equal(GuidGenerator.Get(1), obj.AbstractRefList[0].Id); - Assert.Equal("Test", obj.AbstractRefList[0].Value); - } + [Fact] + public void TestAbstractReferenceableListDeserialization() + { + var obj = (CollectionContainer)Deserialize(AbstractReferenceableListYaml); + Assert.NotNull(obj.AbstractRefList); + Assert.Equal(2, obj.AbstractRefList.Count); + Assert.Equal(obj.AbstractRefList[0], obj.AbstractRefList[1]); + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.AbstractRefList); + Assert.Equal(IdentifierGenerator.Get(1), ids[0]); + Assert.Equal(IdentifierGenerator.Get(2), ids[1]); + Assert.Equal(GuidGenerator.Get(1), obj.AbstractRefList[0].Id); + Assert.Equal("Test", obj.AbstractRefList[0].Value); + } - [Fact] - public void TestConcreteReferenceableDictionarySerialization() - { - var obj = new CollectionContainer(); - var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; - obj.ConcreteRefDictionary.Add("Item1", item); - obj.ConcreteRefDictionary.Add("Item2", item); - var objectReferences = new YamlAssetMetadata(); - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.ConcreteRefDictionary); - ids["Item1"] = IdentifierGenerator.Get(1); - ids["Item2"] = IdentifierGenerator.Get(2); - var path = new YamlAssetPath(); - path.PushMember(nameof(CollectionContainer.ConcreteRefDictionary)); - path.PushItemId(IdentifierGenerator.Get(1)); - objectReferences.Set(path, GuidGenerator.Get(1)); - var yaml = SerializeAsString(obj, objectReferences); - Assert.Equal(ConcreteReferenceableDictionaryYaml, yaml); - } + [Fact] + public void TestConcreteReferenceableDictionarySerialization() + { + var obj = new CollectionContainer(); + var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; + obj.ConcreteRefDictionary.Add("Item1", item); + obj.ConcreteRefDictionary.Add("Item2", item); + var objectReferences = new YamlAssetMetadata(); + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.ConcreteRefDictionary); + ids["Item1"] = IdentifierGenerator.Get(1); + ids["Item2"] = IdentifierGenerator.Get(2); + var path = new YamlAssetPath(); + path.PushMember(nameof(CollectionContainer.ConcreteRefDictionary)); + path.PushItemId(IdentifierGenerator.Get(1)); + objectReferences.Set(path, GuidGenerator.Get(1)); + var yaml = SerializeAsString(obj, objectReferences); + Assert.Equal(ConcreteReferenceableDictionaryYaml, yaml); + } - [Fact] - public void TestConcreteReferenceableDictionaryDeserialization() - { - var obj = (CollectionContainer)Deserialize(ConcreteReferenceableDictionaryYaml); - Assert.NotNull(obj.ConcreteRefDictionary); - Assert.Equal(2, obj.ConcreteRefDictionary.Count); - Assert.Equal(obj.ConcreteRefDictionary["Item1"], obj.ConcreteRefDictionary["Item2"]); - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.ConcreteRefDictionary); - Assert.Equal(IdentifierGenerator.Get(1), ids["Item1"]); - Assert.Equal(IdentifierGenerator.Get(2), ids["Item2"]); - Assert.Equal(GuidGenerator.Get(1), obj.ConcreteRefDictionary["Item1"].Id); - Assert.Equal("Test", obj.ConcreteRefDictionary["Item1"].Value); - } + [Fact] + public void TestConcreteReferenceableDictionaryDeserialization() + { + var obj = (CollectionContainer)Deserialize(ConcreteReferenceableDictionaryYaml); + Assert.NotNull(obj.ConcreteRefDictionary); + Assert.Equal(2, obj.ConcreteRefDictionary.Count); + Assert.Equal(obj.ConcreteRefDictionary["Item1"], obj.ConcreteRefDictionary["Item2"]); + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.ConcreteRefDictionary); + Assert.Equal(IdentifierGenerator.Get(1), ids["Item1"]); + Assert.Equal(IdentifierGenerator.Get(2), ids["Item2"]); + Assert.Equal(GuidGenerator.Get(1), obj.ConcreteRefDictionary["Item1"].Id); + Assert.Equal("Test", obj.ConcreteRefDictionary["Item1"].Value); + } - [Fact] - public void TestAbstractReferenceableDictionarySerialization() - { - var obj = new CollectionContainer(); - var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; - obj.AbstractRefDictionary.Add("Item1", item); - obj.AbstractRefDictionary.Add("Item2", item); - var objectReferences = new YamlAssetMetadata(); - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.AbstractRefDictionary); - ids["Item1"] = IdentifierGenerator.Get(1); - ids["Item2"] = IdentifierGenerator.Get(2); - var path = new YamlAssetPath(); - path.PushMember(nameof(CollectionContainer.AbstractRefDictionary)); - path.PushItemId(IdentifierGenerator.Get(1)); - objectReferences.Set(path, GuidGenerator.Get(1)); - var yaml = SerializeAsString(obj, objectReferences); - Assert.Equal(AbstractReferenceableDictionaryYaml, yaml); - } + [Fact] + public void TestAbstractReferenceableDictionarySerialization() + { + var obj = new CollectionContainer(); + var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; + obj.AbstractRefDictionary.Add("Item1", item); + obj.AbstractRefDictionary.Add("Item2", item); + var objectReferences = new YamlAssetMetadata(); + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.AbstractRefDictionary); + ids["Item1"] = IdentifierGenerator.Get(1); + ids["Item2"] = IdentifierGenerator.Get(2); + var path = new YamlAssetPath(); + path.PushMember(nameof(CollectionContainer.AbstractRefDictionary)); + path.PushItemId(IdentifierGenerator.Get(1)); + objectReferences.Set(path, GuidGenerator.Get(1)); + var yaml = SerializeAsString(obj, objectReferences); + Assert.Equal(AbstractReferenceableDictionaryYaml, yaml); + } - [Fact] - public void TestAbstractReferenceableDictionaryDeserialization() - { - var obj = (CollectionContainer)Deserialize(AbstractReferenceableDictionaryYaml); - Assert.NotNull(obj.AbstractRefDictionary); - Assert.Equal(2, obj.AbstractRefDictionary.Count); - Assert.Equal(obj.AbstractRefDictionary["Item1"], obj.AbstractRefDictionary["Item2"]); - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.AbstractRefDictionary); - Assert.Equal(IdentifierGenerator.Get(1), ids["Item1"]); - Assert.Equal(IdentifierGenerator.Get(2), ids["Item2"]); - Assert.Equal(GuidGenerator.Get(1), obj.AbstractRefDictionary["Item1"].Id); - Assert.Equal("Test", obj.AbstractRefDictionary["Item1"].Value); - } + [Fact] + public void TestAbstractReferenceableDictionaryDeserialization() + { + var obj = (CollectionContainer)Deserialize(AbstractReferenceableDictionaryYaml); + Assert.NotNull(obj.AbstractRefDictionary); + Assert.Equal(2, obj.AbstractRefDictionary.Count); + Assert.Equal(obj.AbstractRefDictionary["Item1"], obj.AbstractRefDictionary["Item2"]); + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.AbstractRefDictionary); + Assert.Equal(IdentifierGenerator.Get(1), ids["Item1"]); + Assert.Equal(IdentifierGenerator.Get(2), ids["Item2"]); + Assert.Equal(GuidGenerator.Get(1), obj.AbstractRefDictionary["Item1"].Id); + Assert.Equal("Test", obj.AbstractRefDictionary["Item1"].Value); + } - [Fact] - public void TestConcreteNonIdentifiableReferenceableListSerialization() - { - var obj = new NonIdentifiableCollectionContainer(); - var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; - obj.ConcreteRefList.Add(item); - obj.ConcreteRefList.Add(item); - var objectReferences = new YamlAssetMetadata(); - var path = new YamlAssetPath(); - path.PushMember(nameof(CollectionContainer.ConcreteRefList)); - path.PushIndex(0); - objectReferences.Set(path, GuidGenerator.Get(1)); - var yaml = SerializeAsString(obj, objectReferences); - Assert.Equal(ConcreteNonIdentifiableReferenceableListYaml, yaml); - } + [Fact] + public void TestConcreteNonIdentifiableReferenceableListSerialization() + { + var obj = new NonIdentifiableCollectionContainer(); + var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; + obj.ConcreteRefList.Add(item); + obj.ConcreteRefList.Add(item); + var objectReferences = new YamlAssetMetadata(); + var path = new YamlAssetPath(); + path.PushMember(nameof(CollectionContainer.ConcreteRefList)); + path.PushIndex(0); + objectReferences.Set(path, GuidGenerator.Get(1)); + var yaml = SerializeAsString(obj, objectReferences); + Assert.Equal(ConcreteNonIdentifiableReferenceableListYaml, yaml); + } - [Fact] - public void TestConcreteNonIdentifiableReferenceableListDeserialization() - { - var obj = (NonIdentifiableCollectionContainer)Deserialize(ConcreteNonIdentifiableReferenceableListYaml); - Assert.NotNull(obj.ConcreteRefList); - Assert.Equal(2, obj.ConcreteRefList.Count); - Assert.Equal(obj.ConcreteRefList[0], obj.ConcreteRefList[1]); - Assert.Equal(GuidGenerator.Get(1), obj.ConcreteRefList[0].Id); - Assert.Equal("Test", obj.ConcreteRefList[0].Value); - } + [Fact] + public void TestConcreteNonIdentifiableReferenceableListDeserialization() + { + var obj = (NonIdentifiableCollectionContainer)Deserialize(ConcreteNonIdentifiableReferenceableListYaml); + Assert.NotNull(obj.ConcreteRefList); + Assert.Equal(2, obj.ConcreteRefList.Count); + Assert.Equal(obj.ConcreteRefList[0], obj.ConcreteRefList[1]); + Assert.Equal(GuidGenerator.Get(1), obj.ConcreteRefList[0].Id); + Assert.Equal("Test", obj.ConcreteRefList[0].Value); + } - [Fact] - public void TestAbstractNonIdentifiableReferenceableListSerialization() - { - var obj = new NonIdentifiableCollectionContainer(); - var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; - obj.AbstractRefList.Add(item); - obj.AbstractRefList.Add(item); - var objectReferences = new YamlAssetMetadata(); - var path = new YamlAssetPath(); - path.PushMember(nameof(CollectionContainer.AbstractRefList)); - path.PushIndex(0); - objectReferences.Set(path, GuidGenerator.Get(1)); - var yaml = SerializeAsString(obj, objectReferences); - Assert.Equal(AbstractNonIdentifiableReferenceableListYaml, yaml); - } + [Fact] + public void TestAbstractNonIdentifiableReferenceableListSerialization() + { + var obj = new NonIdentifiableCollectionContainer(); + var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; + obj.AbstractRefList.Add(item); + obj.AbstractRefList.Add(item); + var objectReferences = new YamlAssetMetadata(); + var path = new YamlAssetPath(); + path.PushMember(nameof(CollectionContainer.AbstractRefList)); + path.PushIndex(0); + objectReferences.Set(path, GuidGenerator.Get(1)); + var yaml = SerializeAsString(obj, objectReferences); + Assert.Equal(AbstractNonIdentifiableReferenceableListYaml, yaml); + } - [Fact] - public void TestAbstractNonIdentifiableReferenceableListDeserialization() - { - var obj = (NonIdentifiableCollectionContainer)Deserialize(AbstractNonIdentifiableReferenceableListYaml); - Assert.NotNull(obj.AbstractRefList); - Assert.Equal(2, obj.AbstractRefList.Count); - Assert.Equal(obj.AbstractRefList[0], obj.AbstractRefList[1]); - Assert.Equal(GuidGenerator.Get(1), obj.AbstractRefList[0].Id); - Assert.Equal("Test", obj.AbstractRefList[0].Value); - } + [Fact] + public void TestAbstractNonIdentifiableReferenceableListDeserialization() + { + var obj = (NonIdentifiableCollectionContainer)Deserialize(AbstractNonIdentifiableReferenceableListYaml); + Assert.NotNull(obj.AbstractRefList); + Assert.Equal(2, obj.AbstractRefList.Count); + Assert.Equal(obj.AbstractRefList[0], obj.AbstractRefList[1]); + Assert.Equal(GuidGenerator.Get(1), obj.AbstractRefList[0].Id); + Assert.Equal("Test", obj.AbstractRefList[0].Value); + } - [Fact] - public void TestConcreteNonIdentifiableReferenceableDictionarySerialization() - { - var obj = new NonIdentifiableCollectionContainer(); - var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; - obj.ConcreteRefDictionary.Add("Item1", item); - obj.ConcreteRefDictionary.Add("Item2", item); - var objectReferences = new YamlAssetMetadata(); - var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.ConcreteRefDictionary); - ids["Item1"] = IdentifierGenerator.Get(1); - ids["Item2"] = IdentifierGenerator.Get(2); - var path = new YamlAssetPath(); - path.PushMember(nameof(CollectionContainer.ConcreteRefDictionary)); - path.PushIndex("Item1"); - objectReferences.Set(path, GuidGenerator.Get(1)); - var yaml = SerializeAsString(obj, objectReferences); - Assert.Equal(ConcreteNonIdentifiableReferenceableDictionaryYaml, yaml); - } + [Fact] + public void TestConcreteNonIdentifiableReferenceableDictionarySerialization() + { + var obj = new NonIdentifiableCollectionContainer(); + var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; + obj.ConcreteRefDictionary.Add("Item1", item); + obj.ConcreteRefDictionary.Add("Item2", item); + var objectReferences = new YamlAssetMetadata(); + var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.ConcreteRefDictionary); + ids["Item1"] = IdentifierGenerator.Get(1); + ids["Item2"] = IdentifierGenerator.Get(2); + var path = new YamlAssetPath(); + path.PushMember(nameof(CollectionContainer.ConcreteRefDictionary)); + path.PushIndex("Item1"); + objectReferences.Set(path, GuidGenerator.Get(1)); + var yaml = SerializeAsString(obj, objectReferences); + Assert.Equal(ConcreteNonIdentifiableReferenceableDictionaryYaml, yaml); + } - [Fact] - public void TestConcreteNonIdentifiableReferenceableDictionaryDeserialization() - { - var obj = (NonIdentifiableCollectionContainer)Deserialize(ConcreteNonIdentifiableReferenceableDictionaryYaml); - Assert.NotNull(obj.ConcreteRefDictionary); - Assert.Equal(2, obj.ConcreteRefDictionary.Count); - Assert.Equal(obj.ConcreteRefDictionary["Item1"], obj.ConcreteRefDictionary["Item2"]); - Assert.Equal(GuidGenerator.Get(1), obj.ConcreteRefDictionary["Item1"].Id); - Assert.Equal("Test", obj.ConcreteRefDictionary["Item1"].Value); - } + [Fact] + public void TestConcreteNonIdentifiableReferenceableDictionaryDeserialization() + { + var obj = (NonIdentifiableCollectionContainer)Deserialize(ConcreteNonIdentifiableReferenceableDictionaryYaml); + Assert.NotNull(obj.ConcreteRefDictionary); + Assert.Equal(2, obj.ConcreteRefDictionary.Count); + Assert.Equal(obj.ConcreteRefDictionary["Item1"], obj.ConcreteRefDictionary["Item2"]); + Assert.Equal(GuidGenerator.Get(1), obj.ConcreteRefDictionary["Item1"].Id); + Assert.Equal("Test", obj.ConcreteRefDictionary["Item1"].Value); + } - [Fact] - public void TestAbstractNonIdentifiableReferenceableDictionarySerialization() - { - var obj = new NonIdentifiableCollectionContainer(); - var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; - obj.AbstractRefDictionary.Add("Item1", item); - obj.AbstractRefDictionary.Add("Item2", item); - var objectReferences = new YamlAssetMetadata(); - var path = new YamlAssetPath(); - path.PushMember(nameof(CollectionContainer.AbstractRefDictionary)); - path.PushIndex("Item1"); - objectReferences.Set(path, GuidGenerator.Get(1)); - var yaml = SerializeAsString(obj, objectReferences); - Assert.Equal(AbstractNonIdentifiableReferenceableDictionaryYaml, yaml); - } + [Fact] + public void TestAbstractNonIdentifiableReferenceableDictionarySerialization() + { + var obj = new NonIdentifiableCollectionContainer(); + var item = new Referenceable { Id = GuidGenerator.Get(1), Value = "Test" }; + obj.AbstractRefDictionary.Add("Item1", item); + obj.AbstractRefDictionary.Add("Item2", item); + var objectReferences = new YamlAssetMetadata(); + var path = new YamlAssetPath(); + path.PushMember(nameof(CollectionContainer.AbstractRefDictionary)); + path.PushIndex("Item1"); + objectReferences.Set(path, GuidGenerator.Get(1)); + var yaml = SerializeAsString(obj, objectReferences); + Assert.Equal(AbstractNonIdentifiableReferenceableDictionaryYaml, yaml); + } - [Fact] - public void TestAbstractNonIdentifiableReferenceableDictionaryDeserialization() - { - var obj = (NonIdentifiableCollectionContainer)Deserialize(AbstractNonIdentifiableReferenceableDictionaryYaml); - Assert.NotNull(obj.AbstractRefDictionary); - Assert.Equal(2, obj.AbstractRefDictionary.Count); - Assert.Equal(obj.AbstractRefDictionary["Item1"], obj.AbstractRefDictionary["Item2"]); - Assert.Equal(GuidGenerator.Get(1), obj.AbstractRefDictionary["Item1"].Id); - Assert.Equal("Test", obj.AbstractRefDictionary["Item1"].Value); - } + [Fact] + public void TestAbstractNonIdentifiableReferenceableDictionaryDeserialization() + { + var obj = (NonIdentifiableCollectionContainer)Deserialize(AbstractNonIdentifiableReferenceableDictionaryYaml); + Assert.NotNull(obj.AbstractRefDictionary); + Assert.Equal(2, obj.AbstractRefDictionary.Count); + Assert.Equal(obj.AbstractRefDictionary["Item1"], obj.AbstractRefDictionary["Item2"]); + Assert.Equal(GuidGenerator.Get(1), obj.AbstractRefDictionary["Item1"].Id); + Assert.Equal("Test", obj.AbstractRefDictionary["Item1"].Value); + } - private static string SerializeAsString(object instance, YamlAssetMetadata objectReferences) + private static string SerializeAsString(object instance, YamlAssetMetadata? objectReferences) + { + using var stream = new MemoryStream(); + var metadata = new AttachedYamlAssetMetadata(); + if (objectReferences != null) { - using (var stream = new MemoryStream()) - { - var metadata = new AttachedYamlAssetMetadata(); - if (objectReferences != null) - { - metadata.AttachMetadata(AssetObjectSerializerBackend.ObjectReferencesKey, objectReferences); - } - - new YamlAssetSerializer().Save(stream, instance, metadata); - stream.Flush(); - stream.Position = 0; - return new StreamReader(stream).ReadToEnd(); - } + metadata.AttachMetadata(AssetObjectSerializerBackend.ObjectReferencesKey, objectReferences); } - private static object Deserialize(string yaml) - { - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(yaml); - writer.Flush(); - stream.Position = 0; - bool aliasOccurred; - AttachedYamlAssetMetadata metadata; - var instance = new YamlAssetSerializer().Load(stream, "MyAsset", null, true, out aliasOccurred, out metadata); - return instance; - } + new YamlAssetSerializer().Save(stream, instance, metadata); + stream.Flush(); + stream.Position = 0; + return new StreamReader(stream).ReadToEnd(); + } + + private static object Deserialize(string yaml) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(yaml); + writer.Flush(); + stream.Position = 0; + var instance = new YamlAssetSerializer().Load(stream, "MyAsset", null, true, out _, out _); + return instance; } } diff --git a/sources/assets/Stride.Core.Assets.Tests/Yaml/TestUFile.cs b/sources/assets/Stride.Core.Assets.Tests/Yaml/TestUFile.cs index fad6a0fb41..c096b1bc97 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Yaml/TestUFile.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Yaml/TestUFile.cs @@ -1,80 +1,74 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Xunit; -using Stride.Core; -using Stride.Core.Annotations; -using Stride.Core.Reflection; + using Stride.Core.Yaml; using Stride.Core.IO; -namespace Stride.Core.Assets.Tests.Yaml +namespace Stride.Core.Assets.Tests.Yaml; + +public class TestUFile { - public class TestUFile + [DataContract("TestUFileClass")] + public class TestUFileClass { - [DataContract("TestUFileClass")] - public class TestUFileClass - { - public UFile File { get; set; } - } + public UFile? File { get; set; } + } - private const string YamlWithFileTag = @"!TestUFileClass -File: !file test.txt -"; + private const string YamlWithFileTag = + """ + !TestUFileClass + File: !file test.txt - private const string YamlWithoutFileTag = @"!TestUFileClass -File: test.txt -"; + """; - private static string Serialize(object instance) - { - using (var stream = new MemoryStream()) - { - AssetYamlSerializer.Default.Serialize(stream, instance); - stream.Flush(); - stream.Position = 0; - return new StreamReader(stream).ReadToEnd(); - } - } + private const string YamlWithoutFileTag = + """ + !TestUFileClass + File: test.txt + + """; + + private static string Serialize(object instance) + { + using var stream = new MemoryStream(); + AssetYamlSerializer.Default.Serialize(stream, instance); + stream.Flush(); + stream.Position = 0; + return new StreamReader(stream).ReadToEnd(); + } - private static T Deserialize(string data) - { - using (var stream = new MemoryStream()) - { - var streamWriter = new StreamWriter(stream); - streamWriter.Write(data); - streamWriter.Flush(); - stream.Position = 0; + private static T Deserialize(string data) + { + using var stream = new MemoryStream(); + var streamWriter = new StreamWriter(stream); + streamWriter.Write(data); + streamWriter.Flush(); + stream.Position = 0; - return (T)AssetYamlSerializer.Default.Deserialize(stream, typeof(T)); - } - } + return (T)AssetYamlSerializer.Default.Deserialize(stream, typeof(T)); + } - [Fact] - public void TestWithFileTagRoundtrip() - { - // Check deserialization works - var obj = Deserialize(YamlWithFileTag); - Assert.Equal("test.txt", obj.File); + [Fact] + public void TestWithFileTagRoundtrip() + { + // Check deserialization works + var obj = Deserialize(YamlWithFileTag); + Assert.Equal("test.txt", obj.File); - // Check object is serialized back properly - var yaml = Serialize(obj); - Assert.Equal(YamlWithFileTag, yaml); - } + // Check object is serialized back properly + var yaml = Serialize(obj); + Assert.Equal(YamlWithFileTag, yaml); + } - [Fact] - public void TestWithoutFileTagRoundtrip() - { - // Check deserialization works - var obj = Deserialize(YamlWithoutFileTag); - Assert.Equal("test.txt", obj.File); + [Fact] + public void TestWithoutFileTagRoundtrip() + { + // Check deserialization works + var obj = Deserialize(YamlWithoutFileTag); + Assert.Equal("test.txt", obj.File); - // Check object is serialized back properly (it should have the file tag now) - var yaml = Serialize(obj); - Assert.Equal(YamlWithFileTag, yaml); - } + // Check object is serialized back properly (it should have the file tag now) + var yaml = Serialize(obj); + Assert.Equal(YamlWithFileTag, yaml); } } diff --git a/sources/assets/Stride.Core.Assets.Tests/Yaml/TestUnloadable.cs b/sources/assets/Stride.Core.Assets.Tests/Yaml/TestUnloadable.cs index 01a793f1ce..026ebcfd5b 100644 --- a/sources/assets/Stride.Core.Assets.Tests/Yaml/TestUnloadable.cs +++ b/sources/assets/Stride.Core.Assets.Tests/Yaml/TestUnloadable.cs @@ -1,138 +1,138 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Xunit; -using Stride.Core; -using Stride.Core.Annotations; -using Stride.Core.Reflection; + using Stride.Core.Yaml; -namespace Stride.Core.Assets.Tests.Yaml +namespace Stride.Core.Assets.Tests.Yaml; + +public class TestUnloadable { - public class TestUnloadable + [DataContract("UnloadableContainer")] + public class UnloadableContainer + { + public List ObjectList { get; } = []; + public object? ObjectMember { get; set; } + } + + private const string YamlInvalidType = + """ + !UnloadableContainerInvalidType + ObjectMember: value0 + + """; + + private const string YamlUnknownMember = + """ + !UnloadableContainer + UnknownMember: value0 + ObjectMember: value1 + + """; + + private const string YamlInvalidMemberType = + """ + !UnloadableContainer + ObjectMember: !UnknownType value0 + + """; + + private const string YamlInvalidCollectionItemType = + """ + !UnloadableContainer + ObjectList: + 01000000010000000100000001000000: value0 + 02000000020000000200000002000000: !UnknownType value1 + + """; + + private static string Serialize(object instance) + { + using var stream = new MemoryStream(); + AssetYamlSerializer.Default.Serialize(stream, instance); + stream.Flush(); + stream.Position = 0; + return new StreamReader(stream).ReadToEnd(); + } + + private static T Deserialize(string data) + { + using var stream = new MemoryStream(); + var streamWriter = new StreamWriter(stream); + streamWriter.Write(data); + streamWriter.Flush(); + stream.Position = 0; + + return (T)AssetYamlSerializer.Default.Deserialize(stream, typeof(T)); + } + + [Fact] + public void TestInvalidTypeToObject() + { + var obj = Deserialize(YamlInvalidType); + + // Check it's an unloadable object + Assert.IsAssignableFrom(obj); + Assert.False(obj is UnloadableContainer); + + // Make sure it is stable when serialized + Assert.Equal(YamlInvalidType, Serialize(obj)); + } + + [Fact] + public void TestInvalidTypeToGivenType() { - [DataContract("UnloadableContainer")] - public class UnloadableContainer - { - public List ObjectList { get; } = new List(); - public object ObjectMember { get; set; } - } - - private const string YamlInvalidType = @"!UnloadableContainerInvalidType -ObjectMember: value0 -"; - - private const string YamlUnknownMember = @"!UnloadableContainer -UnknownMember: value0 -ObjectMember: value1 -"; - - private const string YamlInvalidMemberType = @"!UnloadableContainer -ObjectMember: !UnknownType value0 -"; - - private const string YamlInvalidCollectionItemType = @"!UnloadableContainer -ObjectList: - 01000000010000000100000001000000: value0 - 02000000020000000200000002000000: !UnknownType value1 -"; - - private static string Serialize(object instance) - { - using (var stream = new MemoryStream()) - { - AssetYamlSerializer.Default.Serialize(stream, instance); - stream.Flush(); - stream.Position = 0; - return new StreamReader(stream).ReadToEnd(); - } - } - - private static T Deserialize(string data) - { - using (var stream = new MemoryStream()) - { - var streamWriter = new StreamWriter(stream); - streamWriter.Write(data); - streamWriter.Flush(); - stream.Position = 0; - - return (T)AssetYamlSerializer.Default.Deserialize(stream, typeof(T)); - } - } - - [Fact] - public void TestInvalidTypeToObject() - { - var obj = Deserialize(YamlInvalidType); - - // Check it's an unloadable object - Assert.IsAssignableFrom(obj); - Assert.False(obj is UnloadableContainer); - - // Make sure it is stable when serialized - Assert.Equal(YamlInvalidType, Serialize(obj)); - } - - [Fact] - public void TestInvalidTypeToGivenType() - { - var obj = Deserialize(YamlInvalidType); - - // Check it's an unloadable object - Assert.IsAssignableFrom(obj); - // Check that properties of the expected type deserialized properly - Assert.Equal("value0", obj.ObjectMember); - - // Make sure it is stable when serialized - Assert.Equal(YamlInvalidType, Serialize(obj)); - } - - [Fact] - public void TestUnknownMember() - { - var obj = Deserialize(YamlUnknownMember); - - // Check it's not an unloadable object - Assert.False(obj is IUnloadable); - // Check that properties of the type deserialized properly - Assert.Equal("value1", obj.ObjectMember); - - // It shouldn't be stable since a member is gone - Assert.NotEqual(YamlInvalidType, Serialize(obj)); - } - - [Fact] - public void TestInvalidMemberType() - { - var obj = Deserialize(YamlInvalidMemberType); - - // Check it's not an unloadable object - Assert.False(obj is IUnloadable); - // But it's member should be - Assert.IsAssignableFrom(obj.ObjectMember); - - // It shouldn't be stable since a member is gone - Assert.NotEqual(YamlInvalidType, Serialize(obj)); - } - - [Fact] - public void TestInvalidCollectionItemType() - { - var obj = Deserialize(YamlInvalidCollectionItemType); - - // Check it's not an unloadable object - Assert.False(obj is IUnloadable); - // Check collection members - Assert.False(obj.ObjectList[0] is IUnloadable); - Assert.Equal("value0", obj.ObjectList[0]); - Assert.True(obj.ObjectList[1] is IUnloadable); - - // It shouldn't be stable since a member is gone - Assert.NotEqual(YamlInvalidType, Serialize(obj)); - } + var obj = Deserialize(YamlInvalidType); + + // Check it's an unloadable object + Assert.IsAssignableFrom(obj); + // Check that properties of the expected type deserialized properly + Assert.Equal("value0", obj.ObjectMember); + + // Make sure it is stable when serialized + Assert.Equal(YamlInvalidType, Serialize(obj)); + } + + [Fact] + public void TestUnknownMember() + { + var obj = Deserialize(YamlUnknownMember); + + // Check it's not an unloadable object + Assert.False(obj is IUnloadable); + // Check that properties of the type deserialized properly + Assert.Equal("value1", obj.ObjectMember); + + // It shouldn't be stable since a member is gone + Assert.NotEqual(YamlInvalidType, Serialize(obj)); + } + + [Fact] + public void TestInvalidMemberType() + { + var obj = Deserialize(YamlInvalidMemberType); + + // Check it's not an unloadable object + Assert.False(obj is IUnloadable); + // But it's member should be + Assert.IsAssignableFrom(obj.ObjectMember); + + // It shouldn't be stable since a member is gone + Assert.NotEqual(YamlInvalidType, Serialize(obj)); + } + + [Fact] + public void TestInvalidCollectionItemType() + { + var obj = Deserialize(YamlInvalidCollectionItemType); + + // Check it's not an unloadable object + Assert.False(obj is IUnloadable); + // Check collection members + Assert.False(obj.ObjectList[0] is IUnloadable); + Assert.Equal("value0", obj.ObjectList[0]); + Assert.True(obj.ObjectList[1] is IUnloadable); + + // It shouldn't be stable since a member is gone + Assert.NotEqual(YamlInvalidType, Serialize(obj)); } } diff --git a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYaml.cs b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYaml.cs index d9c2b3143b..907c4a2518 100644 --- a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYaml.cs +++ b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYaml.cs @@ -1,113 +1,104 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.IO; using System.Text; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// Allows to manipulate dynamically a YAML content. +/// +public class DynamicYaml { + private static readonly SerializerSettings DefaultSettings = new(); + private readonly YamlStream yamlStream; + private DynamicYamlMapping? dynamicRootNode; + /// - /// Allows to manipulate dynamically a YAML content. + /// Initializes a new instance of from the specified stream. /// - public class DynamicYaml + /// A stream that contains a YAML content. The stream will be disposed + /// Dispose the stream when reading the YAML content is done. true by default + public DynamicYaml(Stream stream, bool disposeStream = true) { - private static readonly SerializerSettings DefaultSettings = new SerializerSettings(); - private readonly YamlStream yamlStream; - private DynamicYamlMapping dynamicRootNode; - - /// - /// Initializes a new instance of from the specified stream. - /// - /// A stream that contains a YAML content. The stream will be disposed - /// Dispose the stream when reading the YAML content is done. true by default - public DynamicYaml(Stream stream, bool disposeStream = true) + ArgumentNullException.ThrowIfNull(stream); + // transform the stream into string. + string assetAsString; + try { - if (stream == null) throw new ArgumentNullException(nameof(stream)); - // transform the stream into string. - string assetAsString; - try - { - using (var assetStreamReader = new StreamReader(stream, Encoding.UTF8)) - { - assetAsString = assetStreamReader.ReadToEnd(); - } - } - finally + using var assetStreamReader = new StreamReader(stream, Encoding.UTF8); + assetAsString = assetStreamReader.ReadToEnd(); + } + finally + { + if (disposeStream) { - if (disposeStream) - { - stream.Dispose(); - } + stream.Dispose(); } + } - // Load the asset as a YamlNode object - var input = new StringReader(assetAsString); - yamlStream = new YamlStream(); - yamlStream.Load(input); + // Load the asset as a YamlNode object + var input = new StringReader(assetAsString); + yamlStream = []; + yamlStream.Load(input); - if (yamlStream.Documents.Count != 1 || !(yamlStream.Documents[0].RootNode is YamlMappingNode)) - throw new YamlException("Unable to load the given stream"); - } + if (yamlStream.Documents.Count != 1 || yamlStream.Documents[0].RootNode is not YamlMappingNode) + throw new YamlException("Unable to load the given stream"); + } - /// - /// Initializes a new instance of from the specified stream. - /// - /// A text that contains a YAML content - public DynamicYaml(string text) : this(GetSafeStream(text)) - { - } - /// - /// Gets the root YAML node. - /// - public YamlMappingNode RootNode => (YamlMappingNode)yamlStream.Documents[0].RootNode; + /// + /// Initializes a new instance of from the specified stream. + /// + /// A text that contains a YAML content + public DynamicYaml(string text) : this(GetSafeStream(text)) + { + } + /// + /// Gets the root YAML node. + /// + public YamlMappingNode RootNode => (YamlMappingNode)yamlStream.Documents[0].RootNode; - /// - /// Gets a dynamic YAML node around the . - /// - public dynamic DynamicRootNode => dynamicRootNode ?? (dynamicRootNode = new DynamicYamlMapping(RootNode)); + /// + /// Gets a dynamic YAML node around the . + /// + public dynamic DynamicRootNode => dynamicRootNode ??= new DynamicYamlMapping(RootNode); - /// - /// Writes the content of this YAML node to the specified stream. - /// - /// The stream to output YAML to. - /// The settings to use to generate YAML. If null, a default will be used. - public void WriteTo(Stream stream, SerializerSettings settings) - { - if (stream == null) throw new ArgumentNullException(nameof(stream)); - using (var streamWriter = new StreamWriter(stream)) - { - WriteTo(streamWriter, DefaultSettings); - } - } + /// + /// Writes the content of this YAML node to the specified stream. + /// + /// The stream to output YAML to. + /// The settings to use to generate YAML. If null, a default will be used. + public void WriteTo(Stream stream, SerializerSettings settings) + { + ArgumentNullException.ThrowIfNull(stream); + using var streamWriter = new StreamWriter(stream); + WriteTo(streamWriter, DefaultSettings); + } - /// - /// Writes the content of this YAML node to the specified writer. - /// - /// The writer to output YAML to. - /// The settings to use to generate YAML. If null, a default will be used. - public void WriteTo(TextWriter writer, SerializerSettings settings) - { - if (writer == null) throw new ArgumentNullException(nameof(writer)); - var preferredIndent = (settings ?? DefaultSettings).PreferredIndent; - yamlStream.Save(writer, true, preferredIndent); - writer.Flush(); - } + /// + /// Writes the content of this YAML node to the specified writer. + /// + /// The writer to output YAML to. + /// The settings to use to generate YAML. If null, a default will be used. + public void WriteTo(TextWriter writer, SerializerSettings settings) + { + ArgumentNullException.ThrowIfNull(writer); + var preferredIndent = (settings ?? DefaultSettings).PreferredIndent; + yamlStream.Save(writer, true, preferredIndent); + writer.Flush(); + } - public override string ToString() - { - using (var streamWriter = new StringWriter()) - { - WriteTo(streamWriter, DefaultSettings); - return streamWriter.ToString(); - } - } + public override string ToString() + { + using var streamWriter = new StringWriter(); + WriteTo(streamWriter, DefaultSettings); + return streamWriter.ToString(); + } - private static Stream GetSafeStream(string text) - { - if (text == null) throw new ArgumentNullException(nameof(text)); - return new MemoryStream(Encoding.UTF8.GetBytes(text)); - } + private static MemoryStream GetSafeStream(string text) + { + ArgumentNullException.ThrowIfNull(text); + return new MemoryStream(Encoding.UTF8.GetBytes(text)); } } diff --git a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlArray.cs b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlArray.cs index 474b14a3b6..e2e33725e1 100644 --- a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlArray.cs +++ b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlArray.cs @@ -1,72 +1,70 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; using System.Dynamic; -using System.Linq; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// Dynamic version of . +/// +public class DynamicYamlArray : DynamicYamlObject, IDynamicYamlNode, IEnumerable { - /// - /// Dynamic version of . - /// - public class DynamicYamlArray : DynamicYamlObject, IDynamicYamlNode, IEnumerable - { - internal YamlSequenceNode node; + internal YamlSequenceNode node; - public YamlSequenceNode Node => node; + public YamlSequenceNode Node => node; - public int Count => node.Children.Count; + public int Count => node.Children.Count; - YamlNode IDynamicYamlNode.Node => Node; + YamlNode IDynamicYamlNode.Node => Node; - public DynamicYamlArray(YamlSequenceNode node) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - this.node = node; - } + public DynamicYamlArray(YamlSequenceNode node) + { + ArgumentNullException.ThrowIfNull(node); + this.node = node; + } - IEnumerator IEnumerable.GetEnumerator() - { - return node.Children.Select(ConvertToDynamic).ToArray().GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() + { + return node.Children.Select(ConvertToDynamic).ToArray().GetEnumerator(); + } - public override bool TryConvert(ConvertBinder binder, out object result) + public override bool TryConvert(ConvertBinder binder, out object result) + { + if (binder.Type.IsAssignableFrom(node.GetType())) { - if (binder.Type.IsAssignableFrom(node.GetType())) - { - result = node; - } - else - { - throw new InvalidOperationException(); - } - return true; + result = node; } - - public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) + else { - var key = Convert.ToInt32(indexes[0]); - node.Children[key] = ConvertFromDynamic(value); - return true; + throw new InvalidOperationException(); } + return true; + } - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) - { - var key = Convert.ToInt32(indexes[0]); - result = ConvertToDynamic(node.Children[key]); - return true; - } + public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object? value) + { + var key = Convert.ToInt32(indexes[0]); + node.Children[key] = ConvertFromDynamic(value); + return true; + } - public void Add(object value) - { - node.Children.Add(ConvertFromDynamic(value)); - } + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + { + var key = Convert.ToInt32(indexes[0]); + result = ConvertToDynamic(node.Children[key]); + return true; + } - public void RemoveAt(int index) - { - node.Children.RemoveAt(index); - } + public void Add(object value) + { + node.Children.Add(ConvertFromDynamic(value)); + } + + public void RemoveAt(int index) + { + node.Children.RemoveAt(index); } } diff --git a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlEmpty.cs b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlEmpty.cs index 1a2a74512d..5f6795b623 100644 --- a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlEmpty.cs +++ b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlEmpty.cs @@ -1,12 +1,12 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Yaml + +namespace Stride.Core.Yaml; + +/// +/// Placeholder value to remove keys from . +/// +public class DynamicYamlEmpty : DynamicYamlObject { - /// - /// Placeholder value to remove keys from . - /// - public class DynamicYamlEmpty : DynamicYamlObject - { - public static readonly DynamicYamlEmpty Default = new DynamicYamlEmpty(); - } + public static readonly DynamicYamlEmpty Default = new(); } diff --git a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlMapping.cs b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlMapping.cs index e0006276d7..ec54066605 100644 --- a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlMapping.cs +++ b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlMapping.cs @@ -1,393 +1,377 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; using System.Dynamic; -using System.Linq; using Stride.Core.Reflection; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// Dynamic version of . +/// +public class DynamicYamlMapping : DynamicYamlObject, IDynamicYamlNode, IEnumerable { + private readonly YamlMappingNode node; + /// - /// Dynamic version of . + /// A mapping used between a property name (e.g: MyProperty) and the property name as serialized + /// in YAML taking into account overrides (e.g: MyProperty! or MyProperty* or MyProperty*!) /// - public class DynamicYamlMapping : DynamicYamlObject, IDynamicYamlNode, IEnumerable - { - private readonly YamlMappingNode node; - - /// - /// A mapping used between a property name (e.g: MyProperty) and the property name as serialized - /// in YAML taking into account overrides (e.g: MyProperty! or MyProperty* or MyProperty*!) - /// - /// - /// NOTE that both and and node.Children - /// are kept synchronized. - /// - private Dictionary keyMapping; + /// + /// NOTE that both and and node.Children + /// are kept synchronized. + /// + private Dictionary? keyMapping; - /// - /// A mapping between a property name (e.g: MyProperty) and the current Override type - /// - private Dictionary overrides; + /// + /// A mapping between a property name (e.g: MyProperty) and the current Override type + /// + private Dictionary? overrides; - public YamlMappingNode Node => node; + public YamlMappingNode Node => node; - YamlNode IDynamicYamlNode.Node => Node; + YamlNode IDynamicYamlNode.Node => Node; - public DynamicYamlMapping(YamlMappingNode node) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - this.node = node; - ParseOverrides(); - } + public DynamicYamlMapping(YamlMappingNode node) + { + ArgumentNullException.ThrowIfNull(node); + this.node = node; + ParseOverrides(); + } - public void AddChild(object key, object value) - { - var yamlKey = ConvertFromDynamicForKey(key); - var yamlValue = ConvertFromDynamic(value); + public void AddChild(object key, object value) + { + var yamlKey = ConvertFromDynamicForKey(key); + var yamlValue = ConvertFromDynamic(value); - var keyPosition = node.Children.IndexOf(yamlKey); - if (keyPosition != -1) - return; + var keyPosition = node.Children.IndexOf(yamlKey); + if (keyPosition != -1) + return; - node.Children.Add(yamlKey, yamlValue); - } + node.Children.Add(yamlKey, yamlValue); + } - public void MoveChild(object key, int movePosition) - { - var yamlKey = ConvertFromDynamicForKey(key); - var keyPosition = node.Children.IndexOf(yamlKey); + public void MoveChild(object key, int movePosition) + { + var yamlKey = ConvertFromDynamicForKey(key); + var keyPosition = node.Children.IndexOf(yamlKey); - if (keyPosition == movePosition) - return; + if (keyPosition == movePosition) + return; - // Remove child - var item = node.Children[keyPosition]; - node.Children.RemoveAt(keyPosition); + // Remove child + var item = node.Children[keyPosition]; + node.Children.RemoveAt(keyPosition); - // Adjust insertion position (if we insert in a position after the removal position) - if (movePosition > keyPosition) - movePosition--; + // Adjust insertion position (if we insert in a position after the removal position) + if (movePosition > keyPosition) + movePosition--; - // Insert item at new position - node.Children.Insert(movePosition, item.Key, item.Value); - } + // Insert item at new position + node.Children.Insert(movePosition, item.Key, item.Value); + } - public bool ContainsChild(object key) - { - var yamlKey = ConvertFromDynamicForKey(key); - var keyPosition = node.Children.IndexOf(yamlKey); + public bool ContainsChild(object key) + { + var yamlKey = ConvertFromDynamicForKey(key); + var keyPosition = node.Children.IndexOf(yamlKey); - return (keyPosition != -1); - } + return (keyPosition != -1); + } - public void RemoveChild(object key) + public void RemoveChild(object key) + { + var yamlKey = ConvertFromDynamicForKey(key); + var keyPosition = node.Children.IndexOf(yamlKey); + if (keyPosition != -1) { - var yamlKey = ConvertFromDynamicForKey(key); - var keyPosition = node.Children.IndexOf(yamlKey); - if (keyPosition != -1) - { - node.Children.RemoveAt(keyPosition); + node.Children.RemoveAt(keyPosition); - // Removes any override information - if (keyMapping != null && key is string) - { - keyMapping.Remove((string)key); - overrides.Remove((string)key); - } + // Removes any override information + if (keyMapping != null && key is string k) + { + keyMapping.Remove(k); + overrides?.Remove(k); } } + } - /// - /// Renames a property to a new name while keeping all overrides and key mappings - /// - /// Old property name - /// New property name - public void RenameChild(object oldKey, object newKey) - { - var yamlKey = ConvertFromDynamicForKey(oldKey); - var keyPosition = node.Children.IndexOf(yamlKey); + /// + /// Renames a property to a new name while keeping all overrides and key mappings + /// + /// Old property name + /// New property name + public void RenameChild(object oldKey, object newKey) + { + var yamlKey = ConvertFromDynamicForKey(oldKey); + var keyPosition = node.Children.IndexOf(yamlKey); - if (keyPosition < 0) - return; // Key not found, nothing to do + if (keyPosition < 0) + return; // Key not found, nothing to do - SetOverride(newKey.ToString(), GetOverride(oldKey.ToString())); + SetOverride(newKey.ToString()!, GetOverride(oldKey.ToString()!)); - AddChild(newKey, node.Children[keyPosition].Value); + AddChild(newKey, node.Children[keyPosition].Value); - RemoveChild(oldKey); - } + RemoveChild(oldKey); + } - /// - /// Transfers ownership of a property to another parent object and removes it from the current one - /// - /// Old property name - /// New owner for the property - /// New property name - public void TransferChild(object oldKey, object newParent, object newKey) - { - var yamlMapping = newParent as DynamicYamlMapping; - if (yamlMapping == null) - return; + /// + /// Transfers ownership of a property to another parent object and removes it from the current one + /// + /// Old property name + /// New owner for the property + /// New property name + public void TransferChild(object oldKey, object newParent, object newKey) + { + if (newParent is not DynamicYamlMapping yamlMapping) + return; - var yamlKey = ConvertFromDynamicForKey(oldKey); - var keyPosition = node.Children.IndexOf(yamlKey); + var yamlKey = ConvertFromDynamicForKey(oldKey); + var keyPosition = node.Children.IndexOf(yamlKey); - if (keyPosition < 0) - return; // Key not found, nothing to do + if (keyPosition < 0) + return; // Key not found, nothing to do - yamlMapping.SetOverride(newKey.ToString(), GetOverride(oldKey.ToString())); + yamlMapping.SetOverride(newKey.ToString()!, GetOverride(oldKey.ToString()!)); - yamlMapping.AddChild(newKey, node.Children[keyPosition].Value); + yamlMapping.AddChild(newKey, node.Children[keyPosition].Value); - RemoveChild(oldKey); - } + RemoveChild(oldKey); + } - public int IndexOf(object key) - { - var yamlKey = ConvertFromDynamicForKey(key); + public int IndexOf(object key) + { + var yamlKey = ConvertFromDynamicForKey(key); - return node.Children.IndexOf(yamlKey); - } + return node.Children.IndexOf(yamlKey); + } - public override bool TryConvert(ConvertBinder binder, out object result) + public override bool TryConvert(ConvertBinder binder, out object result) + { + if (binder.Type.IsAssignableFrom(node.GetType())) { - if (binder.Type.IsAssignableFrom(node.GetType())) - { - result = node; - } - else - { - throw new InvalidOperationException(); - } - return true; + result = node; } - - public override bool TryGetMember(GetMemberBinder binder, out object result) + else { - YamlNode tempNode; - if (node.Children.TryGetValue(new YamlScalarNode(GetRealPropertyName(binder.Name)), out tempNode)) - { - result = ConvertToDynamic(tempNode); - return true; - } - result = null; - // Probably not very good, but unfortunately we have some asset upgraders that relies on null check to check existence - return true; + throw new InvalidOperationException(); } + return true; + } - public override bool TrySetMember(SetMemberBinder binder, object value) + public override bool TryGetMember(GetMemberBinder binder, out object? result) + { + if (node.Children.TryGetValue(new YamlScalarNode(GetRealPropertyName(binder.Name)), out var tempNode)) { - var key = new YamlScalarNode(GetRealPropertyName(binder.Name)); - - if (value is DynamicYamlEmpty) - node.Children.Remove(key); - else - node.Children[key] = ConvertFromDynamic(value); + result = ConvertToDynamic(tempNode); return true; } + result = null; + // Probably not very good, but unfortunately we have some asset upgraders that relies on null check to check existence + return true; + } + + public override bool TrySetMember(SetMemberBinder binder, object? value) + { + var key = new YamlScalarNode(GetRealPropertyName(binder.Name)); - public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) + if (value is DynamicYamlEmpty) + node.Children.Remove(key); + else + node.Children[key] = ConvertFromDynamic(value); + return true; + } + + public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object? value) + { + var key = ConvertFromDynamicForKey(indexes[0]); + if (value is DynamicYamlEmpty) + node.Children.Remove(key); + else + node.Children[key] = ConvertFromDynamic(value); + return true; + } + + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object? result) + { + var key = ConvertFromDynamicForKey(indexes[0]); + result = GetValue(key); + return true; + } + + /// + /// Gets the override for the specified member. + /// + /// The member name to get the override + /// The type of override (if no override, return + public OverrideType GetOverride(string key) + { + if (overrides == null) { - var key = ConvertFromDynamicForKey(indexes[0]); - if (value is DynamicYamlEmpty) - node.Children.Remove(key); - else - node.Children[key] = ConvertFromDynamic(value); - return true; + return OverrideType.Base; } - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + return overrides.TryGetValue(key, out var type) ? type : OverrideType.Base; + } + + /// + /// Sets the override type for the specified member. + /// + /// The member name to setup an override + /// Type of the override + public void SetOverride(string key, OverrideType type) + { + ArgumentNullException.ThrowIfNull(key); + + YamlNode? previousMemberKey = null; + YamlNode? previousMemberValue = null; + + if (keyMapping == null) { - var key = ConvertFromDynamicForKey(indexes[0]); - result = GetValue(key); - return true; + keyMapping = []; } - - /// - /// Gets the override for the specified member. - /// - /// The member name to get the override - /// The type of override (if no override, return - public OverrideType GetOverride(string key) + else { - if (overrides == null) + if (keyMapping.TryGetValue(key, out var previousMemberName)) { - return OverrideType.Base; + previousMemberKey = new YamlScalarNode(previousMemberName); + node.Children.TryGetValue(previousMemberKey, out previousMemberValue); } - - OverrideType type; - return overrides.TryGetValue(key, out type) ? type : OverrideType.Base; + keyMapping.Remove(key); } - /// - /// Sets the override type for the specified member. - /// - /// The member name to setup an override - /// Type of the override - public void SetOverride(string key, OverrideType type) + if (overrides == null) { - if (key == null) throw new ArgumentNullException(nameof(key)); - - YamlNode previousMemberKey = null; - YamlNode previousMemberValue = null; - - if (keyMapping == null) - { - keyMapping = new Dictionary(); - } - else - { - string previousMemberName; - if (keyMapping.TryGetValue(key, out previousMemberName)) - { - previousMemberKey = new YamlScalarNode(previousMemberName); - node.Children.TryGetValue(previousMemberKey, out previousMemberValue); - } - keyMapping.Remove(key); - } - - if (overrides == null) - { - overrides = new Dictionary(); - } - else - { - overrides.Remove(key); - } + overrides = []; + } + else + { + overrides.Remove(key); + } - // New member name - var newMemberName = type == OverrideType.Base - ? key - : $"{key}{type.ToText()}"; + // New member name + var newMemberName = type == OverrideType.Base + ? key + : $"{key}{type.ToText()}"; - keyMapping[key] = newMemberName; - overrides[key] = type; + keyMapping[key] = newMemberName; + overrides[key] = type; - // Remap the original YAML node with the override type - if (previousMemberKey != null) - { - int index = node.Children.IndexOf(previousMemberKey); - node.Children.RemoveAt(index); - node.Children.Insert(index, new YamlScalarNode(newMemberName), previousMemberValue); - } + // Remap the original YAML node with the override type + if (previousMemberKey != null) + { + int index = node.Children.IndexOf(previousMemberKey); + node.Children.RemoveAt(index); + node.Children.Insert(index, new YamlScalarNode(newMemberName), previousMemberValue); } + } - /// - /// Removes an override information from the specified member. - /// - /// The member name - public void RemoveOverride(string key) + /// + /// Removes an override information from the specified member. + /// + /// The member name + public void RemoveOverride(string key) + { + if (overrides == null) { - if (overrides == null) - { - return; - } + return; + } - // If we have an override we need to remove the override text from the name - if (overrides.Remove(key)) - { - var propertyName = GetRealPropertyName(key); + // If we have an override we need to remove the override text from the name + if (overrides.Remove(key)) + { + var propertyName = GetRealPropertyName(key); - var previousKey = new YamlScalarNode(GetRealPropertyName(propertyName)); - int index = node.Children.IndexOf(previousKey); - var previousMemberValue = node.Children[index].Value; - node.Children.RemoveAt(index); - node.Children.Insert(index, new YamlScalarNode(key), previousMemberValue); + var previousKey = new YamlScalarNode(GetRealPropertyName(propertyName)); + int index = node.Children.IndexOf(previousKey); + var previousMemberValue = node.Children[index].Value; + node.Children.RemoveAt(index); + node.Children.Insert(index, new YamlScalarNode(key), previousMemberValue); - keyMapping[key] = key; - } + keyMapping![key] = key; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return node.Children.Select(x => new KeyValuePair(ConvertToDynamic(x.Key), ConvertToDynamic(x.Value))).ToArray().GetEnumerator(); + } - IEnumerator IEnumerable.GetEnumerator() + /// + /// Helper method to gets the real member key for the specified key (taking into account overrides) + /// + /// The member name + /// A member YamlNode + private YamlNode ConvertFromDynamicForKey(object key) + { + if (key is string k) { - return node.Children.Select(x => new KeyValuePair(ConvertToDynamic(x.Key), ConvertToDynamic(x.Value))).ToArray().GetEnumerator(); + key = GetRealPropertyName(k); } + return ConvertFromDynamic(key); + } - /// - /// Helper method to gets the real member key for the specified key (taking into account overrides) - /// - /// The member name - /// A member YamlNode - private YamlNode ConvertFromDynamicForKey(object key) + private object? GetValue(YamlNode key) + { + if (node.Children.TryGetValue(key, out var result)) { - if (key is string) - { - key = GetRealPropertyName((string)key); - } - return ConvertFromDynamic(key); + return ConvertToDynamic(result); } + return null; + } - private object GetValue(YamlNode key) + private string GetRealPropertyName(string name) + { + if (keyMapping == null) { - YamlNode result; - if (node.Children.TryGetValue(key, out result)) - { - return ConvertToDynamic(result); - } - return null; + return name; } - private string GetRealPropertyName(string name) + if (keyMapping.TryGetValue(name, out var realPropertyName)) { - if (keyMapping == null) - { - return name; - } - - string realPropertyName; - if (keyMapping.TryGetValue(name, out realPropertyName)) - { - return realPropertyName; - } - return name; + return realPropertyName; } + return name; + } - /// - /// This method will extract overrides information and maintain a separate dictionary to ensure mapping between - /// a full property name without override (MyProperty) and with its override (e.g: MyProperty! for sealed MyProperty) - /// - private void ParseOverrides() + /// + /// This method will extract overrides information and maintain a separate dictionary to ensure mapping between + /// a full property name without override (MyProperty) and with its override (e.g: MyProperty! for sealed MyProperty) + /// + private void ParseOverrides() + { + foreach (var keyValue in node) { - foreach (var keyValue in node) + var scalar = keyValue.Key as YamlScalarNode; + if (scalar?.Value != null) { - var scalar = keyValue.Key as YamlScalarNode; - if (scalar?.Value != null) + var isPostFixNew = scalar.Value.EndsWith(OverridePostfixes.PostFixNewText, StringComparison.Ordinal); + var isPostFixSealed = scalar.Value.EndsWith(OverridePostfixes.PostFixSealedText, StringComparison.Ordinal); + if (isPostFixNew || isPostFixSealed) { - var isPostFixNew = scalar.Value.EndsWith(OverridePostfixes.PostFixNewText, StringComparison.Ordinal); - var isPostFixSealed = scalar.Value.EndsWith(OverridePostfixes.PostFixSealedText, StringComparison.Ordinal); - if (isPostFixNew || isPostFixSealed) + var name = scalar.Value; + var type = isPostFixNew ? OverrideType.New : OverrideType.Sealed; + + var isPostFixNewSealedAlt = name.EndsWith(OverridePostfixes.PostFixNewSealedAlt, StringComparison.Ordinal); + var isPostFixNewSealed = name.EndsWith(OverridePostfixes.PostFixNewSealed, StringComparison.Ordinal); + if (isPostFixNewSealed || isPostFixNewSealedAlt) + { + type = OverrideType.New | OverrideType.Sealed; + name = name[..^2]; + } + else { - var name = scalar.Value; - var type = isPostFixNew ? OverrideType.New : OverrideType.Sealed; - - var isPostFixNewSealedAlt = name.EndsWith(OverridePostfixes.PostFixNewSealedAlt, StringComparison.Ordinal); - var isPostFixNewSealed = name.EndsWith(OverridePostfixes.PostFixNewSealed, StringComparison.Ordinal); - if (isPostFixNewSealed || isPostFixNewSealedAlt) - { - type = OverrideType.New | OverrideType.Sealed; - name = name[..^2]; - } - else - { - name = name[..^1]; - } - if (keyMapping == null) - { - keyMapping = new Dictionary(); - } - - keyMapping[name] = scalar.Value; - - if (overrides == null) - { - overrides = new Dictionary(); - } - overrides[name] = type; + name = name[..^1]; } + keyMapping ??= []; + keyMapping[name] = scalar.Value; + + overrides ??= []; + overrides[name] = type; } } } diff --git a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlObject.cs b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlObject.cs index fa9821bf47..d4972a0b0f 100644 --- a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlObject.cs +++ b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlObject.cs @@ -1,50 +1,37 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System.Dynamic; using System.Globalization; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +public abstract class DynamicYamlObject : DynamicObject { - public abstract class DynamicYamlObject : DynamicObject + protected static YamlNode ConvertFromDynamic(object? obj) { - protected static YamlNode ConvertFromDynamic(object obj) + return obj switch { - if (obj == null) - return new YamlScalarNode("null"); - - if (obj is string) - { - return new YamlScalarNode((string)obj); - } - - if (obj is YamlNode) - return (YamlNode)obj; - - if (obj is DynamicYamlMapping) - return ((DynamicYamlMapping)obj).Node; - if (obj is DynamicYamlArray) - return ((DynamicYamlArray)obj).node; - if (obj is DynamicYamlScalar) - return ((DynamicYamlScalar)obj).node; - - if (obj is bool) - return new YamlScalarNode((bool)obj ? "true" : "false"); - - return new YamlScalarNode(string.Format(CultureInfo.InvariantCulture, "{0}", obj)); - } + null => new YamlScalarNode("null"), + string str => new YamlScalarNode(str), + YamlNode node => node, + DynamicYamlMapping mapping => mapping.Node, + DynamicYamlArray array => array.node, + DynamicYamlScalar scalar => scalar.node, + bool b => new YamlScalarNode(b ? "true" : "false"), + _ => new YamlScalarNode(string.Format(CultureInfo.InvariantCulture, "{0}", obj)) + }; + } - public static object ConvertToDynamic(object obj) + public static object ConvertToDynamic(object obj) + { + return obj switch { - if (obj is YamlScalarNode) - return new DynamicYamlScalar((YamlScalarNode)obj); - if (obj is YamlMappingNode) - return new DynamicYamlMapping((YamlMappingNode)obj); - if (obj is YamlSequenceNode) - return new DynamicYamlArray((YamlSequenceNode)obj); - - return obj; - - } + YamlScalarNode scalar => new DynamicYamlScalar(scalar), + YamlMappingNode mapping => new DynamicYamlMapping(mapping), + YamlSequenceNode sequence => new DynamicYamlArray(sequence), + _ => obj + }; } } diff --git a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlScalar.cs b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlScalar.cs index 5291cd2a09..070b62789a 100644 --- a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlScalar.cs +++ b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/DynamicYamlScalar.cs @@ -1,61 +1,59 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Dynamic; using System.Globalization; using System.Linq.Expressions; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// Dynamic version of . +/// +public class DynamicYamlScalar : DynamicYamlObject, IDynamicYamlNode { - /// - /// Dynamic version of . - /// - public class DynamicYamlScalar : DynamicYamlObject, IDynamicYamlNode - { - internal YamlScalarNode node; + internal YamlScalarNode node; - public YamlScalarNode Node => node; + public YamlScalarNode Node => node; - YamlNode IDynamicYamlNode.Node => Node; + YamlNode IDynamicYamlNode.Node => Node; - public DynamicYamlScalar(YamlScalarNode node) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - this.node = node; - } + public DynamicYamlScalar(YamlScalarNode node) + { + ArgumentNullException.ThrowIfNull(node); + this.node = node; + } - public override bool TryConvert(ConvertBinder binder, out object result) - { - result = binder.Type.IsEnum - ? Enum.Parse(binder.Type, node.Value) - : Convert.ChangeType(node.Value, binder.Type, CultureInfo.InvariantCulture); + public override bool TryConvert(ConvertBinder binder, out object result) + { + result = binder.Type.IsEnum + ? Enum.Parse(binder.Type, node.Value) + : Convert.ChangeType(node.Value, binder.Type, CultureInfo.InvariantCulture); - return true; - } + return true; + } - public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) + public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result) + { + if (arg is string str) { - var str = arg as string; - if (str != null) + if (binder.Operation == ExpressionType.Equal) + { + result = node.Value == str; + return true; + } + if (binder.Operation == ExpressionType.NotEqual) { - if (binder.Operation == ExpressionType.Equal) - { - result = node.Value == str; - return true; - } - if (binder.Operation == ExpressionType.NotEqual) - { - result = node.Value != str; - return true; - } + result = node.Value != str; + return true; } - return base.TryBinaryOperation(binder, arg, out result); } + return base.TryBinaryOperation(binder, arg, out result); + } - public override string ToString() - { - return node.Value; - } + public override string ToString() + { + return node.Value; } } diff --git a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/IDynamicYamlNode.cs b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/IDynamicYamlNode.cs index c413da4569..8770ecd864 100644 --- a/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/IDynamicYamlNode.cs +++ b/sources/assets/Stride.Core.Assets.Yaml/DynamicYaml/IDynamicYamlNode.cs @@ -1,11 +1,11 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +public interface IDynamicYamlNode { - public interface IDynamicYamlNode - { - YamlNode Node { get; } - } + YamlNode Node { get; } } diff --git a/sources/assets/Stride.Core.Assets.Yaml/Reflection/OverridePostfixes.cs b/sources/assets/Stride.Core.Assets.Yaml/Reflection/OverridePostfixes.cs index 7e2d951be0..d1ac420f4a 100644 --- a/sources/assets/Stride.Core.Assets.Yaml/Reflection/OverridePostfixes.cs +++ b/sources/assets/Stride.Core.Assets.Yaml/Reflection/OverridePostfixes.cs @@ -1,36 +1,36 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Reflection + +namespace Stride.Core.Reflection; + +public static class OverridePostfixes { - public static class OverridePostfixes - { - internal const char PostFixSealed = '!'; + internal const char PostFixSealed = '!'; - internal const char PostFixNew = '*'; + internal const char PostFixNew = '*'; - internal const string PostFixNewSealed = "*!"; + internal const string PostFixNewSealed = "*!"; - internal const string PostFixNewSealedAlt = "!*"; + internal const string PostFixNewSealedAlt = "!*"; - internal const string PostFixSealedText = "!"; + internal const string PostFixSealedText = "!"; - internal const string PostFixNewText = "*"; + internal const string PostFixNewText = "*"; - public static string ToText(this OverrideType type) + public static string ToText(this OverrideType type) + { + if (type == OverrideType.New) + { + return PostFixNewText; + } + if (type == OverrideType.Sealed) + { + return PostFixSealedText; + } + if (type == (OverrideType.New | OverrideType.Sealed)) { - if (type == OverrideType.New) - { - return PostFixNewText; - } - if (type == OverrideType.Sealed) - { - return PostFixSealedText; - } - if (type == (OverrideType.New | OverrideType.Sealed)) - { - return PostFixNewSealed; - } - return string.Empty; + return PostFixNewSealed; } + return string.Empty; } } diff --git a/sources/assets/Stride.Core.Assets.Yaml/Reflection/OverrideType.cs b/sources/assets/Stride.Core.Assets.Yaml/Reflection/OverrideType.cs index 9f5ced14f8..c65097f7cf 100644 --- a/sources/assets/Stride.Core.Assets.Yaml/Reflection/OverrideType.cs +++ b/sources/assets/Stride.Core.Assets.Yaml/Reflection/OverrideType.cs @@ -1,28 +1,26 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Reflection +namespace Stride.Core.Reflection; + +/// +/// A Type of override used on a member value. +/// +[Flags] +public enum OverrideType { /// - /// A Type of override used on a member value. + /// The value is taken from a base value or this instance if no base (default). /// - [Flags] - public enum OverrideType - { - /// - /// The value is taken from a base value or this instance if no base (default). - /// - Base = 0, // This is strictly not a correct value for a flag, but it is used to make sure default value is always base. When testing for this value, better use IsBase() extension method. + Base = 0, // This is strictly not a correct value for a flag, but it is used to make sure default value is always base. When testing for this value, better use IsBase() extension method. - /// - /// The value is new and overridden locally. Base value is ignored. - /// - New = 1, + /// + /// The value is new and overridden locally. Base value is ignored. + /// + New = 1, - /// - /// The value is sealed and cannot be changed by inherited instances. - /// - Sealed = 2, - } + /// + /// The value is sealed and cannot be changed by inherited instances. + /// + Sealed = 2, } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetAnalysis.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetAnalysis.cs index 16f273239f..ffe04d81ad 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetAnalysis.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetAnalysis.cs @@ -1,166 +1,161 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; - using Stride.Core.Assets.Diagnostics; using Stride.Core.Assets.Tracking; using Stride.Core.Diagnostics; using Stride.Core.IO; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// Analysis for . +/// +public static class AssetAnalysis { - /// - /// Analysis for . - /// - public static class AssetAnalysis + public static LoggerResult Run(IEnumerable items, AssetAnalysisParameters parameters) { - public static LoggerResult Run(IEnumerable items, AssetAnalysisParameters parameters) - { - if (items == null) throw new ArgumentNullException(nameof(items)); - if (parameters == null) throw new ArgumentNullException(nameof(parameters)); + ArgumentNullException.ThrowIfNull(items); + ArgumentNullException.ThrowIfNull(parameters); - var result = new LoggerResult(); - Run(items, result, parameters); - return result; - } - - public static void Run(IEnumerable items, ILogger log, AssetAnalysisParameters parameters) - { - if (items == null) throw new ArgumentNullException(nameof(items)); - if (log == null) throw new ArgumentNullException(nameof(log)); - if (parameters == null) throw new ArgumentNullException(nameof(parameters)); + var result = new LoggerResult(); + Run(items, result, parameters); + return result; + } - foreach (var assetItem in items) - { - Run(assetItem, log, parameters); - } - } + public static void Run(IEnumerable items, ILogger log, AssetAnalysisParameters parameters) + { + ArgumentNullException.ThrowIfNull(items); + ArgumentNullException.ThrowIfNull(log); + ArgumentNullException.ThrowIfNull(parameters); - public static LoggerResult FixAssetReferences(IEnumerable items) + foreach (var assetItem in items) { - var parameters = new AssetAnalysisParameters() { IsProcessingAssetReferences = true, IsLoggingAssetNotFoundAsError = true}; - var result = new LoggerResult(); - Run(items, result, parameters); - return result; + Run(assetItem, log, parameters); } + } - public static void Run(AssetItem assetItem, ILogger log, AssetAnalysisParameters parameters) + public static LoggerResult FixAssetReferences(IEnumerable items) + { + var parameters = new AssetAnalysisParameters() { IsProcessingAssetReferences = true, IsLoggingAssetNotFoundAsError = true}; + var result = new LoggerResult(); + Run(items, result, parameters); + return result; + } + + public static void Run(AssetItem assetItem, ILogger log, AssetAnalysisParameters parameters) + { + ArgumentNullException.ThrowIfNull(assetItem); + ArgumentNullException.ThrowIfNull(log); + ArgumentNullException.ThrowIfNull(parameters); + + if (assetItem.Package is null) { - if (assetItem == null) throw new ArgumentNullException(nameof(assetItem)); - if (log == null) throw new ArgumentNullException(nameof(log)); - if (parameters == null) throw new ArgumentNullException(nameof(parameters)); + throw new InvalidOperationException("AssetItem must belong to an existing package"); + } - if (assetItem.Package == null) - { - throw new InvalidOperationException("AssetItem must belong to an existing package"); - } + var package = assetItem.Package; - var package = assetItem.Package; + // Check that there is no duplicate in assets + if (package.Session is not null) + { + var packages = package.FindDependencies(); - // Check that there is no duplicate in assets - if (package.Session != null) + foreach (var otherPackage in packages) { - var packages = package.FindDependencies(); + var existingAsset = otherPackage.Assets.Find(assetItem.Id); - foreach (var otherPackage in packages) + if (existingAsset is not null) { - var existingAsset = otherPackage.Assets.Find(assetItem.Id); - - if (existingAsset != null) - { - log.Error($"Assets [{existingAsset.FullPath}] with id [{existingAsset.Id}] from Package [{package.FullPath}] is already loaded from package [{existingAsset.Package.FullPath}]"); - } - else + log.Error($"Assets [{existingAsset.FullPath}] with id [{existingAsset.Id}] from Package [{package.FullPath}] is already loaded from package [{existingAsset.Package?.FullPath}]"); + } + else + { + existingAsset = otherPackage.Assets.Find(assetItem.Location); + if (existingAsset is not null) { - existingAsset = otherPackage.Assets.Find(assetItem.Location); - if (existingAsset != null) - { - log.Error($"Assets [{existingAsset.FullPath}] with location [{existingAsset.Location}] from Package [{package.FullPath}] is already loaded from package [{existingAsset.Package.FullPath}]"); - } + log.Error($"Assets [{existingAsset.FullPath}] with location [{existingAsset.Location}] from Package [{package.FullPath}] is already loaded from package [{existingAsset.Package?.FullPath}]"); } } } + } - var assetReferences = AssetReferenceAnalysis.Visit(assetItem.Asset); + var assetReferences = AssetReferenceAnalysis.Visit(assetItem.Asset); - if (package.Session != null && parameters.IsProcessingAssetReferences) - { - UpdateAssetReferences(assetItem, assetReferences, log, parameters); - } - // Update paths for asset items + if (package.Session is not null && parameters.IsProcessingAssetReferences) + { + UpdateAssetReferences(assetItem, assetReferences, log, parameters); + } + // Update paths for asset items - if (parameters.IsProcessingUPaths) - { - // Find where this asset item was previously stored (in a different package for example) - CommonAnalysis.UpdatePaths(assetItem, assetReferences.Where(link => link.Reference is UPath), parameters); - // Source hashes are not processed by analysis, we need to manually indicate them to update - SourceHashesHelper.UpdateUPaths(assetItem.Asset, assetItem.FullPath.GetParent(), parameters.ConvertUPathTo); - } + if (parameters.IsProcessingUPaths) + { + // Find where this asset item was previously stored (in a different package for example) + CommonAnalysis.UpdatePaths(assetItem, assetReferences.Where(link => link.Reference is UPath), parameters); + // Source hashes are not processed by analysis, we need to manually indicate them to update + SourceHashesHelper.UpdateUPaths(assetItem.Asset, assetItem.FullPath.GetParent(), parameters.ConvertUPathTo); } + } + + internal static void UpdateAssetReferences(AssetItem assetItem, IEnumerable assetReferences, ILogger log, AssetAnalysisParameters parameters) + { + var package = assetItem.Package; + var packageName = package?.FullPath?.GetFileNameWithoutExtension() ?? "(Undefined path)"; + bool shouldSetDirtyFlag = false; - internal static void UpdateAssetReferences(AssetItem assetItem, IEnumerable assetReferences, ILogger log, AssetAnalysisParameters parameters) + // Update reference + foreach (var assetReferenceLink in assetReferences.Where(link => link.Reference is IReference)) { - var package = assetItem.Package; - var packageName = package.FullPath?.GetFileNameWithoutExtension() ?? "(Undefined path)"; - bool shouldSetDirtyFlag = false; + var contentReference = (IReference)assetReferenceLink.Reference; + // Update Asset references (AssetReference, AssetBase, reference) + var id = contentReference.Id; + var newItemReference = package?.FindAsset(id); - // Update reference - foreach (var assetReferenceLink in assetReferences.Where(link => link.Reference is IReference)) + // If asset was not found by id try to find by its location + if (newItemReference is null) { - var contentReference = (IReference)assetReferenceLink.Reference; - // Update Asset references (AssetReference, AssetBase, reference) - var id = contentReference.Id; - var newItemReference = package.FindAsset(id); - - // If asset was not found by id try to find by its location - if (newItemReference == null) + newItemReference = package?.FindAsset(contentReference.Location); + if (newItemReference is not null) { - newItemReference = package.FindAsset(contentReference.Location); - if (newItemReference != null) - { - // If asset was found by its location, just emit a warning - log.Warning(package, contentReference, AssetMessageCode.AssetReferenceChanged, contentReference, newItemReference.Id); - } + // If asset was found by its location, just emit a warning + log.Warning(package, contentReference, AssetMessageCode.AssetReferenceChanged, contentReference, newItemReference.Id); } + } - // If asset was not found, display an error or a warning - if (newItemReference == null) + // If asset was not found, display an error or a warning + if (newItemReference is null) + { + if (parameters.IsLoggingAssetNotFoundAsError) { - if (parameters.IsLoggingAssetNotFoundAsError) - { - log.Error(package, contentReference, AssetMessageCode.AssetForPackageNotFound, contentReference, packageName); + log.Error(package, contentReference, AssetMessageCode.AssetForPackageNotFound, contentReference, packageName); - var packageFound = package.Session.Packages.FirstOrDefault(x => x.FindAsset(contentReference.Location) != null); - if (packageFound != null) - { - log.Warning(package, contentReference, AssetMessageCode.AssetFoundInDifferentPackage, contentReference, packageFound.FullPath.GetFileNameWithoutExtension()); - } - } - else + var packageFound = package?.Session?.Packages.FirstOrDefault(x => x.FindAsset(contentReference.Location) is not null); + if (packageFound is not null) { - log.Warning(package, contentReference, AssetMessageCode.AssetForPackageNotFound, contentReference, packageName); + log.Warning(package, contentReference, AssetMessageCode.AssetFoundInDifferentPackage, contentReference, packageFound.FullPath.GetFileNameWithoutExtension()); } - continue; } - - // Only update location that are actually different - var newLocationWithoutExtension = newItemReference.Location; - if (newLocationWithoutExtension != contentReference.Location || newItemReference.Id != contentReference.Id) + else { - assetReferenceLink.UpdateReference(newItemReference.Id, newLocationWithoutExtension); - shouldSetDirtyFlag = true; + log.Warning(package, contentReference, AssetMessageCode.AssetForPackageNotFound, contentReference, packageName); } + continue; } - // Setting the dirty flag is an heavy operation, we want to do it only once - if (shouldSetDirtyFlag) + // Only update location that are actually different + var newLocationWithoutExtension = newItemReference.Location; + if (newLocationWithoutExtension != contentReference.Location || newItemReference.Id != contentReference.Id) { - assetItem.IsDirty = true; + assetReferenceLink.UpdateReference(newItemReference.Id, newLocationWithoutExtension); + shouldSetDirtyFlag = true; } } + + // Setting the dirty flag is an heavy operation, we want to do it only once + if (shouldSetDirtyFlag) + { + assetItem.IsDirty = true; + } } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetAnalysisParameters.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetAnalysisParameters.cs index ad3df36df4..11e0034ac8 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetAnalysisParameters.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetAnalysisParameters.cs @@ -2,28 +2,27 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using Stride.Core.IO; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// Parameters for asset analysis. +/// +public class AssetAnalysisParameters { - /// - /// Parameters for asset analysis. - /// - public class AssetAnalysisParameters - { - public bool IsLoggingAssetNotFoundAsError { get; set; } + public bool IsLoggingAssetNotFoundAsError { get; set; } - public bool IsProcessingAssetReferences { get; set; } + public bool IsProcessingAssetReferences { get; set; } - public bool IsProcessingUPaths { get; set; } + public bool IsProcessingUPaths { get; set; } - public bool SetDirtyFlagOnAssetWhenFixingAbsoluteUFile { get; set; } + public bool SetDirtyFlagOnAssetWhenFixingAbsoluteUFile { get; set; } - public bool SetDirtyFlagOnAssetWhenFixingUFile { get; set; } + public bool SetDirtyFlagOnAssetWhenFixingUFile { get; set; } - public UPathType ConvertUPathTo { get; set; } + public UPathType ConvertUPathTo { get; set; } - public virtual AssetAnalysisParameters Clone() - { - return (AssetAnalysisParameters)MemberwiseClone(); - } + public virtual AssetAnalysisParameters Clone() + { + return (AssetAnalysisParameters)MemberwiseClone(); } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetBaseAnalysis.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetBaseAnalysis.cs index 2cc607e0d3..4344a900c6 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetBaseAnalysis.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetBaseAnalysis.cs @@ -1,185 +1,181 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; - using Stride.Core.Assets.Diagnostics; using Stride.Core.Diagnostics; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// An analysis to validate that all assets in a package have a valid base. +/// In order to be valid, this analysis must be run after a +/// +public sealed class AssetBaseAnalysis : PackageSessionAnalysisBase { /// - /// An analysis to validate that all assets in a package have a valid base. - /// In order to be valid, this analysis must be run after a + /// Initializes a new instance of the class. /// - public sealed class AssetBaseAnalysis : PackageSessionAnalysisBase + /// The package session. + public AssetBaseAnalysis(PackageSession packageSession) + : base(packageSession) { - /// - /// Initializes a new instance of the class. - /// - /// The package session. - public AssetBaseAnalysis(PackageSession packageSession) - : base(packageSession) - { - } + } - /// - /// Performs a wide package validation analysis. - /// - /// The log to output the result of the validation. - public override void Run(ILogger log) - { - if (log == null) throw new ArgumentNullException(nameof(log)); + /// + /// Performs a wide package validation analysis. + /// + /// The log to output the result of the validation. + public override void Run(ILogger log) + { + ArgumentNullException.ThrowIfNull(log); - ValidateAssetBase(log); - } + ValidateAssetBase(log); + } - /// - /// Validates the inheritance of all assets in the package. - /// - /// The log to output the result of the analysis. - /// A collection that contains all valid assets. - public HashSet ValidateAssetBase(ILogger log) - { - var invalidAssets = new HashSet(); - var validAssets = new HashSet(); + /// + /// Validates the inheritance of all assets in the package. + /// + /// The log to output the result of the analysis. + /// A collection that contains all valid assets. + public HashSet ValidateAssetBase(ILogger log) + { + var invalidAssets = new HashSet(); + var validAssets = new HashSet(); - foreach (var package in Session.Packages) + foreach (var package in Session.Packages) + { + foreach (var assetItem in package.Assets) { - foreach (var assetItem in package.Assets) + // This asset has been already flagged as invalid + if (invalidAssets.Contains(assetItem.Id)) { - // This asset has been already flagged as invalid - if (invalidAssets.Contains(assetItem.Id)) - { - continue; - } + continue; + } + + var result = ValidateAssetBase(assetItem); - var result = ValidateAssetBase(assetItem); + if (result.HasErrors) + { + // Copy errors to output log + result.CopyTo(log); + + invalidAssets.Add(assetItem.Id); - if (result.HasErrors) + // Value contains valid base asset, but they are invalid + // if any of the parent base are not valid + foreach (var baseItem in result.Value ?? []) { - // Copy errors to output log - result.CopyTo(log); - - invalidAssets.Add(assetItem.Id); - - // Value contains valid base asset, but they are invalid - // if any of the parent base are not valid - foreach (var baseItem in result.Value) - { - invalidAssets.Add(baseItem.Id); - } - - foreach (var logMessage in result.Messages.OfType()) - { - invalidAssets.Add(logMessage.AssetReference.Id); - } + invalidAssets.Add(baseItem.Id); } - else + + foreach (var logMessage in result.Messages.OfType()) { - validAssets.Add(assetItem.Asset); + if (logMessage.AssetReference is null) continue; + invalidAssets.Add(logMessage.AssetReference.Id); } } + else + { + validAssets.Add(assetItem.Asset); + } } - - return validAssets; } - /// - /// Validates the inheritance of an asset by checking base accessibility up to the root base. - /// - /// The asset item. - /// A logger result with a list of all the base in bottom-up orde. - public LoggerValueResult> ValidateAssetBase(AssetItem assetItem) + return validAssets; + } + + /// + /// Validates the inheritance of an asset by checking base accessibility up to the root base. + /// + /// The asset item. + /// A logger result with a list of all the base in bottom-up orde. + public LoggerValueResult> ValidateAssetBase(AssetItem assetItem) + { + var results = new LoggerValueResult>(); + results.Value?.AddRange(ValidateAssetBase(assetItem, results)); + return results; + } + + /// + /// Validates the inheritance of an asset by checking base accessibility up to the root base. + /// + /// The asset item. + /// The log to output the result of the analysis. + /// A list of all the base in bottom-up order. + /// asset + /// or + /// log + public List ValidateAssetBase(AssetItem assetItem, ILogger log) + { + ArgumentNullException.ThrowIfNull(assetItem); + ArgumentNullException.ThrowIfNull(log); + + var baseItems = new List(); + + // 1) Check that item is actually in the package and is the same instance + var assetItemFound = Session.FindAsset(assetItem.Id); + if (!ReferenceEquals(assetItem.Asset, assetItemFound?.Asset)) { - var results = new LoggerValueResult>(); - results.Value.AddRange(ValidateAssetBase(assetItem, results)); - return results; + var assetReference = assetItem.ToReference(); + log.Error(assetItem.Package, assetReference, AssetMessageCode.AssetForPackageNotFound, assetReference, assetItem.Package?.FullPath.GetFileNameWithoutExtension()); + return baseItems; } - /// - /// Validates the inheritance of an asset by checking base accessibility up to the root base. - /// - /// The asset item. - /// The log to output the result of the analysis. - /// A list of all the base in bottom-up order. - /// asset - /// or - /// log - public List ValidateAssetBase(AssetItem assetItem, ILogger log) + // 2) Iterate on each base and perform validation + var currentAsset = assetItem; + while (currentAsset.Asset.Archetype is not null) { - if (assetItem == null) throw new ArgumentNullException(nameof(assetItem)); - if (log == null) throw new ArgumentNullException(nameof(log)); - - var baseItems = new List(); - - // 1) Check that item is actually in the package and is the same instance - var assetItemFound = Session.FindAsset(assetItem.Id); - if (!ReferenceEquals(assetItem.Asset, assetItemFound.Asset)) + // 2.1) Check that asset has not been already processed + if (baseItems.Contains(currentAsset.Asset)) { - var assetReference = assetItem.ToReference(); - log.Error(assetItem.Package, assetReference, AssetMessageCode.AssetForPackageNotFound, assetReference, assetItem.Package.FullPath.GetFileNameWithoutExtension()); - return baseItems; + // Else this is a circular reference + log.Error(assetItem.Package, currentAsset.ToReference(), AssetMessageCode.InvalidCircularReferences, baseItems.Select(item => item.Id)); + break; } - // 2) Iterate on each base and perform validation - var currentAsset = assetItem; - while (currentAsset.Asset.Archetype != null) - { - // 2.1) Check that asset has not been already processed - if (baseItems.Contains(currentAsset.Asset)) - { - // Else this is a circular reference - log.Error(assetItem.Package, currentAsset.ToReference(), AssetMessageCode.InvalidCircularReferences, baseItems.Select(item => item.Id)); - break; - } + // TODO: here we need to add a deep-scan of each base (including the root) for any embedded assets that are + // - // TODO: here we need to add a deep-scan of each base (including the root) for any embedded assets that are - // + // 2.2) Check that base asset is existing + var baseAssetItem = Session.FindAsset(currentAsset.Asset.Archetype.Id); + if (baseAssetItem == null) + { + AssetLogMessage error; - // 2.2) Check that base asset is existing - var baseAssetItem = Session.FindAsset(currentAsset.Asset.Archetype.Id); - if (baseAssetItem == null) + // If an asset with the same location is registered + // Add this asset as a reference in the error message + var newBaseAsset = Session.FindAsset(currentAsset.Asset.Archetype.Location); + if (newBaseAsset != null) { - AssetLogMessage error; - - // If an asset with the same location is registered - // Add this asset as a reference in the error message - var newBaseAsset = Session.FindAsset(currentAsset.Asset.Archetype.Location); - if (newBaseAsset != null) - { - // If asset location exist, log a message with the new location, but don't perform any automatic fix - error = new AssetLogMessage(currentAsset.Package, currentAsset.ToReference(), LogMessageType.Error, AssetMessageCode.BaseChanged, currentAsset.Asset.Archetype.Location); - error.Related.Add(newBaseAsset.ToReference()); - } - else - { - // Base was not found. The base asset has been removed. - error = new AssetLogMessage(currentAsset.Package, currentAsset.ToReference(), LogMessageType.Error, AssetMessageCode.BaseNotFound); - } - - // Set the member to Base. - error.Member = TypeDescriptorFactory.Default.Find(typeof(Asset)).TryGetMember("Base"); - - // Log the error - log.Log(error); - break; + // If asset location exist, log a message with the new location, but don't perform any automatic fix + error = new AssetLogMessage(currentAsset.Package, currentAsset.ToReference(), LogMessageType.Error, AssetMessageCode.BaseChanged, currentAsset.Asset.Archetype.Location); + error.Related.Add(newBaseAsset.ToReference()); } else { - if (baseAssetItem.GetType() != assetItem.Asset.GetType()) - { - log.Error(currentAsset.Package, currentAsset.ToReference(), AssetMessageCode.BaseInvalidType, baseAssetItem.GetType(), assetItem.Asset.GetType()); - } + // Base was not found. The base asset has been removed. + error = new AssetLogMessage(currentAsset.Package, currentAsset.ToReference(), LogMessageType.Error, AssetMessageCode.BaseNotFound); } - currentAsset = baseAssetItem; - baseItems.Add(currentAsset.Asset); + // Set the member to Base. + error.Member = TypeDescriptorFactory.Default.Find(typeof(Asset)).TryGetMember("Base")!; + + // Log the error + log.Log(error); + break; } - return baseItems; + else + { + if (baseAssetItem.GetType() != assetItem.Asset.GetType()) + { + log.Error(currentAsset.Package, currentAsset.ToReference(), AssetMessageCode.BaseInvalidType, baseAssetItem.GetType(), assetItem.Asset.GetType()); + } + } + + currentAsset = baseAssetItem; + baseItems.Add(currentAsset.Asset); } + return baseItems; } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetCollision.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetCollision.cs index b9515e64b3..41010a56f2 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetCollision.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetCollision.cs @@ -1,163 +1,154 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using Stride.Core.IO; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +public static class AssetCollision { - public static class AssetCollision + /// + /// Cleans the specified input items. + /// + /// The package to process (optional). + /// The input items. + /// The output items. + /// The asset resolver. + /// if set to true [clone input]. + /// If set to true, assets will be cloned with . + /// + /// inputItems + /// or + /// outputItems + /// or + /// assetResolver + /// + /// List cannot contain null items;inputItems + public static void Clean(Package package, ICollection inputItems, ICollection outputItems, AssetResolver assetResolver, bool cloneInput, bool removeUnloadableObjects) { - /// - /// Cleans the specified input items. - /// - /// The package to process (optional). - /// The input items. - /// The output items. - /// The asset resolver. - /// if set to true [clone input]. - /// If set to true, assets will be cloned with . - /// - /// inputItems - /// or - /// outputItems - /// or - /// assetResolver - /// - /// List cannot contain null items;inputItems - public static void Clean(Package package, ICollection inputItems, ICollection outputItems, AssetResolver assetResolver, bool cloneInput, bool removeUnloadableObjects) + ArgumentNullException.ThrowIfNull(inputItems); + ArgumentNullException.ThrowIfNull(outputItems); + ArgumentNullException.ThrowIfNull(assetResolver); + + // Check that all items are non-null + if (inputItems.Any(item => item == null)) { - if (inputItems == null) throw new ArgumentNullException(nameof(inputItems)); - if (outputItems == null) throw new ArgumentNullException(nameof(outputItems)); - if (assetResolver == null) throw new ArgumentNullException(nameof(assetResolver)); + throw new ArgumentException("List cannot contain null items", nameof(inputItems)); + } - // Check that all items are non-null - if (inputItems.Any(item => item == null)) - { - throw new ArgumentException("List cannot contain null items", nameof(inputItems)); - } + var items = inputItems; + if (cloneInput) + { + items = inputItems.Select(item => item.Clone(flags: removeUnloadableObjects ? AssetClonerFlags.RemoveUnloadableObjects : AssetClonerFlags.None)).ToList(); + } - var items = inputItems; - if (cloneInput) + // idRemap should contain only assets that have either 1) their id remapped or 2) their location remapped + var idRemap = new Dictionary>(); + var itemRemap = new Dictionary>(); + foreach (var item in items) + { + if (outputItems.Contains(item)) { - items = inputItems.Select(item => item.Clone(flags: removeUnloadableObjects ? AssetClonerFlags.RemoveUnloadableObjects : AssetClonerFlags.None)).ToList(); + continue; } - // idRemap should contain only assets that have either 1) their id remapped or 2) their location remapped - var idRemap = new Dictionary>(); - var itemRemap = new Dictionary>(); - foreach (var item in items) - { - if (outputItems.Contains(item)) - { - continue; - } - - outputItems.Add(item); - - bool changed = false; - AssetId newId; - if (assetResolver.RegisterId(item.Id, out newId)) - { - changed = true; - } + outputItems.Add(item); - // Note: we ignore name collisions if asset is not referenceable - var referenceable = item.Asset.GetType().GetCustomAttribute()?.Referenceable ?? true; + bool changed = false; + if (assetResolver.RegisterId(item.Id, out var newId)) + { + changed = true; + } - UFile newLocation = null; - if (referenceable && assetResolver.RegisterLocation(item.Location, out newLocation)) - { - changed = true; - } + // Note: we ignore name collisions if asset is not referenceable + var referenceable = item.Asset.GetType().GetCustomAttribute()?.Referenceable ?? true; - var tuple = new Tuple(newId != AssetId.Empty ? newId : item.Id, newLocation ?? item.Location); - if (changed) - { - itemRemap.TryAdd(item, tuple); - } - - idRemap.TryAdd(item.Id, tuple); + UFile? newLocation = null; + if (referenceable && assetResolver.RegisterLocation(item.Location, out newLocation)) + { + changed = true; } - // Process assets - foreach (var item in outputItems) + var tuple = new Tuple(newId != AssetId.Empty ? newId : item.Id, newLocation ?? item.Location); + if (changed) { - Tuple remap; - if (itemRemap.TryGetValue(item, out remap) && (remap.Item1 != item.Asset.Id || remap.Item2 != item.Location)) - { - item.Asset.Id = remap.Item1; - item.Location = remap.Item2; - item.IsDirty = true; - } + itemRemap.TryAdd(item, tuple); + } - // Fix base parts if there are any remap for them as well - // This has to be done before the default resolver below because this fix requires to rewrite the base part completely, since the base part asset is immutable - var assetComposite = item.Asset as IAssetComposite; - if (assetComposite != null) - { - foreach (var basePart in assetComposite.CollectParts()) - { - if (basePart.Base != null && idRemap.TryGetValue(basePart.Base.BasePartAsset.Id, out remap) && IsNewReference(remap, basePart.Base.BasePartAsset)) - { - var newAssetReference = new AssetReference(remap.Item1, remap.Item2); - basePart.UpdateBase(new BasePart(newAssetReference, basePart.Base.BasePartId, basePart.Base.InstanceId)); - item.IsDirty = true; - } - } - } + idRemap.TryAdd(item.Id, tuple); + } - // The loop is a one or two-step. - // - If there is no link to update, and the asset has not been cloned, we can exist immediately - // - If there is links to update, and the asset has not been cloned, we need to clone it and re-enter the loop - // to perform the update of the clone asset - var links = AssetReferenceAnalysis.Visit(item.Asset).Where(link => link.Reference is IReference).ToList(); + // Process assets + foreach (var item in outputItems) + { + if (itemRemap.TryGetValue(item, out var remap) && (remap.Item1 != item.Asset.Id || remap.Item2 != item.Location)) + { + item.Asset.Id = remap.Item1; + item.Location = remap.Item2; + item.IsDirty = true; + } - foreach (var assetLink in links) + // Fix base parts if there are any remap for them as well + // This has to be done before the default resolver below because this fix requires to rewrite the base part completely, since the base part asset is immutable + if (item.Asset is IAssetComposite assetComposite) + { + foreach (var basePart in assetComposite.CollectParts()) { - var assetReference = (IReference)assetLink.Reference; - - var newId = assetReference.Id; - if (idRemap.TryGetValue(newId, out remap) && IsNewReference(remap, assetReference)) + if (basePart.Base != null && idRemap.TryGetValue(basePart.Base.BasePartAsset.Id, out remap) && IsNewReference(remap, basePart.Base.BasePartAsset)) { - assetLink.UpdateReference(remap.Item1, remap.Item2); + var newAssetReference = new AssetReference(remap.Item1, remap.Item2); + basePart.UpdateBase(new BasePart(newAssetReference, basePart.Base.BasePartId, basePart.Base.InstanceId)); item.IsDirty = true; } } } - // Process roots (until references in package are handled in general) - if (package != null) - { - UpdateRootAssets(package.RootAssets, idRemap); - } - } + // The loop is a one or two-step. + // - If there is no link to update, and the asset has not been cloned, we can exist immediately + // - If there is links to update, and the asset has not been cloned, we need to clone it and re-enter the loop + // to perform the update of the clone asset + var links = AssetReferenceAnalysis.Visit(item.Asset).Where(link => link.Reference is IReference).ToList(); - private static void UpdateRootAssets(RootAssetCollection rootAssetCollection, IReadOnlyDictionary> idRemap) - { - foreach (var rootAsset in rootAssetCollection.ToArray()) + foreach (var assetLink in links) { - var id = rootAsset.Id; - Tuple remap; + var assetReference = (IReference)assetLink.Reference; - if (idRemap.TryGetValue(id, out remap) && IsNewReference(remap, rootAsset)) + var newId = assetReference.Id; + if (idRemap.TryGetValue(newId, out remap) && IsNewReference(remap, assetReference)) { - var newRootAsset = new AssetReference(remap.Item1, remap.Item2); - rootAssetCollection.Remove(rootAsset.Id); - rootAssetCollection.Add(newRootAsset); + assetLink.UpdateReference(remap.Item1, remap.Item2); + item.IsDirty = true; } } } - private static bool IsNewReference(Tuple newReference, IReference previousReference) + // Process roots (until references in package are handled in general) + if (package != null) { - return newReference.Item1 != previousReference.Id || - newReference.Item2 != previousReference.Location; + UpdateRootAssets(package.RootAssets, idRemap); } } + + private static void UpdateRootAssets(RootAssetCollection rootAssetCollection, Dictionary> idRemap) + { + foreach (var rootAsset in rootAssetCollection.ToArray()) + { + var id = rootAsset.Id; + if (idRemap.TryGetValue(id, out var remap) && IsNewReference(remap, rootAsset)) + { + var newRootAsset = new AssetReference(remap.Item1, remap.Item2); + rootAssetCollection.Remove(rootAsset.Id); + rootAssetCollection.Add(newRootAsset); + } + } + } + + private static bool IsNewReference(Tuple newReference, IReference previousReference) + { + return newReference.Item1 != previousReference.Id || + newReference.Item2 != previousReference.Location; + } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetDependencies.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetDependencies.cs index bf08d8df6b..c4d6d4b75d 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetDependencies.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetDependencies.cs @@ -1,332 +1,315 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; - -using Stride.Core; -using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// Describes dependencies (in/out/broken) for a specific asset. +/// +/// There are 3 types of dependencies: +///
    +///
  • in dependencies: through the property, contains assets +/// that are referencing this asset.
  • +///
  • out dependencies: through the property, contains assets +/// that are referenced by this asset.
  • +///
  • broken dependencies: through the property, +/// contains output links to assets that are missing.
  • +///
+///
+public class AssetDependencies { - /// - /// Describes dependencies (in/out/broken) for a specific asset. - /// - /// There are 3 types of dependencies: - ///
    - ///
  • in dependencies: through the property, contains assets - /// that are referencing this asset.
  • - ///
  • out dependencies: through the property, contains assets - /// that are referenced by this asset.
  • - ///
  • broken dependencies: through the property, - /// contains output links to assets that are missing.
  • - ///
- ///
- public class AssetDependencies - { - private readonly AssetItem item; - private Dictionary parents; - private Dictionary children; - private Dictionary missingChildren; + private Dictionary? parents; + private Dictionary? children; + private Dictionary? missingChildren; - public AssetDependencies(AssetItem assetItem) - { - if (assetItem == null) throw new ArgumentNullException("assetItem"); - item = assetItem; - } - - public AssetDependencies(AssetDependencies set) - { - if (set == null) throw new ArgumentNullException("set"); - item = set.Item; + public AssetDependencies(AssetItem assetItem) + { + ArgumentNullException.ThrowIfNull(assetItem); + Item = assetItem; + } - // Copy Output refs - foreach (var child in set.LinksOut) - AddLinkOut(child); + public AssetDependencies(AssetDependencies set) + { + ArgumentNullException.ThrowIfNull(set); + Item = set.Item; - // Copy Input refs - foreach (var child in set.LinksIn) - AddLinkIn(child); + // Copy Output refs + foreach (var child in set.LinksOut) + AddLinkOut(child); - // Copy missing refs - foreach (var child in set.BrokenLinksOut) - AddBrokenLinkOut(child.Element, child.Type); - } + // Copy Input refs + foreach (var child in set.LinksIn) + AddLinkIn(child); - public AssetId Id - { - get - { - return item.Id; - } - } + // Copy missing refs + foreach (var child in set.BrokenLinksOut) + AddBrokenLinkOut(child.Element, child.Type); + } - /// - /// Gets the itemReferenced. - /// - /// The itemReferenced. - public AssetItem Item + public AssetId Id + { + get { - get - { - return item; - } + return Item.Id; } + } - /// - /// Gets the links coming into the element. - /// - public IEnumerable LinksIn - { - get - { - return parents != null? parents.Values: Enumerable.Empty(); - } - } + /// + /// Gets the itemReferenced. + /// + /// The itemReferenced. + public AssetItem Item { get; } - /// - /// Gets the links going out of the element. - /// - public IEnumerable LinksOut + /// + /// Gets the links coming into the element. + /// + public IEnumerable LinksIn + { + get { - get - { - return children != null ? children.Values : Enumerable.Empty(); - } + return parents is not null ? parents.Values : []; } + } - /// - /// Gets the links out. - /// - /// The missing references. - public IEnumerable BrokenLinksOut + /// + /// Gets the links going out of the element. + /// + public IEnumerable LinksOut + { + get { - get - { - if (missingChildren == null) - yield break; - - foreach (var reference in missingChildren.Values) - yield return reference; - } + return children is not null ? children.Values : []; } + } - /// - /// Resets this instance and clear all dependencies (including missing) - /// - public void Reset(bool keepParents) + /// + /// Gets the links out. + /// + /// The missing references. + public IEnumerable BrokenLinksOut + { + get { - missingChildren = null; - children = null; + if (missingChildren == null) + yield break; - if (!keepParents) - parents = null; + foreach (var reference in missingChildren.Values) + yield return reference; } + } - /// - /// Gets a value indicating whether this instance has missing references. - /// - /// true if this instance has missing references; otherwise, - /// false. - public bool HasMissingDependencies - { - get - { - return missingChildren != null && missingChildren.Count > 0; - } - } + /// + /// Resets this instance and clear all dependencies (including missing) + /// + public void Reset(bool keepParents) + { + missingChildren = null; + children = null; - /// - /// Gets the number of missing dependencies of the asset. - /// - public int MissingDependencyCount - { - get - { - return missingChildren != null ? missingChildren.Count : 0; - } - } + if (!keepParents) + parents = null; + } - /// - /// Adds a link going into the element. - /// - /// The element the link is coming from - /// The type of link - /// A link from this element already exists - public void AddLinkIn(AssetItem fromItem, ContentLinkType contentLinkType) + /// + /// Gets a value indicating whether this instance has missing references. + /// + /// true if this instance has missing references; otherwise, + /// false. + public bool HasMissingDependencies + { + get { - AddLink(ref parents, new AssetLink(fromItem, contentLinkType)); + return missingChildren?.Count > 0; } + } - /// - /// Adds a link coming from the provided element. - /// - /// The link in - /// A link from this element already exists - public void AddLinkIn(AssetLink contentLink) + /// + /// Gets the number of missing dependencies of the asset. + /// + public int MissingDependencyCount + { + get { - AddLink(ref parents, contentLink); + return (missingChildren?.Count) ?? 0; } + } - /// - /// Gets the link coming from the provided element. - /// - /// The element the link is coming from - /// The link - /// There is not link to the provided element - /// fromItem - public AssetLink GetLinkIn(AssetItem fromItem) - { - if (fromItem == null) throw new ArgumentNullException("fromItem"); + /// + /// Adds a link going into the element. + /// + /// The element the link is coming from + /// The type of link + /// A link from this element already exists + public void AddLinkIn(AssetItem fromItem, ContentLinkType contentLinkType) + { + AddLink(ref parents, new AssetLink(fromItem, contentLinkType)); + } - return GetLink(ref parents, fromItem.Id); - } + /// + /// Adds a link coming from the provided element. + /// + /// The link in + /// A link from this element already exists + public void AddLinkIn(AssetLink contentLink) + { + AddLink(ref parents, contentLink); + } - /// - /// Removes the link coming from the provided element. - /// - /// The element the link is coming from - /// fromItem - /// The removed link - public AssetLink RemoveLinkIn(AssetItem fromItem) - { - if (fromItem == null) throw new ArgumentNullException("fromItem"); + /// + /// Gets the link coming from the provided element. + /// + /// The element the link is coming from + /// The link + /// There is not link to the provided element + /// fromItem + public AssetLink GetLinkIn(AssetItem fromItem) + { + ArgumentNullException.ThrowIfNull(fromItem); - return RemoveLink(ref parents, fromItem.Id, ContentLinkType.All); - } + return GetLink(ref parents, fromItem.Id); + } - /// - /// Adds a link going to the provided element. - /// - /// The element the link is going to - /// The type of link - /// A link to this element already exists - public void AddLinkOut(AssetItem toItem, ContentLinkType contentLinkType) - { - AddLink(ref children, new AssetLink(toItem, contentLinkType)); - } + /// + /// Removes the link coming from the provided element. + /// + /// The element the link is coming from + /// fromItem + /// The removed link + public AssetLink RemoveLinkIn(AssetItem fromItem) + { + ArgumentNullException.ThrowIfNull(fromItem); - /// - /// Adds a link going to the provided element. - /// - /// The link out - /// A link to this element already exists - public void AddLinkOut(AssetLink contentLink) - { - AddLink(ref children, contentLink); - } + return RemoveLink(ref parents, fromItem.Id, ContentLinkType.All); + } - /// - /// Gets the link going to the provided element. - /// - /// The element the link is going to - /// The link - /// There is not link to the provided element - /// toItem - public AssetLink GetLinkOut(AssetItem toItem) - { - if (toItem == null) throw new ArgumentNullException("toItem"); + /// + /// Adds a link going to the provided element. + /// + /// The element the link is going to + /// The type of link + /// A link to this element already exists + public void AddLinkOut(AssetItem toItem, ContentLinkType contentLinkType) + { + AddLink(ref children, new AssetLink(toItem, contentLinkType)); + } - return GetLink(ref children, toItem.Id); - } + /// + /// Adds a link going to the provided element. + /// + /// The link out + /// A link to this element already exists + public void AddLinkOut(AssetLink contentLink) + { + AddLink(ref children, contentLink); + } - /// - /// Removes the link going to the provided element. - /// - /// The element the link is going to - /// toItem - /// The removed link - public AssetLink RemoveLinkOut(AssetItem toItem) - { - if (toItem == null) throw new ArgumentNullException("toItem"); + /// + /// Gets the link going to the provided element. + /// + /// The element the link is going to + /// The link + /// There is not link to the provided element + /// toItem + public AssetLink GetLinkOut(AssetItem toItem) + { + ArgumentNullException.ThrowIfNull(toItem); - return RemoveLink(ref children, toItem.Id, ContentLinkType.All); - } + return GetLink(ref children, toItem.Id); + } - /// - /// Adds a broken link out. - /// - /// the reference to the missing element - /// The type of link - /// A broken link to this element already exists - public void AddBrokenLinkOut(IReference reference, ContentLinkType contentLinkType) - { - AddLink(ref missingChildren, new AssetLink(reference, contentLinkType)); - } + /// + /// Removes the link going to the provided element. + /// + /// The element the link is going to + /// toItem + /// The removed link + public AssetLink RemoveLinkOut(AssetItem toItem) + { + ArgumentNullException.ThrowIfNull(toItem); - /// - /// Adds a broken link out. - /// - /// The broken link - /// A broken link to this element already exists - public void AddBrokenLinkOut(IContentLink contentLink) - { - AddLink(ref missingChildren, new AssetLink(contentLink.Element, contentLink.Type)); - } + return RemoveLink(ref children, toItem.Id, ContentLinkType.All); + } - /// - /// Gets the broken link out to the provided element. - /// - /// The id of the element the link is going to - /// The link - /// There is not link to the provided element - /// toItem - public IContentLink GetBrokenLinkOut(AssetId id) - { - return GetLink(ref missingChildren, id); - } + /// + /// Adds a broken link out. + /// + /// the reference to the missing element + /// The type of link + /// A broken link to this element already exists + public void AddBrokenLinkOut(IReference reference, ContentLinkType contentLinkType) + { + AddLink(ref missingChildren, new AssetLink(reference, contentLinkType)); + } - /// - /// Removes the broken link to the provided element. - /// - /// The id to the missing element - /// toItem - /// The removed link - public IContentLink RemoveBrokenLinkOut(AssetId id) - { - return RemoveLink(ref missingChildren, id, ContentLinkType.All); - } + /// + /// Adds a broken link out. + /// + /// The broken link + /// A broken link to this element already exists + public void AddBrokenLinkOut(IContentLink contentLink) + { + AddLink(ref missingChildren, new AssetLink(contentLink.Element, contentLink.Type)); + } - private void AddLink(ref Dictionary dictionary, AssetLink contentLink) - { - if (dictionary == null) - dictionary = new Dictionary(); + /// + /// Gets the broken link out to the provided element. + /// + /// The id of the element the link is going to + /// The link + /// There is not link to the provided element + /// toItem + public IContentLink GetBrokenLinkOut(AssetId id) + { + return GetLink(ref missingChildren, id); + } - var id = contentLink.Element.Id; - if (dictionary.TryGetValue(id, out var existingLink)) - contentLink.Type |= existingLink.Type; + /// + /// Removes the broken link to the provided element. + /// + /// The id to the missing element + /// toItem + /// The removed link + public IContentLink RemoveBrokenLinkOut(AssetId id) + { + return RemoveLink(ref missingChildren, id, ContentLinkType.All); + } - dictionary[id] = contentLink; - } + private static void AddLink(ref Dictionary? dictionary, AssetLink contentLink) + { + dictionary ??= []; - private AssetLink GetLink(ref Dictionary dictionary, AssetId id) - { - if (dictionary == null || !dictionary.ContainsKey(id)) - throw new ArgumentException("There is currently no link between elements '{0}' and '{1}'".ToFormat(item.Id, id)); + var id = contentLink.Element.Id; + if (dictionary.TryGetValue(id, out var existingLink)) + contentLink.Type |= existingLink.Type; - return dictionary[id]; - } + dictionary[id] = contentLink; + } - private AssetLink RemoveLink(ref Dictionary dictionary, AssetId id, ContentLinkType type) - { - if (dictionary == null || !dictionary.ContainsKey(id)) - throw new ArgumentException("There is currently no link between elements '{0}' and '{1}'".ToFormat(item.Id, id)); + private AssetLink GetLink(ref Dictionary? dictionary, AssetId id) + { + if (dictionary == null || !dictionary.TryGetValue(id, out var value)) + throw new ArgumentException("There is currently no link between elements '{0}' and '{1}'".ToFormat(Item.Id, id)); - var oldLink = dictionary[id]; - var newLink = oldLink; + return value; + } + + private AssetLink RemoveLink(ref Dictionary? dictionary, AssetId id, ContentLinkType type) + { + if (dictionary == null || !dictionary.TryGetValue(id, out var oldLink)) + throw new ArgumentException("There is currently no link between elements '{0}' and '{1}'".ToFormat(Item.Id, id)); + var newLink = oldLink; - newLink.Type &= ~type; - oldLink.Type &= type; + newLink.Type &= ~type; + oldLink.Type &= type; - if (newLink.Type == 0) - dictionary.Remove(id); + if (newLink.Type == 0) + dictionary.Remove(id); - if (dictionary.Count == 0) - dictionary = null; + if (dictionary.Count == 0) + dictionary = null; - return oldLink; - } + return oldLink; } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetDependencyManager.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetDependencyManager.cs index 65e741f73c..648e41ef00 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetDependencyManager.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetDependencyManager.cs @@ -1,137 +1,142 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using System.Collections.Specialized; -using System.Linq; using Stride.Core.Assets.Visitors; -using Stride.Core.Extensions; using Stride.Core.Reflection; using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// A class responsible for providing asset dependencies for a and file tracking dependency. +/// +/// +/// This class provides methods to: +///
    +///
  • Find assets referencing a particular asset (recursively or not)
  • +///
  • Find assets referenced by a particular asset (recursively or not)
  • +///
  • Find missing references
  • +///
  • Find missing references for a particular asset
  • +///
  • Find assets file changed events that have changed on the disk
  • +///
+///
+public sealed class AssetDependencyManager : IAssetDependencyManager, IDisposable { + private readonly PackageSession session; + internal readonly object ThisLock = new(); + internal readonly HashSet Packages; + internal readonly Dictionary Dependencies; + internal readonly Dictionary AssetsWithMissingReferences; + internal readonly Dictionary> MissingReferencesToParent; + private bool isDisposed; + private bool isSessionSaving; + private bool isInitialized; + + /// + /// Occurs when a asset changed. This event is called in the critical section of the dependency manager, + /// meaning that dependencies can be safely computed via method from this callback. + /// + public event DirtyFlagChangedDelegate? AssetChanged; + + /// + /// Initializes a new instance of the class. + /// + /// The session. + /// session + internal AssetDependencyManager(PackageSession session) + { + this.session = session ?? throw new ArgumentNullException(nameof(session)); + this.session.Packages.CollectionChanged += Packages_CollectionChanged; + session.AssetDirtyChanged += Session_AssetDirtyChanged; + AssetsWithMissingReferences = []; + MissingReferencesToParent = []; + Packages = []; + Dependencies = []; + // If the session has already a root package, then initialize the dependency manager directly + if (session.LocalPackages.Any()) + { + Initialize(); + } + } + /// - /// A class responsible for providing asset dependencies for a and file tracking dependency. + /// Gets a value indicating whether this instance is initialized. See remarks. /// + /// true if this instance is initialized; otherwise, false. /// - /// This class provides methods to: - ///
    - ///
  • Find assets referencing a particular asset (recursively or not)
  • - ///
  • Find assets referenced by a particular asset (recursively or not)
  • - ///
  • Find missing references
  • - ///
  • Find missing references for a particular asset
  • - ///
  • Find assets file changed events that have changed on the disk
  • - ///
+ /// If this instance is not initialized, all public methods may block until the full initialization of this instance. ///
- public sealed class AssetDependencyManager : IAssetDependencyManager, IDisposable + public bool IsInitialized => isInitialized; + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() { - private readonly PackageSession session; - internal readonly object ThisLock = new object(); - internal readonly HashSet Packages; - internal readonly Dictionary Dependencies; - internal readonly Dictionary AssetsWithMissingReferences; - internal readonly Dictionary> MissingReferencesToParent; - private bool isDisposed; - private bool isSessionSaving; - private bool isInitialized; + if (isDisposed) + return; - /// - /// Occurs when a asset changed. This event is called in the critical section of the dependency manager, - /// meaning that dependencies can be safely computed via method from this callback. - /// - public event DirtyFlagChangedDelegate AssetChanged; + isDisposed = true; + } - /// - /// Initializes a new instance of the class. - /// - /// The session. - /// session - internal AssetDependencyManager(PackageSession session) - { - this.session = session ?? throw new ArgumentNullException(nameof(session)); - this.session.Packages.CollectionChanged += Packages_CollectionChanged; - session.AssetDirtyChanged += Session_AssetDirtyChanged; - AssetsWithMissingReferences = new Dictionary(); - MissingReferencesToParent = new Dictionary>(); - Packages = new HashSet(); - Dependencies = new Dictionary(); - // If the session has already a root package, then initialize the dependency manager directly - if (session.LocalPackages.Any()) - { - Initialize(); - } - } + /// + public AssetDependencies? ComputeDependencies(AssetId assetId, AssetDependencySearchOptions dependenciesOptions = AssetDependencySearchOptions.All, ContentLinkType linkTypes = ContentLinkType.Reference, HashSet? visited = null) + { + bool recursive = (dependenciesOptions & AssetDependencySearchOptions.Recursive) != 0; + if (visited is null && recursive) + visited = []; - /// - /// Gets a value indicating whether this instance is initialized. See remarks. - /// - /// true if this instance is initialized; otherwise, false. - /// - /// If this instance is not initialized, all public methods may block until the full initialization of this instance. - /// - public bool IsInitialized => isInitialized; + //var clock = Stopwatch.StartNew(); - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() + lock (Initialize()) { - if (isDisposed) - return; + if (!Dependencies.TryGetValue(assetId, out var dependencies)) + return null; - isDisposed = true; - } - - /// - public AssetDependencies ComputeDependencies(AssetId assetId, AssetDependencySearchOptions dependenciesOptions = AssetDependencySearchOptions.All, ContentLinkType linkTypes = ContentLinkType.Reference, HashSet visited = null) - { - bool recursive = (dependenciesOptions & AssetDependencySearchOptions.Recursive) != 0; - if (visited == null && recursive) - visited = new HashSet(); + dependencies = new AssetDependencies(dependencies.Item); - //var clock = Stopwatch.StartNew(); + int inCount = 0, outCount = 0; - lock (Initialize()) + if ((dependenciesOptions & AssetDependencySearchOptions.In) != 0) { - AssetDependencies dependencies; - if (!Dependencies.TryGetValue(assetId, out dependencies)) - return null; + CollectInputReferences(dependencies, assetId, visited, recursive, linkTypes, ref inCount); + } - dependencies = new AssetDependencies(dependencies.Item); + if ((dependenciesOptions & AssetDependencySearchOptions.Out) != 0) + { + visited?.Clear(); + CollectOutputReferences(dependencies, assetId, visited, recursive, linkTypes, ref outCount); + } - int inCount = 0, outCount = 0; + //Console.WriteLine("Time to compute dependencies: {0}ms in: {1} out:{2}", clock.ElapsedMilliseconds, inCount, outCount); - if ((dependenciesOptions & AssetDependencySearchOptions.In) != 0) - { - CollectInputReferences(dependencies, assetId, visited, recursive, linkTypes, ref inCount); - } + return dependencies; + } - if ((dependenciesOptions & AssetDependencySearchOptions.Out) != 0) - { - visited?.Clear(); - CollectOutputReferences(dependencies, assetId, visited, recursive, linkTypes, ref outCount); - } + } - //Console.WriteLine("Time to compute dependencies: {0}ms in: {1} out:{2}", clock.ElapsedMilliseconds, inCount, outCount); - return dependencies; + private object Initialize() + { + lock (ThisLock) + { + if (isInitialized) + { + return ThisLock; } - } - - - private object Initialize() - { - lock (ThisLock) + // If the package is cancelled, don't try to do anything + // A cancellation means that the package session will be destroyed + if (isDisposed) { - if (isInitialized) - { - return ThisLock; - } + return ThisLock; + } + // Initialize with the list of packages + foreach (var package in session.Packages) + { // If the package is cancelled, don't try to do anything // A cancellation means that the package session will be destroyed if (isDisposed) @@ -139,599 +144,593 @@ private object Initialize() return ThisLock; } - // Initialize with the list of packages - foreach (var package in session.Packages) - { - // If the package is cancelled, don't try to do anything - // A cancellation means that the package session will be destroyed - if (isDisposed) - { - return ThisLock; - } - - TrackPackage(package); - } - - isInitialized = true; + TrackPackage(package); } - return ThisLock; + + isInitialized = true; } + return ThisLock; + } - /// - /// Collects all references of an asset dynamically. - /// - /// The result. - /// The asset resolver. - /// if set to true collects references recursively. - /// Indicate if the parent of the provided should be kept or not - /// - /// result - /// or - /// assetResolver - /// - private static void CollectDynamicOutReferences(AssetDependencies result, Func assetResolver, bool isRecursive, bool keepParents) - { - if (result == null) throw new ArgumentNullException(nameof(result)); - if (assetResolver == null) throw new ArgumentNullException(nameof(assetResolver)); + /// + /// Collects all references of an asset dynamically. + /// + /// The result. + /// The asset resolver. + /// if set to true collects references recursively. + /// Indicate if the parent of the provided should be kept or not + /// + /// result + /// or + /// assetResolver + /// + private static void CollectDynamicOutReferences(AssetDependencies result, Func assetResolver, bool isRecursive, bool keepParents) + { + ArgumentNullException.ThrowIfNull(result); + ArgumentNullException.ThrowIfNull(assetResolver); - var addedReferences = new HashSet(); - var itemsToAnalyze = new Queue(); - var referenceCollector = new DependenciesCollector(); + var addedReferences = new HashSet(); + var itemsToAnalyze = new Queue(); + var referenceCollector = new DependenciesCollector(); - // Reset the dependencies/parts. - result.Reset(keepParents); + // Reset the dependencies/parts. + result.Reset(keepParents); - var assetItem = result.Item; + var assetItem = result.Item; - // marked as processed to not add it again - addedReferences.Add(assetItem.Id); - itemsToAnalyze.Enqueue(assetItem); + // marked as processed to not add it again + addedReferences.Add(assetItem.Id); + itemsToAnalyze.Enqueue(assetItem); - while (itemsToAnalyze.Count > 0) - { - var item = itemsToAnalyze.Dequeue(); + while (itemsToAnalyze.Count > 0) + { + var item = itemsToAnalyze.Dequeue(); - foreach (var link in referenceCollector.GetDependencies(item)) - { - if (addedReferences.Contains(link.Element.Id)) - continue; + foreach (var link in referenceCollector.GetDependencies(item)) + { + if (addedReferences.Contains(link.Element.Id)) + continue; - // marked as processed to not add it again - addedReferences.Add(link.Element.Id); + // marked as processed to not add it again + addedReferences.Add(link.Element.Id); - // add the location to the reference location list - var nextItem = assetResolver(link.Element.Id); - if (nextItem != null) - { - result.AddLinkOut(nextItem, link.Type); + // add the location to the reference location list + var nextItem = assetResolver(link.Element.Id); + if (nextItem is not null) + { + result.AddLinkOut(nextItem, link.Type); - // add current element to analyze list, to analyze dependencies recursively - if (isRecursive) - { - itemsToAnalyze.Enqueue(nextItem); - } - } - else + // add current element to analyze list, to analyze dependencies recursively + if (isRecursive) { - result.AddBrokenLinkOut(link); + itemsToAnalyze.Enqueue(nextItem); } } - - if (!isRecursive) + else { - break; + result.AddBrokenLinkOut(link); } } - } - private AssetItem FindAssetFromDependencyOrSession(AssetId assetId) - { - // We cannot return the item from the session but we can only return assets currently tracked by the dependency - // manager - var item = session.FindAsset(assetId); - if (item != null) + if (!isRecursive) { - var dependencies = TrackAsset(assetId); - return dependencies.Item; + break; } - return null; } + } - /// - /// This methods is called when a session is about to being saved. - /// - public void BeginSavingSession() + private AssetItem? FindAssetFromDependencyOrSession(AssetId assetId) + { + // We cannot return the item from the session but we can only return assets currently tracked by the dependency + // manager + var item = session.FindAsset(assetId); + if (item is not null) { - isSessionSaving = true; + var dependencies = TrackAsset(assetId); + return dependencies?.Item; } + return null; + } - /// - /// This methods is called when a session has been saved. - /// - public void EndSavingSession() - { - isSessionSaving = false; - } - - /// - /// Calculate the dependencies for the specified asset either by using the internal cache if the asset is already in the session - /// or by calculating - /// - /// The asset id. - /// The dependencies. - private AssetDependencies CalculateDependencies(AssetId assetId) - { - AssetDependencies dependencies; - Dependencies.TryGetValue(assetId, out dependencies); - return dependencies; - } + /// + /// This methods is called when a session is about to being saved. + /// + public void BeginSavingSession() + { + isSessionSaving = true; + } - /// - /// This method is called when a package needs to be tracked - /// - /// The package to track. - private void TrackPackage(Package package) + /// + /// This methods is called when a session has been saved. + /// + public void EndSavingSession() + { + isSessionSaving = false; + } + + /// + /// Calculate the dependencies for the specified asset either by using the internal cache if the asset is already in the session + /// or by calculating + /// + /// The asset id. + /// The dependencies. + private AssetDependencies? CalculateDependencies(AssetId assetId) + { + Dependencies.TryGetValue(assetId, out var dependencies); + return dependencies; + } + + /// + /// This method is called when a package needs to be tracked + /// + /// The package to track. + private void TrackPackage(Package package) + { + lock (ThisLock) { - lock (ThisLock) - { - if (Packages.Contains(package)) - return; + if (Packages.Contains(package)) + return; - Packages.Add(package); + Packages.Add(package); - foreach (var asset in package.Assets) + foreach (var asset in package.Assets) + { + // If the package is cancelled, don't try to do anything + // A cancellation means that the package session will be destroyed + if (isDisposed) { - // If the package is cancelled, don't try to do anything - // A cancellation means that the package session will be destroyed - if (isDisposed) - { - return; - } - - TrackAsset(asset); + return; } - package.Assets.CollectionChanged += Assets_CollectionChanged; + TrackAsset(asset); } + + package.Assets.CollectionChanged += Assets_CollectionChanged; } + } - /// - /// This method is called when a package needs to be un-tracked - /// - /// The package to un-track. - private void UnTrackPackage(Package package) + /// + /// This method is called when a package needs to be un-tracked + /// + /// The package to un-track. + private void UnTrackPackage(Package package) + { + lock (ThisLock) { - lock (ThisLock) - { - if (!Packages.Contains(package)) - return; - - package.Assets.CollectionChanged -= Assets_CollectionChanged; + if (!Packages.Contains(package)) + return; - foreach (var asset in package.Assets) - { - UnTrackAsset(asset); - } + package.Assets.CollectionChanged -= Assets_CollectionChanged; - Packages.Remove(package); + foreach (var asset in package.Assets) + { + UnTrackAsset(asset); } - } - /// - /// This method is called when an asset needs to be tracked - /// - /// The asset item source. - /// AssetDependencies. - private AssetDependencies TrackAsset(AssetItem assetItemSource) - { - return TrackAsset(assetItemSource.Id); + Packages.Remove(package); } + } - /// - /// This method is called when an asset needs to be tracked - /// - /// AssetDependencies. - private AssetDependencies TrackAsset(AssetId assetId) + /// + /// This method is called when an asset needs to be tracked + /// + /// The asset item source. + /// AssetDependencies. + private AssetDependencies? TrackAsset(AssetItem assetItemSource) + { + return TrackAsset(assetItemSource.Id); + } + + /// + /// This method is called when an asset needs to be tracked + /// + /// AssetDependencies. + private AssetDependencies? TrackAsset(AssetId assetId) + { + lock (ThisLock) { - lock (ThisLock) + if (Dependencies.TryGetValue(assetId, out var dependencies)) + return dependencies; + + // TODO provide an optimized version of TrackAsset method + // taking directly a well known asset (loaded from a Package...etc.) + // to avoid session.FindAsset + var assetItem = session.FindAsset(assetId); + if (assetItem is null) { - AssetDependencies dependencies; - if (Dependencies.TryGetValue(assetId, out dependencies)) - return dependencies; - - // TODO provide an optimized version of TrackAsset method - // taking directly a well known asset (loaded from a Package...etc.) - // to avoid session.FindAsset - var assetItem = session.FindAsset(assetId); - if (assetItem == null) - { - return null; - } + return null; + } - // Clone the asset before using it in this instance to make sure that - // we have some kind of immutable state - // TODO: This is not handling shadow registry + // Clone the asset before using it in this instance to make sure that + // we have some kind of immutable state + // TODO: This is not handling shadow registry - // No need to clone assets from readonly package - var assetItemCloned = assetItem.Package.IsSystem - ? assetItem - : new AssetItem(assetItem.Location, AssetCloner.Clone(assetItem.Asset), assetItem.Package) - { - SourceFolder = assetItem.SourceFolder, - AlternativePath = assetItem.AlternativePath, - }; - - dependencies = new AssetDependencies(assetItemCloned); + // No need to clone assets from readonly package + var assetItemCloned = assetItem.Package?.IsSystem ?? false + ? assetItem + : new AssetItem(assetItem.Location, AssetCloner.Clone(assetItem.Asset), assetItem.Package) + { + SourceFolder = assetItem.SourceFolder, + AlternativePath = assetItem.AlternativePath, + }; - // Adds to global list - Dependencies.Add(assetId, dependencies); + dependencies = new AssetDependencies(assetItemCloned); - // Update dependencies - UpdateAssetDependencies(dependencies); - CheckAllDependencies(); + // Adds to global list + Dependencies.Add(assetId, dependencies); - return dependencies; - } - } + // Update dependencies + UpdateAssetDependencies(dependencies); + CheckAllDependencies(); - private void CheckAllDependencies() - { - //foreach (var dependencies in Dependencies.Values) - //{ - // foreach (var outDependencies in dependencies) - // { - // if (outDependencies.Package == null) - // { - // System.Diagnostics.Debugger.Break(); - // } - // } - //} + return dependencies; } + } - /// - /// This method is called when an asset needs to be un-tracked - /// - /// The asset item source. - private void UnTrackAsset(AssetItem assetItemSource) + private void CheckAllDependencies() + { + //foreach (var dependencies in Dependencies.Values) + //{ + // foreach (var outDependencies in dependencies) + // { + // if (outDependencies.Package is null) + // { + // System.Diagnostics.Debugger.Break(); + // } + // } + //} + } + + /// + /// This method is called when an asset needs to be un-tracked + /// + /// The asset item source. + private void UnTrackAsset(AssetItem assetItemSource) + { + lock (ThisLock) { - lock (ThisLock) - { - var assetId = assetItemSource.Id; - AssetDependencies dependencies; - if (!Dependencies.TryGetValue(assetId, out dependencies)) - return; + var assetId = assetItemSource.Id; + if (!Dependencies.TryGetValue(assetId, out var dependencies)) + return; - // Remove from global list - Dependencies.Remove(assetId); + // Remove from global list + Dependencies.Remove(assetId); - // Remove previous missing dependencies - RemoveMissingDependencies(dependencies); + // Remove previous missing dependencies + RemoveMissingDependencies(dependencies); - // Update [In] dependencies for children - foreach (var childItem in dependencies.LinksOut) + // Update [In] dependencies for children + foreach (var childItem in dependencies.LinksOut) + { + if (Dependencies.TryGetValue(childItem.Item.Id, out var childDependencyItem)) { - AssetDependencies childDependencyItem; - if (Dependencies.TryGetValue(childItem.Item.Id, out childDependencyItem)) - { - childDependencyItem.RemoveLinkIn(dependencies.Item); - } + childDependencyItem.RemoveLinkIn(dependencies.Item); } + } - // Update [Out] dependencies for parents - foreach (var parentDependencies in dependencies.LinksIn) - { - var assetDependencies = Dependencies[parentDependencies.Item.Id]; - var linkOut = assetDependencies.RemoveLinkOut(dependencies.Item); - assetDependencies.AddBrokenLinkOut(linkOut); + // Update [Out] dependencies for parents + foreach (var parentDependencies in dependencies.LinksIn) + { + var assetDependencies = Dependencies[parentDependencies.Item.Id]; + var linkOut = assetDependencies.RemoveLinkOut(dependencies.Item); + assetDependencies.AddBrokenLinkOut(linkOut); - UpdateMissingDependencies(assetDependencies); - } + UpdateMissingDependencies(assetDependencies); } - - CheckAllDependencies(); } - private void UpdateAssetDependencies(AssetDependencies dependencies) - { - lock (ThisLock) - { - // Remove previous missing dependencies - RemoveMissingDependencies(dependencies); + CheckAllDependencies(); + } - // Remove [In] dependencies from previous children - foreach (var referenceAsset in dependencies.LinksOut) - { - var childDependencyItem = TrackAsset(referenceAsset.Item); - childDependencyItem?.RemoveLinkIn(dependencies.Item); - } + private void UpdateAssetDependencies(AssetDependencies dependencies) + { + lock (ThisLock) + { + // Remove previous missing dependencies + RemoveMissingDependencies(dependencies); - // Recalculate [Out] dependencies - CollectDynamicOutReferences(dependencies, FindAssetFromDependencyOrSession, false, true); + // Remove [In] dependencies from previous children + foreach (var referenceAsset in dependencies.LinksOut) + { + var childDependencyItem = TrackAsset(referenceAsset.Item); + childDependencyItem?.RemoveLinkIn(dependencies.Item); + } - // Add [In] dependencies to new children - foreach (var assetLink in dependencies.LinksOut) - { - var childDependencyItem = TrackAsset(assetLink.Item); - childDependencyItem?.AddLinkIn(dependencies.Item, assetLink.Type); - } + // Recalculate [Out] dependencies + CollectDynamicOutReferences(dependencies, FindAssetFromDependencyOrSession, false, true); - // Update missing dependencies - UpdateMissingDependencies(dependencies); + // Add [In] dependencies to new children + foreach (var assetLink in dependencies.LinksOut) + { + var childDependencyItem = TrackAsset(assetLink.Item); + childDependencyItem?.AddLinkIn(dependencies.Item, assetLink.Type); } + + // Update missing dependencies + UpdateMissingDependencies(dependencies); } + } - private void RemoveMissingDependencies(AssetDependencies dependencies) + private void RemoveMissingDependencies(AssetDependencies dependencies) + { + if (AssetsWithMissingReferences.Remove(dependencies.Item.Id)) { - if (AssetsWithMissingReferences.ContainsKey(dependencies.Item.Id)) + foreach (var assetLink in dependencies.BrokenLinksOut) { - AssetsWithMissingReferences.Remove(dependencies.Item.Id); - foreach (var assetLink in dependencies.BrokenLinksOut) + var list = MissingReferencesToParent[assetLink.Element.Id]; + list.Remove(dependencies); + if (list.Count == 0) { - var list = MissingReferencesToParent[assetLink.Element.Id]; - list.Remove(dependencies); - if (list.Count == 0) - { - MissingReferencesToParent.Remove(assetLink.Element.Id); - } + MissingReferencesToParent.Remove(assetLink.Element.Id); } } } + } - private void UpdateMissingDependencies(AssetDependencies dependencies) + private void UpdateMissingDependencies(AssetDependencies dependencies) + { + HashSet? parentDependencyItems; + // If the asset has any missing dependencies, update the fast lookup tables + if (dependencies.HasMissingDependencies) { - HashSet parentDependencyItems; - // If the asset has any missing dependencies, update the fast lookup tables - if (dependencies.HasMissingDependencies) - { - AssetsWithMissingReferences[dependencies.Item.Id] = dependencies; + AssetsWithMissingReferences[dependencies.Item.Id] = dependencies; - foreach (var assetLink in dependencies.BrokenLinksOut) + foreach (var assetLink in dependencies.BrokenLinksOut) + { + if (!MissingReferencesToParent.TryGetValue(assetLink.Element.Id, out parentDependencyItems)) { - if (!MissingReferencesToParent.TryGetValue(assetLink.Element.Id, out parentDependencyItems)) - { - parentDependencyItems = new HashSet(); - MissingReferencesToParent.Add(assetLink.Element.Id, parentDependencyItems); - } - - parentDependencyItems.Add(dependencies); + parentDependencyItems = []; + MissingReferencesToParent.Add(assetLink.Element.Id, parentDependencyItems); } + + parentDependencyItems.Add(dependencies); } + } - var item = dependencies.Item; + var item = dependencies.Item; - // If the new asset was a missing reference, remove all missing references for this asset - if (MissingReferencesToParent.TryGetValue(item.Id, out parentDependencyItems)) + // If the new asset was a missing reference, remove all missing references for this asset + if (MissingReferencesToParent.TryGetValue(item.Id, out parentDependencyItems)) + { + MissingReferencesToParent.Remove(item.Id); + foreach (var parentDependencies in parentDependencyItems) { - MissingReferencesToParent.Remove(item.Id); - foreach (var parentDependencies in parentDependencyItems) - { - // Remove missing dependency from parent - var oldBrokenLink = parentDependencies.RemoveBrokenLinkOut(item.Id); + // Remove missing dependency from parent + var oldBrokenLink = parentDependencies.RemoveBrokenLinkOut(item.Id); - // Update [Out] dependency to parent - parentDependencies.AddLinkOut(item, oldBrokenLink.Type); + // Update [Out] dependency to parent + parentDependencies.AddLinkOut(item, oldBrokenLink.Type); - // Update [In] dependency to current - dependencies.AddLinkIn(parentDependencies.Item, oldBrokenLink.Type); + // Update [In] dependency to current + dependencies.AddLinkIn(parentDependencies.Item, oldBrokenLink.Type); - // Remove global cache for assets with missing references - if (!parentDependencies.HasMissingDependencies) - { - AssetsWithMissingReferences.Remove(parentDependencies.Item.Id); - } + // Remove global cache for assets with missing references + if (!parentDependencies.HasMissingDependencies) + { + AssetsWithMissingReferences.Remove(parentDependencies.Item.Id); } } } + } - private void Session_AssetDirtyChanged(AssetItem asset, bool oldValue, bool newValue) + private void Session_AssetDirtyChanged(AssetItem asset, bool oldValue, bool newValue) + { + // Don't update the dependency manager while saving (setting dirty flag to false) + if (!isSessionSaving) { - // Don't update the dependency manager while saving (setting dirty flag to false) - if (!isSessionSaving) + lock (ThisLock) { - lock (ThisLock) + if (Dependencies.TryGetValue(asset.Id, out var dependencies)) { - AssetDependencies dependencies; - if (Dependencies.TryGetValue(asset.Id, out dependencies)) - { - dependencies.Item.Asset = AssetCloner.Clone(asset.Asset); - dependencies.Item.Version = asset.Version; - UpdateAssetDependencies(dependencies); + dependencies.Item.Asset = AssetCloner.Clone(asset.Asset); + dependencies.Item.Version = asset.Version; + UpdateAssetDependencies(dependencies); - // Notify an asset changed - OnAssetChanged(dependencies.Item, oldValue, newValue); - } + // Notify an asset changed + OnAssetChanged(dependencies.Item, oldValue, newValue); } - - CheckAllDependencies(); } + + CheckAllDependencies(); } + } - private void Packages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void Packages_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - TrackPackage((Package)e.NewItems[0]); - break; - case NotifyCollectionChangedAction.Remove: - UnTrackPackage((Package)e.OldItems[0]); - break; - - case NotifyCollectionChangedAction.Replace: - foreach (var oldPackage in e.OldItems.OfType()) - { - UnTrackPackage(oldPackage); - } + case NotifyCollectionChangedAction.Add: + { + if (e.NewItems?[0] is Package package) + TrackPackage(package); + } + break; + case NotifyCollectionChangedAction.Remove: + { + if (e.OldItems?[0] is Package package) + UnTrackPackage(package); + } + break; - foreach (var packageToCopy in session.Packages) - { - TrackPackage(packageToCopy); - } - break; - } + case NotifyCollectionChangedAction.Replace: + foreach (var oldPackage in e.OldItems?.OfType() ?? []) + { + UnTrackPackage(oldPackage); + } + + foreach (var packageToCopy in session.Packages) + { + TrackPackage(packageToCopy); + } + break; } - - private void Assets_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + } + + private void Assets_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - TrackAsset(((AssetItem)e.NewItems[0])); - break; - case NotifyCollectionChangedAction.Remove: - UnTrackAsset(((AssetItem)e.OldItems[0])); - break; - - case NotifyCollectionChangedAction.Reset: - var collection = (PackageAssetCollection)sender; - - var items = Dependencies.Values.Where(item => ReferenceEquals(item.Item.Package, collection.Package)).ToList(); - foreach (var assetItem in items) - { - UnTrackAsset(assetItem.Item); - } - foreach (var assetItem in collection) - { - TrackAsset(assetItem); - } - break; - } + case NotifyCollectionChangedAction.Add: + { + if (e.NewItems?[0] is AssetItem asset) + TrackAsset(asset); + } + break; + case NotifyCollectionChangedAction.Remove: + { + if (e.OldItems?[0] is AssetItem asset) + UnTrackAsset(asset); + } + break; + + case NotifyCollectionChangedAction.Reset: + var collection = (PackageAssetCollection)sender!; + + var items = Dependencies.Values.Where(item => ReferenceEquals(item.Item.Package, collection.Package)).ToList(); + foreach (var assetItem in items) + { + UnTrackAsset(assetItem.Item); + } + foreach (var assetItem in collection) + { + TrackAsset(assetItem); + } + break; } + } - private void CollectInputReferences(AssetDependencies dependencyRoot, AssetId assetId, HashSet visited, bool recursive, ContentLinkType linkTypes, ref int count) + private void CollectInputReferences(AssetDependencies dependencyRoot, AssetId assetId, HashSet? visited, bool recursive, ContentLinkType linkTypes, ref int count) + { + if (visited is not null) { - if (visited != null) - { - if (visited.Contains(assetId)) - return; + if (visited.Contains(assetId)) + return; - visited.Add(assetId); - } + visited.Add(assetId); + } - count++; + count++; - AssetDependencies dependencies; - Dependencies.TryGetValue(assetId, out dependencies); - if (dependencies != null) + Dependencies.TryGetValue(assetId, out var dependencies); + if (dependencies is not null) + { + foreach (var pair in dependencies.LinksIn) { - foreach (var pair in dependencies.LinksIn) + if ((linkTypes & pair.Type) != 0) { - if ((linkTypes & pair.Type) != 0) - { - dependencyRoot.AddLinkIn(pair); + dependencyRoot.AddLinkIn(pair); - if (visited != null && recursive) - { - CollectInputReferences(dependencyRoot, pair.Item.Id, visited, true, linkTypes, ref count); - } + if (visited is not null && recursive) + { + CollectInputReferences(dependencyRoot, pair.Item.Id, visited, true, linkTypes, ref count); } } } } + } - private void CollectOutputReferences(AssetDependencies dependencyRoot, AssetId assetId, HashSet visited, bool recursive, ContentLinkType linkTypes, ref int count) + private void CollectOutputReferences(AssetDependencies dependencyRoot, AssetId assetId, HashSet? visited, bool recursive, ContentLinkType linkTypes, ref int count) + { + if (visited is not null) { - if (visited != null) - { - if (visited.Contains(assetId)) - return; + if (visited.Contains(assetId)) + return; - visited.Add(assetId); - } + visited.Add(assetId); + } - count++; + count++; - var dependencies = CalculateDependencies(assetId); - if (dependencies == null) - return; + var dependencies = CalculateDependencies(assetId); + if (dependencies is null) + return; - // Add missing references - foreach (var missingRef in dependencies.BrokenLinksOut) - { - dependencyRoot.AddBrokenLinkOut(missingRef); - } + // Add missing references + foreach (var missingRef in dependencies.BrokenLinksOut) + { + dependencyRoot.AddBrokenLinkOut(missingRef); + } - // Add output references - foreach (var child in dependencies.LinksOut) + // Add output references + foreach (var child in dependencies.LinksOut) + { + if ((linkTypes & child.Type) != 0) { - if ((linkTypes & child.Type) != 0) - { - dependencyRoot.AddLinkOut(child); + dependencyRoot.AddLinkOut(child); - if (visited != null && recursive) - { - CollectOutputReferences(dependencyRoot, child.Item.Id, visited, true, linkTypes, ref count); - } + if (visited is not null && recursive) + { + CollectOutputReferences(dependencyRoot, child.Item.Id, visited, true, linkTypes, ref count); } } } + } + /// + /// An interface providing methods to collect of asset references from an . + /// + private interface IDependenciesCollector + { /// - /// An interface providing methods to collect of asset references from an . + /// Get the asset references of an . This function is not recursive. /// - private interface IDependenciesCollector - { - /// - /// Get the asset references of an . This function is not recursive. - /// - /// The item we when the references of - /// - IEnumerable GetDependencies(AssetItem item); - } + /// The item we when the references of + /// + IEnumerable GetDependencies(AssetItem item); + } + + private void OnAssetChanged(AssetItem obj, bool oldValue, bool newValue) + { + // Make sure we clone the item here only if it is necessary + // Cloning the AssetItem is mandatory in order to make sure + // the asset item won't change + AssetChanged?.Invoke(obj.Clone(true), oldValue, newValue); + } - private void OnAssetChanged(AssetItem obj, bool oldValue, bool newValue) + /// + /// Visitor that collect all asset references. + /// + private class DependenciesCollector : AssetVisitorBase, IDependenciesCollector + { + private AssetDependencies? dependencies; + + public IEnumerable GetDependencies(AssetItem item) { - // Make sure we clone the item here only if it is necessary - // Cloning the AssetItem is mandatory in order to make sure - // the asset item won't change - AssetChanged?.Invoke(obj.Clone(true), oldValue, newValue); + dependencies = new AssetDependencies(item); + Visit(item.Asset); + return dependencies.BrokenLinksOut; } - /// - /// Visitor that collect all asset references. - /// - private class DependenciesCollector : AssetVisitorBase, IDependenciesCollector + public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) { - private AssetDependencies dependencies; - - public IEnumerable GetDependencies(AssetItem item) + // references and base + var reference = obj as IReference; + if (reference is null) { - dependencies = new AssetDependencies(item); - Visit(item.Asset); - return dependencies.BrokenLinksOut; + var attachedReference = AttachedReferenceManager.GetAttachedReference(obj); + if (attachedReference is not null && attachedReference.IsProxy) + reference = attachedReference; } - public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) + if (reference is not null) { - // references and base - var reference = obj as IReference; - if (reference == null) - { - var attachedReference = AttachedReferenceManager.GetAttachedReference(obj); - if (attachedReference != null && attachedReference.IsProxy) - reference = attachedReference; - } - - if (reference != null) - { - dependencies.AddBrokenLinkOut(reference, ContentLinkType.Reference); - } - else - { - base.VisitObject(obj, descriptor, visitMembers); - } + dependencies!.AddBrokenLinkOut(reference, ContentLinkType.Reference); } - - public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object value) + else { - if (typeof(Asset).IsAssignableFrom(member.DeclaringType) && member.Name == nameof(Asset.Archetype) && value != null) - { - dependencies.AddBrokenLinkOut((AssetReference)value, ContentLinkType.Reference); - return; - } + base.VisitObject(obj, descriptor, visitMembers); + } + } - base.VisitObjectMember(container, containerDescriptor, member, value); + public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object? value) + { + if (typeof(Asset).IsAssignableFrom(member.DeclaringType) && member.Name == nameof(Asset.Archetype) && value is not null) + { + dependencies!.AddBrokenLinkOut((AssetReference)value, ContentLinkType.Reference); + return; } + + base.VisitObjectMember(container, containerDescriptor, member, value); } } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetDependencySearchOptions.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetDependencySearchOptions.cs index 4b9976ef37..471f40d226 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetDependencySearchOptions.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetDependencySearchOptions.cs @@ -1,38 +1,36 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// Options used when searching asset dependencies. +/// +[Flags] +public enum AssetDependencySearchOptions { /// - /// Options used when searching asset dependencies. + /// Search for in only dependencies. /// - [Flags] - public enum AssetDependencySearchOptions - { - /// - /// Search for in only dependencies. - /// - In = 1, + In = 1, - /// - /// Search for out only dependencies. - /// - Out = 2, + /// + /// Search for out only dependencies. + /// + Out = 2, - /// - /// Search for in and out dependencies. - /// - InOut = In | Out, + /// + /// Search for in and out dependencies. + /// + InOut = In | Out, - /// - /// Search recursively - /// - Recursive = 4, + /// + /// Search recursively + /// + Recursive = 4, - /// - /// Search recursively all in and out dependencies. - /// - All = InOut | Recursive - } + /// + /// Search recursively all in and out dependencies. + /// + All = InOut | Recursive } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetFileChangedEvent.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetFileChangedEvent.cs index f3f94c63ee..936e0bdad4 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetFileChangedEvent.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetFileChangedEvent.cs @@ -1,59 +1,58 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.IO; using Stride.Core.Storage; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// An event that notifies the type of disk change for an asset. +/// +public class AssetFileChangedEvent : EventArgs { /// - /// An event that notifies the type of disk change for an asset. + /// Initializes a new instance of the class. /// - public class AssetFileChangedEvent : EventArgs + /// The package. + /// Type of the change. + /// The asset URL. + public AssetFileChangedEvent(Package package, AssetFileChangedType changeType, UFile assetLocation) { - /// - /// Initializes a new instance of the class. - /// - /// The package. - /// Type of the change. - /// The asset URL. - public AssetFileChangedEvent(Package package, AssetFileChangedType changeType, UFile assetLocation) - { - Package = package; - ChangeType = changeType; - AssetLocation = assetLocation.GetDirectoryAndFileNameWithoutExtension(); // Make sure we are using the location withint the package without the extension - } - - /// - /// Gets the package the event is related to. - /// - /// The package. - public Package Package { get; set; } - - /// - /// Gets the type of the change. - /// - /// The type of the change. - public AssetFileChangedType ChangeType { get; set; } - - /// - /// Gets or sets the asset identifier. - /// - /// The asset identifier. - public Guid AssetId { get; set; } - - /// - /// Gets the asset location relative to the package. - /// - /// The asset location. - public UFile AssetLocation { get; set; } - - /// - /// Gets or sets the hash of the asset source (optional). - /// - /// - /// The hash of the asset source. - /// - public ObjectId? Hash { get; set; } + Package = package; + ChangeType = changeType; + AssetLocation = assetLocation.GetDirectoryAndFileNameWithoutExtension(); // Make sure we are using the location within the package without the extension } + + /// + /// Gets the package the event is related to. + /// + /// The package. + public Package Package { get; set; } + + /// + /// Gets the type of the change. + /// + /// The type of the change. + public AssetFileChangedType ChangeType { get; set; } + + /// + /// Gets or sets the asset identifier. + /// + /// The asset identifier. + public Guid AssetId { get; set; } + + /// + /// Gets the asset location relative to the package. + /// + /// The asset location. + public UFile? AssetLocation { get; set; } + + /// + /// Gets or sets the hash of the asset source (optional). + /// + /// + /// The hash of the asset source. + /// + public ObjectId? Hash { get; set; } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetFileChangedEventSquasher.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetFileChangedEventSquasher.cs index 1620102ed1..60f9354289 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetFileChangedEventSquasher.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetFileChangedEventSquasher.cs @@ -1,100 +1,95 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Linq; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// Used to squash a list of . +/// +internal class AssetFileChangedEventSquasher { + private static readonly ComparerPackageAndLocation ComparerPackageAndLocationInstance = new(); + private readonly Dictionary filteredAssetFileChangedEvents = new(ComparerPackageAndLocationInstance); + /// - /// Used to squash a list of . + /// Squashes the list of events and returned a compact form of it. This method guaranty that for a specific file, there will be only a single event. + /// So for example, if there is a Added + Changed + Deleted event, there will be only a Deleted event in final. /// - internal class AssetFileChangedEventSquasher + /// The current asset file changed events. + /// An enumeration of events. + public IEnumerable Squash(List currentAssetFileChangedEvents) { - private static readonly ComparerPackageAndLocation ComparerPackageAndLocationInstance = new ComparerPackageAndLocation(); - private readonly Dictionary filteredAssetFileChangedEvents = new Dictionary(ComparerPackageAndLocationInstance); + if (currentAssetFileChangedEvents.Count == 0) + return []; - /// - /// Squashes the list of events and returned a compact form of it. This method guaranty that for a specific file, there will be only a single event. - /// So for example, if there is a Added + Changed + Deleted event, there will be only a Deleted event in final. - /// - /// The current asset file changed events. - /// An enumeration of events. - public IEnumerable Squash(List currentAssetFileChangedEvents) + // Compute the list of AssetFileChangedEvent in reverse order + // and squash them per Package/AssetLocation. + // The original list of currentAssetFileChangedEvents is not squashed + // so it means that AssetFileChangedEvent.ChangeType in this list are single flags (e.g. AssetFileChangedType.Added) + // Here we are squashing individual events into a single one for the same URL. + // Though there are few cases: + // - If the new event is Added or Deleted, than It will completely replace previous change types + // - If the new event is Added, it will keep previous AssetFileChangedType.SourceXXX + filteredAssetFileChangedEvents.Clear(); + var eventsCopy = new List(); + foreach (var currentAssetEvent in currentAssetFileChangedEvents) { - if (currentAssetFileChangedEvents.Count == 0) - return Enumerable.Empty(); - - // Compute the list of AssetFileChangedEvent in reverse order - // and squash them per Package/AssetLocation. - // The original list of currentAssetFileChangedEvents is not squashed - // so it means that AssetFileChangedEvent.ChangeType in this list are single flags (e.g. AssetFileChangedType.Added) - // Here we are squashing individual events into a single one for the same URL. - // Though there are few cases: - // - If the new event is Added or Deleted, than It will completely replace previous change types - // - If the new event is Added, it will keep previous AssetFileChangedType.SourceXXX - filteredAssetFileChangedEvents.Clear(); - var eventsCopy = new List(); - foreach (var currentAssetEvent in currentAssetFileChangedEvents) + if (filteredAssetFileChangedEvents.TryGetValue(currentAssetEvent, out var previousEvent)) { - AssetFileChangedEvent previousEvent; - if (filteredAssetFileChangedEvents.TryGetValue(currentAssetEvent, out previousEvent)) + var sourceEventTypes = previousEvent.ChangeType & AssetFileChangedType.SourceEventMask; + // If new event is added or deleted, then it replace completely previous + // squash + if (currentAssetEvent.ChangeType == AssetFileChangedType.Added || + currentAssetEvent.ChangeType == AssetFileChangedType.Deleted) { - var sourceEventTypes = (previousEvent.ChangeType & AssetFileChangedType.SourceEventMask); - // If new event is added or deleted, then it replace completely previous - // squash - if (currentAssetEvent.ChangeType == AssetFileChangedType.Added || - currentAssetEvent.ChangeType == AssetFileChangedType.Deleted) - { - previousEvent.ChangeType = currentAssetEvent.ChangeType; + previousEvent.ChangeType = currentAssetEvent.ChangeType; - // Force source events, we keep them in case of Added - if (currentAssetEvent.ChangeType == AssetFileChangedType.Added) - { - previousEvent.ChangeType |= sourceEventTypes; - } - } - else + // Force source events, we keep them in case of Added + if (currentAssetEvent.ChangeType == AssetFileChangedType.Added) { - // In case of a SourceDeleted event, delete previous (SourceAdded event if any) - if (currentAssetEvent.ChangeType == AssetFileChangedType.SourceDeleted) - { - previousEvent.ChangeType = (previousEvent.ChangeType & (~AssetFileChangedType.SourceEventMask)); - } - - // Else we can merge the event into a single one - previousEvent.ChangeType |= currentAssetEvent.ChangeType; + previousEvent.ChangeType |= sourceEventTypes; } } else { - eventsCopy.Add(currentAssetEvent); - filteredAssetFileChangedEvents.Add(currentAssetEvent, currentAssetEvent); - } + // In case of a SourceDeleted event, delete previous (SourceAdded event if any) + if (currentAssetEvent.ChangeType == AssetFileChangedType.SourceDeleted) + { + previousEvent.ChangeType &= ~AssetFileChangedType.SourceEventMask; + } + // Else we can merge the event into a single one + previousEvent.ChangeType |= currentAssetEvent.ChangeType; + } + } + else + { + eventsCopy.Add(currentAssetEvent); + filteredAssetFileChangedEvents.Add(currentAssetEvent, currentAssetEvent); } - filteredAssetFileChangedEvents.Clear(); - return eventsCopy; } + filteredAssetFileChangedEvents.Clear(); + + return eventsCopy; + } - private class ComparerPackageAndLocation : IEqualityComparer + private class ComparerPackageAndLocation : IEqualityComparer + { + public bool Equals(AssetFileChangedEvent? x, AssetFileChangedEvent? y) { - public bool Equals(AssetFileChangedEvent x, AssetFileChangedEvent y) - { - if (ReferenceEquals(x, y)) - return true; - if (ReferenceEquals(x, null)) - return false; + if (ReferenceEquals(x, y)) + return true; + if (ReferenceEquals(x, null)) + return false; - return x.Package == y.Package && x.AssetLocation == y.AssetLocation; - } + return x.Package == y?.Package && x.AssetLocation == y.AssetLocation; + } - public int GetHashCode(AssetFileChangedEvent obj) - { - var hashCode = (obj.Package != null ? obj.Package.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (obj.AssetLocation != null ? obj.AssetLocation.GetHashCode() : 0); - return hashCode; - } + public int GetHashCode(AssetFileChangedEvent obj) + { + ArgumentNullException.ThrowIfNull(obj); + return HashCode.Combine(obj.Package, obj.AssetLocation); } } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetFileChangedType.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetFileChangedType.cs index 0091b180e4..c0fde840d6 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetFileChangedType.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetFileChangedType.cs @@ -1,48 +1,46 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// Type of a change event for an asset. +/// +[Flags] +public enum AssetFileChangedType { /// - /// Type of a change event for an asset. + /// An asset was added to the disk + /// + Added = 1, + + /// + /// The asset was deleted from the disk + /// + Deleted = 2, + + /// + /// The asset is updated on the disk + /// + Updated = 4, + + /// + /// The asset event mask (Added | Deleted | Updated). + /// + AssetEventMask = Added | Deleted | Updated, + + /// + /// The asset import was modified on the disk + /// + SourceUpdated = 8, + + /// + /// The asset import was deleted from the disk + /// + SourceDeleted = 16, + + /// + /// The source event mask (SourceUpdated | SourceDeleted). /// - [Flags] - public enum AssetFileChangedType - { - /// - /// An asset was added to the disk - /// - Added = 1, - - /// - /// The asset was deleted from the disk - /// - Deleted = 2, - - /// - /// The asset is updated on the disk - /// - Updated = 4, - - /// - /// The asset event mask (Added | Deleted | Updated). - /// - AssetEventMask = Added | Deleted | Updated, - - /// - /// The asset import was modified on the disk - /// - SourceUpdated = 8, - - /// - /// The asset import was deleted from the disk - /// - SourceDeleted = 16, - - /// - /// The source event mask (SourceUpdated | SourceDeleted). - /// - SourceEventMask = SourceUpdated | SourceDeleted, - } + SourceEventMask = SourceUpdated | SourceDeleted, } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetInheritanceSearchOptions.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetInheritanceSearchOptions.cs index 49f44ac2eb..6b5489b3b2 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetInheritanceSearchOptions.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetInheritanceSearchOptions.cs @@ -1,29 +1,26 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; +namespace Stride.Core.Assets.Analysis; -namespace Stride.Core.Assets.Analysis +/// +/// Possible options used when searching asset inheritance. +/// +[Flags] +public enum AssetInheritanceSearchOptions { /// - /// Possible options used when searching asset inheritance. + /// Search for inheritances from base (direct object inheritance). /// - [Flags] - public enum AssetInheritanceSearchOptions - { - /// - /// Search for inheritances from base (direct object inheritance). - /// - Base = 1, + Base = 1, - /// - /// Search for inheritances from compositions. - /// - Composition = 2, + /// + /// Search for inheritances from compositions. + /// + Composition = 2, - /// - /// Search for all types of inheritances. - /// - All = Base | Composition, - } + /// + /// Search for all types of inheritances. + /// + All = Base | Composition, } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetLink.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetLink.cs index c29bdf9703..ca175b35e6 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetLink.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetLink.cs @@ -1,67 +1,63 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; - -using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// Represent a link between Assets. +/// +public struct AssetLink : IContentLink { /// - /// Represent a link between Assets. + /// The asset item pointed by the dependency. /// - public struct AssetLink : IContentLink - { - /// - /// The asset item pointed by the dependency. - /// - public readonly AssetItem Item; + public readonly AssetItem Item; - private ContentLinkType type; + private ContentLinkType type; - private readonly IReference reference; + private readonly IReference reference; - /// - /// Create an asset dependency of type and pointing to - /// - /// The item the dependency is pointing to - /// The type of the dependency between the items - public AssetLink(AssetItem item, ContentLinkType type) - { - if (item == null) throw new ArgumentNullException("item"); + /// + /// Create an asset dependency of type and pointing to + /// + /// The item the dependency is pointing to + /// The type of the dependency between the items + public AssetLink(AssetItem item, ContentLinkType type) + { + ArgumentNullException.ThrowIfNull(item); - Item = item; - this.type = type; - reference = item.ToReference(); - } + Item = item; + this.type = type; + reference = item.ToReference(); + } - // This constructor exists for better factorization of code in AssetDependencies. - // It should not be turned into public as AssetItem is not valid. - internal AssetLink(IReference reference, ContentLinkType type) - { - if (reference == null) throw new ArgumentNullException("reference"); + // This constructor exists for better factorization of code in AssetDependencies. + // It should not be turned into public as AssetItem is not valid. + internal AssetLink(IReference reference, ContentLinkType type) + { + ArgumentNullException.ThrowIfNull(reference); - Item = null; - this.type = type; - this.reference = reference; - } + Item = null!; + this.type = type; + this.reference = reference; + } - public ContentLinkType Type - { - get { return type; } - set { type = value; } - } + public ContentLinkType Type + { + readonly get { return type; } + set { type = value; } + } - public IReference Element { get { return reference; } } + public readonly IReference Element { get { return reference; } } - /// - /// Gets a clone copy of the asset dependency. - /// - /// the clone instance - public AssetLink Clone() - { - return new AssetLink(Item.Clone(true), Type); - } + /// + /// Gets a clone copy of the asset dependency. + /// + /// the clone instance + public readonly AssetLink Clone() + { + return new AssetLink(Item.Clone(true), Type); } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetPartsAnalysis.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetPartsAnalysis.cs index ebe0bbb103..f451fb2ef1 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetPartsAnalysis.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetPartsAnalysis.cs @@ -1,39 +1,32 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; -using Stride.Core.Annotations; +namespace Stride.Core.Assets.Analysis; -namespace Stride.Core.Assets.Analysis +public static class AssetPartsAnalysis { - public static class AssetPartsAnalysis + /// + /// Assigns new unique identifiers for base part in the given . + /// + /// + /// The underlying type of part. + /// The hierarchy which part groups should have new identifiers. + public static void GenerateNewBaseInstanceIds(AssetCompositeHierarchyData hierarchy) + where TAssetPartDesign : class, IAssetPartDesign + where TAssetPart : class, IIdentifiable { - /// - /// Assigns new unique identifiers for base part in the given . - /// - /// - /// The underlying type of part. - /// The hierarchy which part groups should have new identifiers. - public static void GenerateNewBaseInstanceIds([NotNull] AssetCompositeHierarchyData hierarchy) - where TAssetPartDesign : class, IAssetPartDesign - where TAssetPart : class, IIdentifiable + var baseInstanceMapping = new Dictionary(); + foreach (var part in hierarchy.Parts.Values) { - var baseInstanceMapping = new Dictionary(); - foreach (var part in hierarchy.Parts.Values) - { - if (part.Base == null) - continue; + if (part.Base == null) + continue; - Guid newInstanceId; - if (!baseInstanceMapping.TryGetValue(part.Base.InstanceId, out newInstanceId)) - { - newInstanceId = Guid.NewGuid(); - baseInstanceMapping.Add(part.Base.InstanceId, newInstanceId); - } - part.Base = new BasePart(part.Base.BasePartAsset, part.Base.BasePartId, newInstanceId); + if (!baseInstanceMapping.TryGetValue(part.Base.InstanceId, out var newInstanceId)) + { + newInstanceId = Guid.NewGuid(); + baseInstanceMapping.Add(part.Base.InstanceId, newInstanceId); } + part.Base = new BasePart(part.Base.BasePartAsset, part.Base.BasePartId, newInstanceId); } } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetReferenceAnalysis.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetReferenceAnalysis.cs index 81c98b493e..437dc07ab9 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetReferenceAnalysis.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetReferenceAnalysis.cs @@ -1,343 +1,339 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using System.Collections; -using System.Collections.Generic; using Stride.Core.Assets.Visitors; using Stride.Core.IO; using Stride.Core.Reflection; using Stride.Core.Serialization; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// This analysis provides a method for visiting asset and file references +/// ( or or ) +/// +public static class AssetReferenceAnalysis { + private static readonly object CachingLock = new(); + + private static readonly Dictionary> CachingReferences = []; + + private static bool enableCaching; + /// - /// This analysis provides a method for visiting asset and file references - /// ( or or ) + /// Gets or sets the enable caching. Only used when loading packages /// - public static class AssetReferenceAnalysis + /// The enable caching. + internal static bool EnableCaching { - private static readonly object CachingLock = new object(); + get + { + return enableCaching; + } + set + { + lock (CachingLock) + { + if (enableCaching != value) + { + CachingReferences.Clear(); + } - private static readonly Dictionary> CachingReferences = new Dictionary>(); + enableCaching = value; + } + } + } + + /// + /// Gets all references (subclass of and ) from the specified asset + /// + /// The object. + /// A list of references. + public static List Visit(object obj) + { + ArgumentNullException.ThrowIfNull(obj); - private static bool enableCaching; + List? assetReferences = null; - /// - /// Gets or sets the enable caching. Only used when loading packages - /// - /// The enable caching. - internal static bool EnableCaching + lock (CachingLock) { - get + if (enableCaching) { - return enableCaching; - } - set - { - lock (CachingLock) + if (CachingReferences.TryGetValue(obj, out assetReferences)) { - if (enableCaching != value) - { - CachingReferences.Clear(); - } - - enableCaching = value; + assetReferences = new List(assetReferences); } } } - /// - /// Gets all references (subclass of and ) from the specified asset - /// - /// The object. - /// A list of references. - public static List Visit(object obj) + if (assetReferences == null) { - if (obj == null) throw new ArgumentNullException(nameof(obj)); - - List assetReferences = null; + assetReferences = []; + + var assetReferenceVistor = new AssetReferenceVistor { References = assetReferences }; + assetReferenceVistor.Visit(obj); lock (CachingLock) { if (enableCaching) { - if (CachingReferences.TryGetValue(obj, out assetReferences)) - { - assetReferences = new List(assetReferences); - } + CachingReferences[obj] = assetReferences; } } + } - if (assetReferences == null) - { - assetReferences = new List(); - - var assetReferenceVistor = new AssetReferenceVistor { References = assetReferences }; - assetReferenceVistor.Visit(obj); - - lock (CachingLock) - { - if (enableCaching) - { - CachingReferences[obj] = assetReferences; - } - } - } + return assetReferences; + } - return assetReferences; + private class AssetReferenceVistor : AssetVisitorBase + { + public AssetReferenceVistor() + { + References = []; } - private class AssetReferenceVistor : AssetVisitorBase + public List References { get; set; } + + public override void VisitArrayItem(Array array, ArrayDescriptor descriptor, int index, object? item, ITypeDescriptor? itemDescriptor) { - public AssetReferenceVistor() + base.VisitArrayItem(array, descriptor, index, item, itemDescriptor); + var attachedReference = AttachedReferenceManager.GetAttachedReference(item); + if (item is AssetReference assetReference) { - References = new List(); + AddLink(item, + (guid, location) => + { + var newValue = AssetReference.New(guid ?? assetReference.Id, location); + array.SetValue(newValue, index); + return newValue; + }); } + else if (attachedReference != null) + { + AddLink(attachedReference, + (guid, location) => + { + var newValue = guid.HasValue && guid.Value != AssetId.Empty ? AttachedReferenceManager.CreateProxyObject(descriptor.ElementType, guid.Value, location) : null; + array.SetValue(newValue, index); + return newValue; + }); + } + else if (item is UFile) + { + AddLink(item, + (guid, location) => + { + var newValue = new UFile(location); + array.SetValue(newValue, index); + return newValue; + }); + } + else if (item is UDirectory) + { + AddLink(item, + (guid, location) => + { + var newValue = new UFile(location); + array.SetValue(newValue, index); + return newValue; + }); + } + } + + public override void VisitCollectionItem(IEnumerable collection, CollectionDescriptor descriptor, int index, object? item, ITypeDescriptor? itemDescriptor) + { + base.VisitCollectionItem(collection, descriptor, index, item, itemDescriptor); + var assetReference = item as AssetReference; + var attachedReference = AttachedReferenceManager.GetAttachedReference(item); - public List References { get; set; } + // We cannot set links if we do not have indexer accessor + if (!descriptor.HasIndexerAccessors) + return; - public override void VisitArrayItem(Array array, ArrayDescriptor descriptor, int index, object item, ITypeDescriptor itemDescriptor) + if (assetReference != null) { - base.VisitArrayItem(array, descriptor, index, item, itemDescriptor); - var assetReference = item as AssetReference; - var attachedReference = AttachedReferenceManager.GetAttachedReference(item); - if (assetReference != null) + AddLink(assetReference, (guid, location) => { - AddLink(item, - (guid, location) => - { - var newValue = AssetReference.New(guid ?? assetReference.Id, location); - array.SetValue(newValue, index); - return newValue; - }); - } - else if (attachedReference != null) + var link = AssetReference.New(guid ?? assetReference.Id, location); + descriptor.SetValue(collection, index, link); + return link; + }); + } + else if (attachedReference != null) + { + AddLink(attachedReference, (guid, location) => { - AddLink(attachedReference, - (guid, location) => - { - object newValue = guid.HasValue && guid.Value != AssetId.Empty ? AttachedReferenceManager.CreateProxyObject(descriptor.ElementType, guid.Value, location) : null; - array.SetValue(newValue, index); - return newValue; - }); - } - else if (item is UFile) + var link = guid.HasValue && guid.Value != AssetId.Empty ? AttachedReferenceManager.CreateProxyObject(descriptor.ElementType, guid.Value, location) : null; + descriptor.SetValue(collection, index, link); + return link; + }); + } + else if (item is UFile) + { + AddLink(item, (guid, location) => { - AddLink(item, - (guid, location) => - { - var newValue = new UFile(location); - array.SetValue(newValue, index); - return newValue; - }); - } - else if (item is UDirectory) + var link = new UFile(location); + descriptor.SetValue(collection, index, link); + return link; + }); + } + else if (item is UDirectory) + { + AddLink(item, (guid, location) => { - AddLink(item, - (guid, location) => - { - var newValue = new UFile(location); - array.SetValue(newValue, index); - return newValue; - }); - } + var link = new UDirectory(location); + descriptor.SetValue(collection, index, link); + return link; + }); } + } - public override void VisitCollectionItem(IEnumerable collection, CollectionDescriptor descriptor, int index, object item, ITypeDescriptor itemDescriptor) + public override void VisitDictionaryKeyValue(object dictionaryObj, DictionaryDescriptor descriptor, object key, ITypeDescriptor? keyDescriptor, object? value, ITypeDescriptor? valueDescriptor) + { + base.VisitDictionaryKeyValue(dictionaryObj, descriptor, key, keyDescriptor, value, valueDescriptor); + var assetReference = value as AssetReference; + var attachedReference = AttachedReferenceManager.GetAttachedReference(value); + if (assetReference != null) { - base.VisitCollectionItem(collection, descriptor, index, item, itemDescriptor); - var assetReference = item as AssetReference; - var attachedReference = AttachedReferenceManager.GetAttachedReference(item); - - // We cannot set links if we do not have indexer accessor - if (!descriptor.HasIndexerAccessors) - return; + AddLink(assetReference, + (guid, location) => + { + var newValue = AssetReference.New(guid ?? assetReference.Id, location); + descriptor.SetValue(dictionaryObj, key, newValue); + return newValue; + }); + } + else if (attachedReference != null) + { + AddLink(attachedReference, + (guid, location) => + { + var newValue = guid.HasValue && guid.Value != AssetId.Empty ? AttachedReferenceManager.CreateProxyObject(descriptor.ValueType, guid.Value, location) : null; + descriptor.SetValue(dictionaryObj, key, newValue); + return newValue; + }); + } + else if (value is UFile) + { + AddLink(value, + (guid, location) => + { + var newValue = new UFile(location); + descriptor.SetValue(dictionaryObj, key, newValue); + return newValue; + }); + } + else if (value is UDirectory) + { + AddLink(value, + (guid, location) => + { + var newValue = new UDirectory(location); + descriptor.SetValue(dictionaryObj, key, newValue); + return newValue; + }); + } + } - if (assetReference != null) - { - AddLink(assetReference, (guid, location) => + public override void VisitSetItem(IEnumerable setObject, SetDescriptor descriptor, object? item, ITypeDescriptor? itemDescriptor) + { + base.VisitSetItem(setObject, descriptor, item, itemDescriptor); + var assetReference = item as AssetReference; + var attachedReference = AttachedReferenceManager.GetAttachedReference(item); + if (assetReference != null) + { + AddLink(assetReference, + (guid, location) => { var link = AssetReference.New(guid ?? assetReference.Id, location); - descriptor.SetValue(collection, index, link); + descriptor.Add(setObject, link); return link; }); - } - else if (attachedReference != null) - { - AddLink(attachedReference, (guid, location) => + } + else if (attachedReference != null) + { + AddLink(attachedReference, + (guid, location) => { var link = guid.HasValue && guid.Value != AssetId.Empty ? AttachedReferenceManager.CreateProxyObject(descriptor.ElementType, guid.Value, location) : null; - descriptor.SetValue(collection, index, link); + descriptor.Add(setObject, link); return link; }); - } - else if (item is UFile) - { - AddLink(item, (guid, location) => + } + else if (item is UFile) + { + AddLink(item, + (guid, location) => { var link = new UFile(location); - descriptor.SetValue(collection, index, link); + descriptor.Add(setObject, link); return link; }); - } - else if (item is UDirectory) - { - AddLink(item, (guid, location) => + } + else if (item is UDirectory) + { + AddLink(item, + (guid, location) => { var link = new UDirectory(location); - descriptor.SetValue(collection, index, link); + descriptor.Add(setObject, link); return link; }); - } } + } - public override void VisitDictionaryKeyValue(object dictionaryObj, DictionaryDescriptor descriptor, object key, ITypeDescriptor keyDescriptor, object value, ITypeDescriptor valueDescriptor) + public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object? value) + { + base.VisitObjectMember(container, containerDescriptor, member, value); + var assetReference = value as AssetReference; + var attachedReference = AttachedReferenceManager.GetAttachedReference(value); + if (assetReference != null) { - base.VisitDictionaryKeyValue(dictionaryObj, descriptor, key, keyDescriptor, value, valueDescriptor); - var assetReference = value as AssetReference; - var attachedReference = AttachedReferenceManager.GetAttachedReference(value); - if (assetReference != null) - { - AddLink(assetReference, - (guid, location) => - { - var newValue = AssetReference.New(guid ?? assetReference.Id, location); - descriptor.SetValue(dictionaryObj, key, newValue); - return newValue; - }); - } - else if (attachedReference != null) - { - AddLink(attachedReference, - (guid, location) => - { - object newValue = guid.HasValue && guid.Value != AssetId.Empty ? AttachedReferenceManager.CreateProxyObject(descriptor.ValueType, guid.Value, location) : null; - descriptor.SetValue(dictionaryObj, key, newValue); - return newValue; - }); - } - else if (value is UFile) - { - AddLink(value, - (guid, location) => - { - var newValue = new UFile(location); - descriptor.SetValue(dictionaryObj, key, newValue); - return newValue; - }); - } - else if (value is UDirectory) - { - AddLink(value, - (guid, location) => - { - var newValue = new UDirectory(location); - descriptor.SetValue(dictionaryObj, key, newValue); - return newValue; - }); - } + AddLink(assetReference, + (guid, location) => + { + var newValue = AssetReference.New(guid ?? assetReference.Id, location); + member.Set(container, newValue); + return newValue; + }); } - - public override void VisitSetItem(IEnumerable setObject, SetDescriptor descriptor, object item, ITypeDescriptor itemDescriptor) + else if (attachedReference != null) { - base.VisitSetItem(setObject, descriptor, item, itemDescriptor); - var assetReference = item as AssetReference; - var attachedReference = AttachedReferenceManager.GetAttachedReference(item); - if (assetReference != null) - { - AddLink(assetReference, - (guid, location) => - { - var link = AssetReference.New(guid ?? assetReference.Id, location); - descriptor.Add(setObject, link); - return link; - }); - } - else if (attachedReference != null) - { - AddLink(attachedReference, - (guid, location) => - { - object link = guid.HasValue && guid.Value != AssetId.Empty ? AttachedReferenceManager.CreateProxyObject(descriptor.ElementType, guid.Value, location) : null; - descriptor.Add(setObject, link); - return link; - }); - } - else if (item is UFile) - { - AddLink(item, - (guid, location) => - { - var link = new UFile(location); - descriptor.Add(setObject, link); - return link; - }); - } - else if (item is UDirectory) - { - AddLink(item, - (guid, location) => - { - var link = new UDirectory(location); - descriptor.Add(setObject, link); - return link; - }); - } + AddLink(attachedReference, + (guid, location) => + { + var newValue = guid.HasValue && guid.Value != AssetId.Empty ? AttachedReferenceManager.CreateProxyObject(member.Type, guid.Value, location) : null; + member.Set(container, newValue); + return newValue; + }); } - - public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object value) + else if (value is UFile) { - base.VisitObjectMember(container, containerDescriptor, member, value); - var assetReference = value as AssetReference; - var attachedReference = AttachedReferenceManager.GetAttachedReference(value); - if (assetReference != null) - { - AddLink(assetReference, - (guid, location) => - { - var newValue = AssetReference.New(guid ?? assetReference.Id, location); - member.Set(container, newValue); - return newValue; - }); - } - else if (attachedReference != null) - { - AddLink(attachedReference, - (guid, location) => - { - object newValue = guid.HasValue && guid.Value != AssetId.Empty ? AttachedReferenceManager.CreateProxyObject(member.Type, guid.Value, location) : null; - member.Set(container, newValue); - return newValue; - }); - } - else if (value is UFile) - { - AddLink(value, - (guid, location) => - { - var newValue = new UFile(location); - member.Set(container, newValue); - return newValue; - }); - } - else if (value is UDirectory) - { - AddLink(value, - (guid, location) => - { - var newValue = new UDirectory(location); - member.Set(container, newValue); - return newValue; - }); - } + AddLink(value, + (guid, location) => + { + var newValue = new UFile(location); + member.Set(container, newValue); + return newValue; + }); } - - private void AddLink(object value, Func updateReference) + else if (value is UDirectory) { - References.Add(new AssetReferenceLink(CurrentPath.Clone(), value, updateReference)); + AddLink(value, + (guid, location) => + { + var newValue = new UDirectory(location); + member.Set(container, newValue); + return newValue; + }); } } + + private void AddLink(object value, Func updateReference) + { + References.Add(new AssetReferenceLink(CurrentPath.Clone(), value, updateReference)); + } } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetReferenceLink.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetReferenceLink.cs index 797e53eddf..7edc319560 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetReferenceLink.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetReferenceLink.cs @@ -1,64 +1,62 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Diagnostics; using Stride.Core.IO; using Stride.Core.Reflection; -using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// Updatable reference link returned by . +/// +[DebuggerDisplay("{Path}")] +public class AssetReferenceLink { /// - /// Updatable reference link returned by . + /// Initializes a new instance of the class. /// - [DebuggerDisplay("{Path}")] - public class AssetReferenceLink + /// The path. + /// The reference. + /// The update reference. + public AssetReferenceLink(MemberPath path, object reference, Func updateReference) { - /// - /// Initializes a new instance of the class. - /// - /// The path. - /// The reference. - /// The update reference. - public AssetReferenceLink(MemberPath path, object reference, Func updateReference) - { - Path = path; - this.reference = reference; - this.updateReference = updateReference; - } + Path = path; + this.reference = reference; + this.updateReference = updateReference; + } - /// - /// The path to the member holding this reference. - /// - public readonly MemberPath Path; + /// + /// The path to the member holding this reference. + /// + public readonly MemberPath Path; - /// - /// A or . - /// - public object Reference + /// + /// A or . + /// + public object? Reference + { + get { - get - { - return reference; - } + return reference; } + } - /// - /// Updates the reference. - /// - /// The unique identifier. - /// The location. - public void UpdateReference(AssetId? guid, string location) - { - reference = updateReference(guid, location); - } + /// + /// Updates the reference. + /// + /// The unique identifier. + /// The location. + public void UpdateReference(AssetId? guid, string location) + { + reference = updateReference(guid, location); + } - /// - /// A specialized method to update the reference (guid, and location). - /// - private readonly Func updateReference; + /// + /// A specialized method to update the reference (guid, and location). + /// + private readonly Func updateReference; - private object reference; - } + private object? reference; } diff --git a/sources/assets/Stride.Core.Assets/Analysis/AssetResolver.cs b/sources/assets/Stride.Core.Assets/Analysis/AssetResolver.cs index fe5db72914..f2f9243ea2 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/AssetResolver.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/AssetResolver.cs @@ -1,160 +1,156 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; using Stride.Core.IO; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// Helper to find available new asset locations and identifiers. +/// +public sealed class AssetResolver { /// - /// Helper to find available new asset locations and identifiers. + /// Delegate to test if an asset id is already used. + /// + /// The unique identifier. + /// true if an asset id is already used, false otherwise. + public delegate bool ContainsAssetWithIdDelegate(AssetId id); + + /// + /// Initializes a new instance of the class. /// - public sealed class AssetResolver + public AssetResolver() : this(null, null) { - /// - /// Delegate to test if an asset id is already used. - /// - /// The unique identifier. - /// true if an asset id is already used, false otherwise. - public delegate bool ContainsAssetWithIdDelegate(AssetId id); - - /// - /// Initializes a new instance of the class. - /// - public AssetResolver() : this(null, null) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The delegate used to check if an asset location is already used. - /// The delegate used to check if an asset identifier is already used. - public AssetResolver(NamingHelper.ContainsLocationDelegate containsLocation, ContainsAssetWithIdDelegate containsAssetWithId) - { - ExistingIds = new HashSet(); - ExistingLocations = new HashSet(StringComparer.OrdinalIgnoreCase); - ContainsLocation = containsLocation; - ContainsAssetWithId = containsAssetWithId; - } + /// + /// Initializes a new instance of the class. + /// + /// The delegate used to check if an asset location is already used. + /// The delegate used to check if an asset identifier is already used. + public AssetResolver(NamingHelper.ContainsLocationDelegate? containsLocation, ContainsAssetWithIdDelegate? containsAssetWithId) + { + ExistingIds = []; + ExistingLocations = new HashSet(StringComparer.OrdinalIgnoreCase); + ContainsLocation = containsLocation; + ContainsAssetWithId = containsAssetWithId; + } + + /// + /// Gets the locations already used. + /// + /// The locations. + public HashSet ExistingLocations { get; } + + /// + /// Gets the asset ids already used. + /// + /// The existing ids. + public HashSet ExistingIds { get; } + + /// + /// Gets or sets a value indicating whether to always generate a new id on . + /// + /// true if [force new identifier]; otherwise, false. + public bool AlwaysCreateNewId { get; set; } + + /// + /// Gets or sets a delegate to test if a location is already used. + /// + /// A delegate to test if a location is already used. + public NamingHelper.ContainsLocationDelegate? ContainsLocation { get; set; } + + /// + /// Gets or sets a delegate to test if an asset id is already used. + /// + /// A delegate to test if an asset id is already used. + public ContainsAssetWithIdDelegate? ContainsAssetWithId { get; set; } - /// - /// Gets the locations already used. - /// - /// The locations. - public HashSet ExistingLocations { get; } - - /// - /// Gets the asset ids already used. - /// - /// The existing ids. - public HashSet ExistingIds { get; } - - /// - /// Gets or sets a value indicating whether to always generate a new id on . - /// - /// true if [force new identifier]; otherwise, false. - public bool AlwaysCreateNewId { get; set; } - - /// - /// Gets or sets a delegate to test if a location is already used. - /// - /// A delegate to test if a location is already used. - public NamingHelper.ContainsLocationDelegate ContainsLocation { get; set; } - - /// - /// Gets or sets a delegate to test if an asset id is already used. - /// - /// A delegate to test if an asset id is already used. - public ContainsAssetWithIdDelegate ContainsAssetWithId { get; set; } - - /// - /// Finds a name available for a new asset. This method will try to create a name based on an existing name and will append - /// "_" + (number++) on every try. The new location found is added to the known existing locations. - /// - /// The location. - /// The new location. - /// true if there is a new location, false otherwise. - public bool RegisterLocation(UFile location, out UFile newLocation) + /// + /// Finds a name available for a new asset. This method will try to create a name based on an existing name and will append + /// "_" + (number++) on every try. The new location found is added to the known existing locations. + /// + /// The location. + /// The new location. + /// true if there is a new location, false otherwise. + public bool RegisterLocation(UFile location, out UFile newLocation) + { + newLocation = location; + if (IsContainingLocation(location)) { - newLocation = location; - if (IsContainingLocation(location)) - { - newLocation = NamingHelper.ComputeNewName(location, IsContainingLocation); - } - ExistingLocations.Add(newLocation); - return newLocation != location; + newLocation = NamingHelper.ComputeNewName(location, IsContainingLocation); } + ExistingLocations.Add(newLocation); + return newLocation != location; + } - /// - /// Registers an asset identifier for usage. - /// - /// The asset identifier. - /// The new unique identifier if an asset has already been registered with the same id. - /// true if the asset id is already in used. contains a new guid, false otherwise. - public bool RegisterId(AssetId assetId, out AssetId newGuid) + /// + /// Registers an asset identifier for usage. + /// + /// The asset identifier. + /// The new unique identifier if an asset has already been registered with the same id. + /// true if the asset id is already in used. contains a new guid, false otherwise. + public bool RegisterId(AssetId assetId, out AssetId newGuid) + { + newGuid = assetId; + var result = AlwaysCreateNewId || IsContainingId(assetId); + if (result) { - newGuid = assetId; - var result = AlwaysCreateNewId || IsContainingId(assetId); - if (result) - { - newGuid = AssetId.New(); - } - ExistingIds.Add(newGuid); - return result; + newGuid = AssetId.New(); } + ExistingIds.Add(newGuid); + return result; + } - /// - /// Creates a new using an existing package to check the existence of asset locations and ids. - /// - /// The package. - /// A new AssetResolver. - /// package - public static AssetResolver FromPackage(Package package) - { - if (package == null) throw new ArgumentNullException(nameof(package)); + /// + /// Creates a new using an existing package to check the existence of asset locations and ids. + /// + /// The package. + /// A new AssetResolver. + /// package + public static AssetResolver FromPackage(Package package) + { + ArgumentNullException.ThrowIfNull(package); - var packages = package.FindDependencies(true); + var packages = package.FindDependencies(true); - return new AssetResolver(packages.ContainsAsset, packages.ContainsAsset); - } + return new AssetResolver(packages.ContainsAsset, packages.ContainsAsset); + } - /// - /// Creates a new using an existing package to check the existence of asset locations and ids. - /// - /// The packages. - /// A new AssetResolver. - /// package - public static AssetResolver FromPackage(IList packages) - { - if (packages == null) throw new ArgumentNullException(nameof(packages)); - return new AssetResolver(packages.ContainsAsset, packages.ContainsAsset); - } + /// + /// Creates a new using an existing package to check the existence of asset locations and ids. + /// + /// The packages. + /// A new AssetResolver. + /// package + public static AssetResolver FromPackage(IList packages) + { + ArgumentNullException.ThrowIfNull(packages); + return new AssetResolver(packages.ContainsAsset, packages.ContainsAsset); + } - /// - /// Checks whether the is already contained. - /// - private bool IsContainingId(AssetId id) + /// + /// Checks whether the is already contained. + /// + private bool IsContainingId(AssetId id) + { + if (ExistingIds.Contains(id)) { - if (ExistingIds.Contains(id)) - { - return true; - } - return ContainsAssetWithId?.Invoke(id) ?? false; + return true; } + return ContainsAssetWithId?.Invoke(id) ?? false; + } - /// - /// Checks whether the is already contained. - /// - private bool IsContainingLocation(UFile location) + /// + /// Checks whether the is already contained. + /// + private bool IsContainingLocation(UFile location) + { + if (ExistingLocations.Contains(location)) { - if (ExistingLocations.Contains(location)) - { - return true; - } - return ContainsLocation?.Invoke(location) ?? false; + return true; } + return ContainsLocation?.Invoke(location) ?? false; } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/BuildAssetNode.cs b/sources/assets/Stride.Core.Assets/Analysis/BuildAssetNode.cs index 2ba246eba3..edd6eecb6b 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/BuildAssetNode.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/BuildAssetNode.cs @@ -1,312 +1,299 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; using Stride.Core.Assets.Compiler; using Stride.Core.Assets.Visitors; -using Stride.Core; using Stride.Core.Reflection; using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// A structure representing a link (a dependency) between two instances (assets). +/// +public readonly struct BuildAssetLink : IEquatable { /// - /// A structure representing a link (a dependency) between two instances (assets). + /// Initialize a new instance of the structure. /// - public struct BuildAssetLink : IEquatable + /// The source asset of the dependency. + /// The target asset of the dependency. + /// The type of dependency. + public BuildAssetLink(BuildAssetNode source, BuildAssetNode target, BuildDependencyType dependencyType) { - /// - /// Initialize a new instance of the structure. - /// - /// The source asset of the dependency. - /// The target asset of the dependency. - /// The type of dependency. - public BuildAssetLink(BuildAssetNode source, BuildAssetNode target, BuildDependencyType dependencyType) - { - Source = source; - Target = target; - DependencyType = dependencyType; - } + Source = source; + Target = target; + DependencyType = dependencyType; + } - /// - /// The type of dependency. - /// - public BuildDependencyType DependencyType { get; } - - /// - /// The source asset of the dependency. - /// - public BuildAssetNode Source { get; } - - /// - /// The target asset of the dependency. - /// - public BuildAssetNode Target { get; } - - /// - /// Indicates whether this has at least one of the dependency of the given flags. - /// - /// A bitset of . - /// True if it has at least one of the given dependencies, false otherwise. - public bool HasOne(BuildDependencyType type) - { - return (DependencyType & type) != 0; - } + /// + /// The type of dependency. + /// + public BuildDependencyType DependencyType { get; } - /// - /// Indicates whether this has at all dependencies of the given flags. - /// - /// A bitset of . - /// True if it has all the given dependencies, false otherwise. - public bool HasAll(BuildDependencyType type) - { - return (DependencyType & type) == type; - } + /// + /// The source asset of the dependency. + /// + public BuildAssetNode Source { get; } - /// - public bool Equals(BuildAssetLink other) - { - return DependencyType == other.DependencyType && Equals(Source, other.Source) && Equals(Target, other.Target); - } + /// + /// The target asset of the dependency. + /// + public BuildAssetNode Target { get; } - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - return obj is BuildAssetLink && Equals((BuildAssetLink)obj); - } + /// + /// Indicates whether this has at least one of the dependency of the given flags. + /// + /// A bitset of . + /// True if it has at least one of the given dependencies, false otherwise. + public readonly bool HasOne(BuildDependencyType type) + { + return (DependencyType & type) != 0; + } - /// - public override int GetHashCode() - { - unchecked - { - var hashCode = (int)DependencyType; - hashCode = (hashCode * 397) ^ (Source != null ? Source.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (Target != null ? Target.GetHashCode() : 0); - return hashCode; - } - } + /// + /// Indicates whether this has at all dependencies of the given flags. + /// + /// A bitset of . + /// True if it has all the given dependencies, false otherwise. + public readonly bool HasAll(BuildDependencyType type) + { + return (DependencyType & type) == type; + } - public override string ToString() - { - return $"{DependencyType}: {Source} => {Target}"; - } + /// + public readonly bool Equals(BuildAssetLink other) + { + return DependencyType == other.DependencyType && Equals(Source, other.Source) && Equals(Target, other.Target); + } - /// - public static bool operator ==(BuildAssetLink left, BuildAssetLink right) - { - return left.Equals(right); - } + /// + public override readonly bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + return obj is BuildAssetLink link && Equals(link); + } - /// - public static bool operator !=(BuildAssetLink left, BuildAssetLink right) - { - return !left.Equals(right); - } + /// + public override readonly int GetHashCode() + { + return HashCode.Combine(DependencyType, Source, Target); } - public class BuildAssetNode + public override readonly string ToString() { - public static PropertyKey VisitRuntimeTypes = new PropertyKey("VisitRuntimeTypes", typeof(BuildAssetNode)); + return $"{DependencyType}: {Source} => {Target}"; + } - private readonly BuildDependencyManager buildDependencyManager; - private readonly ConcurrentDictionary references = new ConcurrentDictionary(); - private long version = -1; + /// + public static bool operator ==(BuildAssetLink left, BuildAssetLink right) + { + return left.Equals(right); + } - public AssetItem AssetItem { get; } + /// + public static bool operator !=(BuildAssetLink left, BuildAssetLink right) + { + return !left.Equals(right); + } +} + +public class BuildAssetNode +{ + public static PropertyKey VisitRuntimeTypes = new("VisitRuntimeTypes", typeof(BuildAssetNode)); - public Type CompilationContext { get; } + private readonly BuildDependencyManager buildDependencyManager; + private readonly ConcurrentDictionary references = new(); + private long version = -1; - public ICollection References => references.Values; + public AssetItem AssetItem { get; } - public BuildAssetNode(AssetItem assetItem, Type compilationContext, BuildDependencyManager dependencyManager) - { - AssetItem = assetItem; - CompilationContext = compilationContext; - buildDependencyManager = dependencyManager; - } + public Type CompilationContext { get; } - /// - /// Performs analysis on the asset to figure out all the needed dependencies - /// - /// The compiler context - /// True if the node was updated, false otherwise. - public bool Analyze(AssetCompilerContext context) + public ICollection References => references.Values; + + public BuildAssetNode(AssetItem assetItem, Type compilationContext, BuildDependencyManager dependencyManager) + { + AssetItem = assetItem; + CompilationContext = compilationContext; + buildDependencyManager = dependencyManager; + } + + /// + /// Performs analysis on the asset to figure out all the needed dependencies + /// + /// The compiler context + /// True if the node was updated, false otherwise. + public bool Analyze(AssetCompilerContext context) + { + var assetVersion = AssetItem.Version; + if (Interlocked.Exchange(ref version, assetVersion) == assetVersion) { - var assetVersion = AssetItem.Version; - if (Interlocked.Exchange(ref version, assetVersion) == assetVersion) + // This node is up-to-date. Let's check if CompileAsset links are also up-to-date. + // Otherwise we need to refresh this node since this kind of link can bring additional dependencies. + var upToDate = true; + foreach (var node in References) { - // This node is up-to-date. Let's check if CompileAsset links are also up-to-date. - // Otherwise we need to refresh this node since this kind of link can bring additional dependencies. - var upToDate = true; - foreach (var node in References) + if (node.HasOne(BuildDependencyType.CompileAsset)) { - if (node.HasOne(BuildDependencyType.CompileAsset)) - { - if (node.Target.Analyze(context)) - upToDate = false; - } + if (node.Target.Analyze(context)) + upToDate = false; } - - if (upToDate) - return false; // Same version, skip analysis, do not clear links } - var mainCompiler = BuildDependencyManager.AssetCompilerRegistry.GetCompiler(AssetItem.Asset.GetType(), CompilationContext); - if (mainCompiler == null) - return false; // Scripts and such don't have compiler + if (upToDate) + return false; // Same version, skip analysis, do not clear links + } + + var mainCompiler = BuildDependencyManager.AssetCompilerRegistry.GetCompiler(AssetItem.Asset.GetType(), CompilationContext); + if (mainCompiler is null) + return false; // Scripts and such don't have compiler - var typesToInclude = new HashSet(mainCompiler.GetInputTypes(AssetItem)); - var typesToExclude = new HashSet(mainCompiler.GetInputTypesToExclude(AssetItem)); + var typesToInclude = new HashSet(mainCompiler.GetInputTypes(AssetItem)); + var typesToExclude = new HashSet(mainCompiler.GetInputTypesToExclude(AssetItem)); - // Clean up our references - references.Clear(); + // Clean up our references + references.Clear(); - // DependencyManager check - AddDependencies(AssetItem, typesToInclude, typesToExclude); + // DependencyManager check + AddDependencies(AssetItem, typesToInclude, typesToExclude); - // Input files required - foreach (var inputFile in new HashSet(mainCompiler.GetInputFiles(AssetItem))) //directly resolve by input files, in the future we might just want this pass + // Input files required + foreach (var inputFile in new HashSet(mainCompiler.GetInputFiles(AssetItem))) //directly resolve by input files, in the future we might just want this pass + { + if (inputFile.Type == UrlType.Content) { - if (inputFile.Type == UrlType.Content) - { - var asset = AssetItem.Package.Session.FindAsset(inputFile.Path); //this will search all packages - if (asset == null) - continue; //this might be an error tho... but in the end compilation might fail so we let the build engine do the error reporting if it really was a issue + var asset = AssetItem.Package?.Session?.FindAsset(inputFile.Path); //this will search all packages + if (asset is null) + continue; //this might be an error tho... but in the end compilation might fail so we let the build engine do the error reporting if it really was a issue - if (!typesToExclude.Contains(asset.GetType())) - { - // TODO: right now, we consider that assets returned by GetInputFiles must be compiled in AssetCompilationContext. At some point, we might need to be able to specify a custom context. - var dependencyType = inputFile.Type == UrlType.Content ? BuildDependencyType.CompileContent : BuildDependencyType.CompileAsset; //Content means we need to load the content, the rest is just asset dependency - var node = buildDependencyManager.FindOrCreateNode(asset, typeof(AssetCompilationContext)); - var link = new BuildAssetLink(this, node, dependencyType); - references.TryAdd(link, link); - } + if (!typesToExclude.Contains(asset.GetType())) + { + // TODO: right now, we consider that assets returned by GetInputFiles must be compiled in AssetCompilationContext. At some point, we might need to be able to specify a custom context. + var dependencyType = inputFile.Type == UrlType.Content ? BuildDependencyType.CompileContent : BuildDependencyType.CompileAsset; //Content means we need to load the content, the rest is just asset dependency + var node = buildDependencyManager.FindOrCreateNode(asset, typeof(AssetCompilationContext)); + var link = new BuildAssetLink(this, node, dependencyType); + references.TryAdd(link, link); } } + } - bool shouldVisitTypes; - context.Properties.TryGet(VisitRuntimeTypes, out shouldVisitTypes); - if (shouldVisitTypes || mainCompiler.AlwaysCheckRuntimeTypes) + context.Properties.TryGet(VisitRuntimeTypes, out var shouldVisitTypes); + if (shouldVisitTypes || mainCompiler.AlwaysCheckRuntimeTypes) + { + var collector = new RuntimeDependenciesCollector(mainCompiler.GetRuntimeTypes(AssetItem)); + var deps = collector.GetDependencies(AssetItem); + foreach (var reference in deps) { - var collector = new RuntimeDependenciesCollector(mainCompiler.GetRuntimeTypes(AssetItem)); - var deps = collector.GetDependencies(AssetItem); - foreach (var reference in deps) + var asset = AssetItem?.Package?.FindAsset(reference.Id); + if (asset is not null) { - var asset = AssetItem.Package.FindAsset(reference.Id); - if (asset != null) - { - // TODO: right now, we consider that assets found with RuntimeDependenciesCollector must be compiled in AssetCompilationContext. At some point, we might need to be able to specify a custom context. - var dependencyType = BuildDependencyType.Runtime; - var node = buildDependencyManager.FindOrCreateNode(asset, typeof(AssetCompilationContext)); - var link = new BuildAssetLink(this, node, dependencyType); - references.TryAdd(link, link); - } + // TODO: right now, we consider that assets found with RuntimeDependenciesCollector must be compiled in AssetCompilationContext. At some point, we might need to be able to specify a custom context. + var dependencyType = BuildDependencyType.Runtime; + var node = buildDependencyManager.FindOrCreateNode(asset, typeof(AssetCompilationContext)); + var link = new BuildAssetLink(this, node, dependencyType); + references.TryAdd(link, link); } } - return true; } + return true; + } - private void AddDependencies(AssetItem assetItem, HashSet typesToInclude, HashSet typesToExclude) + private void AddDependencies(AssetItem assetItem, HashSet typesToInclude, HashSet typesToExclude) + { + // for now we use the dependency manager itself to resolve runtime dependencies, in the future we might want to unify the builddependency manager with the dependency manager + var dependencies = assetItem.Package?.Session?.DependencyManager.ComputeDependencies(assetItem.Id, AssetDependencySearchOptions.Out); + if (dependencies is not null) { - // for now we use the dependency manager itself to resolve runtime dependencies, in the future we might want to unify the builddependency manager with the dependency manager - var dependencies = assetItem.Package.Session.DependencyManager.ComputeDependencies(assetItem.Id, AssetDependencySearchOptions.Out); - if (dependencies != null) + foreach (var assetDependency in dependencies.LinksOut) { - foreach (var assetDependency in dependencies.LinksOut) + var assetType = assetDependency.Item.Asset.GetType(); + if (!typesToExclude.Contains(assetType)) //filter out what we do not need { - var assetType = assetDependency.Item.Asset.GetType(); - if (!typesToExclude.Contains(assetType)) //filter out what we do not need + foreach (var input in typesToInclude.Where(x => x.AssetType == assetType)) { - foreach (var input in typesToInclude.Where(x => x.AssetType == assetType)) + var node = buildDependencyManager.FindOrCreateNode(assetDependency.Item, input.CompilationContext); + var link = new BuildAssetLink(this, node, input.DependencyType); + references.TryAdd(link, link); + if (link.HasOne(BuildDependencyType.CompileAsset)) { - var node = buildDependencyManager.FindOrCreateNode(assetDependency.Item, input.CompilationContext); - var link = new BuildAssetLink(this, node, input.DependencyType); - references.TryAdd(link, link); - if (link.HasOne(BuildDependencyType.CompileAsset)) - { - // When we have a CompileAsset type of dependency, we want to analyze this asset and extract other assets that it references and are needed by this asset. - AddDependencies(assetDependency.Item, typesToInclude, typesToExclude); - } + // When we have a CompileAsset type of dependency, we want to analyze this asset and extract other assets that it references and are needed by this asset. + AddDependencies(assetDependency.Item, typesToInclude, typesToExclude); } } } } } + } + + public override string ToString() + { + return $"{AssetItem.Location} ({References.Count} refs)"; + } - public override string ToString() + private class RuntimeDependenciesCollector : AssetVisitorBase + { + private object? visitedRuntimeObject; + private readonly HashSet references = []; + private readonly HashSet types; + + public RuntimeDependenciesCollector(IEnumerable enumerable) { - return $"{AssetItem.Location} ({References.Count} refs)"; + types = new HashSet(enumerable); } - private class RuntimeDependenciesCollector : AssetVisitorBase + public IEnumerable GetDependencies(AssetItem item) { - private object visitedRuntimeObject; - private readonly HashSet references = new HashSet(); - private readonly HashSet types; + Visit(item.Asset); + return references; + } - public RuntimeDependenciesCollector(IEnumerable enumerable) + public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) + { + var enteringRuntimeObject = visitedRuntimeObject is null && types.Any(x => x.IsInstanceOfType(obj)); + if (enteringRuntimeObject) { - types = new HashSet(enumerable); + //from now on we want store references + visitedRuntimeObject = obj; } - public IEnumerable GetDependencies(AssetItem item) + if (visitedRuntimeObject is null) { - Visit(item.Asset); - return references; + base.VisitObject(obj, descriptor, visitMembers); } - - public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) + else { - var enteringRuntimeObject = visitedRuntimeObject == null && types.Any(x => x.IsInstanceOfType(obj)); - if (enteringRuntimeObject) + // references and base + if (obj is AssetReference assetRef) { - //from now on we want store references - visitedRuntimeObject = obj; + references.Add(assetRef); } - - if (visitedRuntimeObject == null) + else if (obj is UrlReferenceBase urlRef) { - base.VisitObject(obj, descriptor, visitMembers); + references.Add(urlRef); } - else + else if (AssetRegistry.IsExactContentType(obj.GetType())) { - // references and base - IReference reference = obj as AssetReference; - if (reference != null) + var reference = AttachedReferenceManager.GetAttachedReference(obj); + if (reference is not null) { references.Add(reference); } - else if (obj is UrlReferenceBase urlRef) - { - references.Add(urlRef); - } - else if (AssetRegistry.IsExactContentType(obj.GetType())) - { - reference = AttachedReferenceManager.GetAttachedReference(obj); - if (reference != null) - { - references.Add(reference); - } - } - else - { - base.VisitObject(obj, descriptor, visitMembers); - } } - - if (enteringRuntimeObject) + else { - //from now on we stop storing references - visitedRuntimeObject = null; + base.VisitObject(obj, descriptor, visitMembers); } } + + if (enteringRuntimeObject) + { + //from now on we stop storing references + visitedRuntimeObject = null; + } } } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/BuildDependencyInfo.cs b/sources/assets/Stride.Core.Assets/Analysis/BuildDependencyInfo.cs index 0fa04bfbd6..74af2434a4 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/BuildDependencyInfo.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/BuildDependencyInfo.cs @@ -1,78 +1,71 @@ -using System; + using Stride.Core.Assets.Compiler; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// A structure representing information related to a build dependency between one source assets and a target type of asset. +/// +public readonly struct BuildDependencyInfo : IEquatable { /// - /// A structure representing information related to a build dependency between one source assets and a target type of asset. + /// The compilation context in which to compile the target asset. /// - public struct BuildDependencyInfo : IEquatable - { - /// - /// The compilation context in which to compile the target asset. - /// - /// This context is not relevant if the asset is not compiled, like when is - public readonly Type CompilationContext; - /// - /// The type of asset targeted by this dependency. - /// - public readonly Type AssetType; - /// - /// The type of dependency, indicating whether the target asset must actually be compiled, and whether it should be compiled before the referecing asset or can be at the same time. - /// - public readonly BuildDependencyType DependencyType; + /// This context is not relevant if the asset is not compiled, like when is + public readonly Type CompilationContext; + /// + /// The type of asset targeted by this dependency. + /// + public readonly Type AssetType; + /// + /// The type of dependency, indicating whether the target asset must actually be compiled, and whether it should be compiled before the referecing asset or can be at the same time. + /// + public readonly BuildDependencyType DependencyType; - /// - /// Initializes a new instance of the structure. - /// - /// The type of asset targeted by this dependency info. - /// The compilation context in which to compile the target asset. - /// The type of dependency. - public BuildDependencyInfo(Type assetType, Type compilationContext, BuildDependencyType dependencyType) - { - if (!typeof(Asset).IsAssignableFrom(assetType)) throw new ArgumentException($@"{nameof(assetType)} should inherit from Asset", nameof(assetType)); - if (!typeof(ICompilationContext).IsAssignableFrom(compilationContext)) throw new ArgumentException($@"{nameof(compilationContext)} should inherit from ICompilationContext", nameof(compilationContext)); - AssetType = assetType; - CompilationContext = compilationContext; - DependencyType = dependencyType; - } + /// + /// Initializes a new instance of the structure. + /// + /// The type of asset targeted by this dependency info. + /// The compilation context in which to compile the target asset. + /// The type of dependency. + public BuildDependencyInfo(Type assetType, Type compilationContext, BuildDependencyType dependencyType) + { + if (!typeof(Asset).IsAssignableFrom(assetType)) throw new ArgumentException($@"{nameof(assetType)} should inherit from Asset", nameof(assetType)); + if (!typeof(ICompilationContext).IsAssignableFrom(compilationContext)) throw new ArgumentException($@"{nameof(compilationContext)} should inherit from ICompilationContext", nameof(compilationContext)); + AssetType = assetType; + CompilationContext = compilationContext; + DependencyType = dependencyType; + } - /// - public bool Equals(BuildDependencyInfo other) - { - return ReferenceEquals(CompilationContext, other.CompilationContext) && ReferenceEquals(AssetType, other.AssetType) && DependencyType == other.DependencyType; - } + /// + public readonly bool Equals(BuildDependencyInfo other) + { + return ReferenceEquals(CompilationContext, other.CompilationContext) && ReferenceEquals(AssetType, other.AssetType) && DependencyType == other.DependencyType; + } - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - return obj is BuildDependencyInfo && Equals((BuildDependencyInfo)obj); - } + /// + public override readonly bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + return obj is BuildDependencyInfo info && Equals(info); + } - /// - public override int GetHashCode() - { - unchecked - { - var hashCode = (CompilationContext != null ? CompilationContext.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (AssetType != null ? AssetType.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (int)DependencyType; - return hashCode; - } - } + /// + public override readonly int GetHashCode() + { + return HashCode.Combine(CompilationContext, AssetType, DependencyType); + } - /// - public static bool operator ==(BuildDependencyInfo left, BuildDependencyInfo right) - { - return left.Equals(right); - } + /// + public static bool operator ==(BuildDependencyInfo left, BuildDependencyInfo right) + { + return left.Equals(right); + } - /// - public static bool operator !=(BuildDependencyInfo left, BuildDependencyInfo right) - { - return !left.Equals(right); - } + /// + public static bool operator !=(BuildDependencyInfo left, BuildDependencyInfo right) + { + return !left.Equals(right); } -} \ No newline at end of file +} diff --git a/sources/assets/Stride.Core.Assets/Analysis/BuildDependencyManager.cs b/sources/assets/Stride.Core.Assets/Analysis/BuildDependencyManager.cs index fd1bdbfedd..53e51a490d 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/BuildDependencyManager.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/BuildDependencyManager.cs @@ -1,180 +1,172 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; using Stride.Core.Assets.Compiler; -using Stride.Core.Annotations; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// Build dependency manager +/// Basically is a container of BuildAssetNode +/// +public class BuildDependencyManager { /// - /// Build dependency manager - /// Basically is a container of BuildAssetNode + /// A structure used as key of the dictionary containing all the build nodes /// - public class BuildDependencyManager + private readonly struct BuildNodeDesc : IEquatable { - /// - /// A structure used as key of the dictionary containing all the build nodes - /// - private struct BuildNodeDesc : IEquatable + public readonly AssetId AssetId; + public readonly Type CompilationContext; + + public BuildNodeDesc(AssetId assetId, Type compilationContext) { - public readonly AssetId AssetId; - public readonly Type CompilationContext; - - public BuildNodeDesc(AssetId assetId, Type compilationContext) - { - AssetId = assetId; - CompilationContext = compilationContext; - } - - public bool Equals(BuildNodeDesc other) - { - return AssetId.Equals(other.AssetId) && ReferenceEquals(CompilationContext, other.CompilationContext); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is BuildNodeDesc && Equals((BuildNodeDesc)obj); - } - - public override int GetHashCode() - { - unchecked - { - return (AssetId.GetHashCode() * 397) ^ (CompilationContext?.GetHashCode() ?? 0); - } - } - - public static bool operator ==(BuildNodeDesc left, BuildNodeDesc right) - { - return left.Equals(right); - } - - public static bool operator !=(BuildNodeDesc left, BuildNodeDesc right) - { - return !left.Equals(right); - } + AssetId = assetId; + CompilationContext = compilationContext; } - /// - /// The AssetCompilerRegistry, here mostly for ease of access - /// - public static readonly AssetCompilerRegistry AssetCompilerRegistry = new AssetCompilerRegistry(); + public readonly bool Equals(BuildNodeDesc other) + { + return AssetId.Equals(other.AssetId) && ReferenceEquals(CompilationContext, other.CompilationContext); + } - private readonly ConcurrentDictionary nodes = new ConcurrentDictionary(); + public override readonly bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is BuildNodeDesc desc && Equals(desc); + } - /// - /// Finds or creates a node, notice that this will not perform an analysis on the node, which must be explicitly called on the node - /// - /// The asset item to find or create - /// The context in which the asset is compiled. - /// The build node associated with item - public BuildAssetNode FindOrCreateNode([NotNull] AssetItem item, [NotNull] Type compilationContext) + public override readonly int GetHashCode() { - if (item == null) throw new ArgumentNullException(nameof(item)); - if (compilationContext == null) throw new ArgumentNullException(nameof(compilationContext)); - - var nodeDesc = new BuildNodeDesc(item.Id, compilationContext); - - BuildAssetNode node; - if (!nodes.TryGetValue(nodeDesc, out node)) - { - node = new BuildAssetNode(item, compilationContext, this); - nodes.TryAdd(nodeDesc, node); - } - else if (!ReferenceEquals(node.AssetItem, item)) - { - node = new BuildAssetNode(item, compilationContext, this); - nodes[nodeDesc] = node; - } - - return node; + return HashCode.Combine(AssetId, CompilationContext); } - // TODO: this should be reimplemented at the service level (that consumes the build graph - thumbnails, preview, scene editors...) - //private static void AnalyzeNode([NotNull] BuildAssetNode node, [NotNull] AssetCompilerContext context) - //{ - // if (node == null) throw new ArgumentNullException(nameof(node)); - // if (context == null) throw new ArgumentNullException(nameof(context)); - // node.Analyze(context); - // foreach (var reference in node.References) - // { - // AnalyzeNode(reference.Target, context); - // } - //} - - //public void AssetChanged(AssetItem sender) - //{ - // //var node = FindOrCreateNode(sender, typeof(AssetCompilationContext)); // update only runtime ones ( as they are root ) - // //var context = new AssetCompilerContext { CompilationContext = typeof(AssetCompilationContext) }; - // //AnalyzeNode(node, context); - //} - - /// - /// Finds a node, notice that this will not perform an analysis on the node, which must be explicitly called on the node - /// - /// The asset item to find - /// The context in which the asset is compiled. - /// The build node associated with item or null if it was not found - public BuildAssetNode FindNode([NotNull] AssetItem item, [NotNull] Type compilationContext) + public static bool operator ==(BuildNodeDesc left, BuildNodeDesc right) { - if (item == null) throw new ArgumentNullException(nameof(item)); - if (compilationContext == null) throw new ArgumentNullException(nameof(compilationContext)); + return left.Equals(right); + } + + public static bool operator !=(BuildNodeDesc left, BuildNodeDesc right) + { + return !left.Equals(right); + } + } - var nodeDesc = new BuildNodeDesc(item.Id, compilationContext); + /// + /// The AssetCompilerRegistry, here mostly for ease of access + /// + public static readonly AssetCompilerRegistry AssetCompilerRegistry = new(); - if (!nodes.TryGetValue(nodeDesc, out var node)) - { - return null; - } + private readonly ConcurrentDictionary nodes = new(); - if (!ReferenceEquals(node.AssetItem, item)) - { - nodes.TryRemove(nodeDesc, out node); - return null; - } + /// + /// Finds or creates a node, notice that this will not perform an analysis on the node, which must be explicitly called on the node + /// + /// The asset item to find or create + /// The context in which the asset is compiled. + /// The build node associated with item + public BuildAssetNode FindOrCreateNode(AssetItem item, Type compilationContext) + { + ArgumentNullException.ThrowIfNull(item); + ArgumentNullException.ThrowIfNull(compilationContext); - return node; + var nodeDesc = new BuildNodeDesc(item.Id, compilationContext); + + if (!nodes.TryGetValue(nodeDesc, out var node)) + { + node = new BuildAssetNode(item, compilationContext, this); + nodes.TryAdd(nodeDesc, node); } + else if (!ReferenceEquals(node.AssetItem, item)) + { + node = new BuildAssetNode(item, compilationContext, this); + nodes[nodeDesc] = node; + } + + return node; + } + + // TODO: this should be reimplemented at the service level (that consumes the build graph - thumbnails, preview, scene editors...) + //private static void AnalyzeNode(BuildAssetNode node, AssetCompilerContext context) + //{ + // if (node == null) throw new ArgumentNullException(nameof(node)); + // if (context == null) throw new ArgumentNullException(nameof(context)); + // node.Analyze(context); + // foreach (var reference in node.References) + // { + // AnalyzeNode(reference.Target, context); + // } + //} + + //public void AssetChanged(AssetItem sender) + //{ + // //var node = FindOrCreateNode(sender, typeof(AssetCompilationContext)); // update only runtime ones ( as they are root ) + // //var context = new AssetCompilerContext { CompilationContext = typeof(AssetCompilationContext) }; + // //AnalyzeNode(node, context); + //} + + /// + /// Finds a node, notice that this will not perform an analysis on the node, which must be explicitly called on the node + /// + /// The asset item to find + /// The context in which the asset is compiled. + /// The build node associated with item or null if it was not found + public BuildAssetNode? FindNode(AssetItem item, Type compilationContext) + { + ArgumentNullException.ThrowIfNull(item); + ArgumentNullException.ThrowIfNull(compilationContext); - /// - /// Finds all the nodes associated with the asset - /// - /// The asset item to find - /// The build nodes associated with item or null if it was not found - public IEnumerable FindNodes([NotNull] AssetItem item) + var nodeDesc = new BuildNodeDesc(item.Id, compilationContext); + + if (!nodes.TryGetValue(nodeDesc, out var node)) { - if (item == null) throw new ArgumentNullException(nameof(item)); - return nodes.Where(x => x.Value.AssetItem == item).Select(x => x.Value); + return null; } - /// - /// Removes the node from the build graph - /// - /// The node to remove - public void RemoveNode([NotNull] BuildAssetNode node) + if (!ReferenceEquals(node.AssetItem, item)) { - if (node == null) throw new ArgumentNullException(nameof(node)); - var nodeDesc = new BuildNodeDesc(node.AssetItem.Id, node.CompilationContext); - nodes.TryRemove(nodeDesc, out node); + nodes.TryRemove(nodeDesc, out _); + return null; } - /// - /// Removes the nodes associated with item from the build graph - /// - /// The item to use to find nodes to remove - public void RemoveNode([NotNull] AssetItem item) + return node; + } + + /// + /// Finds all the nodes associated with the asset + /// + /// The asset item to find + /// The build nodes associated with item or null if it was not found + public IEnumerable FindNodes(AssetItem item) + { + ArgumentNullException.ThrowIfNull(item); + return nodes.Where(x => x.Value.AssetItem == item).Select(x => x.Value); + } + + /// + /// Removes the node from the build graph + /// + /// The node to remove + public void RemoveNode(BuildAssetNode node) + { + ArgumentNullException.ThrowIfNull(node); + var nodeDesc = new BuildNodeDesc(node.AssetItem.Id, node.CompilationContext); + nodes.TryRemove(nodeDesc, out _); + } + + /// + /// Removes the nodes associated with item from the build graph + /// + /// The item to use to find nodes to remove + public void RemoveNode(AssetItem item) + { + ArgumentNullException.ThrowIfNull(item); + var assetNodes = FindNodes(item).ToList(); + foreach (var buildAssetNode in assetNodes) { - if (item == null) throw new ArgumentNullException(nameof(item)); - var assetNodes = FindNodes(item).ToList(); - foreach (var buildAssetNode in assetNodes) - { - var nodeDesc = new BuildNodeDesc(item.Id, buildAssetNode.CompilationContext); - nodes.TryRemove(nodeDesc, out _); - } + var nodeDesc = new BuildNodeDesc(item.Id, buildAssetNode.CompilationContext); + nodes.TryRemove(nodeDesc, out _); } } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/BuildDependencyType.cs b/sources/assets/Stride.Core.Assets/Analysis/BuildDependencyType.cs index c13af97e83..e34e53effe 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/BuildDependencyType.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/BuildDependencyType.cs @@ -1,24 +1,21 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Assets.Compiler; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +[Flags] +public enum BuildDependencyType { - [Flags] - public enum BuildDependencyType - { - /// - /// The content generated during compilation needs the content compiled from the target asset to be loaded at runtime. - /// - Runtime = 0x1, - /// - /// The uncompiled target asset is accessed during compilation. - /// - CompileAsset = 0x2, - /// - /// The content compiled from the target asset is needed during compilation. - /// - CompileContent = 0x4 - } + /// + /// The content generated during compilation needs the content compiled from the target asset to be loaded at runtime. + /// + Runtime = 0x1, + /// + /// The uncompiled target asset is accessed during compilation. + /// + CompileAsset = 0x2, + /// + /// The content compiled from the target asset is needed during compilation. + /// + CompileContent = 0x4 } diff --git a/sources/assets/Stride.Core.Assets/Analysis/CollectionItemIdsAnalysis.cs b/sources/assets/Stride.Core.Assets/Analysis/CollectionItemIdsAnalysis.cs index 59c92111dd..a30e2f7e83 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/CollectionItemIdsAnalysis.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/CollectionItemIdsAnalysis.cs @@ -1,90 +1,87 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; using Stride.Core.Diagnostics; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// A static class that analyzes an and fixes issues in of collections contained in this asset. +/// +public static class CollectionItemIdsAnalysis { /// - /// A static class that analyzes an and fixes issues in of collections contained in this asset. + /// Fixes up the of collections contained in the given asset. by generating new ids if there are any duplicate. /// - public static class CollectionItemIdsAnalysis + /// The asset to analyze. + /// A logger to output fixed entries. + /// This method doesn't handle collections in derived assets that will be desynchronized afterwards. + public static void FixupItemIds(AssetItem assetItem, ILogger? logger) { - /// - /// Fixes up the of collections contained in the given asset. by generating new ids if there are any duplicate. - /// - /// The asset to analyze. - /// A logger to output fixed entries. - /// This method doesn't handle collections in derived assets that will be desynchronized afterwards. - public static void FixupItemIds(AssetItem assetItem, ILogger logger) + var visitor = new CollectionItemIdsAnalysisVisitor(assetItem, logger); + visitor.Visit(assetItem); + } + + private class CollectionItemIdsAnalysisVisitor : DataVisitorBase + { + private readonly AssetItem assetItem; + private readonly ILogger? logger; + + public CollectionItemIdsAnalysisVisitor(AssetItem assetItem, ILogger? logger) { - var visitor = new CollectionItemIdsAnalysisVisitor(assetItem, logger); - visitor.Visit(assetItem); + this.assetItem = assetItem; + this.logger = logger; } - private class CollectionItemIdsAnalysisVisitor : DataVisitorBase + protected override bool CanVisit(object obj) { - private readonly AssetItem assetItem; - private readonly ILogger logger; - - public CollectionItemIdsAnalysisVisitor(AssetItem assetItem, ILogger logger) - { - this.assetItem = assetItem; - this.logger = logger; - } - - protected override bool CanVisit(object obj) - { - return !AssetRegistry.IsExactContentType(obj?.GetType()) && base.CanVisit(obj); - } + return !AssetRegistry.IsExactContentType(obj.GetType()) && base.CanVisit(obj); + } - public override void VisitArray(Array array, ArrayDescriptor descriptor) - { - Fixup(array); - base.VisitArray(array, descriptor); - } + public override void VisitArray(Array array, ArrayDescriptor descriptor) + { + Fixup(array); + base.VisitArray(array, descriptor); + } - public override void VisitDictionary(object dictionary, DictionaryDescriptor descriptor) - { - Fixup(dictionary); - base.VisitDictionary(dictionary, descriptor); - } + public override void VisitDictionary(object dictionary, DictionaryDescriptor descriptor) + { + Fixup(dictionary); + base.VisitDictionary(dictionary, descriptor); + } - public override void VisitSet(IEnumerable set, SetDescriptor descriptor) - { - Fixup(set); - base.VisitSet(set, descriptor); - } + public override void VisitSet(IEnumerable set, SetDescriptor descriptor) + { + Fixup(set); + base.VisitSet(set, descriptor); + } - public override void VisitCollection(IEnumerable collection, CollectionDescriptor descriptor) - { - Fixup(collection); - base.VisitCollection(collection, descriptor); - } + public override void VisitCollection(IEnumerable collection, CollectionDescriptor descriptor) + { + Fixup(collection); + base.VisitCollection(collection, descriptor); + } - /// - /// Fixes up the of a collection by generating new ids if there are any duplicate. - /// - /// The collection to fix up. - /// This method doesn't handle collections in derived objects that will be desynchronized afterwards. - private void Fixup(object collection) + /// + /// Fixes up the of a collection by generating new ids if there are any duplicate. + /// + /// The collection to fix up. + /// This method doesn't handle collections in derived objects that will be desynchronized afterwards. + private void Fixup(object collection) + { + if (CollectionItemIdHelper.TryGetCollectionItemIds(collection, out var itemIds)) { - CollectionItemIdentifiers itemIds; - if (CollectionItemIdHelper.TryGetCollectionItemIds(collection, out itemIds)) + var items = new HashSet(); + var localCopy = new CollectionItemIdentifiers(); + itemIds.CloneInto(localCopy, null); + foreach (var id in localCopy) { - var items = new HashSet(); - var localCopy = new CollectionItemIdentifiers(); - itemIds.CloneInto(localCopy, null); - foreach (var id in localCopy) + if (!items.Add(id.Value)) { - if (!items.Add(id.Value)) - { - logger?.Warning($"Duplicate item identifier [{id.Value}] in collection {CurrentPath} of asset [{assetItem.Location}]. Generating a new identifier to remove the duplicate entry."); - itemIds[id.Key] = ItemId.New(); - } + logger?.Warning($"Duplicate item identifier [{id.Value}] in collection {CurrentPath} of asset [{assetItem.Location}]. Generating a new identifier to remove the duplicate entry."); + itemIds[id.Key] = ItemId.New(); } } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/CommonAnalysis.cs b/sources/assets/Stride.Core.Assets/Analysis/CommonAnalysis.cs index b8f8009e93..36f6240153 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/CommonAnalysis.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/CommonAnalysis.cs @@ -1,66 +1,63 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using Stride.Core.IO; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +internal static class CommonAnalysis { - internal static class CommonAnalysis + internal static void UpdatePaths(IFileSynchronizable parentFileSync, IEnumerable paths, AssetAnalysisParameters parameters) { - internal static void UpdatePaths(IFileSynchronizable parentFileSync, IEnumerable paths, AssetAnalysisParameters parameters) - { - if (parameters == null) throw new ArgumentNullException("parameters"); - - var fileDirectory = parentFileSync.FullPath.GetParent(); + ArgumentNullException.ThrowIfNull(parameters); - foreach (var assetReferenceLink in paths) - { - var currentLocation = (UPath)assetReferenceLink.Reference; + var fileDirectory = parentFileSync.FullPath.GetParent(); - // If we need to skip an attribute - var upathAttribute = assetReferenceLink.Path.GetCustomAttribute(); - if (upathAttribute != null && upathAttribute.RelativeTo == UPathRelativeTo.None) - { - continue; - } + foreach (var assetReferenceLink in paths) + { + var currentLocation = (UPath)assetReferenceLink.Reference!; - UPath newLocation = null; + // If we need to skip an attribute + var upathAttribute = assetReferenceLink.Path.GetCustomAttribute(); + if (upathAttribute is not null && upathAttribute.RelativeTo == UPathRelativeTo.None) + { + continue; + } - var uFile = currentLocation as UFile; - var uDirectory = currentLocation as UDirectory; - if (!string.IsNullOrEmpty(uFile)) - { - var previousLocationOnDisk = UPath.Combine(fileDirectory, uFile); + UPath? newLocation = null; - // If UseRelativeForUFile is used, then turn - newLocation = parameters.ConvertUPathTo == UPathType.Relative ? previousLocationOnDisk.MakeRelative(fileDirectory) : previousLocationOnDisk; - } - else if (!string.IsNullOrEmpty(uDirectory)) - { - var previousDirectoryOnDisk = UPath.Combine(fileDirectory, uDirectory); + var uFile = currentLocation as UFile; + var uDirectory = currentLocation as UDirectory; + if (!string.IsNullOrEmpty(uFile)) + { + var previousLocationOnDisk = UPath.Combine(fileDirectory, uFile); - // If UseRelativeForUFile is used, then turn - newLocation = parameters.ConvertUPathTo == UPathType.Relative ? previousDirectoryOnDisk.MakeRelative(fileDirectory) : previousDirectoryOnDisk; - } - // Only update location that are actually different - if (currentLocation != newLocation) - { - assetReferenceLink.UpdateReference(null, newLocation != null ? newLocation.FullPath : null); - if (parameters.SetDirtyFlagOnAssetWhenFixingUFile) - { - parentFileSync.IsDirty = true; - } - } + // If UseRelativeForUFile is used, then turn + newLocation = parameters.ConvertUPathTo == UPathType.Relative ? previousLocationOnDisk.MakeRelative(fileDirectory) : previousLocationOnDisk; + } + else if (!string.IsNullOrEmpty(uDirectory)) + { + var previousDirectoryOnDisk = UPath.Combine(fileDirectory, uDirectory); - // Set dirty flag on asset if the uFile was previously absolute and - // SetDirtyFlagOnAssetWhenFixingAbsoluteUFile = true - if ((currentLocation.IsAbsolute && parameters.ConvertUPathTo == UPathType.Absolute && parameters.SetDirtyFlagOnAssetWhenFixingAbsoluteUFile)) + // If UseRelativeForUFile is used, then turn + newLocation = parameters.ConvertUPathTo == UPathType.Relative ? previousDirectoryOnDisk.MakeRelative(fileDirectory) : previousDirectoryOnDisk; + } + // Only update location that are actually different + if (currentLocation != newLocation) + { + assetReferenceLink.UpdateReference(null, newLocation?.FullPath); + if (parameters.SetDirtyFlagOnAssetWhenFixingUFile) { parentFileSync.IsDirty = true; } } + + // Set dirty flag on asset if the uFile was previously absolute and + // SetDirtyFlagOnAssetWhenFixingAbsoluteUFile = true + if (currentLocation.IsAbsolute && parameters.ConvertUPathTo == UPathType.Absolute && parameters.SetDirtyFlagOnAssetWhenFixingAbsoluteUFile) + { + parentFileSync.IsDirty = true; + } } } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/ContentLinkType.cs b/sources/assets/Stride.Core.Assets/Analysis/ContentLinkType.cs index 200b5861e6..84131043c1 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/ContentLinkType.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/ContentLinkType.cs @@ -1,24 +1,22 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// The different possible types of link between elements. +/// +[Flags] +public enum ContentLinkType { /// - /// The different possible types of link between elements. + /// A simple reference to the asset. /// - [Flags] - public enum ContentLinkType - { - /// - /// A simple reference to the asset. - /// - Reference = 1, + Reference = 1, - /// - /// All type of links. - /// - All = Reference, - } + /// + /// All type of links. + /// + All = Reference, } diff --git a/sources/assets/Stride.Core.Assets/Analysis/IAssetDependencyManager.cs b/sources/assets/Stride.Core.Assets/Analysis/IAssetDependencyManager.cs index 4427c5d135..987cb8317a 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/IAssetDependencyManager.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/IAssetDependencyManager.cs @@ -1,22 +1,17 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using Stride.Core.Annotations; +namespace Stride.Core.Assets.Analysis; -namespace Stride.Core.Assets.Analysis +public interface IAssetDependencyManager { - public interface IAssetDependencyManager - { - /// - /// Computes the dependencies for the specified asset. - /// - /// The asset id. - /// The dependencies options. - /// The type of links to visit while computing the dependencies - /// The list of element already visited. - /// The dependencies, or null if the object is not tracked. - [CanBeNull] - AssetDependencies ComputeDependencies(AssetId assetId, AssetDependencySearchOptions dependenciesOptions = AssetDependencySearchOptions.All, ContentLinkType linkTypes = ContentLinkType.Reference, HashSet visited = null); - } + /// + /// Computes the dependencies for the specified asset. + /// + /// The asset id. + /// The dependencies options. + /// The type of links to visit while computing the dependencies + /// The list of element already visited. + /// The dependencies, or null if the object is not tracked. + AssetDependencies? ComputeDependencies(AssetId assetId, AssetDependencySearchOptions dependenciesOptions = AssetDependencySearchOptions.All, ContentLinkType linkTypes = ContentLinkType.Reference, HashSet? visited = null); } diff --git a/sources/assets/Stride.Core.Assets/Analysis/IContentLink.cs b/sources/assets/Stride.Core.Assets/Analysis/IContentLink.cs index 09b3e11a4b..9238b32a59 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/IContentLink.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/IContentLink.cs @@ -1,24 +1,22 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// The interface for types representing a link between elements. +/// +public interface IContentLink { /// - /// The interface for types representing a link between elements. + /// The reference to the element at the opposite side of the link. /// - public interface IContentLink - { - /// - /// The reference to the element at the opposite side of the link. - /// - IReference Element { get; } + IReference Element { get; } - /// - /// The type of the link. - /// - ContentLinkType Type { get; } - } + /// + /// The type of the link. + /// + ContentLinkType Type { get; } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/IdentifiableObjectAnalysis.cs b/sources/assets/Stride.Core.Assets/Analysis/IdentifiableObjectAnalysis.cs index 0116d5ebf1..e4d35b1505 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/IdentifiableObjectAnalysis.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/IdentifiableObjectAnalysis.cs @@ -1,98 +1,91 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.Text; using Stride.Core.Assets.Visitors; -using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.Diagnostics; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// A static class that visit an object and make sure that none of the it references share the same identifier. In case there are duplicate identifier, +/// the visitor can generate new identifiers for the duplicate +/// +public static class IdentifiableObjectAnalysis { /// - /// A static class that visit an object and make sure that none of the it references share the same identifier. In case there are duplicate identifier, - /// the visitor can generate new identifiers for the duplicate + /// Visits the object and look up for duplicates identifier in instances. /// - public static class IdentifiableObjectAnalysis + /// The object to visit. + /// If true, duplicate identifiers will be fixed by generating new identifiers. + /// A logger to report duplicates and fixes. + /// True if the given object has been modified, false otherwise. + public static bool Visit(object obj, bool fixDuplicate, ILogger? logger = null) { - /// - /// Visits the object and look up for duplicates identifier in instances. - /// - /// The object to visit. - /// If true, duplicate identifiers will be fixed by generating new identifiers. - /// A logger to report duplicates and fixes. - /// True if the given object has been modified, false otherwise. - public static bool Visit(object obj, bool fixDuplicate, [CanBeNull] ILogger logger = null) + var visitor = new IdentifiableObjectAnalysisVisitor(); + visitor.Visit(obj); + var sb = new StringBuilder(); + var hasBeenModified = false; + foreach (var result in visitor.IdentifiablesById) { - var visitor = new IdentifiableObjectAnalysisVisitor(); - visitor.Visit(obj); - var sb = new StringBuilder(); - var hasBeenModified = false; - foreach (var result in visitor.IdentifiablesById) + if (result.Value.Count > 1) { - if (result.Value.Count > 1) + var first = true; + + if (logger != null) { - var first = true; + sb.Clear(); + sb.Append($"Multiple object with same id [{result.Key}]"); + } - if (logger != null) + foreach (var identifiable in result.Value) + { + if (!first && fixDuplicate) { - sb.Clear(); - sb.Append($"Multiple object with same id [{result.Key}]"); + identifiable.Id = Guid.NewGuid(); + hasBeenModified = true; } - foreach (var identifiable in result.Value) + if (logger != null) { - if (!first && fixDuplicate) - { - identifiable.Id = Guid.NewGuid(); - hasBeenModified = true; - } - - if (logger != null) + sb.Append($"\r\n - One instance of [{identifiable.GetType()}] reachable from the following paths:"); + if (identifiable.Id != result.Key) + sb.Append($" (replaced by new id [{identifiable.Id}]"); + foreach (var path in visitor.IdentifiablePaths[identifiable]) { - sb.Append($"\r\n - One instance of [{identifiable.GetType()}] reachable from the following paths:"); - if (identifiable.Id != result.Key) - sb.Append($" (replaced by new id [{identifiable.Id}]"); - foreach (var path in visitor.IdentifiablePaths[identifiable]) - { - sb.Append($"\r\n - [{path}]"); - } + sb.Append($"\r\n - [{path}]"); } - first = false; } - logger.Warning(sb.ToString()); + first = false; } + logger.Warning(sb.ToString()); } - return hasBeenModified; } + return hasBeenModified; + } - private class IdentifiableObjectAnalysisVisitor : AssetVisitorBase - { - public readonly Dictionary> IdentifiablesById = new Dictionary>(); - public readonly Dictionary> IdentifiablePaths = new Dictionary>(); + private class IdentifiableObjectAnalysisVisitor : AssetVisitorBase + { + public readonly Dictionary> IdentifiablesById = []; + public readonly Dictionary> IdentifiablePaths = []; - public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) + public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) + { + if (obj is IIdentifiable identifiable) { - var identifiable = obj as IIdentifiable; - if (identifiable != null) + if (!IdentifiablesById.TryGetValue(identifiable.Id, out var identifiables)) { - HashSet identifiables; - if (!IdentifiablesById.TryGetValue(identifiable.Id, out identifiables)) - { - IdentifiablesById.Add(identifiable.Id, identifiables = new HashSet()); - } - identifiables.Add(identifiable); - List paths; - if (!IdentifiablePaths.TryGetValue(identifiable, out paths)) - { - IdentifiablePaths.Add(identifiable, paths = new List()); - } - paths.Add(CurrentPath.Clone()); + IdentifiablesById.Add(identifiable.Id, identifiables = []); + } + identifiables.Add(identifiable); + if (!IdentifiablePaths.TryGetValue(identifiable, out var paths)) + { + IdentifiablePaths.Add(identifiable, paths = []); } - base.VisitObject(obj, descriptor, visitMembers); + paths.Add(CurrentPath.Clone()); } + base.VisitObject(obj, descriptor, visitMembers); } } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/PackageAnalysis.cs b/sources/assets/Stride.Core.Assets/Analysis/PackageAnalysis.cs index 0792318d99..699c8a722d 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/PackageAnalysis.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/PackageAnalysis.cs @@ -1,233 +1,228 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; + using Stride.Core.Assets.Diagnostics; using Stride.Core.Diagnostics; using Stride.Core.IO; -using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// An analysis to check the validity of a , convert or +/// references to absolute/relative paths, check asset references...etc, change location +/// if location changed. +/// +public sealed class PackageAnalysis { + private readonly Package package; + private readonly PackageAnalysisParameters parameters; + + public PackageAnalysis(Package package, PackageAnalysisParameters? parameters = null) + { + ArgumentNullException.ThrowIfNull(package); + this.parameters = parameters ?? new PackageAnalysisParameters(); + this.package = package; + } + /// - /// An analysis to check the validity of a , convert or - /// references to absolute/relative paths, check asset references...etc, change location - /// if location changed. + /// Gets the parameters used for this analysis. /// - public sealed class PackageAnalysis + /// The parameters. + public PackageAnalysisParameters Parameters { - private readonly Package package; - private readonly PackageAnalysisParameters parameters; - - public PackageAnalysis(Package package, PackageAnalysisParameters parameters = null) + get { - if (package == null) throw new ArgumentNullException("package"); - this.parameters = parameters ?? new PackageAnalysisParameters(); - this.package = package; + return parameters; } + } - /// - /// Gets the parameters used for this analysis. - /// - /// The parameters. - public PackageAnalysisParameters Parameters + /// + /// Runs a full analysis on this package. + /// + /// LoggerResult. + public LoggerResult Run() + { + var log = new LoggerResult(); + Run(log); + return log; + } + + /// + /// Runs a full analysis on this package. + /// + /// The log. + public void Run(ILogger log) + { + ArgumentNullException.ThrowIfNull(log); + + // If the package doesn't have a meta name, fix it here + if (string.IsNullOrWhiteSpace(package.Meta.Name) && package.FullPath != null) { - get - { - return parameters; - } + package.Meta.Name = package.FullPath.GetFileNameWithoutExtension(); + package.IsDirty = true; } - /// - /// Runs a full analysis on this package. - /// - /// LoggerResult. - public LoggerResult Run() + if (Parameters.IsPackageCheckDependencies) { - var log = new LoggerResult(); - Run(log); - return log; + CheckDependencies().CopyTo(log); } - /// - /// Runs a full analysis on this package. - /// - /// The log. - public void Run(ILogger log) + if (Parameters.IsProcessingUPaths) { - if (log == null) throw new ArgumentNullException("log"); - - // If the package doesn't have a meta name, fix it here - if (string.IsNullOrWhiteSpace(package.Meta.Name) && package.FullPath != null) - { - package.Meta.Name = package.FullPath.GetFileNameWithoutExtension(); - package.IsDirty = true; - } + ProcessPackageUPaths(); + } - if (Parameters.IsPackageCheckDependencies) - { - CheckDependencies().CopyTo(log); - } + if (Parameters.IsProcessingAssetReferences) + { + ProcessRootAssetReferences(package.RootAssets, package, log); + } - if (Parameters.IsProcessingUPaths) - { - ProcessPackageUPaths(); - } + ProcessAssets().CopyTo(log); + } - if (Parameters.IsProcessingAssetReferences) - { - ProcessRootAssetReferences(package.RootAssets, package, log); - } + /// + /// Checks the package. + /// + /// LoggerResult. + public LoggerResult CheckDependencies() + { + var log = new LoggerResult(); - ProcessAssets().CopyTo(log); + // Can only check dependencies if we are inside a session + if (package.Session == null) + { + return log; } - /// - /// Checks the package. - /// - /// LoggerResult. - public LoggerResult CheckDependencies() + // If ProjetcPath is null, the package was not saved. + if (Parameters.ConvertUPathTo == UPathType.Relative && package.FullPath == null) { - var log = new LoggerResult(); + log.Error(package, null, AssetMessageCode.PackageFilePathNotSet); + return log; + } - // Can only check dependencies if we are inside a session - if (package.Session == null) + // TODO CSPROJ=XKPKG check deps + /* + // 1. Check all store package references + foreach (var packageDependency in package.Meta.Dependencies) + { + // Try to find the package reference + var subPackage = package.Session.Packages.Find(packageDependency); + if (subPackage == null) { - return log; + // Originally we were fixing DefaultPackage version, but it should now be handled by package upgraders + log.Error(package, null, AssetMessageCode.PackageNotFound, packageDependency); } + } - // If ProjetcPath is null, the package was not saved. - if (Parameters.ConvertUPathTo == UPathType.Relative && package.FullPath == null) + // 2. Check all local package references + foreach (var packageReference in package.LocalDependencies) + { + // Try to find the package reference + var newSubPackage = package.Session.Packages.Find(packageReference.Id); + if (newSubPackage == null) { - log.Error(package, null, AssetMessageCode.PackageFilePathNotSet); - return log; + log.Error(package, null, AssetMessageCode.PackageNotFound, packageReference.Location); + continue; } - // TODO CSPROJ=XKPKG check deps - /* - // 1. Check all store package references - foreach (var packageDependency in package.Meta.Dependencies) + if (newSubPackage.FullPath == null || newSubPackage.IsSystem) { - // Try to find the package reference - var subPackage = package.Session.Packages.Find(packageDependency); - if (subPackage == null) - { - // Originally we were fixing DefaultPackage version, but it should now be handled by package upgraders - log.Error(package, null, AssetMessageCode.PackageNotFound, packageDependency); - } + continue; } - // 2. Check all local package references - foreach (var packageReference in package.LocalDependencies) + // If package was found, check that the path is correctly setup + var pathToSubPackage = Parameters.ConvertUPathTo == UPathType.Relative ? newSubPackage.FullPath.MakeRelative(package.RootDirectory) : newSubPackage.FullPath; + if (packageReference.Location != pathToSubPackage) { - // Try to find the package reference - var newSubPackage = package.Session.Packages.Find(packageReference.Id); - if (newSubPackage == null) - { - log.Error(package, null, AssetMessageCode.PackageNotFound, packageReference.Location); - continue; - } - - if (newSubPackage.FullPath == null || newSubPackage.IsSystem) - { - continue; - } + // Modify package path to be relative if different + packageReference.Location = pathToSubPackage; - // If package was found, check that the path is correctly setup - var pathToSubPackage = Parameters.ConvertUPathTo == UPathType.Relative ? newSubPackage.FullPath.MakeRelative(package.RootDirectory) : newSubPackage.FullPath; - if (packageReference.Location != pathToSubPackage) + if (Parameters.SetDirtyFlagOnAssetWhenFixingUFile) { - // Modify package path to be relative if different - packageReference.Location = pathToSubPackage; - - if (Parameters.SetDirtyFlagOnAssetWhenFixingUFile) - { - package.IsDirty = true; - } + package.IsDirty = true; } } - */ + } + */ - // TODO: Check profiles + // TODO: Check profiles - return log; - } + return log; + } - /// - /// Processes the UPaths on package (but not on assets, use for this) - /// - public void ProcessPackageUPaths() + /// + /// Processes the UPaths on package (but not on assets, use for this) + /// + public void ProcessPackageUPaths() + { + if (package.FullPath == null) { - if (package.FullPath == null) - { - return; - } - - var packageReferenceLinks = AssetReferenceAnalysis.Visit(package); - CommonAnalysis.UpdatePaths(package, packageReferenceLinks.Where(link => link.Reference is UPath), Parameters); + return; } - /// - /// Fix and/or remove invalid RootAssets entries. - /// Note: at some point, we might want to make IReference be part of the same workflow as standard asset references. - /// - /// The root assets to check. - /// The package where to look for root reference. - /// The logger. - private void ProcessRootAssetReferences(RootAssetCollection rootAssets, Package referencedPackage, ILogger log) + var packageReferenceLinks = AssetReferenceAnalysis.Visit(package); + CommonAnalysis.UpdatePaths(package, packageReferenceLinks.Where(link => link.Reference is UPath), Parameters); + } + + /// + /// Fix and/or remove invalid RootAssets entries. + /// Note: at some point, we might want to make IReference be part of the same workflow as standard asset references. + /// + /// The root assets to check. + /// The package where to look for root reference. + /// The logger. + private void ProcessRootAssetReferences(RootAssetCollection rootAssets, Package referencedPackage, ILogger log) + { + foreach (var rootAsset in rootAssets.ToArray()) { - foreach (var rootAsset in rootAssets.ToArray()) - { - // Update Asset references (AssetReference, AssetBase, reference) - var id = rootAsset.Id; - var newItemReference = referencedPackage.FindAsset(id); + // Update Asset references (AssetReference, AssetBase, reference) + var id = rootAsset.Id; + var newItemReference = referencedPackage.FindAsset(id); - // If asset was not found by id try to find by its location - if (newItemReference == null) + // If asset was not found by id try to find by its location + if (newItemReference == null) + { + newItemReference = referencedPackage.FindAsset(rootAsset.Location); + if (newItemReference != null) { - newItemReference = referencedPackage.FindAsset(rootAsset.Location); - if (newItemReference != null) - { - // If asset was found by its location, just emit a warning - log.Warning(package, rootAsset, AssetMessageCode.AssetReferenceChanged, rootAsset, newItemReference.Id); - } + // If asset was found by its location, just emit a warning + log.Warning(package, rootAsset, AssetMessageCode.AssetReferenceChanged, rootAsset, newItemReference.Id); } + } - // If asset was not found, remove the reference - if (newItemReference == null) - { - log.Warning(package, rootAsset, AssetMessageCode.AssetForPackageNotFound, rootAsset, package.FullPath.GetFileNameWithoutExtension()); - rootAssets.Remove(rootAsset.Id); - package.IsDirty = true; - continue; - } + // If asset was not found, remove the reference + if (newItemReference == null) + { + log.Warning(package, rootAsset, AssetMessageCode.AssetForPackageNotFound, rootAsset, package.FullPath.GetFileNameWithoutExtension()); + rootAssets.Remove(rootAsset.Id); + package.IsDirty = true; + continue; + } - // Only update location that are actually different - var newLocationWithoutExtension = newItemReference.Location; - if (newLocationWithoutExtension != rootAsset.Location || newItemReference.Id != rootAsset.Id) - { - rootAssets.Remove(rootAsset.Id); - rootAssets.Add(new AssetReference(newItemReference.Id, newLocationWithoutExtension)); - package.IsDirty = true; - } + // Only update location that are actually different + var newLocationWithoutExtension = newItemReference.Location; + if (newLocationWithoutExtension != rootAsset.Location || newItemReference.Id != rootAsset.Id) + { + rootAssets.Remove(rootAsset.Id); + rootAssets.Add(new AssetReference(newItemReference.Id, newLocationWithoutExtension)); + package.IsDirty = true; } } + } - public LoggerResult ProcessAssets() - { - var log = new LoggerResult(); + public LoggerResult ProcessAssets() + { + var log = new LoggerResult(); - var assets = (package.TemporaryAssets.Count > 0 ? (IEnumerable)package.TemporaryAssets : package.Assets); + var assets = (package.TemporaryAssets.Count > 0 ? (IEnumerable)package.TemporaryAssets : package.Assets); - foreach (var assetItem in assets) - { - AssetAnalysis.Run(assetItem, log, Parameters); - } - - return log; + foreach (var assetItem in assets) + { + AssetAnalysis.Run(assetItem, log, Parameters); } + + return log; } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/PackageAnalysisParameters.cs b/sources/assets/Stride.Core.Assets/Analysis/PackageAnalysisParameters.cs index 09377c365d..e26a904923 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/PackageAnalysisParameters.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/PackageAnalysisParameters.cs @@ -1,12 +1,12 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets.Analysis + +namespace Stride.Core.Assets.Analysis; + +/// +/// Class PackageAnalysisParameters. This class cannot be inherited. +/// +public sealed class PackageAnalysisParameters : AssetAnalysisParameters { - /// - /// Class PackageAnalysisParameters. This class cannot be inherited. - /// - public sealed class PackageAnalysisParameters : AssetAnalysisParameters - { - public bool IsPackageCheckDependencies { get; set; } - } + public bool IsPackageCheckDependencies { get; set; } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/PackageSessionAnalysis.cs b/sources/assets/Stride.Core.Assets/Analysis/PackageSessionAnalysis.cs index 35dc7d871d..4db0b0e417 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/PackageSessionAnalysis.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/PackageSessionAnalysis.cs @@ -1,56 +1,54 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Diagnostics; -using Stride.Core.IO; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// A package analysis provides methods to validate the integrity of a whole package. +/// +public sealed class PackageSessionAnalysis : PackageSessionAnalysisBase { + private readonly PackageAnalysisParameters parameters; + /// - /// A package analysis provides methods to validate the integrity of a whole package. + /// Initializes a new instance of the class. /// - public sealed class PackageSessionAnalysis : PackageSessionAnalysisBase + /// The package session. + /// The parameters. + public PackageSessionAnalysis(PackageSession packageSession, PackageAnalysisParameters parameters) + : base(packageSession) { - private readonly PackageAnalysisParameters parameters; + ArgumentNullException.ThrowIfNull(parameters); + this.parameters = (PackageAnalysisParameters)parameters.Clone(); + this.parameters.IsPackageCheckDependencies = true; + } - /// - /// Initializes a new instance of the class. - /// - /// The package session. - /// The parameters. - public PackageSessionAnalysis(PackageSession packageSession, PackageAnalysisParameters parameters) - : base(packageSession) + /// + /// Gets the parameters. + /// + /// The parameters. + public PackageAnalysisParameters Parameters + { + get { - if (parameters == null) throw new ArgumentNullException("parameters"); - this.parameters = (PackageAnalysisParameters)parameters.Clone(); - this.parameters.IsPackageCheckDependencies = true; + return parameters; } + } - /// - /// Gets the parameters. - /// - /// The parameters. - public PackageAnalysisParameters Parameters - { - get - { - return parameters; - } - } + /// + /// Performs a wide package validation analysis. + /// + /// The log to output the result of the validation. + public override void Run(ILogger log) + { + ArgumentNullException.ThrowIfNull(log); - /// - /// Performs a wide package validation analysis. - /// - /// The log to output the result of the validation. - public override void Run(ILogger log) + foreach (var package in Session.LocalPackages) { - if (log == null) throw new ArgumentNullException("log"); - - foreach (var package in Session.LocalPackages) - { - var analysis = new PackageAnalysis(package, parameters); - analysis.Run(log); - } + var analysis = new PackageAnalysis(package, parameters); + analysis.Run(log); } - } + } } diff --git a/sources/assets/Stride.Core.Assets/Analysis/PackageSessionAnalysisBase.cs b/sources/assets/Stride.Core.Assets/Analysis/PackageSessionAnalysisBase.cs index cb965e5d2b..ee42de278d 100644 --- a/sources/assets/Stride.Core.Assets/Analysis/PackageSessionAnalysisBase.cs +++ b/sources/assets/Stride.Core.Assets/Analysis/PackageSessionAnalysisBase.cs @@ -1,68 +1,50 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using Stride.Core.Diagnostics; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Analysis +namespace Stride.Core.Assets.Analysis; + +/// +/// Base class for all and integrity analysis. +/// +[AssemblyScan] +public abstract class PackageSessionAnalysisBase { + /// - /// Base class for all and integrity analysis. + /// Initializes a new instance of the class. /// - [AssemblyScan] - public abstract class PackageSessionAnalysisBase + /// The package session. + /// packageSession + protected PackageSessionAnalysisBase(PackageSession packageSession) { - private PackageSession packageSession; - - /// - /// Initializes a new instance of the class. - /// - /// The package session. - /// packageSession - protected PackageSessionAnalysisBase(PackageSession packageSession) - { - if (packageSession == null) throw new ArgumentNullException("packageSession"); - this.packageSession = packageSession; - } - - protected PackageSessionAnalysisBase() - { - - } - - /// - /// Gets the session. - /// - /// The session. - public PackageSession Session - { - get - { - return packageSession; - } - set - { - packageSession = value; - } - } + ArgumentNullException.ThrowIfNull(packageSession); + Session = packageSession; + } - /// - /// Performs a wide package validation analysis. - /// - /// Result of the validation. - public LoggerResult Run() - { - if (packageSession == null) throw new InvalidOperationException("packageSession is null"); - var results = new LoggerResult(); - Run(results); - return results; - } + /// + /// Gets the session. + /// + /// The session. + public PackageSession Session { get; set; } - /// - /// Performs a wide package validation analysis. - /// - /// The log to output the result of the validation. - public abstract void Run(ILogger log); + /// + /// Performs a wide package validation analysis. + /// + /// Result of the validation. + public LoggerResult Run() + { + if (Session == null) throw new InvalidOperationException("packageSession is null"); + var results = new LoggerResult(); + Run(results); + return results; } + + /// + /// Performs a wide package validation analysis. + /// + /// The log to output the result of the validation. + public abstract void Run(ILogger log); } diff --git a/sources/assets/Stride.Core.Assets/Asset.cs b/sources/assets/Stride.Core.Assets/Asset.cs index 55df2370e3..0a143583ae 100644 --- a/sources/assets/Stride.Core.Assets/Asset.cs +++ b/sources/assets/Stride.Core.Assets/Asset.cs @@ -1,156 +1,150 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.ComponentModel; -using Stride.Core; using Stride.Core.Annotations; using Stride.Core.IO; using Stride.Core.Reflection; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Base class for Asset. +/// +[DataContract(Inherited = true)] +[AssemblyScan] +public abstract class Asset { + private AssetId id; + + // Note: Please keep this code in sync with Package class + /// + /// Locks the unique identifier for further changes. + /// + internal bool IsIdLocked; + /// - /// Base class for Asset. + /// Initializes a new instance of the class. /// - [DataContract(Inherited = true)] - [AssemblyScan] - public abstract class Asset + protected Asset() { - private AssetId id; + Id = AssetId.New(); + Tags = []; - // Note: Please keep this code in sync with Package class - /// - /// Locks the unique identifier for further changes. - /// - internal bool IsIdLocked; - - /// - /// Initializes a new instance of the class. - /// - protected Asset() + // Initializse asset with default versions (same code as in Package..ctor()) + var defaultPackageVersion = AssetRegistry.GetCurrentFormatVersions(GetType()); + if (defaultPackageVersion != null) { - Id = AssetId.New(); - Tags = new TagCollection(); - - // Initializse asset with default versions (same code as in Package..ctor()) - var defaultPackageVersion = AssetRegistry.GetCurrentFormatVersions(GetType()); - if (defaultPackageVersion != null) - { - SerializedVersion = new Dictionary(defaultPackageVersion); - } + SerializedVersion = new Dictionary(defaultPackageVersion); } + } - /// - /// Gets or sets the unique identifier of this asset. - /// - /// The identifier. - /// Cannot change an Asset Object Id once it is locked - [DataMember(-10000)] - [NonOverridable] - [Display(Browsable = false)] - public AssetId Id + /// + /// Gets or sets the unique identifier of this asset. + /// + /// The identifier. + /// Cannot change an Asset Object Id once it is locked + [DataMember(-10000)] + [NonOverridable] + [Display(Browsable = false)] + public AssetId Id + { + // Note: Please keep this code in sync with Package class + get { - // Note: Please keep this code in sync with Package class - get - { - return id; - } - set - { - if (value != id && IsIdLocked) - throw new InvalidOperationException("Cannot change an Asset Object Id once it is locked by a package"); - - id = value; - } + return id; } - - // Note: Please keep this code in sync with Package class - /// - /// Gets or sets the version number for this asset, used internally when migrating assets. - /// - /// The version. - [DataMember(-8000, DataMemberMode.Assign)] - [DataStyle(DataStyle.Compact)] - [Display(Browsable = false)] - [DefaultValue(null)] - [NonOverridable] - [NonIdentifiableCollectionItems] - public Dictionary SerializedVersion { get; set; } - - /// - /// Gets the tags for this asset. - /// - /// - /// The tags for this asset. - /// - [DataMember(-1000)] - [Display(Browsable = false)] - [NonIdentifiableCollectionItems] - [NonOverridable] - [MemberCollection(NotNullItems = true)] - public TagCollection Tags { get; private set; } - - [DataMember(-500)] - [Display(Browsable = false)] - [NonOverridable] - [DefaultValue(null)] - public AssetReference Archetype { get; set; } - - /// - /// Gets the main source file for this asset, used in the editor. - /// - [DataMemberIgnore] - public virtual UFile MainSource => null; - - /// - /// Creates an asset that inherits from this asset. - /// - /// The location of this asset. - /// An asset that inherits this asset instance - // TODO: turn internal protected and expose only AssetItem.CreateDerivedAsset() - [NotNull] - public Asset CreateDerivedAsset([NotNull] string baseLocation) + set { - Dictionary idRemapping; - return CreateDerivedAsset(baseLocation, out idRemapping); + if (value != id && IsIdLocked) + throw new InvalidOperationException("Cannot change an Asset Object Id once it is locked by a package"); + + id = value; } + } - /// - /// Creates an asset that inherits from this asset. - /// - /// The location of this asset. - /// A dictionary in which will be stored all the remapping done for the child asset. - /// An asset that inherits this asset instance - // TODO: turn internal protected and expose only AssetItem.CreateDerivedAsset() - [NotNull] - public virtual Asset CreateDerivedAsset([NotNull] string baseLocation, out Dictionary idRemapping) - { - if (baseLocation == null) throw new ArgumentNullException(nameof(baseLocation)); + // Note: Please keep this code in sync with Package class + /// + /// Gets or sets the version number for this asset, used internally when migrating assets. + /// + /// The version. + [DataMember(-8000, DataMemberMode.Assign)] + [DataStyle(DataStyle.Compact)] + [Display(Browsable = false)] + [DefaultValue(null)] + [NonOverridable] + [NonIdentifiableCollectionItems] + public Dictionary? SerializedVersion { get; set; } - // Make sure we have identifiers for all items - AssetCollectionItemIdHelper.GenerateMissingItemIds(this); + /// + /// Gets the tags for this asset. + /// + /// + /// The tags for this asset. + /// + [DataMember(-1000)] + [Display(Browsable = false)] + [NonIdentifiableCollectionItems] + [NonOverridable] + [MemberCollection(NotNullItems = true)] + public TagCollection Tags { get; private set; } + + [DataMember(-500)] + [Display(Browsable = false)] + [NonOverridable] + [DefaultValue(null)] + public AssetReference? Archetype { get; set; } - // Clone this asset without overrides (as we want all parameters to inherit from base) - var newAsset = AssetCloner.Clone(this, AssetClonerFlags.GenerateNewIdsForIdentifiableObjects, out idRemapping); + /// + /// Gets the main source file for this asset, used in the editor. + /// + [DataMemberIgnore] + public virtual UFile MainSource => null; - // Create a new identifier for this asset - var newId = AssetId.New(); + /// + /// Creates an asset that inherits from this asset. + /// + /// The location of this asset. + /// An asset that inherits this asset instance + // TODO: turn internal protected and expose only AssetItem.CreateDerivedAsset() + public Asset CreateDerivedAsset(string baseLocation) + { + return CreateDerivedAsset(baseLocation, out _); + } - // Register this new identifier in the remapping dictionary - idRemapping?.Add((Guid)newAsset.Id, (Guid)newId); - - // Write the new id into the new asset. - newAsset.Id = newId; + /// + /// Creates an asset that inherits from this asset. + /// + /// The location of this asset. + /// A dictionary in which will be stored all the remapping done for the child asset. + /// An asset that inherits this asset instance + // TODO: turn internal protected and expose only AssetItem.CreateDerivedAsset() + public virtual Asset CreateDerivedAsset(string baseLocation, out Dictionary idRemapping) + { + ArgumentNullException.ThrowIfNull(baseLocation); - // Create the base of this asset - newAsset.Archetype = new AssetReference(Id, baseLocation); - return newAsset; - } + // Make sure we have identifiers for all items + AssetCollectionItemIdHelper.GenerateMissingItemIds(this); - public override string ToString() - { - return $"{GetType().Name}: {Id}"; - } + // Clone this asset without overrides (as we want all parameters to inherit from base) + var newAsset = AssetCloner.Clone(this, AssetClonerFlags.GenerateNewIdsForIdentifiableObjects, out idRemapping); + + // Create a new identifier for this asset + var newId = AssetId.New(); + + // Register this new identifier in the remapping dictionary + idRemapping?.Add((Guid)newAsset.Id, (Guid)newId); + + // Write the new id into the new asset. + newAsset.Id = newId; + + // Create the base of this asset + newAsset.Archetype = new AssetReference(Id, baseLocation); + return newAsset; + } + + public override string ToString() + { + return $"{GetType().Name}: {Id}"; } } diff --git a/sources/assets/Stride.Core.Assets/AssetAliasAttribute.cs b/sources/assets/Stride.Core.Assets/AssetAliasAttribute.cs index 5278988975..51e19f5649 100644 --- a/sources/assets/Stride.Core.Assets/AssetAliasAttribute.cs +++ b/sources/assets/Stride.Core.Assets/AssetAliasAttribute.cs @@ -1,35 +1,26 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Associates a type name used in YAML content. +/// +public class AssetAliasAttribute : Attribute { + /// - /// Associates a type name used in YAML content. + /// Initializes a new instance of the class. /// - public class AssetAliasAttribute : Attribute + /// The type name. + public AssetAliasAttribute(string @alias) { - private readonly string alias; - - /// - /// Initializes a new instance of the class. - /// - /// The type name. - public AssetAliasAttribute(string @alias) - { - this.alias = alias; - } - - /// - /// Gets the type name. - /// - /// The type name. - public string Alias - { - get - { - return alias; - } - } + Alias = alias; } + + /// + /// Gets the type name. + /// + /// The type name. + public string Alias { get; } } diff --git a/sources/assets/Stride.Core.Assets/AssetCloner.cs b/sources/assets/Stride.Core.Assets/AssetCloner.cs index 51be6d2af3..ad6ffc68bb 100644 --- a/sources/assets/Stride.Core.Assets/AssetCloner.cs +++ b/sources/assets/Stride.Core.Assets/AssetCloner.cs @@ -1,378 +1,374 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.Reflection; using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; using Stride.Core.Storage; using Stride.Core.Yaml; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Allows to clone an asset or values stored in an asset. +/// +public class AssetCloner { + private readonly AssetClonerFlags flags; + private readonly object streamOrValueType; + + private readonly List invariantObjects; + private readonly object[]? objectReferences; + private readonly Dictionary externalIdentifiables; + private readonly Dictionary clonedObjectMapping; + private readonly Dictionary cloningIdRemapping; + public static SerializerSelector ClonerSelector { get; internal set; } + public static PropertyKey> InvariantObjectListProperty = new("InvariantObjectList", typeof(AssetCloner)); + private readonly List cloneReferences; + + static AssetCloner() + { + ClonerSelector = new SerializerSelector(true, true, "Default", "Content", "AssetClone"); + ClonerSelector.SerializerFactories.Add(new GenericSerializerFactory(typeof(IUnloadable), typeof(UnloadableCloneSerializer<>))); + } + /// - /// Allows to clone an asset or values stored in an asset. + /// Initializes a new instance of the struct. /// - public class AssetCloner + /// The value to clone. + /// Cloning flags + /// + private AssetCloner(object value, AssetClonerFlags flags, IEnumerable? externalIdentifiables) { - private readonly AssetClonerFlags flags; - private readonly object streamOrValueType; - - private readonly List invariantObjects; - private readonly object[] objectReferences; - private readonly Dictionary externalIdentifiables; - private readonly Dictionary clonedObjectMapping; - private Dictionary cloningIdRemapping; - public static SerializerSelector ClonerSelector { get; internal set; } - public static PropertyKey> InvariantObjectListProperty = new PropertyKey>("InvariantObjectList", typeof(AssetCloner)); - private List cloneReferences; - - static AssetCloner() - { - ClonerSelector = new SerializerSelector(true, true, "Default", "Content", "AssetClone"); - ClonerSelector.SerializerFactories.Add(new GenericSerializerFactory(typeof(IUnloadable), typeof(UnloadableCloneSerializer<>))); - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value to clone. - /// Cloning flags - /// - private AssetCloner(object value, AssetClonerFlags flags, IEnumerable externalIdentifiables) + this.flags = flags; + invariantObjects = null; + objectReferences = null; + clonedObjectMapping = []; + cloningIdRemapping = []; + // Clone only if value is not a value type + if (!value.GetType().IsValueType) { - this.flags = flags; - invariantObjects = null; - objectReferences = null; - clonedObjectMapping = new Dictionary(); - cloningIdRemapping = new Dictionary(); - // Clone only if value is not a value type - if (value != null && !value.GetType().IsValueType) - { - invariantObjects = new List(); + invariantObjects = []; - // TODO: keepOnlySealedOverride is currently ignored - // TODO Clone is not supporting SourceCodeAsset (The SourceCodeAsset.Text won't be cloned) + // TODO: keepOnlySealedOverride is currently ignored + // TODO Clone is not supporting SourceCodeAsset (The SourceCodeAsset.Text won't be cloned) - var stream = new MemoryStream(); - var writer = new BinarySerializationWriter(stream); - writer.Context.SerializerSelector = ClonerSelector; - writer.Context.Set(InvariantObjectListProperty, invariantObjects); - writer.Context.Set(ContentSerializerContext.SerializeAttachedReferenceProperty, GenerateContentSerializerFlags(flags)); - if (externalIdentifiables != null) + var stream = new MemoryStream(); + var writer = new BinarySerializationWriter(stream); + writer.Context.SerializerSelector = ClonerSelector; + writer.Context.Set(InvariantObjectListProperty, invariantObjects); + writer.Context.Set(ContentSerializerContext.SerializeAttachedReferenceProperty, GenerateContentSerializerFlags(flags)); + if (externalIdentifiables != null) + { + this.externalIdentifiables = []; + foreach (var externalIdentifiable in externalIdentifiables) { - this.externalIdentifiables = new Dictionary(); - foreach (var externalIdentifiable in externalIdentifiables) - { - // Note: here we might have duplicate (possibly broken) references that point to the same object (same id). We can only keep one in the dictionary. - this.externalIdentifiables[externalIdentifiable.Id] = externalIdentifiable; - } - writer.Context.Set(MemberSerializer.ExternalIdentifiables, this.externalIdentifiables); + // Note: here we might have duplicate (possibly broken) references that point to the same object (same id). We can only keep one in the dictionary. + this.externalIdentifiables[externalIdentifiable.Id] = externalIdentifiable; } - writer.SerializeExtended(value, ArchiveMode.Serialize); - writer.Flush(); + writer.Context.Set(MemberSerializer.ExternalIdentifiables, this.externalIdentifiables); + } + writer.SerializeExtended(value, ArchiveMode.Serialize); + writer.Flush(); - if ((flags & AssetClonerFlags.KeepReferences) != 0) - cloneReferences = writer.Context.Get(ReferenceSerializer.CloneReferences); + if ((flags & AssetClonerFlags.KeepReferences) != 0) + cloneReferences = writer.Context.Get(ReferenceSerializer.CloneReferences); - // Retrieve back all object references that were discovered while serializing - // They will be used layer by OnObjectDeserialized when cloning ShadowObject datas - var objectRefs = writer.Context.Get(MemberSerializer.ObjectSerializeReferences); - if (objectRefs != null) + // Retrieve back all object references that were discovered while serializing + // They will be used layer by OnObjectDeserialized when cloning ShadowObject datas + var objectRefs = writer.Context.Get(MemberSerializer.ObjectSerializeReferences); + if (objectRefs != null) + { + // Remap object references to a simple array + objectReferences = new object[objectRefs.Count]; + foreach (var objRef in objectRefs) { - // Remap object references to a simple array - objectReferences = new object[objectRefs.Count]; - foreach (var objRef in objectRefs) - { - objectReferences[objRef.Value] = objRef.Key; - } + objectReferences[objRef.Value] = objRef.Key; } - - streamOrValueType = stream; - } - else - { - streamOrValueType = value; } + + streamOrValueType = stream; + } + else + { + streamOrValueType = value; } + } - /// - /// Clones the current value of this cloner with the specified new shadow registry (optional) - /// - /// A dictionary containing the remapping of if has been passed to the cloner. - /// A clone of the value associated with this cloner. - private object Clone([NotNull] out Dictionary idRemapping) + /// + /// Clones the current value of this cloner with the specified new shadow registry (optional) + /// + /// A dictionary containing the remapping of if has been passed to the cloner. + /// A clone of the value associated with this cloner. + private object Clone(out Dictionary idRemapping) + { + if (streamOrValueType is Stream stream) { - if (streamOrValueType is Stream stream) + stream.Position = 0; + var reader = new BinarySerializationReader(stream); + reader.Context.SerializerSelector = ClonerSelector; + reader.Context.Set(InvariantObjectListProperty, invariantObjects); + reader.Context.Set(ContentSerializerContext.SerializeAttachedReferenceProperty, GenerateContentSerializerFlags(flags)); + if (externalIdentifiables != null) { - stream.Position = 0; - var reader = new BinarySerializationReader(stream); - reader.Context.SerializerSelector = ClonerSelector; - reader.Context.Set(InvariantObjectListProperty, invariantObjects); - reader.Context.Set(ContentSerializerContext.SerializeAttachedReferenceProperty, GenerateContentSerializerFlags(flags)); - if (externalIdentifiables != null) - { - if ((flags & AssetClonerFlags.ClearExternalReferences) != 0) - externalIdentifiables.Clear(); - - reader.Context.Set(MemberSerializer.ExternalIdentifiables, externalIdentifiables); - } - if ((flags & AssetClonerFlags.KeepReferences) != 0) - reader.Context.Set(ReferenceSerializer.CloneReferences, cloneReferences); - reader.Context.Set(MemberSerializer.ObjectDeserializeCallback, OnObjectDeserialized); - object newObject = null; - reader.SerializeExtended(ref newObject, ArchiveMode.Deserialize); + if ((flags & AssetClonerFlags.ClearExternalReferences) != 0) + externalIdentifiables.Clear(); - if ((flags & AssetClonerFlags.RemoveUnloadableObjects) != 0) - { - UnloadableObjectRemover.Run(newObject); - } + reader.Context.Set(MemberSerializer.ExternalIdentifiables, externalIdentifiables); + } + if ((flags & AssetClonerFlags.KeepReferences) != 0) + reader.Context.Set(ReferenceSerializer.CloneReferences, cloneReferences); + reader.Context.Set(MemberSerializer.ObjectDeserializeCallback, OnObjectDeserialized); + object newObject = null!; + reader.SerializeExtended(ref newObject, ArchiveMode.Deserialize); - idRemapping = cloningIdRemapping; - return newObject; + if ((flags & AssetClonerFlags.RemoveUnloadableObjects) != 0) + { + UnloadableObjectRemover.Run(newObject); } - // Else this is a value type, so it is cloned automatically - idRemapping = new Dictionary(); - return streamOrValueType; + + idRemapping = cloningIdRemapping; + return newObject; } + // Else this is a value type, so it is cloned automatically + idRemapping = []; + return streamOrValueType; + } - private ObjectId GetHashId() + private ObjectId GetHashId() + { + // This methods use the stream that is already filled-up by the standard binary serialization of the object + // Here we add ids and overrides metadata informations to the stream in order to calculate an accurate id + if (streamOrValueType is MemoryStream stream) { - // This methods use the stream that is already filled-up by the standard binary serialization of the object - // Here we add ids and overrides metadata informations to the stream in order to calculate an accurate id - var stream = streamOrValueType as MemoryStream; - if (stream != null) + // ------------------------------------------------------ + // Un-comment the following code to debug the ObjectId of the serialized version without taking into account overrides + // ------------------------------------------------------ + //var savedPosition = stream.Position; + //stream.Position = 0; + //var intermediateHashId = ObjectId.FromBytes(stream.ToArray()); + //stream.Position = savedPosition; + + var writer = new BinarySerializationWriter(stream); + + // Write invariant objects + foreach (var invarialtObject in invariantObjects) { - // ------------------------------------------------------ - // Un-comment the following code to debug the ObjectId of the serialized version without taking into account overrides - // ------------------------------------------------------ - //var savedPosition = stream.Position; - //stream.Position = 0; - //var intermediateHashId = ObjectId.FromBytes(stream.ToArray()); - //stream.Position = savedPosition; - - var writer = new BinarySerializationWriter(stream); - - // Write invariant objects - foreach (var invarialtObject in invariantObjects) - { - writer.SerializeExtended(invarialtObject, ArchiveMode.Serialize); - } - - writer.Flush(); - stream.Position = 0; - - return ObjectId.FromBytes(stream.ToArray()); + writer.SerializeExtended(invarialtObject, ArchiveMode.Serialize); } - return ObjectId.Empty; + writer.Flush(); + stream.Position = 0; + + return ObjectId.FromBytes(stream.ToArray()); } - private void OnObjectDeserialized(int i, object newObject) + return ObjectId.Empty; + } + + private void OnObjectDeserialized(int i, object newObject) + { + if (objectReferences != null && newObject != null) { - if (objectReferences != null && newObject != null) - { - var previousObject = objectReferences[i]; + var previousObject = objectReferences[i]; - //// If the object is an attached reference, there is no need to copy the shadow object - //if (AttachedReferenceManager.GetAttachedReference(previousObject) != null) - //{ - // return; - //} + //// If the object is an attached reference, there is no need to copy the shadow object + //if (AttachedReferenceManager.GetAttachedReference(previousObject) != null) + //{ + // return; + //} - ShadowObject.Copy(previousObject, newObject); + ShadowObject.Copy(previousObject, newObject); - // NOTE: we don't use Add because of strings that might be duplicated - clonedObjectMapping[previousObject] = newObject; + // NOTE: we don't use Add because of strings that might be duplicated + clonedObjectMapping[previousObject] = newObject; - if ((flags & AssetClonerFlags.RemoveItemIds) != AssetClonerFlags.RemoveItemIds) + if ((flags & AssetClonerFlags.RemoveItemIds) != AssetClonerFlags.RemoveItemIds) + { + if (CollectionItemIdHelper.TryGetCollectionItemIds(previousObject, out var sourceIds)) { - CollectionItemIdentifiers sourceIds; - if (CollectionItemIdHelper.TryGetCollectionItemIds(previousObject, out sourceIds)) - { - var newIds = CollectionItemIdHelper.GetCollectionItemIds(newObject); - sourceIds.CloneInto(newIds, clonedObjectMapping); - } + var newIds = CollectionItemIdHelper.GetCollectionItemIds(newObject); + sourceIds.CloneInto(newIds, clonedObjectMapping); } + } - if ((flags & AssetClonerFlags.GenerateNewIdsForIdentifiableObjects) == AssetClonerFlags.GenerateNewIdsForIdentifiableObjects) + if ((flags & AssetClonerFlags.GenerateNewIdsForIdentifiableObjects) == AssetClonerFlags.GenerateNewIdsForIdentifiableObjects) + { + if (newObject is IIdentifiable identifiable) { - var identifiable = newObject as IIdentifiable; - if (identifiable != null) - { - var newId = Guid.NewGuid(); - cloningIdRemapping[identifiable.Id] = newId; - identifiable.Id = newId; - } + var newId = Guid.NewGuid(); + cloningIdRemapping[identifiable.Id] = newId; + identifiable.Id = newId; } } } + } - /// - /// Clones the specified asset using asset serialization. - /// - /// The asset. - /// Flags used to control the cloning process - /// - /// A dictionary containing the remapping of if has been passed to the cloner. - /// A clone of the asset. - public static object Clone(object asset, AssetClonerFlags flags, HashSet externalIdentifiable, [NotNull] out Dictionary idRemapping) + /// + /// Clones the specified asset using asset serialization. + /// + /// The asset. + /// Flags used to control the cloning process + /// + /// A dictionary containing the remapping of if has been passed to the cloner. + /// A clone of the asset. + [return: NotNullIfNotNull(nameof(asset))] + public static object? Clone(object? asset, AssetClonerFlags flags, HashSet? externalIdentifiable, out Dictionary idRemapping) + { + if (asset == null) { - if (asset == null) - { - idRemapping = new Dictionary(); - return null; - } - var cloner = new AssetCloner(asset, flags, externalIdentifiable); - var newObject = cloner.Clone(out idRemapping); - return newObject; + idRemapping = []; + return null; } + var cloner = new AssetCloner(asset, flags, externalIdentifiable); + var newObject = cloner.Clone(out idRemapping); + return newObject; + } - /// - /// Clones the specified asset using asset serialization. - /// - /// The asset. - /// Flags used to control the cloning process - /// A dictionary containing the remapping of if has been passed to the cloner. - /// A clone of the asset. - public static object Clone(object asset, AssetClonerFlags flags, [NotNull] out Dictionary idRemapping) - { - return Clone(asset, flags, null, out idRemapping); - } + /// + /// Clones the specified asset using asset serialization. + /// + /// The asset. + /// Flags used to control the cloning process + /// A dictionary containing the remapping of if has been passed to the cloner. + /// A clone of the asset. + [return: NotNullIfNotNull(nameof(asset))] + public static object? Clone(object? asset, AssetClonerFlags flags, out Dictionary idRemapping) + { + return Clone(asset, flags, null, out idRemapping); + } - /// - /// Clones the specified asset using asset serialization. - /// - /// The asset. - /// Flags used to control the cloning process - /// A clone of the asset. - public static object Clone(object asset, AssetClonerFlags flags = AssetClonerFlags.None) - { - return Clone(asset, flags, out Dictionary _); - } + /// + /// Clones the specified asset using asset serialization. + /// + /// The asset. + /// Flags used to control the cloning process + /// A clone of the asset. + [return: NotNullIfNotNull(nameof(asset))] + public static object? Clone(object? asset, AssetClonerFlags flags = AssetClonerFlags.None) + { + return Clone(asset, flags, out Dictionary _); + } - /// - /// Clones the specified asset using asset serialization. - /// - /// The type of the asset. - /// The asset. - /// Flags used to control the cloning process - /// A clone of the asset. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Clone(T asset, AssetClonerFlags flags = AssetClonerFlags.None) - { - return Clone(asset, flags, out Dictionary _); - } + /// + /// Clones the specified asset using asset serialization. + /// + /// The type of the asset. + /// The asset. + /// Flags used to control the cloning process + /// A clone of the asset. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [return: NotNullIfNotNull(nameof(asset))] + public static T? Clone(T? asset, AssetClonerFlags flags = AssetClonerFlags.None) + { + return Clone(asset, flags, out Dictionary _); + } - /// - /// Clones the specified asset using asset serialization. - /// - /// The type of the asset. - /// The asset. - /// Flags used to control the cloning process - /// A dictionary containing the remapping of if has been passed to the cloner. - /// A clone of the asset. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Clone(T asset, AssetClonerFlags flags, [NotNull] out Dictionary idRemapping) - { - return (T)Clone((object)asset, flags, out idRemapping); - } + /// + /// Clones the specified asset using asset serialization. + /// + /// The type of the asset. + /// The asset. + /// Flags used to control the cloning process + /// A dictionary containing the remapping of if has been passed to the cloner. + /// A clone of the asset. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [return: NotNullIfNotNull(nameof(asset))] + public static T? Clone(T? asset, AssetClonerFlags flags, out Dictionary idRemapping) + { + return (T?)Clone((object?)asset, flags, out idRemapping); + } + + /// + /// Clones the specified asset using asset serialization. + /// + /// The type of the asset. + /// The asset. + /// Flags used to control the cloning process + /// + /// A dictionary containing the remapping of if has been passed to the cloner. + /// A clone of the asset. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [return: NotNullIfNotNull(nameof(asset))] + public static T? Clone(T? asset, AssetClonerFlags flags, HashSet externalIdentifiable, out Dictionary idRemapping) + { + return (T?)Clone((object?)asset, flags, externalIdentifiable, out idRemapping); + } - /// - /// Clones the specified asset using asset serialization. - /// - /// The type of the asset. - /// The asset. - /// Flags used to control the cloning process - /// - /// A dictionary containing the remapping of if has been passed to the cloner. - /// A clone of the asset. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Clone(T asset, AssetClonerFlags flags, HashSet externalIdentifiable, [NotNull] out Dictionary idRemapping) + /// + /// Generates a runtime hash id from the serialization of this asset. + /// + /// The asset to get the runtime hash id + /// Flags used to control the serialization process + /// An object id + internal static ObjectId ComputeHash(object asset, AssetClonerFlags flags = AssetClonerFlags.None) + { + if (asset == null) { - return (T)Clone((object)asset, flags, externalIdentifiable, out idRemapping); + return ObjectId.Empty; } - /// - /// Generates a runtime hash id from the serialization of this asset. - /// - /// The asset to get the runtime hash id - /// Flags used to control the serialization process - /// An object id - internal static ObjectId ComputeHash(object asset, AssetClonerFlags flags = AssetClonerFlags.None) - { - if (asset == null) - { - return ObjectId.Empty; - } + var cloner = new AssetCloner(asset, flags, null); + var result = cloner.GetHashId(); + return result; + } - var cloner = new AssetCloner(asset, flags, null); - var result = cloner.GetHashId(); - return result; - } + private static ContentSerializerContext.AttachedReferenceSerialization GenerateContentSerializerFlags(AssetClonerFlags flags) + { + ContentSerializerContext.AttachedReferenceSerialization refFlag; + if ((flags & AssetClonerFlags.ReferenceAsNull) != 0) + refFlag = ContentSerializerContext.AttachedReferenceSerialization.AsNull; + else if ((flags & AssetClonerFlags.KeepReferences) != 0) + refFlag = ContentSerializerContext.AttachedReferenceSerialization.Clone; + else + refFlag = ContentSerializerContext.AttachedReferenceSerialization.AsSerializableVersion; + return refFlag; + } - private static ContentSerializerContext.AttachedReferenceSerialization GenerateContentSerializerFlags(AssetClonerFlags flags) + private class UnloadableCloneSerializer : DataSerializer where T : class, IUnloadable + { + private DataSerializer? parentSerializer; + + public override void Initialize(SerializerSelector serializerSelector) { - ContentSerializerContext.AttachedReferenceSerialization refFlag; - if ((flags & AssetClonerFlags.ReferenceAsNull) != 0) - refFlag = ContentSerializerContext.AttachedReferenceSerialization.AsNull; - else if ((flags & AssetClonerFlags.KeepReferences) != 0) - refFlag = ContentSerializerContext.AttachedReferenceSerialization.Clone; - else - refFlag = ContentSerializerContext.AttachedReferenceSerialization.AsSerializableVersion; - return refFlag; + parentSerializer = serializerSelector.GetSerializer(typeof(T).BaseType!); } - private class UnloadableCloneSerializer : DataSerializer where T : class, IUnloadable + public override void PreSerialize(ref T obj, ArchiveMode mode, SerializationStream stream) { - private DataSerializer parentSerializer; - - public override void Initialize(SerializerSelector serializerSelector) + var invariantObjectList = stream.Context.Get(InvariantObjectListProperty)!; + if (mode == ArchiveMode.Serialize) { - parentSerializer = serializerSelector.GetSerializer(typeof(T).BaseType); + stream.Write(invariantObjectList.Count); + invariantObjectList.Add(obj); } - - public override void PreSerialize(ref T obj, ArchiveMode mode, SerializationStream stream) + else { - var invariantObjectList = stream.Context.Get(InvariantObjectListProperty); - if (mode == ArchiveMode.Serialize) + var index = stream.Read(); + + if (index >= invariantObjectList.Count) { - stream.Write(invariantObjectList.Count); - invariantObjectList.Add(obj); + throw new InvalidOperationException($"The type [{typeof(T).FullName}] cannot be only be used for clone serialization"); } - else - { - var index = stream.Read(); - - if (index >= invariantObjectList.Count) - { - throw new InvalidOperationException($"The type [{typeof(T).FullName}] cannot be only be used for clone serialization"); - } - - var invariant = invariantObjectList[index] as T; - if (invariant == null) - { - throw new InvalidOperationException($"Unexpected null {typeof(T).FullName} while cloning"); - } - // Create a new object to avoid exception in case its identity is important - obj = (T)Activator.CreateInstance(typeof(T), invariant.TypeName, invariant.AssemblyName, invariant.Error, invariant.ParsingEvents); + if (invariantObjectList[index] is not T invariant) + { + throw new InvalidOperationException($"Unexpected null {typeof(T).FullName} while cloning"); } - } - public override void Serialize(ref T obj, ArchiveMode mode, SerializationStream stream) - { - // Process with parent serializer first - object parentObj = obj; - parentSerializer?.Serialize(ref parentObj, mode, stream); - obj = (T)parentObj; + // Create a new object to avoid exception in case its identity is important + obj = (T)Activator.CreateInstance(typeof(T), invariant.TypeName, invariant.AssemblyName, invariant.Error, invariant.ParsingEvents)!; } } + + public override void Serialize(ref T obj, ArchiveMode mode, SerializationStream stream) + { + // Process with parent serializer first + object parentObj = obj; + parentSerializer?.Serialize(ref parentObj, mode, stream); + obj = (T)parentObj; + } } } diff --git a/sources/assets/Stride.Core.Assets/AssetClonerFlags.cs b/sources/assets/Stride.Core.Assets/AssetClonerFlags.cs index f353a288c5..f00fe93953 100644 --- a/sources/assets/Stride.Core.Assets/AssetClonerFlags.cs +++ b/sources/assets/Stride.Core.Assets/AssetClonerFlags.cs @@ -1,49 +1,46 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Flags used by +/// +[Flags] +public enum AssetClonerFlags { /// - /// Flags used by + /// No special flags while cloning. + /// + None, + + /// + /// Attached references will be cloned as null + /// + ReferenceAsNull = 1, + + /// + /// Remove ids attached to item of collections when cloning + /// + RemoveItemIds = 2, + + /// + /// Removes invalid objects + /// + RemoveUnloadableObjects = 4, + + /// + /// Generates new ids for objects that implement . + /// + GenerateNewIdsForIdentifiableObjects = 8, + + /// + /// Clears any external references in the cloned object + /// + ClearExternalReferences = 16, + + /// + /// Attached references will be kept as is /// - [Flags] - public enum AssetClonerFlags - { - /// - /// No special flags while cloning. - /// - None, - - /// - /// Attached references will be cloned as null - /// - ReferenceAsNull = 1, - - /// - /// Remove ids attached to item of collections when cloning - /// - RemoveItemIds = 2, - - /// - /// Removes invalid objects - /// - RemoveUnloadableObjects = 4, - - /// - /// Generates new ids for objects that implement . - /// - GenerateNewIdsForIdentifiableObjects = 8, - - /// - /// Clears any external references in the cloned object - /// - ClearExternalReferences = 16, - - /// - /// Attached references will be kept as is - /// - KeepReferences = 32, - } + KeepReferences = 32, } diff --git a/sources/assets/Stride.Core.Assets/AssetCollectionItemIdHelper.cs b/sources/assets/Stride.Core.Assets/AssetCollectionItemIdHelper.cs index 299a230d96..f01b2d6672 100644 --- a/sources/assets/Stride.Core.Assets/AssetCollectionItemIdHelper.cs +++ b/sources/assets/Stride.Core.Assets/AssetCollectionItemIdHelper.cs @@ -1,16 +1,14 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Reflection; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +// TODO: at some point we should converge to a state where collection ids, which are for override and asset specific, should move from Core.Design to this assembly (with related yaml serializer). Meanwhile, we have to split some of the logic in an unclean manner. +public static class AssetCollectionItemIdHelper { - // TODO: at some point we should converge to a state where collection ids, which are for override and asset specific, should move from Core.Design to this assembly (with related yaml serializer). Meanwhile, we have to split some of the logic in an unclean manner. - public static class AssetCollectionItemIdHelper + public static void GenerateMissingItemIds(object? rootObject) { - public static void GenerateMissingItemIds(object rootObject) - { - var visitor = new CollectionIdGenerator(); - visitor.Visit(rootObject); - } + var visitor = new CollectionIdGenerator(); + visitor.Visit(rootObject); } } diff --git a/sources/assets/Stride.Core.Assets/AssetComposite.cs b/sources/assets/Stride.Core.Assets/AssetComposite.cs index dd17ca0344..9de6aa7de0 100644 --- a/sources/assets/Stride.Core.Assets/AssetComposite.cs +++ b/sources/assets/Stride.Core.Assets/AssetComposite.cs @@ -1,24 +1,17 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Core; -using Stride.Core.Yaml.Serialization; +namespace Stride.Core.Assets; -namespace Stride.Core.Assets +/// +/// Base class for an asset that supports inheritance by composition. +/// +public abstract class AssetComposite : Asset, IAssetComposite { - /// - /// Base class for an asset that supports inheritance by composition. - /// - public abstract class AssetComposite : Asset, IAssetComposite - { - [Obsolete("The AssetPart struct might be removed soon")] - public abstract IEnumerable CollectParts(); + [Obsolete("The AssetPart struct might be removed soon")] + public abstract IEnumerable CollectParts(); - public abstract IIdentifiable FindPart(Guid partId); + public abstract IIdentifiable? FindPart(Guid partId); - public abstract bool ContainsPart(Guid id); - } + public abstract bool ContainsPart(Guid id); } diff --git a/sources/assets/Stride.Core.Assets/AssetCompositeHierarchy.cs b/sources/assets/Stride.Core.Assets/AssetCompositeHierarchy.cs index 9b98bce9e5..da8eca7026 100644 --- a/sources/assets/Stride.Core.Assets/AssetCompositeHierarchy.cs +++ b/sources/assets/Stride.Core.Assets/AssetCompositeHierarchy.cs @@ -1,125 +1,116 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using System.Diagnostics.Contracts; -using Stride.Core; -using Stride.Core.Annotations; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public abstract partial class AssetCompositeHierarchy : AssetComposite + where TAssetPartDesign : class, IAssetPartDesign + where TAssetPart : class, IIdentifiable { - public abstract partial class AssetCompositeHierarchy : AssetComposite - where TAssetPartDesign : class, IAssetPartDesign - where TAssetPart : class, IIdentifiable + /// + /// Gets or sets the container of the hierarchy of asset parts. + /// + [DataMember(100)] + [Display(Browsable = false)] + public AssetCompositeHierarchyData Hierarchy { get; set; } = new AssetCompositeHierarchyData(); + + /// + /// Gets the parent of the given part. + /// + /// + /// The part that is the parent of the given part, or null if the given part is at the root level. + /// Implementations of this method should not rely on the property to determine the parent. + /// The given part is null. + public abstract TAssetPart? GetParent(TAssetPart part); + + /// + /// Gets the index of the given part in the child list of its parent, or in the list of root if this part is a root part. + /// + /// The part for which to retrieve the index. + /// The index of the part, or a negative value if the part is an orphan part that is not a member of this asset. + /// The given part is null. + [Pure] + public abstract int IndexOf(TAssetPart part); + + /// + /// Gets the child of the given part that matches the given index. + /// + /// The part for which to retrieve a child. + /// The index of the child to retrieve. + /// The the child of the given part that matches the given index. + /// The given part is null. + /// The given index is out of range. + [Pure] + public abstract TAssetPart GetChild(TAssetPart part, int index); + + /// + /// Gets the number of children in the given part. + /// + /// The part for which to retrieve the number of children. + /// The number of children in the given part. + /// The given part is null. + [Pure] + public abstract int GetChildCount(TAssetPart part); + + /// + /// Enumerates parts that are children of the given part. + /// + /// The part for which to enumerate child parts. + /// If true, child parts will be enumerated recursively. + /// A sequence containing the child parts of the given part. + /// Implementations of this method should not rely on the property to enumerate. + [Pure] + public abstract IEnumerable EnumerateChildParts(TAssetPart part, bool isRecursive); + + /// + /// Enumerates design parts that are children of the given design part. + /// + /// The design part for which to enumerate child parts. + /// The hierarchy data object in which the design parts can be retrieved. + /// If true, child design parts will be enumerated recursively. + /// A sequence containing the child design parts of the given design part. + [Pure] + public IEnumerable EnumerateChildPartDesigns(TAssetPartDesign partDesign, AssetCompositeHierarchyData hierarchyData, bool isRecursive) { - /// - /// Gets or sets the container of the hierarchy of asset parts. - /// - [DataMember(100)] - [NotNull] - [Display(Browsable = false)] - public AssetCompositeHierarchyData Hierarchy { get; set; } = new AssetCompositeHierarchyData(); - - /// - /// Gets the parent of the given part. - /// - /// - /// The part that is the parent of the given part, or null if the given part is at the root level. - /// Implementations of this method should not rely on the property to determine the parent. - /// The given part is null. - [CanBeNull] - public abstract TAssetPart GetParent([NotNull] TAssetPart part); - - /// - /// Gets the index of the given part in the child list of its parent, or in the list of root if this part is a root part. - /// - /// The part for which to retrieve the index. - /// The index of the part, or a negative value if the part is an orphan part that is not a member of this asset. - /// The given part is null. - [Pure] - public abstract int IndexOf([NotNull] TAssetPart part); - - /// - /// Gets the child of the given part that matches the given index. - /// - /// The part for which to retrieve a child. - /// The index of the child to retrieve. - /// The the child of the given part that matches the given index. - /// The given part is null. - /// The given index is out of range. - [Pure] - public abstract TAssetPart GetChild([NotNull] TAssetPart part, int index); - - /// - /// Gets the number of children in the given part. - /// - /// The part for which to retrieve the number of children. - /// The number of children in the given part. - /// The given part is null. - [Pure] - public abstract int GetChildCount([NotNull] TAssetPart part); - - /// - /// Enumerates parts that are children of the given part. - /// - /// The part for which to enumerate child parts. - /// If true, child parts will be enumerated recursively. - /// A sequence containing the child parts of the given part. - /// Implementations of this method should not rely on the property to enumerate. - [NotNull, Pure] - public abstract IEnumerable EnumerateChildParts([NotNull] TAssetPart part, bool isRecursive); - - /// - /// Enumerates design parts that are children of the given design part. - /// - /// The design part for which to enumerate child parts. - /// The hierarchy data object in which the design parts can be retrieved. - /// If true, child design parts will be enumerated recursively. - /// A sequence containing the child design parts of the given design part. - [NotNull, Pure] - public IEnumerable EnumerateChildPartDesigns([NotNull] TAssetPartDesign partDesign, AssetCompositeHierarchyData hierarchyData, bool isRecursive) - { - return EnumerateChildParts(partDesign.Part, isRecursive).Select(e => hierarchyData.Parts[e.Id]); - } + return EnumerateChildParts(partDesign.Part, isRecursive).Select(e => hierarchyData.Parts[e.Id]); + } - /// - [NotNull] - [Obsolete("The AssetPart struct might be removed soon")] - public override IEnumerable CollectParts() - { - return Hierarchy.Parts.Values.Select(x => new AssetPart(x.Part.Id, x.Base, newBase => x.Base = newBase)); - } + /// + [Obsolete("The AssetPart struct might be removed soon")] + public override IEnumerable CollectParts() + { + return Hierarchy.Parts.Values.Select(x => new AssetPart(x.Part.Id, x.Base, newBase => x.Base = newBase)); + } - /// - [CanBeNull] - public override IIdentifiable FindPart(Guid partId) - { - return Hierarchy.Parts.Values.FirstOrDefault(x => x.Part.Id == partId)?.Part; - } + /// + public override IIdentifiable? FindPart(Guid partId) + { + return Hierarchy.Parts.Values.FirstOrDefault(x => x.Part.Id == partId)?.Part; + } - /// - public override bool ContainsPart(Guid id) - { - return Hierarchy.Parts.ContainsKey(id); - } + /// + public override bool ContainsPart(Guid id) + { + return Hierarchy.Parts.ContainsKey(id); + } - /// - public override Asset CreateDerivedAsset(string baseLocation, out Dictionary idRemapping) + /// + public override Asset CreateDerivedAsset(string baseLocation, out Dictionary idRemapping) + { + var newAsset = (AssetCompositeHierarchy)base.CreateDerivedAsset(baseLocation, out idRemapping); + // Part ids have changed during the clone, we have to make sure the collection of part is properly refreshed. + newAsset.Hierarchy.Parts.RefreshKeys(); + + var instanceId = Guid.NewGuid(); + foreach (var part in Hierarchy.Parts.Values) { - var newAsset = (AssetCompositeHierarchy)base.CreateDerivedAsset(baseLocation, out idRemapping); - // Part ids have changed during the clone, we have to make sure the collection of part is properly refreshed. - newAsset.Hierarchy.Parts.RefreshKeys(); - - var instanceId = Guid.NewGuid(); - foreach (var part in Hierarchy.Parts.Values) - { - var newPart = newAsset.Hierarchy.Parts[idRemapping[part.Part.Id]]; - newPart.Base = new BasePart(new AssetReference(Id, baseLocation), part.Part.Id, instanceId); - } - - return newAsset; + var newPart = newAsset.Hierarchy.Parts[idRemapping[part.Part.Id]]; + newPart.Base = new BasePart(new AssetReference(Id, baseLocation), part.Part.Id, instanceId); } + return newAsset; } + } diff --git a/sources/assets/Stride.Core.Assets/AssetCompositeHierarchyData.cs b/sources/assets/Stride.Core.Assets/AssetCompositeHierarchyData.cs index b3164e956f..0ea0f540b1 100644 --- a/sources/assets/Stride.Core.Assets/AssetCompositeHierarchyData.cs +++ b/sources/assets/Stride.Core.Assets/AssetCompositeHierarchyData.cs @@ -1,38 +1,31 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Core; using Stride.Core.Annotations; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// A class containing the information of a hierarchy of asset parts contained in an . +/// +/// The type used for the design information of a part. +/// The type used for the actual parts, +[DataContract("AssetCompositeHierarchyData")] +public class AssetCompositeHierarchyData + where TAssetPartDesign : class, IAssetPartDesign + where TAssetPart : class, IIdentifiable { /// - /// A class containing the information of a hierarchy of asset parts contained in an . + /// Gets a collection if identifier of all the parts that are root of this hierarchy. /// - /// The type used for the design information of a part. - /// The type used for the actual parts, - [DataContract("AssetCompositeHierarchyData")] - public class AssetCompositeHierarchyData - where TAssetPartDesign : class, IAssetPartDesign - where TAssetPart : class, IIdentifiable - { - /// - /// Gets a collection if identifier of all the parts that are root of this hierarchy. - /// - [DataMember(10)] - [NonIdentifiableCollectionItems] - [NotNull] - public List RootParts { get; } = new List(); + [DataMember(10)] + [NonIdentifiableCollectionItems] + public List RootParts { get; } = []; - /// - /// Gets a collection of all the parts, root or not, contained in this hierarchy. - /// - [DataMember(20)] - [NonIdentifiableCollectionItems] - [ItemNotNull, NotNull] - public AssetPartCollection Parts { get; } = new AssetPartCollection(); - } + /// + /// Gets a collection of all the parts, root or not, contained in this hierarchy. + /// + [DataMember(20)] + [NonIdentifiableCollectionItems] + public AssetPartCollection Parts { get; } = []; } diff --git a/sources/assets/Stride.Core.Assets/AssetCompositeHierarchyExtensions.cs b/sources/assets/Stride.Core.Assets/AssetCompositeHierarchyExtensions.cs index c72b8e2014..8e8c60159a 100644 --- a/sources/assets/Stride.Core.Assets/AssetCompositeHierarchyExtensions.cs +++ b/sources/assets/Stride.Core.Assets/AssetCompositeHierarchyExtensions.cs @@ -1,57 +1,51 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using System.Diagnostics.Contracts; -using System.Linq; -using Stride.Core; -using Stride.Core.Annotations; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Extension methods for and +/// +public static class AssetCompositeHierarchyExtensions { /// - /// Extension methods for and + /// Enumerates the root design parts of this hierarchy. /// - public static class AssetCompositeHierarchyExtensions + /// The type used for the design information of a part. + /// The type used for the actual parts, + /// This hierarchy. + /// A sequence containing the root design parts of this hierarchy. + [Pure] + public static IEnumerable EnumerateRootPartDesigns(this AssetCompositeHierarchyData asset) + where TAssetPartDesign : class, IAssetPartDesign + where TAssetPart : class, IIdentifiable { - /// - /// Enumerates the root design parts of this hierarchy. - /// - /// The type used for the design information of a part. - /// The type used for the actual parts, - /// This hierarchy. - /// A sequence containing the root design parts of this hierarchy. - [ItemNotNull, NotNull, Pure] - public static IEnumerable EnumerateRootPartDesigns([NotNull] this AssetCompositeHierarchyData asset) - where TAssetPartDesign : class, IAssetPartDesign - where TAssetPart : class, IIdentifiable - { - if (asset == null) throw new ArgumentNullException(nameof(asset)); - return asset.RootParts.Select(x => asset.Parts[x.Id]); - } + ArgumentNullException.ThrowIfNull(asset); + return asset.RootParts.Select(x => asset.Parts[x.Id]); + } - /// - /// Merges the hierarchy into this hierarchy. - /// - /// - /// This method does not check whether the two hierarchies have independent parts and will fail otherwise. - /// - /// The type used for the design information of a part. - /// The type used for the actual parts, - /// This hierarchy. - /// The other hierarchy which parts will added to this hierarchy. - public static void MergeInto([NotNull] this AssetCompositeHierarchyData asset, - [NotNull] AssetCompositeHierarchyData other) - where TAssetPartDesign : class, IAssetPartDesign - where TAssetPart : class, IIdentifiable + /// + /// Merges the hierarchy into this hierarchy. + /// + /// + /// This method does not check whether the two hierarchies have independent parts and will fail otherwise. + /// + /// The type used for the design information of a part. + /// The type used for the actual parts, + /// This hierarchy. + /// The other hierarchy which parts will added to this hierarchy. + public static void MergeInto(this AssetCompositeHierarchyData asset, + AssetCompositeHierarchyData other) + where TAssetPartDesign : class, IAssetPartDesign + where TAssetPart : class, IIdentifiable + { + ArgumentNullException.ThrowIfNull(asset); + asset.RootParts.AddRange(other.RootParts); + foreach (var part in other.Parts) { - if (asset == null) throw new ArgumentNullException(nameof(asset)); - asset.RootParts.AddRange(other.RootParts); - foreach (var part in other.Parts) - { - asset.Parts.Add(part.Value); - } + asset.Parts.Add(part.Value); } } } diff --git a/sources/assets/Stride.Core.Assets/AssetContentTypeAttribute.cs b/sources/assets/Stride.Core.Assets/AssetContentTypeAttribute.cs index 5422791dbf..bf934bfaf7 100644 --- a/sources/assets/Stride.Core.Assets/AssetContentTypeAttribute.cs +++ b/sources/assets/Stride.Core.Assets/AssetContentTypeAttribute.cs @@ -1,28 +1,27 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Describes which runtime-type, loadable through the , corresponds to the associated asset type. +/// +[AttributeUsage(AttributeTargets.Class)] +public class AssetContentTypeAttribute : Attribute { /// - /// Describes which runtime-type, loadable through the , corresponds to the associated asset type. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Class)] - public class AssetContentTypeAttribute : Attribute + /// The content type corresponding to the associated asset type. + public AssetContentTypeAttribute(Type contentType) { - /// - /// Initializes a new instance of the class. - /// - /// The content type corresponding to the associated asset type. - public AssetContentTypeAttribute(Type contentType) - { - ContentType = contentType; - } - - /// - /// The content type corresponding to the associated asset type. - /// - public Type ContentType { get; } + ContentType = contentType; } + + /// + /// The content type corresponding to the associated asset type. + /// + public Type ContentType { get; } } diff --git a/sources/assets/Stride.Core.Assets/AssetDescriptionAttribute.cs b/sources/assets/Stride.Core.Assets/AssetDescriptionAttribute.cs index 4a1bf0af90..9c3f6ad936 100644 --- a/sources/assets/Stride.Core.Assets/AssetDescriptionAttribute.cs +++ b/sources/assets/Stride.Core.Assets/AssetDescriptionAttribute.cs @@ -1,44 +1,42 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Associates meta-information to a particular . +/// +[AttributeUsage(AttributeTargets.Class)] +public class AssetDescriptionAttribute : Attribute { /// - /// Associates meta-information to a particular . + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Class)] - public class AssetDescriptionAttribute : Attribute + /// The file extensions supported by a type of asset. + public AssetDescriptionAttribute(string fileExtensions) { - /// - /// Initializes a new instance of the class. - /// - /// The file extensions supported by a type of asset. - public AssetDescriptionAttribute(string fileExtensions) - { - FileExtensions = fileExtensions; - } + FileExtensions = fileExtensions; + } - /// - /// Gets the file extensions supported by a type of asset. - /// - /// The extension. - public string FileExtensions { get; } + /// + /// Gets the file extensions supported by a type of asset. + /// + /// The extension. + public string FileExtensions { get; } - /// - /// Gets whether this asset can be an archetype. - /// - public bool AllowArchetype { get; set; } = true; + /// + /// Gets whether this asset can be an archetype. + /// + public bool AllowArchetype { get; set; } = true; - /// - /// Defines if an asset is referenceable through an . - /// Asset name collision is allowed in this case because they exist only at compile-time. - /// - public bool Referenceable { get; set; } = true; + /// + /// Defines if an asset is referenceable through an . + /// Asset name collision is allowed in this case because they exist only at compile-time. + /// + public bool Referenceable { get; set; } = true; - /// - /// Always mark this asset type as root. - /// - public bool AlwaysMarkAsRoot { get; set; } - } + /// + /// Always mark this asset type as root. + /// + public bool AlwaysMarkAsRoot { get; set; } } diff --git a/sources/assets/Stride.Core.Assets/AssetException.cs b/sources/assets/Stride.Core.Assets/AssetException.cs index e6be0d698f..3cab9007e8 100644 --- a/sources/assets/Stride.Core.Assets/AssetException.cs +++ b/sources/assets/Stride.Core.Assets/AssetException.cs @@ -1,47 +1,44 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// An AssetException. +/// +public class AssetException : Exception { /// - /// An AssetException. + /// Initializes a new instance of the class. /// - public class AssetException : Exception + public AssetException() { - /// - /// Initializes a new instance of the class. - /// - public AssetException() - { - } + } - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - public AssetException(string message) : base(message) - { - } + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public AssetException(string message) : base(message) + { + } - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - /// The formatted arguments. - public AssetException(string message, params object[] formattedArguments) - : base(message.ToFormat(formattedArguments)) - { - } + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + /// The formatted arguments. + public AssetException(string message, params object[] formattedArguments) + : base(message.ToFormat(formattedArguments)) + { + } - /// - /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public AssetException(string message, Exception innerException) : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public AssetException(string message, Exception innerException) : base(message, innerException) + { } } diff --git a/sources/assets/Stride.Core.Assets/AssetFactory.cs b/sources/assets/Stride.Core.Assets/AssetFactory.cs index 65256630ac..be4fe9284c 100644 --- a/sources/assets/Stride.Core.Assets/AssetFactory.cs +++ b/sources/assets/Stride.Core.Assets/AssetFactory.cs @@ -1,19 +1,17 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// A base implementation of the interface. +/// +/// The type of asset this factory can create. +public abstract class AssetFactory : IAssetFactory where T : Asset { - /// - /// A base implementation of the interface. - /// - /// The type of asset this factory can create. - public abstract class AssetFactory : IAssetFactory where T : Asset - { - /// - public Type AssetType => typeof(T); + /// + public Type AssetType => typeof(T); - /// - public abstract T New(); - } + /// + public abstract T New(); } diff --git a/sources/assets/Stride.Core.Assets/AssetFileSerializer.cs b/sources/assets/Stride.Core.Assets/AssetFileSerializer.cs index ccd78a6a89..cc4b0a322f 100644 --- a/sources/assets/Stride.Core.Assets/AssetFileSerializer.cs +++ b/sources/assets/Stride.Core.Assets/AssetFileSerializer.cs @@ -1,178 +1,155 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; using Stride.Core.Assets.Serializers; using Stride.Core.Assets.Yaml; -using Stride.Core; using Stride.Core.Diagnostics; using Stride.Core.IO; -using Stride.Core.Reflection; -using Stride.Core.Yaml; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public class AssetLoadResult { - public class AssetLoadResult + public AssetLoadResult(T asset, ILogger? logger, bool aliasOccurred, AttachedYamlAssetMetadata yamlMetadata) { - public AssetLoadResult(T asset, ILogger logger, bool aliasOccurred, AttachedYamlAssetMetadata yamlMetadata) - { - if (yamlMetadata == null) throw new ArgumentNullException(nameof(yamlMetadata)); - Asset = asset; - Logger = logger; - AliasOccurred = aliasOccurred; - YamlMetadata = yamlMetadata; - } + ArgumentNullException.ThrowIfNull(yamlMetadata); + Asset = asset; + Logger = logger; + AliasOccurred = aliasOccurred; + YamlMetadata = yamlMetadata; + } - public T Asset { get; } + public T Asset { get; } - public ILogger Logger { get; } + public ILogger? Logger { get; } - public bool AliasOccurred { get; } + public bool AliasOccurred { get; } - public AttachedYamlAssetMetadata YamlMetadata { get; } - } + public AttachedYamlAssetMetadata YamlMetadata { get; } +} + +/// +/// Main entry point for serializing/deserializing . +/// +public static class AssetFileSerializer +{ + private static readonly List RegisteredSerializerFactories = []; /// - /// Main entry point for serializing/deserializing . + /// The default serializer. /// - public static class AssetFileSerializer - { - private static readonly List RegisteredSerializerFactories = new List(); - - /// - /// The default serializer. - /// - public static readonly IAssetSerializer Default = new YamlAssetSerializer(); + public static readonly IAssetSerializer Default = new YamlAssetSerializer(); - static AssetFileSerializer() - { - Register((YamlAssetSerializer)Default); - Register(SourceCodeAssetSerializer.Default); - } - - /// - /// Registers the specified serializer factory. - /// - /// The serializer factory. - /// serializerFactory - public static void Register(IAssetSerializerFactory serializerFactory) - { - if (serializerFactory == null) throw new ArgumentNullException(nameof(serializerFactory)); - if (!RegisteredSerializerFactories.Contains(serializerFactory)) - RegisteredSerializerFactories.Add(serializerFactory); - } + static AssetFileSerializer() + { + Register((YamlAssetSerializer)Default); + Register(SourceCodeAssetSerializer.Default); + } - /// - /// Finds a serializer for the specified asset file extension. - /// - /// The asset file extension. - /// IAssetSerializerFactory. - public static IAssetSerializer FindSerializer(string assetFileExtension) - { - if (assetFileExtension == null) throw new ArgumentNullException(nameof(assetFileExtension)); - assetFileExtension = assetFileExtension.ToLowerInvariant(); - for (int i = RegisteredSerializerFactories.Count - 1; i >= 0; i--) - { - var assetSerializerFactory = RegisteredSerializerFactories[i]; - var factory = assetSerializerFactory.TryCreate(assetFileExtension); - if (factory != null) - { - return factory; - } - } - return null; - } + /// + /// Registers the specified serializer factory. + /// + /// The serializer factory. + /// serializerFactory + public static void Register(IAssetSerializerFactory serializerFactory) + { + ArgumentNullException.ThrowIfNull(serializerFactory); + if (!RegisteredSerializerFactories.Contains(serializerFactory)) + RegisteredSerializerFactories.Add(serializerFactory); + } - /// - /// Deserializes an from the specified stream. - /// - /// Type of the asset - /// The file path. - /// The logger. - /// An instance of Asset not a valid asset asset object file. - public static AssetLoadResult Load(string filePath, ILogger log = null) + /// + /// Finds a serializer for the specified asset file extension. + /// + /// The asset file extension. + /// IAssetSerializerFactory. + public static IAssetSerializer? FindSerializer(string assetFileExtension) + { + ArgumentNullException.ThrowIfNull(assetFileExtension); + assetFileExtension = assetFileExtension.ToLowerInvariant(); + for (int i = RegisteredSerializerFactories.Count - 1; i >= 0; i--) { - using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + var assetSerializerFactory = RegisteredSerializerFactories[i]; + var factory = assetSerializerFactory.TryCreate(assetFileExtension); + if (factory != null) { - var result = Load(stream, filePath, log); - return result; + return factory; } } + return null; + } - public static AssetLoadResult Load(Stream stream, UFile filePath, ILogger log = null) - { - if (filePath == null) throw new ArgumentNullException(nameof(filePath)); - var assetFileExtension = Path.GetExtension(filePath).ToLowerInvariant(); - - var serializer = FindSerializer(assetFileExtension); - if (serializer == null) - { - throw new InvalidOperationException("Unable to find a serializer for [{0}]".ToFormat(assetFileExtension)); - } - bool aliasOccurred; - AttachedYamlAssetMetadata yamlMetadata; - var asset = (T)serializer.Load(stream, filePath, log, true, out aliasOccurred, out yamlMetadata); - return new AssetLoadResult(asset, log, aliasOccurred, yamlMetadata); - } + /// + /// Deserializes an from the specified stream. + /// + /// Type of the asset + /// The file path. + /// The logger. + /// An instance of Asset not a valid asset asset object file. + public static AssetLoadResult Load(string filePath, ILogger? log = null) + { + using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + var result = Load(stream, filePath, log); + return result; + } - /// - /// Serializes an to the specified file path. - /// - /// The file path. - /// The asset object. - /// - /// The logger. - /// filePath - public static void Save(string filePath, object asset, AttachedYamlAssetMetadata yamlMetadata, ILogger log = null) - { - if (filePath == null) throw new ArgumentNullException(nameof(filePath)); + public static AssetLoadResult Load(Stream stream, UFile filePath, ILogger? log = null) + { + ArgumentNullException.ThrowIfNull(filePath); + var assetFileExtension = Path.GetExtension(filePath).ToLowerInvariant(); - // Creates automatically the directory when saving an asset. - filePath = FileUtility.GetAbsolutePath(filePath); - var directoryPath = Path.GetDirectoryName(filePath); - if (directoryPath != null && !Directory.Exists(directoryPath)) - { - Directory.CreateDirectory(directoryPath); - } + var serializer = FindSerializer(assetFileExtension) + ?? throw new InvalidOperationException("Unable to find a serializer for [{0}]".ToFormat(assetFileExtension)); + var asset = (T)serializer.Load(stream, filePath, log, true, out var aliasOccurred, out var yamlMetadata); + return new AssetLoadResult(asset, log, aliasOccurred, yamlMetadata); + } - using (var stream = new MemoryStream()) - { - Save(stream, asset, yamlMetadata, log); - File.WriteAllBytes(filePath, stream.ToArray()); - } - } + /// + /// Serializes an to the specified file path. + /// + /// The file path. + /// The asset object. + /// + /// The logger. + /// filePath + public static void Save(string filePath, object asset, AttachedYamlAssetMetadata? yamlMetadata, ILogger? log = null) + { + ArgumentNullException.ThrowIfNull(filePath); - /// - /// Serializes an to the specified stream. - /// - /// The stream. - /// The asset object. - /// - /// The logger. - /// - /// stream - /// or - /// assetFileExtension - /// - public static void Save(Stream stream, object asset, AttachedYamlAssetMetadata yamlMetadata, ILogger log = null) + // Creates automatically the directory when saving an asset. + filePath = FileUtility.GetAbsolutePath(filePath); + var directoryPath = Path.GetDirectoryName(filePath); + if (directoryPath != null && !Directory.Exists(directoryPath)) { - if (stream == null) throw new ArgumentNullException(nameof(stream)); - if (asset == null) return; + Directory.CreateDirectory(directoryPath); + } - var assetFileExtension = AssetRegistry.GetDefaultExtension(asset.GetType()); - if (assetFileExtension == null) - { - throw new ArgumentException("Unable to find a serializer for the specified asset. No asset file extension registered to AssetRegistry"); - } + using var stream = new MemoryStream(); + Save(stream, asset, yamlMetadata, log); + File.WriteAllBytes(filePath, stream.ToArray()); + } - var serializer = FindSerializer(assetFileExtension); - if (serializer == null) - { - throw new InvalidOperationException($"Unable to find a serializer for [{assetFileExtension}]"); - } - serializer.Save(stream, asset, yamlMetadata, log); - } + /// + /// Serializes an to the specified stream. + /// + /// The stream. + /// The asset object. + /// + /// The logger. + /// + /// stream + /// or + /// assetFileExtension + /// + public static void Save(Stream stream, object asset, AttachedYamlAssetMetadata? yamlMetadata, ILogger? log = null) + { + ArgumentNullException.ThrowIfNull(stream); + if (asset == null) return; + + var assetFileExtension = AssetRegistry.GetDefaultExtension(asset.GetType()) + ?? throw new ArgumentException("Unable to find a serializer for the specified asset. No asset file extension registered to AssetRegistry"); + var serializer = FindSerializer(assetFileExtension) + ?? throw new InvalidOperationException($"Unable to find a serializer for [{assetFileExtension}]"); + serializer.Save(stream, asset, yamlMetadata, log); } } diff --git a/sources/assets/Stride.Core.Assets/AssetFolder.cs b/sources/assets/Stride.Core.Assets/AssetFolder.cs index d637ecc074..1b6cec5395 100644 --- a/sources/assets/Stride.Core.Assets/AssetFolder.cs +++ b/sources/assets/Stride.Core.Assets/AssetFolder.cs @@ -1,63 +1,61 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Diagnostics; -using Stride.Core; using Stride.Core.IO; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// A location relative to a package from where assets will be loaded +/// +[DataContract("AssetFolder")] +[DebuggerDisplay("Assets: {Path}")] +public sealed class AssetFolder { + private UDirectory path; + /// - /// A location relative to a package from where assets will be loaded + /// Initializes a new instance of the class. /// - [DataContract("AssetFolder")] - [DebuggerDisplay("Assets: {Path}")] - public sealed class AssetFolder + public AssetFolder() { - private UDirectory path; + } - /// - /// Initializes a new instance of the class. - /// - public AssetFolder() - { - } + /// + /// Initializes a new instance of the class. + /// + /// The folder. + public AssetFolder(UDirectory path) : this() + { + ArgumentNullException.ThrowIfNull(path); + this.path = path; + } - /// - /// Initializes a new instance of the class. - /// - /// The folder. - public AssetFolder(UDirectory path) : this() + /// + /// Gets or sets the folder. + /// + /// The folder. + public UDirectory Path + { + get { - if (path == null) throw new ArgumentNullException("path"); - this.path = path; + return path; } - - /// - /// Gets or sets the folder. - /// - /// The folder. - public UDirectory Path + set { - get - { - return path; - } - set - { - if (value == null) throw new ArgumentNullException(); - path = value; - } + ArgumentNullException.ThrowIfNull(value); + path = value; } + } - public AssetFolder Clone() + public AssetFolder Clone() + { + var sourceFolder = new AssetFolder(); + if (Path != null) { - var sourceFolder = new AssetFolder(); - if (Path != null) - { - sourceFolder.Path = path; - } - return sourceFolder; + sourceFolder.Path = path; } + return sourceFolder; } } diff --git a/sources/assets/Stride.Core.Assets/AssetFolderCollection.cs b/sources/assets/Stride.Core.Assets/AssetFolderCollection.cs index f9718212c5..426170e872 100644 --- a/sources/assets/Stride.Core.Assets/AssetFolderCollection.cs +++ b/sources/assets/Stride.Core.Assets/AssetFolderCollection.cs @@ -1,143 +1,131 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using Stride.Core; using Stride.Core.Diagnostics; using Stride.Core.IO; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// A collection of +/// +[DataContract("AssetFolderCollection")] +[DebuggerTypeProxy(typeof(CollectionDebugView))] +[DebuggerDisplay("Count = {Count}")] +public sealed class AssetFolderCollection : IList, IReadOnlyList { + private readonly List folders; + /// - /// A collection of + /// Initializes a new instance of the class. /// - [DataContract("AssetFolderCollection")] - [DebuggerTypeProxy(typeof(CollectionDebugView))] - [DebuggerDisplay("Count = {Count}")] - public sealed class AssetFolderCollection : IList, IReadOnlyList + public AssetFolderCollection() { - private readonly List folders; + folders = []; + } - /// - /// Initializes a new instance of the class. - /// - public AssetFolderCollection() + /// + public void Add(AssetFolder item) + { + ArgumentNullException.ThrowIfNull(item); + if (item.Path == null) { - folders = new List(); + throw new ArgumentOutOfRangeException(nameof(item), "SourceFolder.Folder cannot be null"); } - /// - public void Add(AssetFolder item) + // If a folder is already added, use it and squash the item to add to the existing folder. + var existingFolder = Find(item.Path); + if (existingFolder == null) { - if (item == null) throw new ArgumentNullException("item"); - if (item.Path == null) - { - throw new ArgumentOutOfRangeException("item", "SourceFolder.Folder cannot be null"); - } - - // If a folder is already added, use it and squash the item to add to the existing folder. - var existingFolder = Find(item.Path); - if (existingFolder == null) - { - folders.Add(item); - } + folders.Add(item); } + } - public AssetFolder Find(UDirectory folder) - { - return folders.FirstOrDefault(sourceFolder => sourceFolder.Path == folder); - } + public AssetFolder? Find(UDirectory folder) + { + return folders.FirstOrDefault(sourceFolder => sourceFolder.Path == folder); + } - /// - public void Clear() - { - folders.Clear(); - } + /// + public void Clear() + { + folders.Clear(); + } - /// - public bool Contains(AssetFolder item) - { - if (item == null) throw new ArgumentNullException("item"); - return folders.Contains(item); - } + /// + public bool Contains(AssetFolder item) + { + ArgumentNullException.ThrowIfNull(item); + return folders.Contains(item); + } - /// - public void CopyTo(AssetFolder[] array, int arrayIndex) - { - folders.CopyTo(array, arrayIndex); - } + /// + public void CopyTo(AssetFolder[] array, int arrayIndex) + { + folders.CopyTo(array, arrayIndex); + } - /// - /// Clones this instance to the specified instance. - /// - /// The folders. - /// folders - public void CloneTo(AssetFolderCollection foldersTo) + /// + /// Clones this instance to the specified instance. + /// + /// The folders. + /// folders + public void CloneTo(AssetFolderCollection foldersTo) + { + ArgumentNullException.ThrowIfNull(foldersTo); + foreach (var sourceFolder in this) { - if (foldersTo == null) throw new ArgumentNullException("folders"); - foreach (var sourceFolder in this) - { - foldersTo.Add(sourceFolder.Clone()); - } + foldersTo.Add(sourceFolder.Clone()); } + } - /// - public bool Remove(AssetFolder item) - { - if (item == null) throw new ArgumentNullException("item"); - return folders.Remove(item); - } + /// + public bool Remove(AssetFolder item) + { + ArgumentNullException.ThrowIfNull(item); + return folders.Remove(item); + } - /// - public int Count - { - get - { - return folders.Count; - } - } + /// + public int Count => folders.Count; - /// - bool ICollection.IsReadOnly - { - get - { - return false; - } - } + /// + bool ICollection.IsReadOnly => false; - public AssetFolder this[int index] => folders[index]; + public AssetFolder this[int index] => folders[index]; - /// - public IEnumerator GetEnumerator() - { - return folders.GetEnumerator(); - } + /// + public IEnumerator GetEnumerator() + { + return folders.GetEnumerator(); + } - /// - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)folders).GetEnumerator(); - } + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)folders).GetEnumerator(); + } - int IList.IndexOf(AssetFolder item) - { - return folders.IndexOf(item); - } + int IList.IndexOf(AssetFolder item) + { + return folders.IndexOf(item); + } - void IList.Insert(int index, AssetFolder item) - { - folders.Insert(index, item); - } + void IList.Insert(int index, AssetFolder item) + { + folders.Insert(index, item); + } - void IList.RemoveAt(int index) - { - folders.RemoveAt(index); - } + void IList.RemoveAt(int index) + { + folders.RemoveAt(index); + } - AssetFolder IList.this[int index] { get { return folders[index]; } set { folders[index] = value; } } + AssetFolder IList.this[int index] + { + get { return folders[index]; } + set { folders[index] = value; } } } diff --git a/sources/assets/Stride.Core.Assets/AssetFormatVersionAttribute.cs b/sources/assets/Stride.Core.Assets/AssetFormatVersionAttribute.cs index efbcc707fc..d2e335bb78 100644 --- a/sources/assets/Stride.Core.Assets/AssetFormatVersionAttribute.cs +++ b/sources/assets/Stride.Core.Assets/AssetFormatVersionAttribute.cs @@ -1,59 +1,56 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Describes what format version this asset currently uses, for asset upgrading. +/// +[AttributeUsage(AttributeTargets.Class)] +public class AssetFormatVersionAttribute : Attribute { /// - /// Describes what format version this asset currently uses, for asset upgrading. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Class)] - public class AssetFormatVersionAttribute : Attribute + /// The dependency name. + /// The current format version of this asset. + /// The minimum format version that supports upgrade for this asset. + public AssetFormatVersionAttribute(string name, int version, int minUpgradableVersion = 0) + : this(name, "0.0." + version, minUpgradableVersion != 0 ? "0.0." + minUpgradableVersion : null) { - /// - /// Initializes a new instance of the class. - /// - /// The dependency name. - /// The current format version of this asset. - /// The minimum format version that supports upgrade for this asset. - public AssetFormatVersionAttribute(string name, int version, int minUpgradableVersion = 0) - : this(name, "0.0." + version, minUpgradableVersion != 0 ? "0.0." + minUpgradableVersion : null) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The dependency name. - /// The current format version of this asset. - /// The minimum format version that supports upgrade for this asset. - public AssetFormatVersionAttribute(string name, string version, string minUpgradableVersion = null) - { - Name = name; - Version = PackageVersion.Parse(version); - MinUpgradableVersion = PackageVersion.Parse(minUpgradableVersion ?? "0"); - } + /// + /// Initializes a new instance of the class. + /// + /// The dependency name. + /// The current format version of this asset. + /// The minimum format version that supports upgrade for this asset. + public AssetFormatVersionAttribute(string name, string version, string? minUpgradableVersion = null) + { + Name = name; + Version = PackageVersion.Parse(version); + MinUpgradableVersion = PackageVersion.Parse(minUpgradableVersion ?? "0"); + } - /// - /// Gets or sets the dependency name. - /// - public string Name { get; set; } + /// + /// Gets or sets the dependency name. + /// + public string Name { get; set; } - /// - /// Gets the current format version of this asset. - /// - /// - /// The current format version of this asset. - /// - public PackageVersion Version { get; set; } + /// + /// Gets the current format version of this asset. + /// + /// + /// The current format version of this asset. + /// + public PackageVersion Version { get; set; } - /// - /// Gets the minimum format version that supports upgrade for this asset. - /// - /// - /// The minimum format version that supports upgrade for this asset. - /// - public PackageVersion MinUpgradableVersion { get; set; } - } + /// + /// Gets the minimum format version that supports upgrade for this asset. + /// + /// + /// The minimum format version that supports upgrade for this asset. + /// + public PackageVersion MinUpgradableVersion { get; set; } } diff --git a/sources/assets/Stride.Core.Assets/AssetHash.cs b/sources/assets/Stride.Core.Assets/AssetHash.cs index 7945a68452..435e78c52f 100644 --- a/sources/assets/Stride.Core.Assets/AssetHash.cs +++ b/sources/assets/Stride.Core.Assets/AssetHash.cs @@ -1,23 +1,23 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Storage; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Helper to compute a stable hash from an asset including all meta informations (ids, overrides). +/// +public static class AssetHash { /// - /// Helper to compute a stable hash from an asset including all meta informations (ids, overrides). + /// Computes a stable hash from an asset including all meta informations (ids, overrides). /// - public static class AssetHash + /// An object instance + /// Flags used to control the serialization process + /// a stable hash + public static ObjectId Compute(object asset, AssetClonerFlags flags = AssetClonerFlags.None) { - /// - /// Computes a stable hash from an asset including all meta informations (ids, overrides). - /// - /// An object instance - /// Flags used to control the serialization process - /// a stable hash - public static ObjectId Compute(object asset, AssetClonerFlags flags = AssetClonerFlags.None) - { - return AssetCloner.ComputeHash(asset, flags); - } + return AssetCloner.ComputeHash(asset, flags); } } diff --git a/sources/assets/Stride.Core.Assets/AssetImporterBase.cs b/sources/assets/Stride.Core.Assets/AssetImporterBase.cs index 5a9e09f191..ab4dfa513b 100644 --- a/sources/assets/Stride.Core.Assets/AssetImporterBase.cs +++ b/sources/assets/Stride.Core.Assets/AssetImporterBase.cs @@ -1,42 +1,39 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using Stride.Core.IO; -namespace Stride.Core.Assets -{ - public abstract class AssetImporterBase : IAssetImporter - { - public abstract Guid Id { get; } +namespace Stride.Core.Assets; - public virtual string Name => GetType().Name; +public abstract class AssetImporterBase : IAssetImporter +{ + public abstract Guid Id { get; } - public abstract string Description { get; } + public virtual string Name => GetType().Name; - public int Order { get; protected set; } + public abstract string Description { get; } - public abstract string SupportedFileExtensions { get; } + public int Order { get; protected set; } - public virtual bool IsSupportingFile(string filePath) - { - if (filePath == null) throw new ArgumentNullException(nameof(filePath)); - var file = new UFile(filePath); - if (file.GetFileExtension() == null) return false; + public abstract string SupportedFileExtensions { get; } - return FileUtility.GetFileExtensionsAsSet(SupportedFileExtensions).Contains(file.GetFileExtension()); - } + public virtual bool IsSupportingFile(string filePath) + { + ArgumentNullException.ThrowIfNull(filePath); + var file = new UFile(filePath); + if (file.GetFileExtension() == null) return false; - public abstract IEnumerable RootAssetTypes { get; } + return FileUtility.GetFileExtensionsAsSet(SupportedFileExtensions).Contains(file.GetFileExtension()); + } - public virtual IEnumerable AdditionalAssetTypes { get { yield break; } } + public abstract IEnumerable RootAssetTypes { get; } - public AssetImporterParameters GetDefaultParameters(bool isForReImport) - { - return new AssetImporterParameters(RootAssetTypes.Concat(AdditionalAssetTypes)); - } + public virtual IEnumerable AdditionalAssetTypes { get { yield break; } } - public abstract IEnumerable Import(UFile rawAssetPath, AssetImporterParameters importParameters); + public AssetImporterParameters GetDefaultParameters(bool isForReImport) + { + return new AssetImporterParameters(RootAssetTypes.Concat(AdditionalAssetTypes)); } + + public abstract IEnumerable Import(UFile rawAssetPath, AssetImporterParameters importParameters); } diff --git a/sources/assets/Stride.Core.Assets/AssetImporterParameters.cs b/sources/assets/Stride.Core.Assets/AssetImporterParameters.cs index 1c1415884a..96bc8145aa 100644 --- a/sources/assets/Stride.Core.Assets/AssetImporterParameters.cs +++ b/sources/assets/Stride.Core.Assets/AssetImporterParameters.cs @@ -1,107 +1,101 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Core; using Stride.Core.Diagnostics; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Parameters used by +/// +public class AssetImporterParameters { /// - /// Parameters used by + /// Gets or sets the logger to use during the import. /// - public class AssetImporterParameters - { - /// - /// Gets or sets the logger to use during the import. - /// - public Logger Logger { get; set; } + public Logger Logger { get; set; } - /// - /// Initializes a new instance of the class. - /// - public AssetImporterParameters() - { - InputParameters = new PropertyCollection(); - SelectedOutputTypes = new Dictionary(); - } + /// + /// Initializes a new instance of the class. + /// + public AssetImporterParameters() + { + InputParameters = new PropertyCollection(); + SelectedOutputTypes = []; + } - /// - /// Initializes a new instance of the class. - /// - /// The supported types. - public AssetImporterParameters(params Type[] supportedTypes) : this((IEnumerable)supportedTypes) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The supported types. + public AssetImporterParameters(params Type[] supportedTypes) : this((IEnumerable)supportedTypes) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The supported types. - /// supportedTypes - /// Invalid type [{0}]. Type must be assignable to Asset.ToFormat(type);supportedTypes - public AssetImporterParameters(IEnumerable supportedTypes) : this() + /// + /// Initializes a new instance of the class. + /// + /// The supported types. + /// supportedTypes + /// Invalid type [{0}]. Type must be assignable to Asset.ToFormat(type);supportedTypes + public AssetImporterParameters(IEnumerable supportedTypes) : this() + { + ArgumentNullException.ThrowIfNull(supportedTypes); + foreach (var type in supportedTypes) { - if (supportedTypes == null) throw new ArgumentNullException("supportedTypes"); - foreach (var type in supportedTypes) + if (!typeof(Asset).IsAssignableFrom(type)) { - if (!typeof(Asset).IsAssignableFrom(type)) - { - throw new ArgumentException("Invalid type [{0}]. Type must be assignable to Asset".ToFormat(type), "supportedTypes"); - } - SelectedOutputTypes[type] = true; + throw new ArgumentException("Invalid type [{0}]. Type must be assignable to Asset".ToFormat(type), nameof(supportedTypes)); } + SelectedOutputTypes[type] = true; } + } - /// - /// Gets the import input parameters. - /// - /// The import input parameters. - public PropertyCollection InputParameters { get; private set; } + /// + /// Gets the import input parameters. + /// + /// The import input parameters. + public PropertyCollection InputParameters { get; private set; } - /// - /// Gets the selected output types. - /// - /// The selected output types. - public Dictionary SelectedOutputTypes { get; private set; } + /// + /// Gets the selected output types. + /// + /// The selected output types. + public Dictionary SelectedOutputTypes { get; private set; } - /// - /// Determines whether the specified type is type selected for output by this importer. - /// - /// A Type asset - /// true if the specified type is type selected for output by this importer; otherwise, false. - public bool IsTypeSelectedForOutput() where T : Asset - { - return IsTypeSelectedForOutput(typeof(T)); - } + /// + /// Determines whether the specified type is type selected for output by this importer. + /// + /// A Type asset + /// true if the specified type is type selected for output by this importer; otherwise, false. + public bool IsTypeSelectedForOutput() where T : Asset + { + return IsTypeSelectedForOutput(typeof(T)); + } - /// - /// Determines whether the specified type is type selected for output by this importer. - /// - /// The type. - /// true if the specified type is type selected for output by this importer; otherwise, false. - public bool IsTypeSelectedForOutput(Type type) + /// + /// Determines whether the specified type is type selected for output by this importer. + /// + /// The type. + /// true if the specified type is type selected for output by this importer; otherwise, false. + public bool IsTypeSelectedForOutput(Type type) + { + if (SelectedOutputTypes.TryGetValue(type, out var isSelected)) { - bool isSelected; - if (SelectedOutputTypes.TryGetValue(type, out isSelected)) - { - return isSelected; - } - return false; + return isSelected; } + return false; + } - /// - /// Gets a value indicating whether this instance has valid selected output types. - /// - /// true if this instance has selected output types; otherwise, false. - public bool HasSelectedOutputTypes + /// + /// Gets a value indicating whether this instance has valid selected output types. + /// + /// true if this instance has selected output types; otherwise, false. + public bool HasSelectedOutputTypes + { + get { - get - { - return SelectedOutputTypes.Count > 0 && SelectedOutputTypes.Any(selectedOutputType => selectedOutputType.Value); - } + return SelectedOutputTypes.Count > 0 && SelectedOutputTypes.Any(selectedOutputType => selectedOutputType.Value); } } } diff --git a/sources/assets/Stride.Core.Assets/AssetItem.cs b/sources/assets/Stride.Core.Assets/AssetItem.cs index 5aa3f753b6..5cedd00d0c 100644 --- a/sources/assets/Stride.Core.Assets/AssetItem.cs +++ b/sources/assets/Stride.Core.Assets/AssetItem.cs @@ -1,319 +1,302 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Threading; + using Stride.Core.Assets.Tracking; using Stride.Core.Assets.Yaml; -using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.IO; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// An asset item part of a accessible through . +/// +[DataContract("AssetItem")] +public sealed class AssetItem : IFileSynchronizable { + private UFile location; + private Asset asset; + private bool isDirty; + private HashSet? sourceFiles; + /// - /// An asset item part of a accessible through . + /// The default comparer use only the id of an assetitem to match assets. /// - [DataContract("AssetItem")] - public sealed class AssetItem : IFileSynchronizable + public static readonly IEqualityComparer DefaultComparerById = new AssetItemComparerById(); + + /// + /// Initializes a new instance of the class. + /// + /// The location inside the package. + /// The asset. + /// location + /// asset + public AssetItem(UFile location, Asset asset) : this(location, asset, null) { - private UFile location; - private Asset asset; - private bool isDirty; - private HashSet sourceFiles; - - /// - /// The default comparer use only the id of an assetitem to match assets. - /// - public static readonly IEqualityComparer DefaultComparerById = new AssetItemComparerById(); - - /// - /// Initializes a new instance of the class. - /// - /// The location inside the package. - /// The asset. - /// location - /// asset - public AssetItem([NotNull] UFile location, [NotNull] Asset asset) : this(location, asset, null) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The location. - /// The asset. - /// The package. - /// location - /// asset - internal AssetItem([NotNull] UFile location, [NotNull] Asset asset, Package package) - { - this.location = location ?? throw new ArgumentNullException(nameof(location)); - this.asset = asset ?? throw new ArgumentNullException(nameof(asset)); - Package = package; - isDirty = true; - } + /// + /// Initializes a new instance of the class. + /// + /// The location. + /// The asset. + /// The package. + /// location + /// asset + internal AssetItem(UFile location, Asset asset, Package? package) + { + this.location = location ?? throw new ArgumentNullException(nameof(location)); + this.asset = asset ?? throw new ArgumentNullException(nameof(asset)); + Package = package; + isDirty = true; + } - /// - /// Gets the location of this asset. - /// - /// The location. - [NotNull] - [DataMember] - public UFile Location { get => location; internal set => location = value ?? throw new ArgumentNullException(nameof(value)); } - - /// - /// Gets or sets the real location of this asset if it is overriden (similar to `Link` in C# project files). - /// - [CanBeNull] - public UFile AlternativePath { get; set; } - - /// - /// Gets the directory where the assets will be stored on the disk relative to the . The directory - /// will update the list found in - /// - /// The directory. - public UDirectory SourceFolder { get; set; } - - /// - /// Gets the unique identifier of this asset. - /// - /// The unique identifier. - public AssetId Id => asset.Id; - - /// - /// Gets the package where this asset is stored. - /// - /// The package. - [DataMember] - public Package Package { get; internal set; } - - /// - /// Gets the attached metadata for YAML serialization. - /// - [DataMemberIgnore] - public AttachedYamlAssetMetadata YamlMetadata { get; } = new AttachedYamlAssetMetadata(); - - /// - /// Converts this item to a reference. - /// - /// AssetReference. - [NotNull] - public AssetReference ToReference() - { - return new AssetReference(Id, Location); - } + /// + /// Gets the location of this asset. + /// + /// The location. + [DataMember] + public UFile Location { get => location; internal set => location = value ?? throw new ArgumentNullException(nameof(value)); } - /// - /// Clones this instance without the attached package. - /// - /// The new location that will be used in the cloned . If this parameter - /// is null, it keeps the original location of the asset. - /// The new asset that will be used in the cloned . If this parameter - /// is null, it clones the original asset. otherwise, the specified asset is used as-is in the new - /// (no clone on newAsset is performed) - /// Flags used with . - /// A clone of this instance. - [NotNull] - public AssetItem Clone(UFile newLocation = null, Asset newAsset = null, AssetClonerFlags flags = AssetClonerFlags.None) - { - return Clone(false, newLocation, newAsset, flags); - } + /// + /// Gets or sets the real location of this asset if it is overriden (similar to `Link` in C# project files). + /// + public UFile? AlternativePath { get; set; } - /// - /// Clones this instance without the attached package. - /// - /// if set to true copy package information, only used by the . - /// The new location that will be used in the cloned . If this parameter - /// is null, it keeps the original location of the asset. - /// The new asset that will be used in the cloned . If this parameter - /// is null, it clones the original asset. otherwise, the specified asset is used as-is in the new - /// (no clone on newAsset is performed) - /// Flags used with . - /// A clone of this instance. - [NotNull] - public AssetItem Clone(bool keepPackage, UFile newLocation = null, Asset newAsset = null, AssetClonerFlags flags = AssetClonerFlags.None) + /// + /// Gets the directory where the assets will be stored on the disk relative to the . The directory + /// will update the list found in + /// + /// The directory. + public UDirectory? SourceFolder { get; set; } + + /// + /// Gets the unique identifier of this asset. + /// + /// The unique identifier. + public AssetId Id => asset.Id; + + /// + /// Gets the package where this asset is stored. + /// + /// The package. + [DataMember] + public Package? Package { get; internal set; } + + /// + /// Gets the attached metadata for YAML serialization. + /// + [DataMemberIgnore] + public AttachedYamlAssetMetadata YamlMetadata { get; } = new AttachedYamlAssetMetadata(); + + /// + /// Converts this item to a reference. + /// + /// AssetReference. + public AssetReference ToReference() + { + return new AssetReference(Id, Location); + } + + /// + /// Clones this instance without the attached package. + /// + /// The new location that will be used in the cloned . If this parameter + /// is null, it keeps the original location of the asset. + /// The new asset that will be used in the cloned . If this parameter + /// is null, it clones the original asset. otherwise, the specified asset is used as-is in the new + /// (no clone on newAsset is performed) + /// Flags used with . + /// A clone of this instance. + public AssetItem Clone(UFile? newLocation = null, Asset? newAsset = null, AssetClonerFlags flags = AssetClonerFlags.None) + { + return Clone(false, newLocation, newAsset, flags); + } + + /// + /// Clones this instance without the attached package. + /// + /// if set to true copy package information, only used by the . + /// The new location that will be used in the cloned . If this parameter + /// is null, it keeps the original location of the asset. + /// The new asset that will be used in the cloned . If this parameter + /// is null, it clones the original asset. otherwise, the specified asset is used as-is in the new + /// (no clone on newAsset is performed) + /// Flags used with . + /// A clone of this instance. + public AssetItem Clone(bool keepPackage, UFile? newLocation = null, Asset? newAsset = null, AssetClonerFlags flags = AssetClonerFlags.None) + { + // Set the package after the new AssetItem(), to make sure that isDirty is not going to call a notification on the + // package + var item = new AssetItem(newLocation ?? location, newAsset ?? AssetCloner.Clone(Asset, flags), keepPackage ? Package : null) { - // Set the package after the new AssetItem(), to make sure that isDirty is not going to call a notification on the - // package - var item = new AssetItem(newLocation ?? location, newAsset ?? AssetCloner.Clone(Asset, flags), keepPackage ? Package : null) - { - isDirty = isDirty, - SourceFolder = SourceFolder, - version = Version, - AlternativePath = AlternativePath, - }; - YamlMetadata.CopyInto(item.YamlMetadata); - return item; - } + isDirty = isDirty, + SourceFolder = SourceFolder, + version = Version, + AlternativePath = AlternativePath, + }; + YamlMetadata.CopyInto(item.YamlMetadata); + return item; + } - /// - /// Gets the full absolute path of this asset on the disk, taking into account the , and the - /// . See remarks. - /// - /// The full absolute path of this asset on the disk. - /// - /// This value is only valid if this instance is attached to a , and that the package has - /// a non null . - /// - [NotNull] - public UFile FullPath + /// + /// Gets the full absolute path of this asset on the disk, taking into account the , and the + /// . See remarks. + /// + /// The full absolute path of this asset on the disk. + /// + /// This value is only valid if this instance is attached to a , and that the package has + /// a non null . + /// + public UFile FullPath + { + get { - get + var localSourceFolder = SourceFolder ?? + (Package is not null + ? Package.GetDefaultAssetFolder() + : UDirectory.This); + + // Root directory of package + var rootDirectory = Package?.RootDirectory is not null ? Package.RootDirectory : null; + + // If the source folder is absolute, make it relative to the root directory + if (localSourceFolder.IsAbsolute && rootDirectory is not null) { - var localSourceFolder = SourceFolder ?? (Package != null ? - Package.GetDefaultAssetFolder() - : UDirectory.This ); - - // Root directory of package - var rootDirectory = Package != null && Package.RootDirectory != null ? Package.RootDirectory : null; - - // If the source folder is absolute, make it relative to the root directory - if (localSourceFolder.IsAbsolute) - { - if (rootDirectory != null) - { - localSourceFolder = localSourceFolder.MakeRelative(rootDirectory); - } - } - - rootDirectory = rootDirectory != null ? UPath.Combine(rootDirectory, localSourceFolder) : localSourceFolder; - - var locationAndExtension = AlternativePath ?? new UFile(Location + AssetRegistry.GetDefaultExtension(Asset.GetType())); - return rootDirectory != null ? UPath.Combine(rootDirectory, locationAndExtension) : locationAndExtension; + localSourceFolder = localSourceFolder.MakeRelative(rootDirectory); } + + rootDirectory = rootDirectory is not null ? UPath.Combine(rootDirectory, localSourceFolder) : localSourceFolder; + + var locationAndExtension = AlternativePath ?? new UFile(Location + AssetRegistry.GetDefaultExtension(Asset.GetType())); + return rootDirectory is not null ? UPath.Combine(rootDirectory, locationAndExtension) : locationAndExtension; } + } + + /// + /// Gets or sets the asset. + /// + /// The asset. + [DataMember] + public Asset Asset { get => asset; internal set => asset = value ?? throw new ArgumentNullException(nameof(value)); } + + /// + /// Gets the modified time. See remarks. + /// + /// The modified time. + /// + /// By default, contains the last modified time of the asset from the disk. If IsDirty is also updated from false to true + /// , this time will get current time of modification. + /// + [DataMember] + public DateTime ModifiedTime { get; internal set; } + + private long version; + + /// + /// Gets the asset version incremental counter, increased everytime the asset is edited. + /// + [DataMember] + public long Version { get => Interlocked.Read(ref version); internal set => Interlocked.Exchange(ref version, value); } - /// - /// Gets or sets the asset. - /// - /// The asset. - [NotNull] - [DataMember] - public Asset Asset { get => asset; internal set => asset = value ?? throw new ArgumentNullException(nameof(value)); } - - /// - /// Gets the modified time. See remarks. - /// - /// The modified time. - /// - /// By default, contains the last modified time of the asset from the disk. If IsDirty is also updated from false to true - /// , this time will get current time of modification. - /// - [DataMember] - public DateTime ModifiedTime { get; internal set; } - - private long version; - - /// - /// Gets the asset version incremental counter, increased everytime the asset is edited. - /// - [DataMember] - public long Version { get => Interlocked.Read(ref version); internal set => Interlocked.Exchange(ref version, value); } - - /// - /// Gets or sets a value indicating whether this instance is dirty. See remarks. - /// - /// true if this instance is dirty; otherwise, false. - /// - /// When an asset is modified, this property must be set to true in order to track assets changes. - /// - public bool IsDirty + /// + /// Gets or sets a value indicating whether this instance is dirty. See remarks. + /// + /// true if this instance is dirty; otherwise, false. + /// + /// When an asset is modified, this property must be set to true in order to track assets changes. + /// + public bool IsDirty + { + get => isDirty; + set { - get => isDirty; - set + if (value && !isDirty) { - if (value && !isDirty) - { - ModifiedTime = DateTime.Now; - } + ModifiedTime = DateTime.Now; + } - Interlocked.Increment(ref version); - sourceFiles?.Clear(); + Interlocked.Increment(ref version); + sourceFiles?.Clear(); - var oldValue = isDirty; - isDirty = value; - Package?.OnAssetDirtyChanged(this, oldValue, value); - } + var oldValue = isDirty; + isDirty = value; + Package?.OnAssetDirtyChanged(this, oldValue, value); } + } + + public bool IsDeleted { get; set; } - public bool IsDeleted { get; set; } + public override string ToString() + { + return $"[{Asset.GetType().Name}] {location}"; + } + + /// + /// Creates a child asset that is inheriting the values of this asset. + /// + /// A new asset inheriting the values of this asset. + public Asset CreateDerivedAsset() + { + return Asset.CreateDerivedAsset(Location, out _); + } - public override string ToString() + /// + /// Finds the base item referenced by this item from the current session (using the setup + /// on this instance) + /// + /// The base item or null if not found. + public AssetItem? FindBase() + { + if (Package?.Session is null || Asset.Archetype is null) { - return $"[{Asset.GetType().Name}] {location}"; + return null; } + var session = Package.Session; + return session.FindAsset(Asset.Archetype.Id); + } + + /// + /// In case was null, generates it. + /// + public void UpdateSourceFolders() + { + Package?.UpdateSourceFolders([this]); + } - /// - /// Creates a child asset that is inheriting the values of this asset. - /// - /// A new asset inheriting the values of this asset. - [NotNull] - public Asset CreateDerivedAsset() + public ISet RetrieveCompilationInputFiles() + { + if (sourceFiles == null) { - Dictionary idRemapping; - return Asset.CreateDerivedAsset(Location, out idRemapping); + var collector = new SourceFilesCollector(); + sourceFiles = collector.GetCompilationInputFiles(Asset); } - /// - /// Finds the base item referenced by this item from the current session (using the setup - /// on this instance) - /// - /// The base item or null if not found. - [CanBeNull] - public AssetItem FindBase() + return sourceFiles; + } + + private class AssetItemComparerById : IEqualityComparer + { + public bool Equals(AssetItem? x, AssetItem? y) { - if (Package?.Session == null || Asset.Archetype == null) + if (ReferenceEquals(x, y)) + return true; + + if (x is null || y is null) { - return null; + return false; } - var session = Package.Session; - return session.FindAsset(Asset.Archetype.Id); - } - - /// - /// In case was null, generates it. - /// - public void UpdateSourceFolders() - { - Package.UpdateSourceFolders(new[] { this }); - } - public ISet RetrieveCompilationInputFiles() - { - if (sourceFiles == null) + if (ReferenceEquals(x.Asset, y.Asset)) { - var collector = new SourceFilesCollector(); - sourceFiles = collector.GetCompilationInputFiles(Asset); + return true; } - return sourceFiles; + return x.Id == y.Id; } - private class AssetItemComparerById : IEqualityComparer + public int GetHashCode(AssetItem obj) { - public bool Equals(AssetItem x, AssetItem y) - { - if (ReferenceEquals(x, y)) - return true; - - if (x == null || y == null) - { - return false; - } - - if (ReferenceEquals(x.Asset, y.Asset)) - { - return true; - } - - return x.Id == y.Id; - } - - public int GetHashCode(AssetItem obj) - { - return obj.Id.GetHashCode(); - } + return obj.Id.GetHashCode(); } } } diff --git a/sources/assets/Stride.Core.Assets/AssetItemExtensions.cs b/sources/assets/Stride.Core.Assets/AssetItemExtensions.cs index 5fdc044fb2..304abfe4e7 100644 --- a/sources/assets/Stride.Core.Assets/AssetItemExtensions.cs +++ b/sources/assets/Stride.Core.Assets/AssetItemExtensions.cs @@ -1,45 +1,41 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.IO; -using Stride.Core.Annotations; using Stride.Core.IO; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public static class AssetItemExtensions { - public static class AssetItemExtensions + /// + /// Gets the asset filename relative to its .csproj file for . + /// + /// The asset item. + /// + public static string GetProjectInclude(this AssetItem assetItem) { - /// - /// Gets the asset filename relative to its .csproj file for . - /// - /// The asset item. - /// - public static string GetProjectInclude([NotNull] this AssetItem assetItem) - { - var assetFullPath = assetItem.FullPath; - var projectFullPath = (assetItem.Package.Container as SolutionProject)?.FullPath; - return assetFullPath.MakeRelative(projectFullPath.GetFullDirectory()).ToOSPath(); - } + var assetFullPath = assetItem.FullPath; + var projectFullPath = (assetItem.Package?.Container as SolutionProject)?.FullPath; + return assetFullPath.MakeRelative(projectFullPath.GetFullDirectory()).ToOSPath(); + } - /// - /// If the asset is a , gets the generated file full path. - /// - /// The asset item. - /// - [NotNull] - public static UFile GetGeneratedAbsolutePath([NotNull] this AssetItem assetItem) - { - return new UFile(assetItem.FullPath + ".cs"); - } + /// + /// If the asset is a , gets the generated file full path. + /// + /// The asset item. + /// + public static UFile GetGeneratedAbsolutePath(this AssetItem assetItem) + { + return new UFile(assetItem.FullPath + ".cs"); + } - /// - /// If the asset is a , gets the generated file path relative to its containing .csproj. - /// - /// The asset item. - /// - public static string GetGeneratedInclude([NotNull] this AssetItem assetItem) - { - return GetProjectInclude(assetItem) + ".cs"; - } + /// + /// If the asset is a , gets the generated file path relative to its containing .csproj. + /// + /// The asset item. + /// + public static string GetGeneratedInclude(this AssetItem assetItem) + { + return GetProjectInclude(assetItem) + ".cs"; } } diff --git a/sources/assets/Stride.Core.Assets/AssetLogger.cs b/sources/assets/Stride.Core.Assets/AssetLogger.cs index f2295ecc44..394a777dca 100644 --- a/sources/assets/Stride.Core.Assets/AssetLogger.cs +++ b/sources/assets/Stride.Core.Assets/AssetLogger.cs @@ -1,30 +1,30 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Assets.Diagnostics; using Stride.Core.Diagnostics; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +internal sealed class AssetLogger : Logger { - class AssetLogger : Logger - { - private readonly Package package; - private readonly IReference assetReference; - private readonly string assetFullPath; - private readonly ILogger loggerToForward; + private readonly Package? package; + private readonly IReference? assetReference; + private readonly string assetFullPath; + private readonly ILogger loggerToForward; - public AssetLogger(Package package, IReference assetReference, string assetFullPath, ILogger loggerToForward) - { - this.package = package; - this.assetReference = assetReference; - this.assetFullPath = assetFullPath; - this.loggerToForward = loggerToForward; - ActivateLog(LogMessageType.Debug); - } + public AssetLogger(Package? package, IReference? assetReference, string assetFullPath, ILogger loggerToForward) + { + this.package = package; + this.assetReference = assetReference; + this.assetFullPath = assetFullPath; + this.loggerToForward = loggerToForward; + ActivateLog(LogMessageType.Debug); + } - protected override void LogRaw(ILogMessage logMessage) - { - loggerToForward?.Log(AssetLogMessage.From(package, assetReference, logMessage, assetFullPath)); - } + protected override void LogRaw(ILogMessage logMessage) + { + loggerToForward?.Log(AssetLogMessage.From(package, assetReference, logMessage, assetFullPath)); } } diff --git a/sources/assets/Stride.Core.Assets/AssetMember.cs b/sources/assets/Stride.Core.Assets/AssetMember.cs index 6ba2759b9e..545e861ed2 100644 --- a/sources/assets/Stride.Core.Assets/AssetMember.cs +++ b/sources/assets/Stride.Core.Assets/AssetMember.cs @@ -3,21 +3,20 @@ using Stride.Core.Reflection; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Represents a member of an asset. +/// +public struct AssetMember { /// - /// Represents a member of an asset. + /// The asset. /// - public struct AssetMember - { - /// - /// The asset. - /// - public Asset Asset; + public Asset Asset; - /// - /// The path to the member in the asset. - /// - public MemberPath MemberPath; - } + /// + /// The path to the member in the asset. + /// + public MemberPath MemberPath; } diff --git a/sources/assets/Stride.Core.Assets/AssetMigration.cs b/sources/assets/Stride.Core.Assets/AssetMigration.cs index 574a6a56f7..2b08010aa8 100644 --- a/sources/assets/Stride.Core.Assets/AssetMigration.cs +++ b/sources/assets/Stride.Core.Assets/AssetMigration.cs @@ -1,185 +1,170 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using System.Globalization; -using System.IO; -using System.Linq; using Stride.Core.Assets.Serializers; using Stride.Core.Diagnostics; -using Stride.Core; using Stride.Core.Yaml; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Helper for migrating asset to newer versions. +/// +public static class AssetMigration { - /// - /// Helper for migrating asset to newer versions. - /// - public static class AssetMigration + public static bool MigrateAssetIfNeeded(AssetMigrationContext context, PackageLoadingAssetFile loadAsset, string dependencyName, PackageVersion? untilVersion = null) { - public static bool MigrateAssetIfNeeded(AssetMigrationContext context, PackageLoadingAssetFile loadAsset, string dependencyName, PackageVersion untilVersion = null) - { - var assetFullPath = loadAsset.FilePath.FullPath; + var assetFullPath = loadAsset.FilePath.FullPath; - // Determine if asset was Yaml or not - var assetFileExtension = Path.GetExtension(assetFullPath); - if (assetFileExtension == null) - return false; + // Determine if asset was Yaml or not + var assetFileExtension = Path.GetExtension(assetFullPath); + if (assetFileExtension == null) + return false; - assetFileExtension = assetFileExtension.ToLowerInvariant(); + assetFileExtension = assetFileExtension.ToLowerInvariant(); - var serializer = AssetFileSerializer.FindSerializer(assetFileExtension); - if (!(serializer is YamlAssetSerializer)) - return false; + var serializer = AssetFileSerializer.FindSerializer(assetFileExtension); + if (serializer is not YamlAssetSerializer) + return false; - // We've got a Yaml asset, let's get expected and serialized versions - var serializedVersion = PackageVersion.Zero; - PackageVersion expectedVersion; - Type assetType; + // We've got a Yaml asset, let's get expected and serialized versions + var serializedVersion = PackageVersion.Zero; + PackageVersion expectedVersion; + Type assetType; - // Read from Yaml file the asset version and its type (to get expected version) - // Note: It tries to read as few as possible (SerializedVersion is expected to be right after Id, so it shouldn't try to read further than that) - using (var assetStream = loadAsset.OpenStream()) - using (var streamReader = new StreamReader(assetStream)) - { - var yamlEventReader = new EventReader(new Parser(streamReader)); + // Read from Yaml file the asset version and its type (to get expected version) + // Note: It tries to read as few as possible (SerializedVersion is expected to be right after Id, so it shouldn't try to read further than that) + using (var assetStream = loadAsset.OpenStream()) + using (var streamReader = new StreamReader(assetStream)) + { + var yamlEventReader = new EventReader(new Parser(streamReader)); - // Skip header - yamlEventReader.Expect(); - yamlEventReader.Expect(); - var mappingStart = yamlEventReader.Expect(); + // Skip header + yamlEventReader.Expect(); + yamlEventReader.Expect(); + var mappingStart = yamlEventReader.Expect(); - var tagTypeRegistry = AssetYamlSerializer.Default.GetSerializerSettings().TagTypeRegistry; - bool typeAliased; - assetType = tagTypeRegistry.TypeFromTag(mappingStart.Tag, out typeAliased); + var tagTypeRegistry = AssetYamlSerializer.Default.GetSerializerSettings().TagTypeRegistry; + assetType = tagTypeRegistry.TypeFromTag(mappingStart.Tag, out var typeAliased); - var expectedVersions = AssetRegistry.GetCurrentFormatVersions(assetType); - expectedVersion = expectedVersions?.FirstOrDefault(x => x.Key == dependencyName).Value ?? PackageVersion.Zero; + var expectedVersions = AssetRegistry.GetCurrentFormatVersions(assetType); + expectedVersion = expectedVersions?.FirstOrDefault(x => x.Key == dependencyName).Value ?? PackageVersion.Zero; - Scalar assetKey; - while ((assetKey = yamlEventReader.Allow()) != null) + Scalar assetKey; + while ((assetKey = yamlEventReader.Allow()) != null) + { + // Only allow Id before SerializedVersion + if (assetKey.Value == nameof(Asset.Id)) { - // Only allow Id before SerializedVersion - if (assetKey.Value == nameof(Asset.Id)) - { - yamlEventReader.Skip(); - } - else if (assetKey.Value == nameof(Asset.SerializedVersion)) + yamlEventReader.Skip(); + } + else if (assetKey.Value == nameof(Asset.SerializedVersion)) + { + // Check for old format: only a scalar + var scalarVersion = yamlEventReader.Allow(); + if (scalarVersion != null) { - // Check for old format: only a scalar - var scalarVersion = yamlEventReader.Allow(); - if (scalarVersion != null) - { - serializedVersion = PackageVersion.Parse("0.0." + Convert.ToInt32(scalarVersion.Value, CultureInfo.InvariantCulture)); + serializedVersion = PackageVersion.Parse("0.0." + Convert.ToInt32(scalarVersion.Value, CultureInfo.InvariantCulture)); + + // Let's update to new format + using var yamlAsset = loadAsset.AsYamlAsset(); + yamlAsset.DynamicRootNode.RemoveChild(nameof(Asset.SerializedVersion)); + AssetUpgraderBase.SetSerializableVersion(yamlAsset.DynamicRootNode, dependencyName, serializedVersion); - // Let's update to new format - using (var yamlAsset = loadAsset.AsYamlAsset()) + var baseBranch = yamlAsset.DynamicRootNode["~Base"]; + if (baseBranch != null) + { + var baseAsset = baseBranch["Asset"]; + if (baseAsset != null) { - yamlAsset.DynamicRootNode.RemoveChild(nameof(Asset.SerializedVersion)); - AssetUpgraderBase.SetSerializableVersion(yamlAsset.DynamicRootNode, dependencyName, serializedVersion); - - var baseBranch = yamlAsset.DynamicRootNode["~Base"]; - if (baseBranch != null) - { - var baseAsset = baseBranch["Asset"]; - if (baseAsset != null) - { - baseAsset.RemoveChild(nameof(Asset.SerializedVersion)); - AssetUpgraderBase.SetSerializableVersion(baseAsset, dependencyName, serializedVersion); - } - } + baseAsset.RemoveChild(nameof(Asset.SerializedVersion)); + AssetUpgraderBase.SetSerializableVersion(baseAsset, dependencyName, serializedVersion); } } - else + } + else + { + // New format: package => version mapping + yamlEventReader.Expect(); + + while (!yamlEventReader.Accept()) { - // New format: package => version mapping - yamlEventReader.Expect(); + var packageName = yamlEventReader.Expect().Value; + var packageVersion = PackageVersion.Parse(yamlEventReader.Expect().Value); - while (!yamlEventReader.Accept()) + // For now, we handle only one dependency at a time + if (packageName == dependencyName) { - var packageName = yamlEventReader.Expect().Value; - var packageVersion = PackageVersion.Parse(yamlEventReader.Expect().Value); - - // For now, we handle only one dependency at a time - if (packageName == dependencyName) - { - serializedVersion = packageVersion; - } + serializedVersion = packageVersion; } - - yamlEventReader.Expect(); } - break; - } - else - { - // If anything else than Id or SerializedVersion, let's stop - break; + + yamlEventReader.Expect(); } + break; + } + else + { + // If anything else than Id or SerializedVersion, let's stop + break; } } + } - if (serializedVersion > expectedVersion) - { - // Try to open an asset newer than what we support (probably generated by a newer Stride) - throw new InvalidOperationException($"Asset of type {assetType} has been serialized with newer version {serializedVersion}, but only version {expectedVersion} is supported. Was this asset created with a newer version of Stride?"); - } - - if (serializedVersion < expectedVersion) - { - // Perform asset upgrade - context.Log.Verbose($"{Path.GetFullPath(assetFullPath)} needs update, from version {serializedVersion} to version {expectedVersion}"); - - using (var yamlAsset = loadAsset.AsYamlAsset()) - { - var yamlRootNode = yamlAsset.RootNode; + if (serializedVersion > expectedVersion) + { + // Try to open an asset newer than what we support (probably generated by a newer Stride) + throw new InvalidOperationException($"Asset of type {assetType} has been serialized with newer version {serializedVersion}, but only version {expectedVersion} is supported. Was this asset created with a newer version of Stride?"); + } - // Check if there is any asset updater - var assetUpgraders = AssetRegistry.GetAssetUpgraders(assetType, dependencyName); - if (assetUpgraders == null) - { - throw new InvalidOperationException($"Asset of type {assetType} should be updated from version {serializedVersion} to {expectedVersion}, but no asset migration path was found"); - } + if (serializedVersion < expectedVersion) + { + // Perform asset upgrade + context.Log.Verbose($"{Path.GetFullPath(assetFullPath)} needs update, from version {serializedVersion} to version {expectedVersion}"); - // Instantiate asset updaters - var currentVersion = serializedVersion; - while (currentVersion != expectedVersion) - { - PackageVersion targetVersion; - // This will throw an exception if no upgrader is available for the given version, exiting the loop in case of error. - var upgrader = assetUpgraders.GetUpgrader(currentVersion, out targetVersion); + using var yamlAsset = loadAsset.AsYamlAsset(); + var yamlRootNode = yamlAsset.RootNode; - // Stop if the next version would be higher than what is expected - if (untilVersion != null && targetVersion > untilVersion) - break; + // Check if there is any asset updater + var assetUpgraders = AssetRegistry.GetAssetUpgraders(assetType, dependencyName) + ?? throw new InvalidOperationException($"Asset of type {assetType} should be updated from version {serializedVersion} to {expectedVersion}, but no asset migration path was found"); - upgrader.Upgrade(context, dependencyName, currentVersion, targetVersion, yamlRootNode, loadAsset); - currentVersion = targetVersion; - } + // Instantiate asset updaters + var currentVersion = serializedVersion; + while (currentVersion != expectedVersion) + { + // This will throw an exception if no upgrader is available for the given version, exiting the loop in case of error. + var upgrader = assetUpgraders.GetUpgrader(currentVersion, out var targetVersion); - // Make sure asset is updated to latest version - YamlNode serializedVersionNode; - PackageVersion newSerializedVersion = null; - if (yamlRootNode.Children.TryGetValue(new YamlScalarNode(nameof(Asset.SerializedVersion)), out serializedVersionNode)) - { - var newSerializedVersionForDefaultPackage = ((YamlMappingNode)serializedVersionNode).Children[new YamlScalarNode(dependencyName)]; - newSerializedVersion = PackageVersion.Parse(((YamlScalarNode)newSerializedVersionForDefaultPackage).Value); - } + // Stop if the next version would be higher than what is expected + if (untilVersion != null && targetVersion > untilVersion) + break; - if (untilVersion == null && newSerializedVersion != expectedVersion) - { - throw new InvalidOperationException($"Asset of type {assetType} was migrated, but still its new version {newSerializedVersion} doesn't match expected version {expectedVersion}."); - } + upgrader.Upgrade(context, dependencyName, currentVersion, targetVersion, yamlRootNode, loadAsset); + currentVersion = targetVersion; + } - context.Log.Verbose($"{Path.GetFullPath(assetFullPath)} updated from version {serializedVersion} to version {expectedVersion}"); - } + // Make sure asset is updated to latest version + PackageVersion? newSerializedVersion = null; + if (yamlRootNode.Children.TryGetValue(new YamlScalarNode(nameof(Asset.SerializedVersion)), out var serializedVersionNode)) + { + var newSerializedVersionForDefaultPackage = ((YamlMappingNode)serializedVersionNode).Children[new YamlScalarNode(dependencyName)]; + newSerializedVersion = PackageVersion.Parse(((YamlScalarNode)newSerializedVersionForDefaultPackage).Value); + } - return true; + if (untilVersion == null && newSerializedVersion != expectedVersion) + { + throw new InvalidOperationException($"Asset of type {assetType} was migrated, but still its new version {newSerializedVersion} doesn't match expected version {expectedVersion}."); } - return false; + context.Log.Verbose($"{Path.GetFullPath(assetFullPath)} updated from version {serializedVersion} to version {expectedVersion}"); + + return true; } + + return false; } } diff --git a/sources/assets/Stride.Core.Assets/AssetMigrationContext.cs b/sources/assets/Stride.Core.Assets/AssetMigrationContext.cs index 411d9a98bf..57a0b663cb 100644 --- a/sources/assets/Stride.Core.Assets/AssetMigrationContext.cs +++ b/sources/assets/Stride.Core.Assets/AssetMigrationContext.cs @@ -1,46 +1,43 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using Stride.Core.Diagnostics; using Stride.Core.Serialization.Contents; -using Stride.Core.Yaml; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Context used by . +/// +public class AssetMigrationContext { /// - /// Context used by . + /// Initializes a new instance of . /// - public class AssetMigrationContext + /// + /// + /// + /// + public AssetMigrationContext(Package? package, IReference? assetReference, string assetFullPath, ILogger log) { - /// - /// Initializes a new instance of . - /// - /// - /// - /// - /// - public AssetMigrationContext(Package package, IReference assetReference, string assetFullPath, ILogger log) - { - if (log == null) throw new ArgumentNullException(nameof(log)); - Package = package; - AssetReference = assetReference; - AssetFullPath = assetFullPath; - Log = new AssetLogger(package, assetReference, assetFullPath, log); - } + ArgumentNullException.ThrowIfNull(log); + Package = package; + AssetReference = assetReference; + AssetFullPath = assetFullPath; + Log = new AssetLogger(package, assetReference, assetFullPath, log); + } - /// - /// The current package where the current asset is being migrated. This is null when the asset being migrated is a package. - /// - public Package Package { get; } + /// + /// The current package where the current asset is being migrated. This is null when the asset being migrated is a package. + /// + public Package? Package { get; } - public IReference AssetReference { get; } + public IReference? AssetReference { get; } - public string AssetFullPath { get; } + public string AssetFullPath { get; } - /// - /// The logger for this context. - /// - public ILogger Log { get; } - } + /// + /// The logger for this context. + /// + public ILogger Log { get; } } diff --git a/sources/assets/Stride.Core.Assets/AssetPart.cs b/sources/assets/Stride.Core.Assets/AssetPart.cs index 7a79d92efe..f83eb35e22 100644 --- a/sources/assets/Stride.Core.Assets/AssetPart.cs +++ b/sources/assets/Stride.Core.Assets/AssetPart.cs @@ -1,81 +1,70 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// A part asset contained by an asset that is . +/// +[DataContract("AssetPart")] +[Obsolete("This struct might be removed soon")] +public readonly struct AssetPart : IEquatable { - /// - /// A part asset contained by an asset that is . - /// - [DataContract("AssetPart")] - [Obsolete("This struct might be removed soon")] - public struct AssetPart : IEquatable + public AssetPart(Guid partId, BasePart? basePart, Action baseUpdater) { - public AssetPart(Guid partId, BasePart basePart, Action baseUpdater) - { - if (baseUpdater == null) throw new ArgumentNullException(nameof(baseUpdater)); - if (partId == Guid.Empty) throw new ArgumentException(@"A part Id cannot be empty.", nameof(partId)); - PartId = partId; - Base = basePart; - this.baseUpdater = baseUpdater; - } + ArgumentNullException.ThrowIfNull(baseUpdater); + if (partId == Guid.Empty) throw new ArgumentException("A part Id cannot be empty.", nameof(partId)); + PartId = partId; + Base = basePart; + this.baseUpdater = baseUpdater; + } - /// - /// Asset identifier. - /// - public readonly Guid PartId; + /// + /// Asset identifier. + /// + public readonly Guid PartId; - /// - /// Base asset identifier. - /// - public readonly BasePart Base; + /// + /// Base asset identifier. + /// + public readonly BasePart? Base; - private readonly Action baseUpdater; + private readonly Action baseUpdater; - public void UpdateBase(BasePart newBase) - { - baseUpdater(newBase); - } + public readonly void UpdateBase(BasePart newBase) + { + baseUpdater(newBase); + } - /// - public bool Equals(AssetPart other) - { - return PartId.Equals(other.PartId) && - Equals(Base?.BasePartAsset.Id, other.Base?.BasePartAsset.Id) && - Equals(Base?.BasePartId, other.Base?.BasePartId) && - Equals(Base?.InstanceId, other.Base?.InstanceId); - } + /// + public readonly bool Equals(AssetPart other) + { + return PartId.Equals(other.PartId) && + Equals(Base?.BasePartAsset.Id, other.Base?.BasePartAsset.Id) && + Equals(Base?.BasePartId, other.Base?.BasePartId) && + Equals(Base?.InstanceId, other.Base?.InstanceId); + } - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is AssetPart && Equals((AssetPart)obj); - } + /// + public override readonly bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is AssetPart assetPart && Equals(assetPart); + } - /// - public override int GetHashCode() - { - unchecked - { - var hashCode = PartId.GetHashCode(); - if (Base != null) - { - hashCode = (hashCode*397) ^ Base.GetHashCode(); - } - return hashCode; - } - } + /// + public override readonly int GetHashCode() + { + return HashCode.Combine(PartId, Base); + } - public static bool operator ==(AssetPart left, AssetPart right) - { - return left.Equals(right); - } + public static bool operator ==(AssetPart left, AssetPart right) + { + return left.Equals(right); + } - public static bool operator !=(AssetPart left, AssetPart right) - { - return !left.Equals(right); - } + public static bool operator !=(AssetPart left, AssetPart right) + { + return !left.Equals(right); } } diff --git a/sources/assets/Stride.Core.Assets/AssetPartCollection.cs b/sources/assets/Stride.Core.Assets/AssetPartCollection.cs index 553ac54553..6a27245f52 100644 --- a/sources/assets/Stride.Core.Assets/AssetPartCollection.cs +++ b/sources/assets/Stride.Core.Assets/AssetPartCollection.cs @@ -1,101 +1,96 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Core; -using Stride.Core.Annotations; + using Stride.Core.Serialization; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +[DataSerializer(typeof(AssetPartCollectionSerializer<,>), Mode = DataSerializerGenericMode.GenericArguments)] +public sealed class AssetPartCollection : SortedList + where TAssetPartDesign : IAssetPartDesign + where TAssetPart : IIdentifiable { - [DataSerializer(typeof(AssetPartCollectionSerializer<,>), Mode = DataSerializerGenericMode.GenericArguments)] - public sealed class AssetPartCollection : SortedList - where TAssetPartDesign : IAssetPartDesign - where TAssetPart : IIdentifiable + public void Add(TAssetPartDesign part) { - public void Add([NotNull] TAssetPartDesign part) - { - if (part == null) throw new ArgumentNullException(nameof(part)); - Add(part.Part.Id, part); - } + if (part == null) throw new ArgumentNullException(nameof(part)); + Add(part.Part.Id, part); + } - public void Add(KeyValuePair part) - { - if (part.Value == null) throw new ArgumentNullException(nameof(part)); - if (part.Key != part.Value.Part.Id) throw new ArgumentException(@"The guid of the key does not match the guid of the value", nameof(part)); - Add(part.Key, part.Value); - } + public void Add(KeyValuePair part) + { + if (part.Value == null) throw new ArgumentNullException(nameof(part)); + if (part.Key != part.Value.Part.Id) throw new ArgumentException(@"The guid of the key does not match the guid of the value", nameof(part)); + Add(part.Key, part.Value); + } - /// - /// Refreshes the keys of this collection. Must be called if some ids of the contained parts have changed. - /// - public void RefreshKeys() + /// + /// Refreshes the keys of this collection. Must be called if some ids of the contained parts have changed. + /// + public void RefreshKeys() + { + var values = Values.ToList(); + Clear(); + foreach (var value in values) { - var values = Values.ToList(); - Clear(); - foreach (var value in values) - { - Add(value.Part.Id, value); - } + Add(value.Part.Id, value); } } +} - public class AssetPartCollectionSerializer : DataSerializer>, IDataSerializerGenericInstantiation - where TAssetPartDesign : IAssetPartDesign - where TAssetPart : IIdentifiable +public class AssetPartCollectionSerializer : DataSerializer>, IDataSerializerGenericInstantiation + where TAssetPartDesign : IAssetPartDesign + where TAssetPart : IIdentifiable +{ + private DataSerializer valueSerializer; + + /// + public override void Initialize(SerializerSelector serializerSelector) { - private DataSerializer valueSerializer; + valueSerializer = MemberSerializer.Create(serializerSelector); + } - /// - public override void Initialize(SerializerSelector serializerSelector) + /// + public override void PreSerialize(ref AssetPartCollection obj, ArchiveMode mode, SerializationStream stream) + { + if (mode == ArchiveMode.Deserialize) { - valueSerializer = MemberSerializer.Create(serializerSelector); + // TODO: Peek the SortedList size + if (obj == null) + obj = []; + else + obj.Clear(); } + } - /// - public override void PreSerialize(ref AssetPartCollection obj, ArchiveMode mode, SerializationStream stream) + /// + public override void Serialize(ref AssetPartCollection obj, ArchiveMode mode, SerializationStream stream) + { + if (mode == ArchiveMode.Deserialize) { - if (mode == ArchiveMode.Deserialize) + // Should be null if it was + var count = stream.ReadInt32(); + for (var i = 0; i < count; ++i) { - // TODO: Peek the SortedList size - if (obj == null) - obj = new AssetPartCollection(); - else - obj.Clear(); + var value = default(TAssetPartDesign); + valueSerializer.Serialize(ref value, mode, stream); + var key = value.Part.Id; + obj.Add(key, value); } } - - /// - public override void Serialize(ref AssetPartCollection obj, ArchiveMode mode, SerializationStream stream) + else if (mode == ArchiveMode.Serialize) { - if (mode == ArchiveMode.Deserialize) + stream.Write(obj.Count); + foreach (var item in obj) { - // Should be null if it was - var count = stream.ReadInt32(); - for (var i = 0; i < count; ++i) - { - var value = default(TAssetPartDesign); - valueSerializer.Serialize(ref value, mode, stream); - var key = value.Part.Id; - obj.Add(key, value); - } - } - else if (mode == ArchiveMode.Serialize) - { - stream.Write(obj.Count); - foreach (var item in obj) - { - valueSerializer.Serialize(item.Value, stream); - } + valueSerializer.Serialize(item.Value, stream); } } + } - /// - public void EnumerateGenericInstantiations(SerializerSelector serializerSelector, [NotNull] IList genericInstantiations) - { - genericInstantiations.Add(typeof(Guid)); - genericInstantiations.Add(typeof(TAssetPartDesign)); - } + /// + public void EnumerateGenericInstantiations(SerializerSelector serializerSelector, IList genericInstantiations) + { + genericInstantiations.Add(typeof(Guid)); + genericInstantiations.Add(typeof(TAssetPartDesign)); } } diff --git a/sources/assets/Stride.Core.Assets/AssetReference.cs b/sources/assets/Stride.Core.Assets/AssetReference.cs index ab08fac29c..475cb876c3 100644 --- a/sources/assets/Stride.Core.Assets/AssetReference.cs +++ b/sources/assets/Stride.Core.Assets/AssetReference.cs @@ -1,181 +1,173 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; -using Stride.Core.Annotations; + +using System.Diagnostics.CodeAnalysis; using Stride.Core.IO; using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// An asset reference. +/// +[DataContract("aref")] +[DataStyle(DataStyle.Compact)] +[DataSerializer(typeof(AssetReferenceDataSerializer))] +public sealed class AssetReference : IReference, IEquatable { /// - /// An asset reference. + /// Initializes a new instance of the class. /// - [DataContract("aref")] - [DataStyle(DataStyle.Compact)] - [DataSerializer(typeof(AssetReferenceDataSerializer))] - public sealed class AssetReference : IReference, IEquatable + /// The unique identifier of the asset. + /// The location. + public AssetReference(AssetId id, UFile location) { - /// - /// Initializes a new instance of the class. - /// - /// The unique identifier of the asset. - /// The location. - public AssetReference(AssetId id, UFile location) - { - Location = location; - Id = id; - } + Location = location; + Id = id; + } - /// - /// Gets or sets the unique identifier of the reference asset. - /// - /// The unique identifier of the reference asset.. - [DataMember(10)] - public AssetId Id { get; init; } - - /// - /// Gets or sets the location of the asset. - /// - /// The location. - [DataMember(20)] - public string Location { get; init; } - - public bool Equals(AssetReference other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(Location, other.Location) && Id.Equals(other.Id); - } + /// + /// Gets or sets the unique identifier of the reference asset. + /// + /// The unique identifier of the reference asset.. + [DataMember(10)] + public AssetId Id { get; init; } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return Equals(obj as AssetReference); - } + /// + /// Gets or sets the location of the asset. + /// + /// The location. + [DataMember(20)] + public string Location { get; init; } - public override int GetHashCode() - { - unchecked - { - return ((Location?.GetHashCode() ?? 0) * 397) ^ Id.GetHashCode(); - } - } + public bool Equals(AssetReference? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Location, other.Location) && Id.Equals(other.Id); + } - /// - /// Implements the ==. - /// - /// The left. - /// The right. - /// The result of the operator. - public static bool operator ==(AssetReference left, AssetReference right) - { - return Equals(left, right); - } + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as AssetReference); + } - /// - /// Implements the !=. - /// - /// The left. - /// The right. - /// The result of the operator. - public static bool operator !=(AssetReference left, AssetReference right) - { - return !Equals(left, right); - } + public override int GetHashCode() + { + return HashCode.Combine(Location, Id); + } + + /// + /// Implements the ==. + /// + /// The left. + /// The right. + /// The result of the operator. + public static bool operator ==(AssetReference? left, AssetReference? right) + { + return Equals(left, right); + } - /// - public override string ToString() + /// + /// Implements the !=. + /// + /// The left. + /// The right. + /// The result of the operator. + public static bool operator !=(AssetReference? left, AssetReference? right) + { + return !Equals(left, right); + } + + /// + public override string ToString() + { + // WARNING: This should not be modified as it is used for serializing + return $"{Id}:{Location}"; + } + + /// + /// Tries to parse an asset reference in the format "GUID:Location". + /// + /// The identifier. + /// The location. + /// true if parsing was successful, false otherwise. + public static AssetReference New(AssetId id, UFile location) + { + return new AssetReference(id, location); + } + + /// + /// Tries to parse an asset reference in the format "[GUID/]GUID:Location". The first GUID is optional and is used to store the ID of the reference. + /// + /// The asset reference. + /// The unique identifier of asset pointed by this reference. + /// The location. + /// true if parsing was successful, false otherwise. + /// assetReferenceText + public static bool TryParse(string assetReferenceText, out AssetId id, [MaybeNullWhen(false)] out UFile location) + { + ArgumentNullException.ThrowIfNull(assetReferenceText); + + id = AssetId.Empty; + location = null; + var indexFirstSlash = assetReferenceText.IndexOf('/'); + var indexBeforelocation = assetReferenceText.IndexOf(':'); + if (indexBeforelocation < 0) { - // WARNING: This should not be modified as it is used for serializing - return $"{Id}:{Location}"; + return false; } - - /// - /// Tries to parse an asset reference in the format "GUID:Location". - /// - /// The identifier. - /// The location. - /// true if parsing was successful, false otherwise. - [NotNull] - public static AssetReference New(AssetId id, UFile location) + var startNextGuid = 0; + if (indexFirstSlash > 0 && indexFirstSlash < indexBeforelocation) { - return new AssetReference(id, location); + startNextGuid = indexFirstSlash + 1; } - /// - /// Tries to parse an asset reference in the format "[GUID/]GUID:Location". The first GUID is optional and is used to store the ID of the reference. - /// - /// The asset reference. - /// The unique identifier of asset pointed by this reference. - /// The location. - /// true if parsing was successful, false otherwise. - /// assetReferenceText - public static bool TryParse([NotNull] string assetReferenceText, out AssetId id, out UFile location) + if (!AssetId.TryParse(assetReferenceText[startNextGuid..indexBeforelocation], out id)) { - if (assetReferenceText == null) throw new ArgumentNullException(nameof(assetReferenceText)); - - id = AssetId.Empty; - location = null; - var indexFirstSlash = assetReferenceText.IndexOf('/'); - var indexBeforelocation = assetReferenceText.IndexOf(':'); - if (indexBeforelocation < 0) - { - return false; - } - var startNextGuid = 0; - if (indexFirstSlash > 0 && indexFirstSlash < indexBeforelocation) - { - startNextGuid = indexFirstSlash + 1; - } - - if (!AssetId.TryParse(assetReferenceText.Substring(startNextGuid, indexBeforelocation - startNextGuid), out id)) - { - return false; - } - - location = new UFile(assetReferenceText.Substring(indexBeforelocation + 1)); - - return true; + return false; } - /// - /// Tries to parse an asset reference in the format "GUID:Location". - /// - /// The asset reference. - /// The reference. - /// true if parsing was successful, false otherwise. - public static bool TryParse([NotNull] string assetReferenceText, out AssetReference assetReference) - { - if (assetReferenceText == null) throw new ArgumentNullException(nameof(assetReferenceText)); + location = new UFile(assetReferenceText[(indexBeforelocation + 1)..]); - assetReference = null; - AssetId assetId; - UFile location; - if (!TryParse(assetReferenceText, out assetId, out location)) - { - return false; - } - assetReference = New(assetId, location); - return true; - } + return true; } /// - /// Extension methods for + /// Tries to parse an asset reference in the format "GUID:Location". /// - public static class AssetReferenceExtensions + /// The asset reference. + /// The reference. + /// true if parsing was successful, false otherwise. + public static bool TryParse(string assetReferenceText, [MaybeNullWhen(false)] out AssetReference assetReference) { - /// - /// Determines whether the specified asset reference has location. If the reference is null, return false. - /// - /// The asset reference. - /// true if the specified asset reference has location; otherwise, false. - public static bool HasLocation(this AssetReference assetReference) + ArgumentNullException.ThrowIfNull(assetReferenceText); + + if (!TryParse(assetReferenceText, out var assetId, out var location)) { - return assetReference != null && assetReference.Location != null; + assetReference = null; + return false; } + assetReference = New(assetId, location); + return true; + } +} + +/// +/// Extension methods for +/// +public static class AssetReferenceExtensions +{ + /// + /// Determines whether the specified asset reference has location. If the reference is null, return false. + /// + /// The asset reference. + /// true if the specified asset reference has location; otherwise, false. + public static bool HasLocation(this AssetReference assetReference) + { + return assetReference?.Location != null; } } diff --git a/sources/assets/Stride.Core.Assets/AssetReferenceDataSerializer.cs b/sources/assets/Stride.Core.Assets/AssetReferenceDataSerializer.cs index 465cf39465..28170adffb 100644 --- a/sources/assets/Stride.Core.Assets/AssetReferenceDataSerializer.cs +++ b/sources/assets/Stride.Core.Assets/AssetReferenceDataSerializer.cs @@ -1,31 +1,29 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Serialization; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Serializer for . +/// +public sealed class AssetReferenceDataSerializer : DataSerializer { - /// - /// Serializer for . - /// - /// - public sealed class AssetReferenceDataSerializer : DataSerializer + /// + public override void Serialize(ref AssetReference assetReference, ArchiveMode mode, SerializationStream stream) { - /// - public override void Serialize(ref AssetReference assetReference, ArchiveMode mode, SerializationStream stream) + if (mode == ArchiveMode.Serialize) + { + stream.Write(assetReference.Id); + stream.Write(assetReference.Location); + } + else { - if (mode == ArchiveMode.Serialize) - { - stream.Write(assetReference.Id); - stream.Write(assetReference.Location); - } - else - { - var id = stream.Read(); - var location = stream.ReadString(); + var id = stream.Read(); + var location = stream.ReadString(); - assetReference = new AssetReference(id, location); - } + assetReference = new AssetReference(id, location); } } } diff --git a/sources/assets/Stride.Core.Assets/AssetRegistry.cs b/sources/assets/Stride.Core.Assets/AssetRegistry.cs index 40a591b20d..2df0ea6cd2 100644 --- a/sources/assets/Stride.Core.Assets/AssetRegistry.cs +++ b/sources/assets/Stride.Core.Assets/AssetRegistry.cs @@ -1,13 +1,10 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using Stride.Core.Assets.Analysis; -using Stride.Core; using Stride.Core.Diagnostics; using Stride.Core.Reflection; using Stride.Core.Serialization.Contents; @@ -15,374 +12,370 @@ using Stride.Core.Yaml.Serialization; using Stride.Core.Serialization; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// A registry for various content associated with assets. +/// +public static class AssetRegistry { + private static readonly Logger Log = GlobalLogger.GetLogger("Assets.Registry"); + + private static readonly Dictionary RegisteredDefaultAssetExtension = []; + private static readonly HashSet AssetTypes = []; + private static readonly HashSet RegisteredPackageSessionAnalysisTypes = []; + private static readonly List RegisteredImportersInternal = []; + private static readonly Dictionary> RegisteredFormatVersions = []; + private static readonly HashSet AlwaysMarkAsRootAssetTypes = []; + private static readonly Dictionary, AssetUpgraderCollection> RegisteredAssetUpgraders = []; + private static readonly Dictionary RegisteredAssetFileExtensions = new(StringComparer.InvariantCultureIgnoreCase); + private static readonly Dictionary RegisteredPackageUpgraders = []; + private static readonly HashSet RegisteredEngineAssemblies = []; + private static readonly HashSet RegisteredAssetAssemblies = []; + private static readonly HashSet RegisteredSerializerFactories = []; + private static readonly List RegisteredDataVisitNodes = []; + private static readonly Dictionary> RegisteredAssetFactories = []; + private static readonly Dictionary?> RegisteredContentTypes = []; // FIXME values are always null + private static readonly Dictionary> AssignableToContent = []; + private static readonly Dictionary> ContentToAssetTypes = []; + private static readonly Dictionary AssetToContentTypes = []; + + // Global lock used to secure the registry with threads + private static readonly object RegistryLock = new(); + /// - /// A registry for various content associated with assets. + /// Gets the list of engine assemblies currently registered. /// - public static class AssetRegistry - { - private static readonly Logger Log = GlobalLogger.GetLogger("Assets.Registry"); - - private static readonly Dictionary RegisteredDefaultAssetExtension = new Dictionary(); - private static readonly HashSet AssetTypes = new HashSet(); - private static readonly HashSet RegisteredPackageSessionAnalysisTypes = new HashSet(); - private static readonly List RegisteredImportersInternal = new List(); - private static readonly Dictionary> RegisteredFormatVersions = new Dictionary>(); - private static readonly HashSet AlwaysMarkAsRootAssetTypes = new HashSet(); - private static readonly Dictionary, AssetUpgraderCollection> RegisteredAssetUpgraders = new Dictionary, AssetUpgraderCollection>(); - private static readonly Dictionary RegisteredAssetFileExtensions = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - private static readonly Dictionary RegisteredPackageUpgraders = new Dictionary(); - private static readonly HashSet RegisteredEngineAssemblies = new HashSet(); - private static readonly HashSet RegisteredAssetAssemblies = new HashSet(); - private static readonly HashSet RegisteredSerializerFactories = new HashSet(); - private static readonly List RegisteredDataVisitNodes = new List(); - private static readonly Dictionary> RegisteredAssetFactories = new Dictionary>(); - private static readonly Dictionary> RegisteredContentTypes = new Dictionary>(); - private static readonly Dictionary> AssignableToContent = new Dictionary>(); - private static readonly Dictionary> ContentToAssetTypes = new Dictionary>(); - private static readonly Dictionary AssetToContentTypes = new Dictionary(); - - // Global lock used to secure the registry with threads - private static readonly object RegistryLock = new object(); - - /// - /// Gets the list of engine assemblies currently registered. - /// - public static IEnumerable EngineAssemblies { get { lock (RegistryLock) { return RegisteredEngineAssemblies.ToList(); } } } - - /// - /// Gets the list of asset assemblies currently registered. - /// - public static IEnumerable AssetAssemblies { get { lock (RegistryLock) { return RegisteredAssetAssemblies.ToList(); } } } - - /// - /// Gets the supported platforms. - /// - /// The supported platforms. - public static SolutionPlatformCollection SupportedPlatforms { get; } - - /// - /// Gets an enumeration of registered importers. - /// - /// The registered importers. - public static IEnumerable RegisteredImporters { get { lock (RegistryLock) { return RegisteredImportersInternal; } } } - - /// - /// Registers the supported platforms. - /// - /// The platforms. - /// platforms - public static void RegisterSupportedPlatforms(List platforms) - { - if (platforms == null) throw new ArgumentNullException(nameof(platforms)); - if (SupportedPlatforms.Count > 0) throw new InvalidOperationException("Cannot register new platforms. RegisterSupportedPlatforms can only be called once"); - SupportedPlatforms.AddRange(platforms); - } - - /// - /// Determines whether the file is an asset file type. - /// - /// The file. - /// true if [is asset file file] [the specified file]; otherwise, false. - public static bool IsAssetFileExtension(string extension) - { - if (extension == null) return false; - lock (RegistryLock) - { - return RegisteredAssetFileExtensions.ContainsKey(extension); - } + public static IEnumerable EngineAssemblies { get { lock (RegistryLock) { return [.. RegisteredEngineAssemblies]; } } } + + /// + /// Gets the list of asset assemblies currently registered. + /// + public static IEnumerable AssetAssemblies { get { lock (RegistryLock) { return [.. RegisteredAssetAssemblies]; } } } + + /// + /// Gets the supported platforms. + /// + /// The supported platforms. + public static SolutionPlatformCollection SupportedPlatforms { get; } + + /// + /// Gets an enumeration of registered importers. + /// + /// The registered importers. + public static IEnumerable RegisteredImporters { get { lock (RegistryLock) { return RegisteredImportersInternal; } } } + + /// + /// Registers the supported platforms. + /// + /// The platforms. + /// platforms + public static void RegisterSupportedPlatforms(List platforms) + { + ArgumentNullException.ThrowIfNull(platforms); + if (SupportedPlatforms.Count > 0) throw new InvalidOperationException("Cannot register new platforms. RegisterSupportedPlatforms can only be called once"); + SupportedPlatforms.AddRange(platforms); + } + + /// + /// Determines whether the file is an asset file type. + /// + /// The file. + /// true if [is asset file file] [the specified file]; otherwise, false. + public static bool IsAssetFileExtension(string? extension) + { + if (extension == null) return false; + lock (RegistryLock) + { + return RegisteredAssetFileExtensions.ContainsKey(extension); } + } - /// - /// Gets the asset type from the extension. If no asset type is found, return null. - /// - /// The extension of the asset file. - /// Type of the associated asset or null if not found. - public static Type GetAssetTypeFromFileExtension(string extension) + /// + /// Gets the asset type from the extension. If no asset type is found, return null. + /// + /// The extension of the asset file. + /// Type of the associated asset or null if not found. + public static Type? GetAssetTypeFromFileExtension(string extension) + { + ArgumentNullException.ThrowIfNull(extension); + lock (RegistryLock) { - if (extension == null) throw new ArgumentNullException(nameof(extension)); - lock (RegistryLock) - { - Type result; - RegisteredAssetFileExtensions.TryGetValue(extension, out result); - return result; - } + RegisteredAssetFileExtensions.TryGetValue(extension, out var result); + return result; } + } - public static bool IsProjectCodeGeneratorAssetFileExtension(string extension) + public static bool IsProjectCodeGeneratorAssetFileExtension(string? extension) + { + if (extension == null) return false; + lock (RegisteredAssetFileExtensions) { - if (extension == null) return false; - lock (RegisteredAssetFileExtensions) + var valid = RegisteredAssetFileExtensions.ContainsKey(extension); + if (valid) { - var valid = RegisteredAssetFileExtensions.ContainsKey(extension); - if (valid) + var type = RegisteredDefaultAssetExtension.Where(x => x.Value == extension).Select(x => x.Key).FirstOrDefault(); + if (type != null) { - var type = RegisteredDefaultAssetExtension.Where(x => x.Value == extension).Select(x => x.Key).FirstOrDefault(); - if (type != null) - { - return typeof(IProjectFileGeneratorAsset).IsAssignableFrom(type); - } + return typeof(IProjectFileGeneratorAsset).IsAssignableFrom(type); } - return false; } + return false; } + } - public static bool IsProjectAssetFileExtension(string extension) + public static bool IsProjectAssetFileExtension(string? extension) + { + if (extension == null) return false; + lock (RegisteredAssetFileExtensions) { - if (extension == null) return false; - lock (RegisteredAssetFileExtensions) + var valid = RegisteredAssetFileExtensions.ContainsKey(extension); + if (valid) { - var valid = RegisteredAssetFileExtensions.ContainsKey(extension); - if (valid) + var type = RegisteredDefaultAssetExtension.Where(x => x.Value == extension).Select(x => x.Key).FirstOrDefault(); + if (type != null) { - var type = RegisteredDefaultAssetExtension.Where(x => x.Value == extension).Select(x => x.Key).FirstOrDefault(); - if (type != null) - { - return typeof(IProjectAsset).IsAssignableFrom(type); - } + return typeof(IProjectAsset).IsAssignableFrom(type); } - return false; } + return false; } + } - /// - /// Gets the default file associated with an asset. - /// - /// The type. - /// System.String. - public static string GetDefaultExtension(Type assetType) + /// + /// Gets the default file associated with an asset. + /// + /// The type. + /// System.String. + public static string? GetDefaultExtension(Type assetType) + { + IsAssetOrPackageType(assetType, true); + lock (RegistryLock) { - IsAssetOrPackageType(assetType, true); - lock (RegistryLock) - { - string extension; - RegisteredDefaultAssetExtension.TryGetValue(assetType, out extension); - return extension; - } + RegisteredDefaultAssetExtension.TryGetValue(assetType, out var extension); + return extension; } + } - /// - /// Gets the current format version of an asset. - /// - /// The asset type. - /// The current format version of this asset. - public static SortedList GetCurrentFormatVersions(Type assetType) + /// + /// Gets the current format version of an asset. + /// + /// The asset type. + /// The current format version of this asset. + public static SortedList? GetCurrentFormatVersions(Type assetType) + { + IsAssetOrPackageType(assetType, true); + lock (RegistryLock) { - IsAssetOrPackageType(assetType, true); - lock (RegistryLock) - { - SortedList versions; - RegisteredFormatVersions.TryGetValue(assetType, out versions); - return versions; - } + RegisteredFormatVersions.TryGetValue(assetType, out var versions); + return versions; } + } - /// - /// Gets the of an asset type, if available. - /// - /// The asset type. - /// The dependency name. - /// The of an asset type if available, or null otherwise. - public static AssetUpgraderCollection GetAssetUpgraders(Type assetType, string dependencyName) + /// + /// Gets the of an asset type, if available. + /// + /// The asset type. + /// The dependency name. + /// The of an asset type if available, or null otherwise. + public static AssetUpgraderCollection? GetAssetUpgraders(Type assetType, string dependencyName) + { + IsAssetOrPackageType(assetType, true); + lock (RegistryLock) { - IsAssetOrPackageType(assetType, true); - lock (RegistryLock) - { - AssetUpgraderCollection upgraders; - RegisteredAssetUpgraders.TryGetValue(new KeyValuePair(assetType, dependencyName), out upgraders); - return upgraders; - } + RegisteredAssetUpgraders.TryGetValue(new KeyValuePair(assetType, dependencyName), out var upgraders); + return upgraders; } + } - public static PackageUpgrader GetPackageUpgrader(string packageName) + public static PackageUpgrader? GetPackageUpgrader(string packageName) + { + lock (RegistryLock) { - lock (RegistryLock) - { - PackageUpgrader upgrader; - RegisteredPackageUpgraders.TryGetValue(packageName, out upgrader); - return upgrader; - } + RegisteredPackageUpgraders.TryGetValue(packageName, out var upgrader); + return upgrader; } + } - /// - /// Gets the default file associated with an asset. - /// - /// Type of the asset. - /// System.String. - public static string GetDefaultExtension() where T : Asset - { - return GetDefaultExtension(typeof(T)); - } + /// + /// Gets the default file associated with an asset. + /// + /// Type of the asset. + /// System.String. + public static string? GetDefaultExtension() where T : Asset + { + return GetDefaultExtension(typeof(T)); + } - public static IEnumerable GetPackageSessionAnalysisTypes() + public static IEnumerable GetPackageSessionAnalysisTypes() + { + lock (RegistryLock) { - lock (RegistryLock) - { - return RegisteredPackageSessionAnalysisTypes; - } + return RegisteredPackageSessionAnalysisTypes; } + } - public static IAssetFactory GetAssetFactory(string typeName) + public static IAssetFactory? GetAssetFactory(string typeName) + { + ArgumentNullException.ThrowIfNull(typeName); + lock (RegistryLock) { - if (typeName == null) throw new ArgumentNullException(nameof(typeName)); - lock (RegistryLock) - { - IAssetFactory factory; - RegisteredAssetFactories.TryGetValue(typeName, out factory); - return factory; - } + RegisteredAssetFactories.TryGetValue(typeName, out var factory); + return factory; } + } - public static IEnumerable> GetAllAssetFactories() + public static IEnumerable> GetAllAssetFactories() + { + lock (RegistryLock) { - lock (RegistryLock) - { - return RegisteredAssetFactories.Values.ToList(); - } + return [.. RegisteredAssetFactories.Values]; } + } - public static bool IsAssetTypeAlwaysMarkAsRoot(Type type) + public static bool IsAssetTypeAlwaysMarkAsRoot(Type type) + { + lock (AlwaysMarkAsRootAssetTypes) { - lock (AlwaysMarkAsRootAssetTypes) - { - return AlwaysMarkAsRootAssetTypes.Contains(type); - } + return AlwaysMarkAsRootAssetTypes.Contains(type); } + } - /// - /// Returns an array of asset types that are non-abstract and public. - /// - /// An array of elements. - public static Type[] GetPublicTypes() + /// + /// Returns an array of asset types that are non-abstract and public. + /// + /// An array of elements. + public static Type[] GetPublicTypes() + { + lock (RegistryLock) { - lock (RegistryLock) - { - return AssetTypes.ToArray(); - } + return [.. AssetTypes]; } + } - public static IList GetContentTypes() + public static IList GetContentTypes() + { + lock (RegistryLock) { - lock (RegistryLock) - { - return RegisteredContentTypes.Keys.ToList(); - } + return [.. RegisteredContentTypes.Keys]; } + } - public static Type GetContentType(Type assetType) + public static Type? GetContentType(Type assetType) + { + IsAssetType(assetType, true); + lock (RegistryLock) { - IsAssetType(assetType, true); - lock (RegistryLock) + var currentType = assetType; + while (currentType != null) { - var currentType = assetType; - while (currentType != null) - { - Type contentType; - if (AssetToContentTypes.TryGetValue(assetType, out contentType)) - return contentType; + if (AssetToContentTypes.TryGetValue(assetType, out var contentType)) + return contentType; - currentType = currentType.BaseType; - } - return null; + currentType = currentType.BaseType; } + return null; } + } - /// - /// Whether a property of this type can be set to asset content, outputs the assets it can hold if true - /// - /// - /// The difference between this one and is that this one returns the asset types, - /// meaning the types that would be compiled into 'content' to be used at runtime. - /// This means that the types contained in are not directly assignable to - /// - public static bool CanPropertyHandleAssets([Annotations.NotNull] Type propertyType, [MaybeNullWhen(false)] out HashSet assetTypes) + /// + /// Whether a property of this type can be set to asset content, outputs the assets it can hold if true + /// + /// + /// The difference between this one and is that this one returns the asset types, + /// meaning the types that would be compiled into 'content' to be used at runtime. + /// This means that the types contained in are not directly assignable to + /// + public static bool CanPropertyHandleAssets(Type propertyType, [MaybeNullWhen(false)] out HashSet assetTypes) + { + lock (RegistryLock) { - lock (RegistryLock) + if (typeof(AssetReference).IsAssignableFrom(propertyType)) { - if (typeof(AssetReference).IsAssignableFrom(propertyType)) - { - assetTypes = GetAssetTypes(propertyType).ToHashSet(); - return true; - } + assetTypes = [.. GetAssetTypes(propertyType)]; + return true; + } - if (UrlReferenceBase.TryGetAssetType(propertyType, out var assetType)) - { - propertyType = assetType; - } + if (UrlReferenceBase.TryGetAssetType(propertyType, out var assetType)) + { + propertyType = assetType; + } - if (AssignableToContent.TryGetValue(propertyType, out var concreteContentTypes)) + if (AssignableToContent.TryGetValue(propertyType, out var concreteContentTypes)) + { + assetTypes = []; + foreach (var item in concreteContentTypes) { - assetTypes = new HashSet(); - foreach (var item in concreteContentTypes) + if (ContentToAssetTypes.TryGetValue(item, out var values)) { - if (ContentToAssetTypes.TryGetValue(item, out var values)) + foreach (var type in values) { - foreach (var type in values) - { - assetTypes.Add(type); - } + assetTypes.Add(type); } } - return true; } - - assetTypes = null; - return false; + return true; } + + assetTypes = null; + return false; } + } - /// - /// Whether a property of this type can be set to content, outputs the content types it can hold if true - /// - /// - /// The difference between this one and is that this one returns the content types, - /// meaning the concrete types that are types that would be compiled into 'content' to be used at runtime. - /// The types contained in are not guaranteed to be assignable to - /// - public static bool CanPropertyHandleContent(Type propertyType, [MaybeNullWhen(false)] out IReadOnlyList contentTypes) + /// + /// Whether a property of this type can be set to content, outputs the content types it can hold if true + /// + /// + /// The difference between this one and is that this one returns the content types, + /// meaning the concrete types that are types that would be compiled into 'content' to be used at runtime. + /// The types contained in are not guaranteed to be assignable to + /// + public static bool CanPropertyHandleContent(Type propertyType, [MaybeNullWhen(false)] out IReadOnlyList contentTypes) + { + lock (RegistryLock) { - lock (RegistryLock) + if (UrlReferenceBase.TryGetAssetType(propertyType, out var assetType)) { - if (UrlReferenceBase.TryGetAssetType(propertyType, out var assetType)) - { - propertyType = assetType; - } - - if (AssignableToContent.TryGetValue(propertyType, out var concreteContentTypes)) - { - contentTypes = concreteContentTypes; - return true; - } + propertyType = assetType; + } - contentTypes = null; - return false; + if (AssignableToContent.TryGetValue(propertyType, out var concreteContentTypes)) + { + contentTypes = concreteContentTypes; + return true; } + + contentTypes = null; + return false; } + } - public static IReadOnlyList GetAssetTypes(Type contentType) + public static IReadOnlyList GetAssetTypes(Type contentType) + { + lock (RegistryLock) { - lock (RegistryLock) + var currentType = contentType; + if (UrlReferenceBase.TryGetAssetType(contentType, out var assetType)) { - var currentType = contentType; - if (UrlReferenceBase.TryGetAssetType(contentType, out var assetType)) - { - currentType = assetType; - } - List assetTypes; - return ContentToAssetTypes.TryGetValue(currentType, out assetTypes) ? new List(assetTypes) : new List(); + currentType = assetType; } + return ContentToAssetTypes.TryGetValue(currentType, out var assetTypes) ? new List(assetTypes) : []; } + } - /// - /// Finds the importer associated with an asset by the file of the file to import. - /// - /// The file to import. - /// An instance of the importer of null if not found. - public static IEnumerable FindImporterForFile(string file) - { - if (file == null) throw new ArgumentNullException(nameof(file)); + /// + /// Finds the importer associated with an asset by the file of the file to import. + /// + /// The file to import. + /// An instance of the importer of null if not found. + public static IEnumerable FindImporterForFile(string file) + { + ArgumentNullException.ThrowIfNull(file); + return Impl(); + + IEnumerable Impl() + { lock (RegistryLock) { foreach (var importer in RegisteredImportersInternal) @@ -394,551 +387,538 @@ public static IEnumerable FindImporterForFile(string file) } } } + } - /// - /// Finds an importer by its id. - /// - /// The importer identifier. - /// An instance of the importer of null if not found. - public static IAssetImporter FindImporterById(Guid importerId) + /// + /// Finds an importer by its id. + /// + /// The importer identifier. + /// An instance of the importer of null if not found. + public static IAssetImporter? FindImporterById(Guid importerId) + { + lock (RegistryLock) { - lock (RegistryLock) - { - return RegisteredImportersInternal.FirstOrDefault(t => t.Id == importerId); - } + return RegisteredImportersInternal.FirstOrDefault(t => t.Id == importerId); } + } - /// - /// Registers a for the specified asset type. - /// - /// The importer. - /// importer - public static void RegisterImporter(IAssetImporter importer) - { - if (importer == null) throw new ArgumentNullException(nameof(importer)); + /// + /// Registers a for the specified asset type. + /// + /// The importer. + /// importer + public static void RegisterImporter(IAssetImporter importer) + { + ArgumentNullException.ThrowIfNull(importer); - // Register this importer - lock (RegistryLock) + // Register this importer + lock (RegistryLock) + { + var existingImporter = FindImporterById(importer.Id); + if (existingImporter != null) { - var existingImporter = FindImporterById(importer.Id); - if (existingImporter != null) - { - RegisteredImportersInternal.Remove(existingImporter); - } - - RegisteredImportersInternal.Add(importer); - RegisteredImportersInternal.Sort( (left, right) => left.Order.CompareTo(right.Order)); + RegisteredImportersInternal.Remove(existingImporter); } + + RegisteredImportersInternal.Add(importer); + RegisteredImportersInternal.Sort( (left, right) => left.Order.CompareTo(right.Order)); } + } - public static IEnumerable GetDataVisitNodes() + public static IEnumerable GetDataVisitNodes() + { + lock (RegistryLock) { - lock (RegistryLock) - { - return RegisteredDataVisitNodes; - } + return RegisteredDataVisitNodes; } + } - /// - /// Can a property of this type hold content - /// - /// - /// You may want to use instead if you're checking if this type is a concrete content type - /// - public static bool CanBeAssignedToContentTypes([Annotations.NotNull] Type type, bool checkIsUrlType) + /// + /// Can a property of this type hold content + /// + /// + /// You may want to use instead if you're checking if this type is a concrete content type + /// + public static bool CanBeAssignedToContentTypes([Annotations.NotNull] Type type, bool checkIsUrlType) + { + lock (RegistryLock) { - lock (RegistryLock) - { - if (checkIsUrlType && (type.IsAssignableTo(typeof(AssetReference)) || UrlReferenceBase.IsUrlReferenceType(type))) - return true; + if (checkIsUrlType && (type.IsAssignableTo(typeof(AssetReference)) || UrlReferenceBase.IsUrlReferenceType(type))) + return true; - return AssignableToContent.ContainsKey(type); - } + return AssignableToContent.ContainsKey(type); } + } - /// - /// Is this type a concrete content type, or derives from a concrete content type - /// - /// - /// You may want to use instead if you're checking - /// whether a property of this type could hold at least one type of content - /// - public static bool IsExactContentType(Type type) + /// + /// Is this type a concrete content type, or derives from a concrete content type + /// + /// + /// You may want to use instead if you're checking + /// whether a property of this type could hold at least one type of content + /// + public static bool IsExactContentType([NotNullWhen(true)] Type? type) + { + lock (RegistryLock) { - lock (RegistryLock) + var currentType = type; + while (currentType != null) { - var currentType = type; - while (currentType != null) - { - if (RegisteredContentTypes.ContainsKey(currentType)) - return true; + if (RegisteredContentTypes.ContainsKey(currentType)) + return true; - currentType = currentType.BaseType; - } - return false; + currentType = currentType.BaseType; } + return false; } + } + + private static void RegisterEngineAssembly(Assembly assembly) + { + ArgumentNullException.ThrowIfNull(assembly); - private static void RegisterEngineAssembly(Assembly assembly) + lock (RegistryLock) { - if (assembly == null) throw new ArgumentNullException(nameof(assembly)); + if (!RegisteredEngineAssemblies.Add(assembly)) + return; - lock (RegistryLock) + var assemblyScanTypes = AssemblyRegistry.GetScanTypes(assembly); + if (assemblyScanTypes != null) { - if (!RegisteredEngineAssemblies.Add(assembly)) - return; - - var assemblyScanTypes = AssemblyRegistry.GetScanTypes(assembly); - if (assemblyScanTypes != null) + // Process reference types. + if (assemblyScanTypes.Types.TryGetValue(typeof(ReferenceSerializerAttribute), out var referenceTypes)) { - // Process reference types. - List referenceTypes; - if (assemblyScanTypes.Types.TryGetValue(typeof(ReferenceSerializerAttribute), out referenceTypes)) + foreach (var type in referenceTypes) { - foreach (var type in referenceTypes) - { - RegisteredContentTypes.Add(type, null); + RegisteredContentTypes.Add(type, null); - AddToAssignable(type, type); + AddToAssignable(type, type); - foreach (var @interface in type.GetInterfaces()) - AddToAssignable(@interface, type); + foreach (var @interface in type.GetInterfaces()) + AddToAssignable(@interface, type); - for (var baseType = type; baseType != null; baseType = baseType.BaseType) - { - if (baseType.IsAbstract == false) - continue; + for (var baseType = type; baseType != null; baseType = baseType.BaseType) + { + if (!baseType.IsAbstract) + continue; - AddToAssignable(baseType, type); - } + AddToAssignable(baseType, type); + } - static void AddToAssignable(Type key, Type value) - { - ref var list = ref CollectionsMarshal.GetValueRefOrAddDefault(AssignableToContent, key, out _); - list ??= new List(); - list.Add(value); - } + static void AddToAssignable(Type key, Type value) + { + ref var list = ref CollectionsMarshal.GetValueRefOrAddDefault(AssignableToContent, key, out _); + list ??= []; + list.Add(value); } } } } } + } - private static void UnregisterEngineAssembly(Assembly assembly) - { - if (assembly == null) throw new ArgumentNullException(nameof(assembly)); + private static void UnregisterEngineAssembly(Assembly assembly) + { + ArgumentNullException.ThrowIfNull(assembly); - lock (RegistryLock) - { - if (!RegisteredEngineAssemblies.Contains(assembly)) - return; + lock (RegistryLock) + { + if (!RegisteredEngineAssemblies.Contains(assembly)) + return; - RegisteredEngineAssemblies.Remove(assembly); + RegisteredEngineAssemblies.Remove(assembly); - foreach (var type in RegisteredContentTypes.Keys.Where(x => x.Assembly == assembly).ToList()) - { - RegisteredContentTypes.Remove(type); + foreach (var type in RegisteredContentTypes.Keys.Where(x => x.Assembly == assembly).ToList()) + { + RegisteredContentTypes.Remove(type); - RemoveFromAssignable(type, type); + RemoveFromAssignable(type, type); - foreach (var @interface in type.GetInterfaces()) - RemoveFromAssignable(@interface, type); + foreach (var @interface in type.GetInterfaces()) + RemoveFromAssignable(@interface, type); - for (var baseType = type; baseType != null; baseType = baseType.BaseType) - { - if (baseType.IsAbstract == false) - continue; + for (var baseType = type; baseType != null; baseType = baseType.BaseType) + { + if (!baseType.IsAbstract) + continue; - RemoveFromAssignable(baseType, type); - } + RemoveFromAssignable(baseType, type); + } - static void RemoveFromAssignable(Type key, Type value) - { - var list = AssignableToContent[key]; - list.Remove(value); - if (list.Count == 0) - AssignableToContent.Remove(key); - } + static void RemoveFromAssignable(Type key, Type value) + { + var list = AssignableToContent[key]; + list.Remove(value); + if (list.Count == 0) + AssignableToContent.Remove(key); } } } + } + + private static void RegisterAssetAssembly(Assembly assembly) + { + ArgumentNullException.ThrowIfNull(assembly); - private static void RegisterAssetAssembly(Assembly assembly) + lock (RegistryLock) { - if (assembly == null) throw new ArgumentNullException(nameof(assembly)); + if (RegisteredAssetAssemblies.Contains(assembly)) + return; - lock (RegistryLock) - { - if (RegisteredAssetAssemblies.Contains(assembly)) - return; + RegisteredAssetAssemblies.Add(assembly); - RegisteredAssetAssemblies.Add(assembly); + var assemblyScanTypes = AssemblyRegistry.GetScanTypes(assembly); + if (assemblyScanTypes != null) + { + var instantiatedObjects = new Dictionary(); - var assemblyScanTypes = AssemblyRegistry.GetScanTypes(assembly); - if (assemblyScanTypes != null) + // Register serializer factories + if (assemblyScanTypes.Types.TryGetValue(typeof(IYamlSerializableFactory), out var types)) { - List types; - - var instantiatedObjects = new Dictionary(); - - // Register serializer factories - if (assemblyScanTypes.Types.TryGetValue(typeof(IYamlSerializableFactory), out types)) + foreach (var type in types) { - foreach (var type in types) + // Register serializer factories + if (!type.IsAbstract && + type.GetCustomAttribute() != null && type.GetConstructor(Type.EmptyTypes) != null) { - // Register serializer factories - if (!type.IsAbstract && - type.GetCustomAttribute() != null && type.GetConstructor(Type.EmptyTypes) != null) + try { - try - { - var instance = Activator.CreateInstance(type); - instantiatedObjects.Add(type, instance); - RegisteredSerializerFactories.Add((IYamlSerializableFactory)instance); - } - catch (Exception ex) - { - Log.Error($"Unable to instantiate serializer factory [{type}]", ex); - } + var instance = Activator.CreateInstance(type)!; + instantiatedObjects.Add(type, instance); + RegisteredSerializerFactories.Add((IYamlSerializableFactory)instance); + } + catch (Exception ex) + { + Log.Error($"Unable to instantiate serializer factory [{type}]", ex); } } } + } - // Custom visitors - if (assemblyScanTypes.Types.TryGetValue(typeof(IDataCustomVisitor), out types)) + // Custom visitors + if (assemblyScanTypes.Types.TryGetValue(typeof(IDataCustomVisitor), out types)) + { + foreach (var type in types) { - foreach (var type in types) + if (!type.IsAbstract && type.GetConstructor(Type.EmptyTypes) != null) { - if (!type.IsAbstract && type.GetConstructor(Type.EmptyTypes) != null) + try { - try + if (!instantiatedObjects.TryGetValue(type, out var instance)) { - object instance; - if (!instantiatedObjects.TryGetValue(type, out instance)) - { - instance = Activator.CreateInstance(type); - instantiatedObjects.Add(type, instance); - } - RegisteredDataVisitNodes.Add((IDataCustomVisitor)instance); - } - catch (Exception ex) - { - Log.Error($"Unable to instantiate custom visitor [{type}]", ex); + instance = Activator.CreateInstance(type)!; + instantiatedObjects.Add(type, instance); } + RegisteredDataVisitNodes.Add((IDataCustomVisitor)instance); + } + catch (Exception ex) + { + Log.Error($"Unable to instantiate custom visitor [{type}]", ex); } } } + } - // Asset importer - if (assemblyScanTypes.Types.TryGetValue(typeof(IAssetImporter), out types)) + // Asset importer + if (assemblyScanTypes.Types.TryGetValue(typeof(IAssetImporter), out types)) + { + foreach (var type in types) { - foreach (var type in types) + if (!type.IsAbstract && type.GetConstructor(Type.EmptyTypes) != null) { - if (!type.IsAbstract && type.GetConstructor(Type.EmptyTypes) != null) + try { - try - { - object instance; - if (!instantiatedObjects.TryGetValue(type, out instance)) - { - instance = Activator.CreateInstance(type); - instantiatedObjects.Add(type, instance); - } - // Register the importer instance - RegisterImporter((IAssetImporter)instance); - } - catch (Exception ex) + if (!instantiatedObjects.TryGetValue(type, out var instance)) { - Log.Error($"Unable to instantiate importer [{type.Name}]", ex); + instance = Activator.CreateInstance(type)!; + instantiatedObjects.Add(type, instance); } + // Register the importer instance + RegisterImporter((IAssetImporter)instance); + } + catch (Exception ex) + { + Log.Error($"Unable to instantiate importer [{type.Name}]", ex); } } } + } - // Register asset factory - if (assemblyScanTypes.Types.TryGetValue(typeof(IAssetFactory<>), out types)) + // Register asset factory + if (assemblyScanTypes.Types.TryGetValue(typeof(IAssetFactory<>), out types)) + { + foreach (var type in types) { - foreach (var type in types) + if (!type.IsAbstract && !type.IsGenericTypeDefinition && type.GetConstructor(Type.EmptyTypes) != null) { - if (!type.IsAbstract && !type.IsGenericTypeDefinition && type.GetConstructor(Type.EmptyTypes) != null) + if (!instantiatedObjects.TryGetValue(type, out var instance)) { - object instance; - if (!instantiatedObjects.TryGetValue(type, out instance)) - { - instance = Activator.CreateInstance(type); - instantiatedObjects.Add(type, instance); - } - RegisteredAssetFactories.Add(type.Name, (IAssetFactory)instance); + instance = Activator.CreateInstance(type)!; + instantiatedObjects.Add(type, instance); } + RegisteredAssetFactories.Add(type.Name, (IAssetFactory)instance); } } + } - // Package upgraders - if (assemblyScanTypes.Types.TryGetValue(typeof(PackageUpgraderAttribute), out types)) + // Package upgraders + if (assemblyScanTypes.Types.TryGetValue(typeof(PackageUpgraderAttribute), out types)) + { + foreach (var type in types) { - foreach (var type in types) + var packageUpgraderAttribute = type.GetCustomAttribute(); + if (packageUpgraderAttribute != null) { - var packageUpgraderAttribute = type.GetCustomAttribute(); - if (packageUpgraderAttribute != null) + try { - try - { - var packageUpgrader = (PackageUpgrader)Activator.CreateInstance(type); - packageUpgrader.Attribute = packageUpgraderAttribute; - foreach (var packageName in packageUpgraderAttribute.PackageNames) - RegisteredPackageUpgraders[packageName] = packageUpgrader; - } - catch (Exception ex) - { - Log.Error($"Unable to instantiate package upgrader [{type.Name}]", ex); - } + var packageUpgrader = (PackageUpgrader)Activator.CreateInstance(type)!; + packageUpgrader.Attribute = packageUpgraderAttribute; + foreach (var packageName in packageUpgraderAttribute.PackageNames) + RegisteredPackageUpgraders[packageName] = packageUpgrader; + } + catch (Exception ex) + { + Log.Error($"Unable to instantiate package upgrader [{type.Name}]", ex); } } } + } - // Package analyzers - if (assemblyScanTypes.Types.TryGetValue(typeof(PackageSessionAnalysisBase), out types)) + // Package analyzers + if (assemblyScanTypes.Types.TryGetValue(typeof(PackageSessionAnalysisBase), out types)) + { + foreach (var type in types) { - foreach (var type in types) + if (type.GetConstructor(Type.EmptyTypes) != null) { - if (type.GetConstructor(Type.EmptyTypes) != null) - { - RegisteredPackageSessionAnalysisTypes.Add(type); - } + RegisteredPackageSessionAnalysisTypes.Add(type); } } + } - // Asset types (and Package type) - var assemblyContainsPackageType = assembly == typeof(Package).Assembly; - if (assemblyScanTypes.Types.TryGetValue(typeof(Asset), out types) || assemblyContainsPackageType) + // Asset types (and Package type) + var assemblyContainsPackageType = assembly == typeof(Package).Assembly; + if (assemblyScanTypes.Types.TryGetValue(typeof(Asset), out types) || assemblyContainsPackageType) + { + if (assemblyContainsPackageType) + { + var extraTypes = new[] { typeof(Package) }; + types = types?.Concat(extraTypes).ToList() ?? [.. extraTypes]; + } + foreach (var assetType in types!) { - if (assemblyContainsPackageType) + // Store in a list all asset types loaded + if (assetType.IsPublic && !assetType.IsAbstract) { - var extraTypes = new[] { typeof(Package) }; - types = types?.Concat(extraTypes).ToList() ?? extraTypes.ToList(); + AssetTypes.Add(assetType); } - foreach (var assetType in types) - { - // Store in a list all asset types loaded - if (assetType.IsPublic && !assetType.IsAbstract) - { - AssetTypes.Add(assetType); - } - // Asset FileExtensions - var assetDescriptionAttribute = assetType.GetCustomAttribute(); - if (assetDescriptionAttribute != null) + // Asset FileExtensions + var assetDescriptionAttribute = assetType.GetCustomAttribute(); + if (assetDescriptionAttribute != null) + { + if (assetDescriptionAttribute.FileExtensions != null) { - if (assetDescriptionAttribute.FileExtensions != null) - { - var extensions = FileUtility.GetFileExtensions(assetDescriptionAttribute.FileExtensions); - RegisteredDefaultAssetExtension[assetType] = extensions.FirstOrDefault(); - foreach (var extension in extensions) - { - RegisteredAssetFileExtensions.TryAdd(extension, assetType); - } - } - - if (assetDescriptionAttribute.AlwaysMarkAsRoot) + var extensions = FileUtility.GetFileExtensions(assetDescriptionAttribute.FileExtensions); + RegisteredDefaultAssetExtension[assetType] = extensions.FirstOrDefault(); + foreach (var extension in extensions) { - lock (AlwaysMarkAsRootAssetTypes) - { - AlwaysMarkAsRootAssetTypes.Add(assetType); - } + RegisteredAssetFileExtensions.TryAdd(extension, assetType); } } - // Content type associated to assets - var assetContentType = assetType.GetCustomAttribute(); - if (assetContentType != null) + if (assetDescriptionAttribute.AlwaysMarkAsRoot) { - List assetTypes; - if (!ContentToAssetTypes.TryGetValue(assetContentType.ContentType, out assetTypes)) + lock (AlwaysMarkAsRootAssetTypes) { - assetTypes = new List(); - ContentToAssetTypes[assetContentType.ContentType] = assetTypes; + AlwaysMarkAsRootAssetTypes.Add(assetType); } - assetTypes.Add(assetType); - AssetToContentTypes.Add(assetType, assetContentType.ContentType); } + } - // Asset format version (process name by name) - var assetFormatVersions = assetType.GetCustomAttributes(); - foreach (var assetFormatVersion in assetFormatVersions) + // Content type associated to assets + var assetContentType = assetType.GetCustomAttribute(); + if (assetContentType != null) + { + if (!ContentToAssetTypes.TryGetValue(assetContentType.ContentType, out var assetTypes)) { - var formatVersion = assetFormatVersion.Version; - var minVersion = assetFormatVersion.MinUpgradableVersion; - SortedList formatVersions; - if (!RegisteredFormatVersions.TryGetValue(assetType, out formatVersions)) - { - RegisteredFormatVersions.Add(assetType, formatVersions = new SortedList()); - } - formatVersions.Add(assetFormatVersion.Name, formatVersion); + assetTypes = []; + ContentToAssetTypes[assetContentType.ContentType] = assetTypes; + } + assetTypes.Add(assetType); + AssetToContentTypes.Add(assetType, assetContentType.ContentType); + } - // Asset upgraders (only those matching current name) - var assetUpgraders = assetType.GetCustomAttributes().Where(x => x.Name == assetFormatVersion.Name); - AssetUpgraderCollection upgraderCollection = null; - foreach (var upgrader in assetUpgraders) - { - if (upgraderCollection == null) - upgraderCollection = new AssetUpgraderCollection(assetType, formatVersion); + // Asset format version (process name by name) + var assetFormatVersions = assetType.GetCustomAttributes(); + foreach (var assetFormatVersion in assetFormatVersions) + { + var formatVersion = assetFormatVersion.Version; + var minVersion = assetFormatVersion.MinUpgradableVersion; + if (!RegisteredFormatVersions.TryGetValue(assetType, out var formatVersions)) + { + RegisteredFormatVersions.Add(assetType, formatVersions = []); + } + formatVersions.Add(assetFormatVersion.Name, formatVersion); - upgraderCollection.RegisterUpgrader(upgrader.AssetUpgraderType, upgrader.StartVersion, upgrader.TargetVersion); - } - if (upgraderCollection != null) - { - upgraderCollection.Validate(minVersion); - RegisteredAssetUpgraders.Add(new KeyValuePair(assetType, assetFormatVersion.Name), upgraderCollection); - } + // Asset upgraders (only those matching current name) + var assetUpgraders = assetType.GetCustomAttributes().Where(x => x.Name == assetFormatVersion.Name); + AssetUpgraderCollection? upgraderCollection = null; + foreach (var upgrader in assetUpgraders) + { + (upgraderCollection ??= new AssetUpgraderCollection(assetType, formatVersion)).RegisterUpgrader(upgrader.AssetUpgraderType, upgrader.StartVersion, upgrader.TargetVersion); + } + if (upgraderCollection != null) + { + upgraderCollection.Validate(minVersion); + RegisteredAssetUpgraders.Add(new KeyValuePair(assetType, assetFormatVersion.Name), upgraderCollection); } } } } } } + } - private static void UnregisterAssetAssembly(Assembly assembly) - { - if (assembly == null) throw new ArgumentNullException(nameof(assembly)); - - lock (RegistryLock) - { - if (!RegisteredAssetAssemblies.Contains(assembly)) - return; - - RegisteredAssetAssemblies.Remove(assembly); + private static void UnregisterAssetAssembly(Assembly assembly) + { + ArgumentNullException.ThrowIfNull(assembly); - foreach (var typeToRemove in RegisteredDefaultAssetExtension.Keys.Where(type => type.Assembly == assembly).ToList()) - { - RegisteredDefaultAssetExtension.Remove(typeToRemove); - } + lock (RegistryLock) + { + if (!RegisteredAssetAssemblies.Contains(assembly)) + return; - foreach (var typeToRemove in AssetTypes.ToList().Where(type => type.Assembly == assembly)) - { - AssetTypes.Remove(typeToRemove); - } + RegisteredAssetAssemblies.Remove(assembly); - foreach (var typeToRemove in RegisteredPackageSessionAnalysisTypes.Where(type => type.Assembly == assembly).ToList()) - { - RegisteredPackageSessionAnalysisTypes.Remove(typeToRemove); - } + foreach (var typeToRemove in RegisteredDefaultAssetExtension.Keys.Where(type => type.Assembly == assembly).ToList()) + { + RegisteredDefaultAssetExtension.Remove(typeToRemove); + } - foreach (var instance in RegisteredImportersInternal.Where(instance => instance.GetType().Assembly == assembly).ToList()) - { - RegisteredImportersInternal.Remove(instance); - } + foreach (var typeToRemove in AssetTypes.ToList().Where(type => type.Assembly == assembly)) + { + AssetTypes.Remove(typeToRemove); + } - foreach (var typeToRemove in RegisteredFormatVersions.Keys.Where(type => type.Assembly == assembly).ToList()) - { - RegisteredFormatVersions.Remove(typeToRemove); - } + foreach (var typeToRemove in RegisteredPackageSessionAnalysisTypes.Where(type => type.Assembly == assembly).ToList()) + { + RegisteredPackageSessionAnalysisTypes.Remove(typeToRemove); + } - foreach (var typeToRemove in RegisteredAssetUpgraders.Keys.Where(type => type.Key.Assembly == assembly).ToList()) - { - RegisteredAssetUpgraders.Remove(typeToRemove); - } + foreach (var instance in RegisteredImportersInternal.Where(instance => instance.GetType().Assembly == assembly).ToList()) + { + RegisteredImportersInternal.Remove(instance); + } - foreach (var extensionToRemove in RegisteredAssetFileExtensions.Where(keyValue => keyValue.Value.Assembly == assembly).Select(keyValue => keyValue.Key).ToList()) - { - RegisteredAssetFileExtensions.Remove(extensionToRemove); - } + foreach (var typeToRemove in RegisteredFormatVersions.Keys.Where(type => type.Assembly == assembly).ToList()) + { + RegisteredFormatVersions.Remove(typeToRemove); + } - foreach (var upgraderToRemove in RegisteredPackageUpgraders.Where(keyValue => keyValue.Value.GetType().Assembly == assembly).Select(keyValue => keyValue.Key).ToList()) - { - RegisteredPackageUpgraders.Remove(upgraderToRemove); - } + foreach (var typeToRemove in RegisteredAssetUpgraders.Keys.Where(type => type.Key.Assembly == assembly).ToList()) + { + RegisteredAssetUpgraders.Remove(typeToRemove); + } - foreach (var instance in RegisteredSerializerFactories.Where(instance => instance.GetType().Assembly == assembly).ToList()) - { - RegisteredSerializerFactories.Remove(instance); - } + foreach (var extensionToRemove in RegisteredAssetFileExtensions.Where(keyValue => keyValue.Value.Assembly == assembly).Select(keyValue => keyValue.Key).ToList()) + { + RegisteredAssetFileExtensions.Remove(extensionToRemove); + } - foreach (var instance in RegisteredDataVisitNodes.Where(instance => instance.GetType().Assembly == assembly).ToList()) - { - RegisteredDataVisitNodes.Remove(instance); - } + foreach (var upgraderToRemove in RegisteredPackageUpgraders.Where(keyValue => keyValue.Value.GetType().Assembly == assembly).Select(keyValue => keyValue.Key).ToList()) + { + RegisteredPackageUpgraders.Remove(upgraderToRemove); } - } - /// - /// Check if the specified type is an asset. - /// - /// Type of the asset. - /// A boolean indicating whether this method should throw an exception if the type is not an asset type. - /// true if the asset is an asset type, false otherwise. - public static bool IsAssetType(Type assetType, bool throwException = false) - { - if (assetType == null) - throw new ArgumentNullException(nameof(assetType)); + foreach (var instance in RegisteredSerializerFactories.Where(instance => instance.GetType().Assembly == assembly).ToList()) + { + RegisteredSerializerFactories.Remove(instance); + } - if (!typeof(Asset).IsAssignableFrom(assetType)) + foreach (var instance in RegisteredDataVisitNodes.Where(instance => instance.GetType().Assembly == assembly).ToList()) { - if (throwException) - throw new ArgumentException("Type [{0}] must be assignable to Asset or be a Package".ToFormat(assetType), nameof(assetType)); - return false; + RegisteredDataVisitNodes.Remove(instance); } - return true; } + } + + /// + /// Check if the specified type is an asset. + /// + /// Type of the asset. + /// A boolean indicating whether this method should throw an exception if the type is not an asset type. + /// true if the asset is an asset type, false otherwise. + public static bool IsAssetType(Type assetType, bool throwException = false) + { + ArgumentNullException.ThrowIfNull(assetType); - /// - /// Check if the specified type is an asset. - /// - /// Type of the asset. - /// A boolean indicating whether this method should throw an exception if the type is not an asset type. - /// true if the asset is an asset type, false otherwise. - public static bool IsAssetOrPackageType(Type assetType, bool throwException = false) + if (!typeof(Asset).IsAssignableFrom(assetType)) { - if (assetType == null) - throw new ArgumentNullException(nameof(assetType)); + if (throwException) + throw new ArgumentException("Type [{0}] must be assignable to Asset or be a Package".ToFormat(assetType), nameof(assetType)); + return false; + } + return true; + } - if (!typeof(Asset).IsAssignableFrom(assetType) && assetType != typeof(Package)) - { - if (throwException) - throw new ArgumentException("Type [{0}] must be assignable to Asset or be a Package".ToFormat(assetType), nameof(assetType)); - return false; - } - return true; + /// + /// Check if the specified type is an asset. + /// + /// Type of the asset. + /// A boolean indicating whether this method should throw an exception if the type is not an asset type. + /// true if the asset is an asset type, false otherwise. + public static bool IsAssetOrPackageType(Type assetType, bool throwException = false) + { + ArgumentNullException.ThrowIfNull(assetType); + + if (!typeof(Asset).IsAssignableFrom(assetType) && assetType != typeof(Package)) + { + if (throwException) + throw new ArgumentException("Type [{0}] must be assignable to Asset or be a Package".ToFormat(assetType), nameof(assetType)); + return false; } + return true; + } - static AssetRegistry() + static AssetRegistry() + { + SupportedPlatforms = []; + // Statically find all assemblies related to assets and register them + foreach (var assembly in AssemblyRegistry.Find(AssemblyCommonCategories.Engine)) { - SupportedPlatforms = new SolutionPlatformCollection(); - // Statically find all assemblies related to assets and register them - foreach (var assembly in AssemblyRegistry.Find(AssemblyCommonCategories.Engine)) - { - RegisterEngineAssembly(assembly); - } - foreach (var assembly in AssemblyRegistry.Find(AssemblyCommonCategories.Assets)) - { - RegisterAssetAssembly(assembly); - } - AssemblyRegistry.AssemblyRegistered += AssemblyRegistryAssemblyRegistered; - AssemblyRegistry.AssemblyUnregistered += AssemblyRegistryOnAssemblyUnregistered; + RegisterEngineAssembly(assembly); + } + foreach (var assembly in AssemblyRegistry.Find(AssemblyCommonCategories.Assets)) + { + RegisterAssetAssembly(assembly); } + AssemblyRegistry.AssemblyRegistered += AssemblyRegistryAssemblyRegistered; + AssemblyRegistry.AssemblyUnregistered += AssemblyRegistryOnAssemblyUnregistered; + } - private static void AssemblyRegistryOnAssemblyUnregistered(object sender, AssemblyRegisteredEventArgs e) + private static void AssemblyRegistryOnAssemblyUnregistered(object? sender, AssemblyRegisteredEventArgs e) + { + if (e.Categories.Contains(AssemblyCommonCategories.Assets)) { - if (e.Categories.Contains(AssemblyCommonCategories.Assets)) - { - UnregisterAssetAssembly(e.Assembly); - } - if (e.Categories.Contains(AssemblyCommonCategories.Engine)) - { - UnregisterEngineAssembly(e.Assembly); - } + UnregisterAssetAssembly(e.Assembly); + } + if (e.Categories.Contains(AssemblyCommonCategories.Engine)) + { + UnregisterEngineAssembly(e.Assembly); } + } - private static void AssemblyRegistryAssemblyRegistered(object sender, AssemblyRegisteredEventArgs e) + private static void AssemblyRegistryAssemblyRegistered(object? sender, AssemblyRegisteredEventArgs e) + { + // Handle delay-loading assemblies + if (e.Categories.Contains(AssemblyCommonCategories.Engine)) { - // Handle delay-loading assemblies - if (e.Categories.Contains(AssemblyCommonCategories.Engine)) - { - RegisterEngineAssembly(e.Assembly); - } - if (e.Categories.Contains(AssemblyCommonCategories.Assets)) - { - RegisterAssetAssembly(e.Assembly); - } + RegisterEngineAssembly(e.Assembly); + } + if (e.Categories.Contains(AssemblyCommonCategories.Assets)) + { + RegisterAssetAssembly(e.Assembly); } } } diff --git a/sources/assets/Stride.Core.Assets/AssetSelector.cs b/sources/assets/Stride.Core.Assets/AssetSelector.cs index 1c0b40a1b9..67684ade7a 100644 --- a/sources/assets/Stride.Core.Assets/AssetSelector.cs +++ b/sources/assets/Stride.Core.Assets/AssetSelector.cs @@ -1,17 +1,15 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using Stride.Core; + using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// An asset selector +/// +[DataContract(Inherited = true)] +public abstract class AssetSelector { - /// - /// An asset selector - /// - [DataContract(Inherited = true)] - public abstract class AssetSelector - { - public abstract IEnumerable Select(PackageSession packageSession, IContentIndexMap contentIndexMap); - } + public abstract IEnumerable Select(PackageSession packageSession, IContentIndexMap contentIndexMap); } diff --git a/sources/assets/Stride.Core.Assets/AssetTracker.cs b/sources/assets/Stride.Core.Assets/AssetTracker.cs index 1e406c2800..d10054e1f7 100644 --- a/sources/assets/Stride.Core.Assets/AssetTracker.cs +++ b/sources/assets/Stride.Core.Assets/AssetTracker.cs @@ -1,37 +1,43 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.Collections.Specialized; -using System.Linq; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +// TODO: Use this class as a base for AssetSourceTracker and AssetDependencyManager +/// +/// Base class for tracking assets and executing an action on each change. +/// +public abstract class AssetTracker : IDisposable { - // TODO: Use this class as a base for AssetSourceTracker and AssetDependencyManager - /// - /// Base class for tracking assets and executing an action on each change. - /// - public abstract class AssetTracker : IDisposable + private readonly PackageSession session; + private readonly HashSet packages = []; + + protected AssetTracker(PackageSession session) { - private readonly PackageSession session; - private readonly HashSet packages = new HashSet(); + this.session = session; + } - protected AssetTracker(PackageSession session) + protected void Start() + { + session.AssetDirtyChanged += Session_AssetDirtyChanged; + session.Packages.CollectionChanged += Packages_CollectionChanged; + foreach (var package in session.Packages) { - this.session = session; + TrackPackage(package); } + } - protected void Start() - { - session.AssetDirtyChanged += Session_AssetDirtyChanged; - session.Packages.CollectionChanged += Packages_CollectionChanged; - foreach (var package in session.Packages) - { - TrackPackage(package); - } - } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - public void Dispose() + protected virtual void Dispose(bool disposing) + { + if (disposing) { session.AssetDirtyChanged -= Session_AssetDirtyChanged; session.Packages.CollectionChanged -= Packages_CollectionChanged; @@ -40,114 +46,120 @@ public void Dispose() UnTrackPackage(package); } } + } - /// - /// Called when a new asset is tracked. - /// - /// - public abstract void TrackAsset(AssetItem assetItem); - - /// - /// Called when an asset changes. - /// - /// - public abstract void NotifyAssetChanged(Asset asset); - - /// - /// Called when an asset stop being tracked. - /// - /// - public abstract void UnTrackAsset(AssetItem assetItem); - - /// - /// This method is called when a package needs to be tracked - /// - /// The package to track. - private void TrackPackage(Package package) - { - if (packages.Contains(package)) - return; + /// + /// Called when a new asset is tracked. + /// + /// + public abstract void TrackAsset(AssetItem assetItem); - packages.Add(package); + /// + /// Called when an asset changes. + /// + /// + public abstract void NotifyAssetChanged(Asset asset); - foreach (var asset in package.Assets) - { - TrackAsset(asset); - } + /// + /// Called when an asset stop being tracked. + /// + /// + public abstract void UnTrackAsset(AssetItem assetItem); - package.Assets.CollectionChanged += Assets_CollectionChanged; - } + /// + /// This method is called when a package needs to be tracked + /// + /// The package to track. + private void TrackPackage(Package package) + { + if (packages.Contains(package)) + return; + + packages.Add(package); - /// - /// This method is called when a package needs to be un-tracked - /// - /// The package to un-track. - private void UnTrackPackage(Package package) + foreach (var asset in package.Assets) { - if (!packages.Contains(package)) - return; + TrackAsset(asset); + } - package.Assets.CollectionChanged -= Assets_CollectionChanged; + package.Assets.CollectionChanged += Assets_CollectionChanged; + } - foreach (var asset in package.Assets) - { - UnTrackAsset(asset); - } + /// + /// This method is called when a package needs to be un-tracked + /// + /// The package to un-track. + private void UnTrackPackage(Package package) + { + if (!packages.Contains(package)) + return; - packages.Remove(package); - } + package.Assets.CollectionChanged -= Assets_CollectionChanged; - private void Session_AssetDirtyChanged(AssetItem asset, bool oldValue, bool newValue) + foreach (var asset in package.Assets) { - // TODO: Don't update the source tracker while saving (cf AssetSourceTracker) - - NotifyAssetChanged(asset.Asset); + UnTrackAsset(asset); } - private void Packages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + packages.Remove(package); + } + + private void Session_AssetDirtyChanged(AssetItem asset, bool oldValue, bool newValue) + { + // TODO: Don't update the source tracker while saving (cf AssetSourceTracker) + + NotifyAssetChanged(asset.Asset); + } + + private void Packages_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - TrackPackage((Package)e.NewItems[0]); - break; - case NotifyCollectionChangedAction.Remove: - UnTrackPackage((Package)e.OldItems[0]); - break; - - case NotifyCollectionChangedAction.Replace: - foreach (var oldPackage in e.OldItems.OfType()) - { - UnTrackPackage(oldPackage); - } - - foreach (var package in session.Packages) - { + case NotifyCollectionChangedAction.Add: + { + if (e.NewItems?[0] is Package package) TrackPackage(package); - } - break; - } + } + break; + case NotifyCollectionChangedAction.Remove: + { + if (e.OldItems?[0] is Package package) + UnTrackPackage(package); + } + break; + + case NotifyCollectionChangedAction.Replace: + foreach (var oldPackage in e.OldItems?.OfType() ?? []) + { + UnTrackPackage(oldPackage); + } + + foreach (var package in session.Packages) + { + TrackPackage(package); + } + break; } + } - private void Assets_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void Assets_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - foreach (AssetItem assetItem in e.NewItems) - { - TrackAsset(assetItem); - } - break; - case NotifyCollectionChangedAction.Remove: - foreach (AssetItem assetItem in e.OldItems) - { - UnTrackAsset(assetItem); - } - break; - default: - throw new NotSupportedException("Reset is not supported by the asset tracker."); - } + case NotifyCollectionChangedAction.Add: + foreach (var assetItem in e.NewItems?.OfType() ?? []) + { + TrackAsset(assetItem); + } + break; + case NotifyCollectionChangedAction.Remove: + foreach (var assetItem in e.OldItems?.OfType() ?? []) + { + UnTrackAsset(assetItem); + } + break; + default: + throw new NotSupportedException("Reset is not supported by the asset tracker."); } } } diff --git a/sources/assets/Stride.Core.Assets/AssetUpgraderAttribute.cs b/sources/assets/Stride.Core.Assets/AssetUpgraderAttribute.cs index 094c999602..6dc5bbd482 100644 --- a/sources/assets/Stride.Core.Assets/AssetUpgraderAttribute.cs +++ b/sources/assets/Stride.Core.Assets/AssetUpgraderAttribute.cs @@ -1,66 +1,62 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; +namespace Stride.Core.Assets; -namespace Stride.Core.Assets +/// +/// Describes which upgrader type to use to upgrade an asset, depending on this current version number. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public class AssetUpgraderAttribute : Attribute { /// - /// Describes which upgrader type to use to upgrade an asset, depending on this current version number. + /// Initializes a new instance of the with a range of supported initial version numbers. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class AssetUpgraderAttribute : Attribute + /// The dependency name. + /// The minimal initial version number this upgrader can work on. + /// The target version number of this upgrader. + /// The type of upgrader to instantiate to upgrade the asset. + public AssetUpgraderAttribute(string name, string startMinVersion, string targetVersion, Type assetUpgraderType) { - /// - /// Initializes a new instance of the with a range of supported initial version numbers. - /// - /// The dependency name. - /// The minimal initial version number this upgrader can work on. - /// The target version number of this upgrader. - /// The type of upgrader to instantiate to upgrade the asset. - public AssetUpgraderAttribute(string name, string startMinVersion, string targetVersion, Type assetUpgraderType) - { - Name = name; - StartVersion = PackageVersion.Parse(startMinVersion); - TargetVersion = PackageVersion.Parse(targetVersion); + Name = name; + StartVersion = PackageVersion.Parse(startMinVersion); + TargetVersion = PackageVersion.Parse(targetVersion); - if (!typeof(IAssetUpgrader).IsAssignableFrom(assetUpgraderType)) - throw new ArgumentException(@"The assetUpgraderType must implement IAssetUpgrader interface", nameof(assetUpgraderType)); - if (TargetVersion <= StartVersion) - throw new ArgumentException(@"The target version is lower or equal to the start version.", nameof(targetVersion)); - AssetUpgraderType = assetUpgraderType; - } + if (!typeof(IAssetUpgrader).IsAssignableFrom(assetUpgraderType)) + throw new ArgumentException("The assetUpgraderType must implement IAssetUpgrader interface", nameof(assetUpgraderType)); + if (TargetVersion <= StartVersion) + throw new ArgumentException("The target version is lower or equal to the start version.", nameof(targetVersion)); + AssetUpgraderType = assetUpgraderType; + } - /// - /// Initializes a new instance of the with a single supported initial version number. - /// - /// The dependency name. - /// The initial version number this upgrader can work on. - /// The target version number of this upgrader. - /// The type of upgrader to instantiate to upgrade the asset. - public AssetUpgraderAttribute(string name, int startVersion, int targetVersion, Type assetUpgraderType) - : this(name, "0.0." + startVersion, "0.0." + targetVersion, assetUpgraderType) - { - } + /// + /// Initializes a new instance of the with a single supported initial version number. + /// + /// The dependency name. + /// The initial version number this upgrader can work on. + /// The target version number of this upgrader. + /// The type of upgrader to instantiate to upgrade the asset. + public AssetUpgraderAttribute(string name, int startVersion, int targetVersion, Type assetUpgraderType) + : this(name, "0.0." + startVersion, "0.0." + targetVersion, assetUpgraderType) + { + } - /// - /// Gets or sets the dependency name. - /// - public string Name { get; set; } + /// + /// Gets or sets the dependency name. + /// + public string Name { get; set; } - /// - /// Gets or sets the minimal initial version number this upgrader can work on. - /// - public PackageVersion StartVersion { get; set; } + /// + /// Gets or sets the minimal initial version number this upgrader can work on. + /// + public PackageVersion StartVersion { get; set; } - /// - /// Gets or sets the target version number of this upgrader. - /// - public PackageVersion TargetVersion { get; set; } + /// + /// Gets or sets the target version number of this upgrader. + /// + public PackageVersion TargetVersion { get; set; } - /// - /// Gets or sets the type of upgrader to instantiate to upgrade the asset. - /// - public Type AssetUpgraderType { get; set; } - } + /// + /// Gets or sets the type of upgrader to instantiate to upgrade the asset. + /// + public Type AssetUpgraderType { get; set; } } diff --git a/sources/assets/Stride.Core.Assets/AssetUpgraderBase.cs b/sources/assets/Stride.Core.Assets/AssetUpgraderBase.cs index f971f16cd1..1da08445f0 100644 --- a/sources/assets/Stride.Core.Assets/AssetUpgraderBase.cs +++ b/sources/assets/Stride.Core.Assets/AssetUpgraderBase.cs @@ -1,69 +1,68 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; + using Stride.Core.Yaml; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public abstract class AssetUpgraderBase : IAssetUpgrader { - public abstract class AssetUpgraderBase : IAssetUpgrader + public void Upgrade(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, YamlMappingNode yamlAssetNode, PackageLoadingAssetFile assetFile) { - public void Upgrade(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, YamlMappingNode yamlAssetNode, PackageLoadingAssetFile assetFile) - { - dynamic asset = new DynamicYamlMapping(yamlAssetNode); + dynamic asset = new DynamicYamlMapping(yamlAssetNode); - // upgrade the asset - var baseBranch = asset["~Base"]; - var basePartsBranch = asset["~BaseParts"] as DynamicYamlArray; + // upgrade the asset + var baseBranch = asset["~Base"]; + var basePartsBranch = asset["~BaseParts"] as DynamicYamlArray; - // Detect in what kind of override context we are - var overrideHint = (baseBranch != null || (basePartsBranch != null && basePartsBranch.Node.Children.Count > 0)) - ? OverrideUpgraderHint.Derived - : OverrideUpgraderHint.Unknown; + // Detect in what kind of override context we are + var overrideHint = (baseBranch != null || (basePartsBranch?.Node.Children.Count > 0)) + ? OverrideUpgraderHint.Derived + : OverrideUpgraderHint.Unknown; - // Upgrade the asset - UpgradeAsset(context, currentVersion, targetVersion, asset, assetFile, overrideHint); - SetSerializableVersion(asset, dependencyName, targetVersion); + // Upgrade the asset + UpgradeAsset(context, currentVersion, targetVersion, asset, assetFile, overrideHint); + SetSerializableVersion(asset, dependencyName, targetVersion); - // Upgrade its base - if (baseBranch != null) - { - UpgradeBase(context, dependencyName, currentVersion, targetVersion, baseBranch, assetFile); - } + // Upgrade its base + if (baseBranch != null) + { + UpgradeBase(context, dependencyName, currentVersion, targetVersion, baseBranch, assetFile); + } - // Upgrade base parts - if (basePartsBranch != null) + // Upgrade base parts + if (basePartsBranch != null) + { + foreach (dynamic assetBase in basePartsBranch) { - foreach (dynamic assetBase in basePartsBranch) - { - UpgradeBase(context, dependencyName, currentVersion, targetVersion, assetBase, assetFile); - } + UpgradeBase(context, dependencyName, currentVersion, targetVersion, assetBase, assetFile); } } + } - private void UpgradeBase(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, dynamic assetBase, PackageLoadingAssetFile assetFile) + private void UpgradeBase(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, dynamic assetBase, PackageLoadingAssetFile assetFile) + { + var baseAsset = assetBase["Asset"]; + if (baseAsset != null) { - var baseAsset = assetBase["Asset"]; - if (baseAsset != null) - { - UpgradeAsset(context, currentVersion, targetVersion, baseAsset, assetFile, OverrideUpgraderHint.Base); - SetSerializableVersion(baseAsset, dependencyName, targetVersion); - } + UpgradeAsset(context, currentVersion, targetVersion, baseAsset, assetFile, OverrideUpgraderHint.Base); + SetSerializableVersion(baseAsset, dependencyName, targetVersion); } + } - protected abstract void UpgradeAsset(AssetMigrationContext context, PackageVersion currentVersion, PackageVersion targetVersion, dynamic asset, PackageLoadingAssetFile assetFile, OverrideUpgraderHint overrideHint); + protected abstract void UpgradeAsset(AssetMigrationContext context, PackageVersion currentVersion, PackageVersion targetVersion, dynamic asset, PackageLoadingAssetFile assetFile, OverrideUpgraderHint overrideHint); - public static void SetSerializableVersion(dynamic asset, string dependencyName, PackageVersion value) + public static void SetSerializableVersion(dynamic asset, string dependencyName, PackageVersion value) + { + if (asset.IndexOf(nameof(Asset.SerializedVersion)) == -1) { - if (asset.IndexOf(nameof(Asset.SerializedVersion)) == -1) - { - asset.SerializedVersion = new YamlMappingNode(); - - // Ensure that it is stored right after the asset Id - asset.MoveChild(nameof(Asset.SerializedVersion), asset.IndexOf(nameof(Asset.Id)) + 1); - } + asset.SerializedVersion = new YamlMappingNode(); - asset.SerializedVersion[dependencyName] = value; + // Ensure that it is stored right after the asset Id + asset.MoveChild(nameof(Asset.SerializedVersion), asset.IndexOf(nameof(Asset.Id)) + 1); } + + asset.SerializedVersion[dependencyName] = value; } } diff --git a/sources/assets/Stride.Core.Assets/AssetUpgraderCollection.cs b/sources/assets/Stride.Core.Assets/AssetUpgraderCollection.cs index f4ac645c48..6b381111df 100644 --- a/sources/assets/Stride.Core.Assets/AssetUpgraderCollection.cs +++ b/sources/assets/Stride.Core.Assets/AssetUpgraderCollection.cs @@ -1,110 +1,104 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public class AssetUpgraderCollection { - public class AssetUpgraderCollection + private readonly struct VersionRange : IComparable { - private struct VersionRange : IComparable - { - private readonly PackageVersion minimum; - public readonly PackageVersion Target; - - public VersionRange(PackageVersion minimum, PackageVersion target) - { - this.minimum = minimum; - Target = target; - } + private readonly PackageVersion minimum; + public readonly PackageVersion Target; - public bool Contains(PackageVersion value) - { - return minimum <= value && value < Target; - } - - public bool Overlap(VersionRange other) - { - return minimum < other.Target && other.minimum < Target; - } + public VersionRange(PackageVersion minimum, PackageVersion target) + { + this.minimum = minimum; + Target = target; + } - public int CompareTo(VersionRange other) - { - return minimum.CompareTo(other.minimum); - } + public readonly bool Contains(PackageVersion? value) + { + return minimum <= value && value < Target; } - private readonly SortedList upgraders = new SortedList(); - private readonly Dictionary instances = new Dictionary(); - private readonly PackageVersion currentVersion; + public readonly bool Overlap(VersionRange other) + { + return minimum < other.Target && other.minimum < Target; + } - public AssetUpgraderCollection(Type assetType, PackageVersion currentVersion) + public readonly int CompareTo(VersionRange other) { - this.currentVersion = currentVersion; - AssetRegistry.IsAssetOrPackageType(assetType, true); - AssetType = assetType; + return minimum.CompareTo(other.minimum); } + } - public Type AssetType { get; private set; } + private readonly SortedList upgraders = []; + private readonly Dictionary instances = []; + private readonly PackageVersion currentVersion; - internal void RegisterUpgrader(Type upgraderType, PackageVersion startVersion, PackageVersion targetVersion) - { - lock (upgraders) - { - if (targetVersion > currentVersion) - throw new ArgumentException("The upgrader has a target version higher that the current version."); + public AssetUpgraderCollection(Type assetType, PackageVersion currentVersion) + { + this.currentVersion = currentVersion; + AssetRegistry.IsAssetOrPackageType(assetType, true); + AssetType = assetType; + } - var range = new VersionRange(startVersion, targetVersion); + public Type AssetType { get; } - if (upgraders.Any(x => x.Key.Overlap(range))) - { - throw new ArgumentException("The upgrader overlaps with another upgrader."); - } + internal void RegisterUpgrader(Type upgraderType, PackageVersion startVersion, PackageVersion targetVersion) + { + lock (upgraders) + { + if (targetVersion > currentVersion) + throw new ArgumentException("The upgrader has a target version higher that the current version."); - upgraders.Add(new VersionRange(startVersion, targetVersion), upgraderType); + var range = new VersionRange(startVersion, targetVersion); + + if (upgraders.Any(x => x.Key.Overlap(range))) + { + throw new ArgumentException("The upgrader overlaps with another upgrader."); } + + upgraders.Add(new VersionRange(startVersion, targetVersion), upgraderType); } + } - internal void Validate(PackageVersion minVersion) + internal void Validate(PackageVersion minVersion) + { + lock (upgraders) { - lock (upgraders) + var version = minVersion; + foreach (var upgrader in upgraders) { - var version = minVersion; - foreach (var upgrader in upgraders) - { - if (!upgrader.Key.Contains(version)) - continue; - - version = upgrader.Key.Target; - if (version == currentVersion) - break; - } - - if (version != currentVersion) - throw new InvalidOperationException("No upgrader for asset type [{0}] allow to reach version {1}".ToFormat(AssetType.Name, currentVersion)); + if (!upgrader.Key.Contains(version)) + continue; + + version = upgrader.Key.Target; + if (version == currentVersion) + break; } + + if (version != currentVersion) + throw new InvalidOperationException("No upgrader for asset type [{0}] allow to reach version {1}".ToFormat(AssetType.Name, currentVersion)); } + } - public IAssetUpgrader GetUpgrader(PackageVersion initialVersion, out PackageVersion targetVersion) + public IAssetUpgrader GetUpgrader(PackageVersion initialVersion, out PackageVersion targetVersion) + { + lock (upgraders) { - lock (upgraders) + var upgrader = upgraders.FirstOrDefault(x => x.Key.Contains(initialVersion)); + if (upgrader.Value == null) + throw new InvalidOperationException("No upgrader found for version {0} of asset type [{1}]".ToFormat(currentVersion, AssetType.Name)); + targetVersion = upgrader.Key.Target; + + if (!instances.TryGetValue(upgrader.Value, out var result)) { - var upgrader = upgraders.FirstOrDefault(x => x.Key.Contains(initialVersion)); - if (upgrader.Value == null) - throw new InvalidOperationException("No upgrader found for version {0} of asset type [{1}]".ToFormat(currentVersion, AssetType.Name)); - targetVersion = upgrader.Key.Target; - - IAssetUpgrader result; - if (!instances.TryGetValue(upgrader.Value, out result)) - { - // Cache the upgrader instances - result = (IAssetUpgrader)Activator.CreateInstance(upgrader.Value); - instances.Add(upgrader.Value, result); - } - return result; + // Cache the upgrader instances + result = (IAssetUpgrader)Activator.CreateInstance(upgrader.Value)!; + instances.Add(upgrader.Value, result); } + return result; } } } diff --git a/sources/assets/Stride.Core.Assets/AssetWithSource.cs b/sources/assets/Stride.Core.Assets/AssetWithSource.cs index 3968681aeb..c8143eb198 100644 --- a/sources/assets/Stride.Core.Assets/AssetWithSource.cs +++ b/sources/assets/Stride.Core.Assets/AssetWithSource.cs @@ -1,30 +1,29 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System.ComponentModel; -using Stride.Core; using Stride.Core.IO; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// An asset that references a source file used during the compilation of the asset. +/// +[DataContract] +public abstract class AssetWithSource : Asset, IAssetWithSource { /// - /// An asset that references a source file used during the compilation of the asset. + /// Gets or sets the source file of this asset. /// - [DataContract] - public abstract class AssetWithSource : Asset, IAssetWithSource - { - /// - /// Gets or sets the source file of this asset. - /// - /// The source. - /// - /// The source file of this asset. - /// - [DataMember(-50)] - [DefaultValue(null)] - [SourceFileMember(false)] - public UFile Source { get; set; } = new UFile(""); + /// The source. + /// + /// The source file of this asset. + /// + [DataMember(-50)] + [DefaultValue("")] + [SourceFileMember(false)] + public UFile Source { get; set; } = ""; - [DataMemberIgnore] - public override UFile MainSource => Source; - } + [DataMemberIgnore] + public override UFile MainSource => Source; } diff --git a/sources/assets/Stride.Core.Assets/BasePart.cs b/sources/assets/Stride.Core.Assets/BasePart.cs index 3996eadf25..6be3d8fbf1 100644 --- a/sources/assets/Stride.Core.Assets/BasePart.cs +++ b/sources/assets/Stride.Core.Assets/BasePart.cs @@ -1,171 +1,158 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Assets.Serializers; -using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.Reflection; using Stride.Core.Serialization; using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +[DataContract] +[DataSerializer(typeof(BasePartDataSerializer))] +public sealed class BasePart : IEquatable { - [DataContract] - [DataSerializer(typeof(BasePartDataSerializer))] - public sealed class BasePart : IEquatable + /// + public bool Equals(BasePart? other) { - /// - public bool Equals(BasePart other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(BasePartAsset, other.BasePartAsset) && BasePartId.Equals(other.BasePartId) && InstanceId.Equals(other.InstanceId); - } + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(BasePartAsset, other.BasePartAsset) && BasePartId.Equals(other.BasePartId) && InstanceId.Equals(other.InstanceId); + } - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return Equals(obj as BasePart); - } + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as BasePart); + } - /// - public override int GetHashCode() - { - unchecked - { - // ReSharper disable NonReadonlyMemberInGetHashCode - this property is not supposed to be changed except by asset analysis - var hashCode = BasePartAsset != null ? BasePartAsset.GetHashCode() : 0; - // ReSharper restore NonReadonlyMemberInGetHashCode - hashCode = (hashCode * 397) ^ BasePartId.GetHashCode(); - hashCode = (hashCode * 397) ^ InstanceId.GetHashCode(); - return hashCode; - } - } + /// + public override int GetHashCode() + { + return HashCode.Combine(BasePartAsset, BasePartId, InstanceId); + } - /// - public static bool operator ==(BasePart left, BasePart right) - { - return Equals(left, right); - } + /// + public static bool operator ==(BasePart? left, BasePart? right) + { + return Equals(left, right); + } - /// - public static bool operator !=(BasePart left, BasePart right) - { - return !Equals(left, right); - } + /// + public static bool operator !=(BasePart? left, BasePart? right) + { + return !Equals(left, right); + } - public BasePart([NotNull] AssetReference basePartAsset, Guid basePartId, Guid instanceId) - { - if (basePartAsset == null) throw new ArgumentNullException(nameof(basePartAsset)); - if (basePartId == Guid.Empty) throw new ArgumentException(nameof(basePartAsset)); - if (instanceId == Guid.Empty) throw new ArgumentException(nameof(basePartAsset)); - BasePartAsset = basePartAsset; - BasePartId = basePartId; - InstanceId = instanceId; - } + public BasePart(AssetReference basePartAsset, Guid basePartId, Guid instanceId) + { + ArgumentNullException.ThrowIfNull(basePartAsset); + if (basePartId == Guid.Empty) throw new ArgumentException(null, nameof(basePartAsset)); + if (instanceId == Guid.Empty) throw new ArgumentException(null, nameof(basePartAsset)); + BasePartAsset = basePartAsset; + BasePartId = basePartId; + InstanceId = instanceId; + } - [DataMember(10)] - // This property might be updated by asset analysis, we want to keep a public setter for that reason. But it shouldn't be used in normal cases! (until we get a better solution) - public AssetReference BasePartAsset { get; set; } + [DataMember(10)] + // This property might be updated by asset analysis, we want to keep a public setter for that reason. But it shouldn't be used in normal cases! (until we get a better solution) + public AssetReference BasePartAsset { get; set; } - [DataMember(20)] - public Guid BasePartId { get; init; } + [DataMember(20)] + public Guid BasePartId { get; init; } - [DataMember(30)] - public Guid InstanceId { get; init; } + [DataMember(30)] + public Guid InstanceId { get; init; } - [CanBeNull] - public IIdentifiable ResolvePart(PackageSession session) - { - var assetItem = session.FindAsset(BasePartAsset.Id); - var asset = assetItem?.Asset as AssetComposite; - return asset?.FindPart(BasePartId); - } + public IIdentifiable? ResolvePart(PackageSession session) + { + var assetItem = session.FindAsset(BasePartAsset.Id); + var asset = assetItem?.Asset as AssetComposite; + return asset?.FindPart(BasePartId); } +} - public class BasePartDataSerializer : DataSerializer +public class BasePartDataSerializer : DataSerializer +{ + /// + public override void Serialize(ref BasePart basePart, ArchiveMode mode, SerializationStream stream) { - /// - public override void Serialize(ref BasePart basePart, ArchiveMode mode, SerializationStream stream) + if (mode == ArchiveMode.Serialize) + { + stream.Write(basePart.BasePartAsset); + stream.Write(basePart.BasePartId); + stream.Write(basePart.InstanceId); + } + else { - if (mode == ArchiveMode.Serialize) - { - stream.Write(basePart.BasePartAsset); - stream.Write(basePart.BasePartId); - stream.Write(basePart.InstanceId); - } - else - { - var asset = stream.Read(); - var baseId = stream.Read(); - var instanceId = stream.Read(); - basePart = new BasePart(asset, baseId, instanceId); - } + var asset = stream.Read(); + var baseId = stream.Read(); + var instanceId = stream.Read(); + basePart = new BasePart(asset, baseId, instanceId); } } +} - [YamlSerializerFactory(YamlAssetProfile.Name)] - internal class BasePartYamlSerializer : ObjectSerializer, IDataCustomVisitor +[YamlSerializerFactory(YamlAssetProfile.Name)] +internal class BasePartYamlSerializer : ObjectSerializer, IDataCustomVisitor +{ + public override IYamlSerializable? TryCreate(Core.Yaml.Serialization.SerializerContext context, ITypeDescriptor typeDescriptor) { - public override IYamlSerializable TryCreate(Core.Yaml.Serialization.SerializerContext context, ITypeDescriptor typeDescriptor) - { - return CanVisit(typeDescriptor.Type) ? this : null; - } + return CanVisit(typeDescriptor.Type) ? this : null; + } - protected override void CreateOrTransformObject(ref ObjectContext objectContext) - { - objectContext.Instance = objectContext.SerializerContext.IsSerializing ? new BasePartMutable((BasePart)objectContext.Instance) : new BasePartMutable(); - } + protected override void CreateOrTransformObject(ref ObjectContext objectContext) + { + objectContext.Instance = objectContext.SerializerContext.IsSerializing ? new BasePartMutable((BasePart)objectContext.Instance) : new BasePartMutable(); + } - protected override void TransformObjectAfterRead(ref ObjectContext objectContext) - { - objectContext.Instance = ((BasePartMutable)objectContext.Instance).ToBasePart(); - } + protected override void TransformObjectAfterRead(ref ObjectContext objectContext) + { + objectContext.Instance = ((BasePartMutable)objectContext.Instance).ToBasePart(); + } - private class BasePartMutable + private class BasePartMutable + { + public BasePartMutable() { - public BasePartMutable() - { - } - - public BasePartMutable(BasePart item) - { - BasePartAsset = item.BasePartAsset; - BasePartId = item.BasePartId; - InstanceId = item.InstanceId; - } - - // ReSharper disable MemberCanBePrivate.Local - // ReSharper disable FieldCanBeMadeReadOnly.Local - [DataMember(10)] - public AssetReference BasePartAsset; - - [DataMember(20)] - public Guid BasePartId; - - [DataMember(30)] - public Guid InstanceId; - // ReSharper restore FieldCanBeMadeReadOnly.Local - // ReSharper restore MemberCanBePrivate.Local - - public BasePart ToBasePart() - { - return new BasePart(BasePartAsset, BasePartId, InstanceId); - } } - public bool CanVisit(Type type) + public BasePartMutable(BasePart item) { - return type == typeof(BasePart); + BasePartAsset = item.BasePartAsset; + BasePartId = item.BasePartId; + InstanceId = item.InstanceId; } - public void Visit(ref VisitorContext context) + // ReSharper disable MemberCanBePrivate.Local + // ReSharper disable FieldCanBeMadeReadOnly.Local + [DataMember(10)] + public AssetReference BasePartAsset; + + [DataMember(20)] + public Guid BasePartId; + + [DataMember(30)] + public Guid InstanceId; + // ReSharper restore FieldCanBeMadeReadOnly.Local + // ReSharper restore MemberCanBePrivate.Local + + public BasePart ToBasePart() { - context.Visitor.VisitObject(context.Instance, context.Descriptor, true); + return new BasePart(BasePartAsset, BasePartId, InstanceId); } } + public bool CanVisit(Type type) + { + return type == typeof(BasePart); + } + + public void Visit(ref VisitorContext context) + { + context.Visitor.VisitObject(context.Instance, context.Descriptor, true); + } } diff --git a/sources/assets/Stride.Core.Assets/Bundle.cs b/sources/assets/Stride.Core.Assets/Bundle.cs index 8c506f49c4..88e27c1606 100644 --- a/sources/assets/Stride.Core.Assets/Bundle.cs +++ b/sources/assets/Stride.Core.Assets/Bundle.cs @@ -1,44 +1,42 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; + using System.ComponentModel; using System.Diagnostics; -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Description of an asset bundle. +/// +[DataContract("Bundle")] +[DebuggerDisplay("Bundle [{Name}] Selectors[{Selectors.Count}] Dependencies[{Dependencies.Count}]")] +public sealed class Bundle { /// - /// Description of an asset bundle. + /// Gets or sets the name of this bundle. /// - [DataContract("Bundle")] - [DebuggerDisplay("Bundle [{Name}] Selectors[{Selectors.Count}] Dependencies[{Dependencies.Count}]")] - public sealed class Bundle - { - /// - /// Gets or sets the name of this bundle. - /// - /// The name. - public string Name { get; set; } + /// The name. + public string Name { get; set; } - /// - /// Gets the selectors used by this bundle. - /// - /// The selectors. - public List Selectors { get; } = new List(); + /// + /// Gets the selectors used by this bundle. + /// + /// The selectors. + public List Selectors { get; } = []; - /// - /// Gets the bundle dependencies. - /// - /// The dependencies. - public List Dependencies { get; } = new List(); + /// + /// Gets the bundle dependencies. + /// + /// The dependencies. + public List Dependencies { get; } = []; - /// - /// Gets the output group (used in conjonction with `ProjectBuildProfile.OutputGroupDirectories` to control where file will be put). - /// - /// - /// The output group. - /// - [DefaultValue(null)] - public string OutputGroup { get; set; } - } + /// + /// Gets the output group (used in conjonction with `ProjectBuildProfile.OutputGroupDirectories` to control where file will be put). + /// + /// + /// The output group. + /// + [DefaultValue(null)] + public string? OutputGroup { get; set; } } diff --git a/sources/assets/Stride.Core.Assets/BundleCollection.cs b/sources/assets/Stride.Core.Assets/BundleCollection.cs index 1e37e9e2d6..aeef0b726e 100644 --- a/sources/assets/Stride.Core.Assets/BundleCollection.cs +++ b/sources/assets/Stride.Core.Assets/BundleCollection.cs @@ -1,38 +1,27 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// A collection of bundles. +/// +[DataContract("!Bundles")] +public class BundleCollection : List { /// - /// A collection of bundles. + /// Initializes a new instance of the class. /// - [DataContract("!Bundles")] - public class BundleCollection : List + /// The package. + internal BundleCollection(Package package) { - private readonly Package package; - - /// - /// Initializes a new instance of the class. - /// - /// The package. - internal BundleCollection(Package package) - { - this.package = package; - } - - /// - /// Gets the package this bundle collection is defined for. - /// - /// The package. - [DataMemberIgnore] - private Package Package - { - get - { - return package; - } - } + Package = package; } + + /// + /// Gets the package this bundle collection is defined for. + /// + /// The package. + [DataMemberIgnore] + private Package Package { get; } } diff --git a/sources/assets/Stride.Core.Assets/CollectionIdGenerator.cs b/sources/assets/Stride.Core.Assets/CollectionIdGenerator.cs index 5b0169ae89..359a2721a9 100644 --- a/sources/assets/Stride.Core.Assets/CollectionIdGenerator.cs +++ b/sources/assets/Stride.Core.Assets/CollectionIdGenerator.cs @@ -1,134 +1,131 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; -using System.Linq; using Stride.Core.Annotations; using Stride.Core.Reflection; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// A visitor that will generate a collection for each collection or dictionary of the visited object, +/// and attach it to the related collection. +/// +public class CollectionIdGenerator : DataVisitorBase { - /// - /// A visitor that will generate a collection for each collection or dictionary of the visited object, - /// and attach it to the related collection. - /// - public class CollectionIdGenerator : DataVisitorBase - { - private int inNonIdentifiableType; - private HashSet nonIdentifiableCollection; + private int inNonIdentifiableType; + private HashSet? nonIdentifiableCollection; - protected override bool CanVisit(object obj) - { - return !AssetRegistry.IsExactContentType(obj?.GetType()) && base.CanVisit(obj); - } + protected override bool CanVisit(object obj) + { + return !AssetRegistry.IsExactContentType(obj.GetType()) && base.CanVisit(obj); + } - public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) + public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) + { + var localInNonIdentifiableType = false; + try { - var localInNonIdentifiableType = false; - try - { - if (descriptor.Attributes.OfType().Any()) - { - localInNonIdentifiableType = true; - inNonIdentifiableType++; - } - base.VisitObject(obj, descriptor, visitMembers); - } - finally + if (descriptor.Attributes.OfType().Any()) { - if (localInNonIdentifiableType) - inNonIdentifiableType--; + localInNonIdentifiableType = true; + inNonIdentifiableType++; } + base.VisitObject(obj, descriptor, visitMembers); } + finally + { + if (localInNonIdentifiableType) + inNonIdentifiableType--; + } + } - public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object value) + public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object? value) + { + if (member.GetCustomAttributes(true).Any()) { - if (member.GetCustomAttributes(true).Any()) + // Value types (collection that are struct) will automatically be considered as non-identifiable. + if (value?.GetType().IsValueType == false) { - // Value types (collection that are struct) will automatically be considered as non-identifiable. - if (value?.GetType().IsValueType == false) - { - nonIdentifiableCollection = nonIdentifiableCollection ?? new HashSet(); - nonIdentifiableCollection.Add(value); - } + nonIdentifiableCollection ??= []; + nonIdentifiableCollection.Add(value); } - base.VisitObjectMember(container, containerDescriptor, member, value); } + base.VisitObjectMember(container, containerDescriptor, member, value); + } - public override void VisitArray(Array array, ArrayDescriptor descriptor) + public override void VisitArray(Array array, ArrayDescriptor descriptor) + { + if (ShouldGenerateItemIdCollection(array)) { - if (ShouldGenerateItemIdCollection(array)) + var itemIds = CollectionItemIdHelper.GetCollectionItemIds(array); + for (var i = 0; i < array.Length; ++i) { - var itemIds = CollectionItemIdHelper.GetCollectionItemIds(array); - for (var i = 0; i < array.Length; ++i) - { - itemIds.Add(i, ItemId.New()); - } + itemIds.Add(i, ItemId.New()); } - base.VisitArray(array, descriptor); } + base.VisitArray(array, descriptor); + } - public override void VisitCollection(IEnumerable collection, CollectionDescriptor descriptor) + public override void VisitCollection(IEnumerable collection, CollectionDescriptor descriptor) + { + if (ShouldGenerateItemIdCollection(collection)) { - if (ShouldGenerateItemIdCollection(collection)) + var itemIds = CollectionItemIdHelper.GetCollectionItemIds(collection); + var count = descriptor.GetCollectionCount(collection); + for (var i = 0; i < count; ++i) { - var itemIds = CollectionItemIdHelper.GetCollectionItemIds(collection); - var count = descriptor.GetCollectionCount(collection); - for (var i = 0; i < count; ++i) - { - itemIds.Add(i, ItemId.New()); - } + itemIds.Add(i, ItemId.New()); } - base.VisitCollection(collection, descriptor); } + base.VisitCollection(collection, descriptor); + } - public override void VisitDictionary(object dictionary, DictionaryDescriptor descriptor) + public override void VisitDictionary(object dictionary, DictionaryDescriptor descriptor) + { + if (ShouldGenerateItemIdCollection(dictionary)) { - if (ShouldGenerateItemIdCollection(dictionary)) + var itemIds = CollectionItemIdHelper.GetCollectionItemIds(dictionary); + foreach (var element in descriptor.GetEnumerator(dictionary)) { - var itemIds = CollectionItemIdHelper.GetCollectionItemIds(dictionary); - foreach (var element in descriptor.GetEnumerator(dictionary)) - { - itemIds.Add(element.Key, ItemId.New()); - } + itemIds.Add(element.Key, ItemId.New()); } - base.VisitDictionary(dictionary, descriptor); } + base.VisitDictionary(dictionary, descriptor); + } - public override void VisitSet(IEnumerable set, SetDescriptor descriptor) + public override void VisitSet(IEnumerable set, SetDescriptor descriptor) + { + if (ShouldGenerateItemIdCollection(set)) { - if (ShouldGenerateItemIdCollection(set)) + IEnumerator enumerator = (set as IEnumerable).GetEnumerator(); + var itemIds = CollectionItemIdHelper.GetCollectionItemIds(set); + while (enumerator.MoveNext()) { - IEnumerator enumerator = (set as IEnumerable).GetEnumerator(); - var itemIds = CollectionItemIdHelper.GetCollectionItemIds(set); - while (enumerator.MoveNext()) - { - itemIds.Add(enumerator.Current, ItemId.New()); - } + itemIds.Add(enumerator.Current, ItemId.New()); } - base.VisitSet(set, descriptor); } + base.VisitSet(set, descriptor); + } - private bool ShouldGenerateItemIdCollection(object collection) - { - // Do not generate for value types (collections that are struct) or null - if (collection?.GetType().IsValueType != false) - return false; + private bool ShouldGenerateItemIdCollection(object collection) + { + // Do not generate for value types (collections that are struct) or null + if (collection?.GetType().IsValueType != false) + return false; - // Do not generate if within a type that doesn't use identifiable collections - if (inNonIdentifiableType > 0) - return false; + // Do not generate if within a type that doesn't use identifiable collections + if (inNonIdentifiableType > 0) + return false; - // Do not generate if item id collection already exists - if (CollectionItemIdHelper.HasCollectionItemIds(collection)) - return false; + // Do not generate if item id collection already exists + if (CollectionItemIdHelper.HasCollectionItemIds(collection)) + return false; - // Do not generate if the collection has been flagged to not be identifiable - if (nonIdentifiableCollection?.Contains(collection) == true) - return false; + // Do not generate if the collection has been flagged to not be identifiable + if (nonIdentifiableCollection?.Contains(collection) == true) + return false; - return true; - } + return true; } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/AssetBuildStep.cs b/sources/assets/Stride.Core.Assets/Compiler/AssetBuildStep.cs index 6e608fc81a..fc47aa555c 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/AssetBuildStep.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/AssetBuildStep.cs @@ -1,38 +1,35 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using Stride.Core.BuildEngine; -using Stride.Core.Serialization; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// Represents a list of instances that compiles a given asset. +/// +public class AssetBuildStep : ListBuildStep { /// - /// Represents a list of instances that compiles a given asset. + /// Initializes a new instance of the class. /// - public class AssetBuildStep : ListBuildStep + /// The asset that can be build by this build step. + public AssetBuildStep(AssetItem assetItem) { - /// - /// Initializes a new instance of the class. - /// - /// The asset that can be build by this build step. - public AssetBuildStep(AssetItem assetItem) - { - if (assetItem == null) throw new ArgumentNullException("assetItem"); - AssetItem = assetItem; - } - - /// - /// Gets the corresponding to the asset being built by this build step. - /// - public AssetItem AssetItem { get; private set; } + ArgumentNullException.ThrowIfNull(assetItem); + AssetItem = assetItem; + } - /// - public override string ToString() - { - return $"Asset build steps [{AssetItem.Asset?.GetType().Name ?? "(null)"}:'{AssetItem.Location}'] ({Count} items)"; - } + /// + /// Gets the corresponding to the asset being built by this build step. + /// + public AssetItem AssetItem { get; private set; } - public override string OutputLocation => AssetItem.Location; + /// + public override string ToString() + { + return $"Asset build steps [{AssetItem.Asset?.GetType().Name ?? "(null)"}:'{AssetItem.Location}'] ({Count} items)"; } + + public override string OutputLocation => AssetItem.Location; } diff --git a/sources/assets/Stride.Core.Assets/Compiler/AssetCommand.cs b/sources/assets/Stride.Core.Assets/Compiler/AssetCommand.cs index 0218b941c6..48705e8374 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/AssetCommand.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/AssetCommand.cs @@ -1,63 +1,59 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; using Stride.Core.BuildEngine; using Stride.Core.Serialization; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// A command processing an . +/// +public abstract class AssetCommand : IndexFileCommand { - /// - /// A command processing an . - /// - public abstract class AssetCommand : IndexFileCommand - { - public string Url { get; set; } + public string Url { get; set; } - protected AssetCommand(string url) - { - Url = url; - } + protected AssetCommand(string url) + { + Url = url; } +} + +public abstract class AssetCommand : AssetCommand +{ + protected readonly IAssetFinder AssetFinder; - public abstract class AssetCommand : AssetCommand + /// + /// This is useful if the asset binary format has changed and we want to bump the version to force re-evaluation/compilation of the command + /// + protected int Version; + + protected AssetCommand(string url, T parameters, IAssetFinder assetFinder) + : base (url) { - protected readonly IAssetFinder AssetFinder; + Parameters = parameters; + AssetFinder = assetFinder; + } - /// - /// This is useful if the asset binary format has changed and we want to bump the version to force re-evaluation/compilation of the command - /// - protected int Version; + public T Parameters { get; set; } + + public override string Title => $"Asset command processing {Url}"; - protected AssetCommand(string url, T parameters, IAssetFinder assetFinder) - : base (url) - { - Parameters = parameters; - AssetFinder = assetFinder; - } + protected override void ComputeParameterHash(BinarySerializationWriter writer) + { + base.ComputeParameterHash(writer); - public T Parameters { get; set; } + writer.Serialize(ref Version); - public override string Title => $"Asset command processing {Url}"; - - protected override void ComputeParameterHash(BinarySerializationWriter writer) - { - base.ComputeParameterHash(writer); - - writer.Serialize(ref Version); - - var url = Url; - var assetParameters = Parameters; - writer.SerializeExtended(ref assetParameters, ArchiveMode.Serialize); - writer.Serialize(ref url, ArchiveMode.Serialize); - } - - public override string ToString() - { - // TODO provide automatic asset to string via YAML - return $"[{Url}] {Parameters}"; - } + var url = Url; + var assetParameters = Parameters; + writer.SerializeExtended(ref assetParameters, ArchiveMode.Serialize); + writer.Serialize(ref url, ArchiveMode.Serialize); + } + + public override string ToString() + { + // TODO provide automatic asset to string via YAML + return $"[{Url}] {Parameters}"; } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/AssetCompilationContext.cs b/sources/assets/Stride.Core.Assets/Compiler/AssetCompilationContext.cs index e877cbf38f..34822da810 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/AssetCompilationContext.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/AssetCompilationContext.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets.Compiler -{ - public class AssetCompilationContext : ICompilationContext - { - } -} + +namespace Stride.Core.Assets.Compiler; + +public class AssetCompilationContext : ICompilationContext; diff --git a/sources/assets/Stride.Core.Assets/Compiler/AssetCompiledArgs.cs b/sources/assets/Stride.Core.Assets/Compiler/AssetCompiledArgs.cs index d99da9f7ad..f29eaf72f1 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/AssetCompiledArgs.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/AssetCompiledArgs.cs @@ -1,35 +1,33 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// The class represents the argument of the event raised by the class. +/// +public class AssetCompiledArgs : EventArgs { /// - /// The class represents the argument of the event raised by the class. + /// Constructs an instance. /// - public class AssetCompiledArgs : EventArgs + /// The asset that has been compiled. Cannot be null. + /// The result of the asset compilation. Cannot be null. + public AssetCompiledArgs(AssetItem asset, AssetCompilerResult result) { - /// - /// Constructs an instance. - /// - /// The asset that has been compiled. Cannot be null. - /// The result of the asset compilation. Cannot be null. - public AssetCompiledArgs(AssetItem asset, AssetCompilerResult result) - { - if (asset == null) throw new ArgumentNullException("asset"); - if (result == null) throw new ArgumentNullException("result"); - Asset = asset; - Result = result; - } + ArgumentNullException.ThrowIfNull(asset); + ArgumentNullException.ThrowIfNull(result); + Asset = asset; + Result = result; + } - /// - /// The asset item that has just been compiled. - /// - public AssetItem Asset { get; set; } + /// + /// The asset item that has just been compiled. + /// + public AssetItem Asset { get; set; } - /// - /// The result of the asset compilation. - /// - public AssetCompilerResult Result { get; set; } - } + /// + /// The result of the asset compilation. + /// + public AssetCompilerResult Result { get; set; } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerAttribute.cs b/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerAttribute.cs index 052630aada..e5d205e391 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerAttribute.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerAttribute.cs @@ -1,29 +1,28 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Annotations; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// Attribute to define an asset compiler for a . +/// +[AttributeUsage(AttributeTargets.Class)] +[BaseTypeRequired(typeof(IAssetCompiler))] +public class AssetCompilerAttribute : DynamicTypeAttributeBase { - /// - /// Attribute to define an asset compiler for a . - /// - [AttributeUsage(AttributeTargets.Class)] - [BaseTypeRequired(typeof(IAssetCompiler))] - public class AssetCompilerAttribute : DynamicTypeAttributeBase - { - public Type CompilationContext { get; private set; } + public Type CompilationContext { get; private set; } - public AssetCompilerAttribute(Type type, Type compilationContextType) - : base(type) - { - CompilationContext = compilationContextType; - } + public AssetCompilerAttribute(Type type, Type compilationContextType) + : base(type) + { + CompilationContext = compilationContextType; + } - public AssetCompilerAttribute(string typeName, Type compilationContextType) - : base(typeName) - { - CompilationContext = compilationContextType; - } + public AssetCompilerAttribute(string typeName, Type compilationContextType) + : base(typeName) + { + CompilationContext = compilationContextType; } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerBase.cs b/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerBase.cs index 0533d29090..e365932d25 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerBase.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerBase.cs @@ -1,131 +1,128 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; + using Stride.Core.Assets.Analysis; using Stride.Core.Assets.Tracking; using Stride.Core.IO; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// Base implementation for suitable to compile a single type of . +/// +public abstract class AssetCompilerBase : IAssetCompiler { - /// - /// Base implementation for suitable to compile a single type of . - /// - public abstract class AssetCompilerBase : IAssetCompiler + /// + public virtual IEnumerable GetInputFiles(AssetItem assetItem) + { + yield break; + } + + /// + public virtual IEnumerable GetInputTypes(AssetItem assetItem) + { + yield break; + } + + /// + public virtual IEnumerable GetInputTypesToExclude(AssetItem assetItem) { - /// - public virtual IEnumerable GetInputFiles(AssetItem assetItem) + yield break; + } + + public virtual bool AlwaysCheckRuntimeTypes { get; } = true; + + public virtual IEnumerable GetRuntimeTypes(AssetItem assetItem) + { + yield break; + } + + public AssetCompilerResult Prepare(AssetCompilerContext context, AssetItem assetItem) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(assetItem); + + var result = new AssetCompilerResult(GetType().Name) { - yield break; - } + BuildSteps = new AssetBuildStep(assetItem) + }; - /// - public virtual IEnumerable GetInputTypes(AssetItem assetItem) + // Only use the path to the asset without its extension + var fullPath = assetItem.FullPath; + if (!fullPath.IsAbsolute) { - yield break; + throw new InvalidOperationException("assetItem must be an absolute path"); } - /// - public virtual IEnumerable GetInputTypesToExclude(AssetItem assetItem) + // Try to compile only if we're sure that the sources exist. + if (EnsureSourcesExist(result, assetItem)) { - yield break; + Prepare(context, assetItem, assetItem.Location.GetDirectoryAndFileName(), result); } - public virtual bool AlwaysCheckRuntimeTypes { get; } = true; + return result; + } - public virtual IEnumerable GetRuntimeTypes(AssetItem assetItem) - { - yield break; - } + /// + /// Compiles the asset from the specified package. + /// + /// The context to use to compile the asset. + /// The asset to compile + /// The absolute URL to the asset, relative to the storage. + /// The result where the commands and logs should be output. + protected abstract void Prepare(AssetCompilerContext context, AssetItem assetItem, string targetUrlInStorage, AssetCompilerResult result); - public AssetCompilerResult Prepare(AssetCompilerContext context, AssetItem assetItem) - { - if (context == null) throw new ArgumentNullException(nameof(context)); - if (assetItem == null) throw new ArgumentNullException(nameof(assetItem)); + /// + /// Returns the absolute path on the disk of an that is relative to the asset location. + /// + /// The asset on which is based the relative path. + /// The path relative to the asset path that must be converted to an absolute path. + /// The absolute path on the disk of the argument. + /// The argument is a null or empty . + protected static UFile GetAbsolutePath(AssetItem assetItem, UFile relativePath) + { + if (string.IsNullOrEmpty(relativePath)) throw new ArgumentException("The relativePath argument is null or empty"); + var assetDirectory = assetItem.FullPath.GetParent(); + var assetSource = UPath.Combine(assetDirectory, relativePath); + return assetSource; + } - var result = new AssetCompilerResult(GetType().Name) - { - BuildSteps = new AssetBuildStep(assetItem) - }; + /// + /// Ensures that the sources of an exist. + /// + /// The in which to output log of potential errors. + /// The asset to check. + /// true if the source file exists, false otherwise. + /// Any of the argument is null. + private static bool EnsureSourcesExist(AssetCompilerResult result, AssetItem assetItem) + { + ArgumentNullException.ThrowIfNull(result); + ArgumentNullException.ThrowIfNull(assetItem); - // Only use the path to the asset without its extension - var fullPath = assetItem.FullPath; - if (!fullPath.IsAbsolute) - { - throw new InvalidOperationException("assetItem must be an absolute path"); - } + var collector = new SourceFilesCollector(); + var sourceMembers = collector.GetSourceMembers(assetItem.Asset); - // Try to compile only if we're sure that the sources exist. - if (EnsureSourcesExist(result, assetItem)) + foreach (var member in sourceMembers) + { + if (string.IsNullOrEmpty(member.Value)) { - Prepare(context, assetItem, assetItem.Location.GetDirectoryAndFileName(), result); + result.Error($"Source is null for Asset [{assetItem}] in property [{member.Key}]"); + return false; } - return result; - } - - /// - /// Compiles the asset from the specified package. - /// - /// The context to use to compile the asset. - /// The asset to compile - /// The absolute URL to the asset, relative to the storage. - /// The result where the commands and logs should be output. - protected abstract void Prepare(AssetCompilerContext context, AssetItem assetItem, string targetUrlInStorage, AssetCompilerResult result); - - /// - /// Returns the absolute path on the disk of an that is relative to the asset location. - /// - /// The asset on which is based the relative path. - /// The path relative to the asset path that must be converted to an absolute path. - /// The absolute path on the disk of the argument. - /// The argument is a null or empty . - protected static UFile GetAbsolutePath(AssetItem assetItem, UFile relativePath) - { - if (string.IsNullOrEmpty(relativePath)) throw new ArgumentException("The relativePath argument is null or empty"); + // Get absolute path of asset source on disk var assetDirectory = assetItem.FullPath.GetParent(); - var assetSource = UPath.Combine(assetDirectory, relativePath); - return assetSource; - } + var assetSource = UPath.Combine(assetDirectory, member.Value); - /// - /// Ensures that the sources of an exist. - /// - /// The in which to output log of potential errors. - /// The asset to check. - /// true if the source file exists, false otherwise. - /// Any of the argument is null. - private static bool EnsureSourcesExist(AssetCompilerResult result, AssetItem assetItem) - { - if (result == null) throw new ArgumentNullException(nameof(result)); - if (assetItem == null) throw new ArgumentNullException(nameof(assetItem)); - - var collector = new SourceFilesCollector(); - var sourceMembers = collector.GetSourceMembers(assetItem.Asset); - - foreach (var member in sourceMembers) + // Ensure the file exists + if (!File.Exists(assetSource)) { - if (string.IsNullOrEmpty(member.Value)) - { - result.Error($"Source is null for Asset [{assetItem}] in property [{member.Key}]"); - return false; - } - - // Get absolute path of asset source on disk - var assetDirectory = assetItem.FullPath.GetParent(); - var assetSource = UPath.Combine(assetDirectory, member.Value); - - // Ensure the file exists - if (!File.Exists(assetSource)) - { - result.Error($"Unable to find the source file '{assetSource}' for Asset [{assetItem}]"); - return false; - } + result.Error($"Unable to find the source file '{assetSource}' for Asset [{assetItem}]"); + return false; } - - return true; } + + return true; } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerContext.cs b/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerContext.cs index 00f978e286..dc1cb35a27 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerContext.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerContext.cs @@ -1,40 +1,36 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; +namespace Stride.Core.Assets.Compiler; -namespace Stride.Core.Assets.Compiler +/// +/// The context used when compiling an asset in a Package. +/// +public class AssetCompilerContext : CompilerContext { /// - /// The context used when compiling an asset in a Package. + /// Gets or sets the name of the profile being built. /// - public class AssetCompilerContext : CompilerContext - { - /// - /// Gets or sets the name of the profile being built. - /// - public string Profile { get; set; } + public string Profile { get; set; } - /// - /// Gets or sets the build configuration (Debug, Release, AppStore, Testing) - /// - public string BuildConfiguration { get; set; } + /// + /// Gets or sets the build configuration (Debug, Release, AppStore, Testing) + /// + public string BuildConfiguration { get; set; } - /// - /// Gets or sets the entry package this build was called upon. - /// - public Package Package { get; set; } + /// + /// Gets or sets the entry package this build was called upon. + /// + public Package Package { get; set; } - /// - /// Gets or sets the target platform for compiler is being used for. - /// - /// The platform. - public PlatformType Platform { get; set; } + /// + /// Gets or sets the target platform for compiler is being used for. + /// + /// The platform. + public PlatformType Platform { get; set; } - /// - /// The compilation context type of this compiler context - /// - public Type CompilationContext; - } + /// + /// The compilation context type of this compiler context + /// + public Type CompilationContext; } diff --git a/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerRegistry.cs b/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerRegistry.cs index c77433d7c8..e5e6681c30 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerRegistry.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerRegistry.cs @@ -1,274 +1,264 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Reflection; -using Stride.Core; using Stride.Core.Diagnostics; +using Stride.Core.Extensions; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// A registry containing the asset compilers of the assets. +/// +public sealed class AssetCompilerRegistry { + private readonly HashSet assembliesToRegister = []; + private readonly Logger log = GlobalLogger.GetLogger("AssetsCompiler.AttributeBasedRegistry"); + + private readonly HashSet registeredAssemblies = []; + private readonly Dictionary typeToCompiler = []; + private bool assembliesChanged; + + /// + /// Create an instance of that registry + /// + public AssetCompilerRegistry() + { + // Statically find all assemblies related to assets and register them + var assemblies = AssemblyRegistry.Find(AssemblyCommonCategories.Assets); + foreach (var assembly in assemblies) + { + RegisterAssembly(assembly); + } + + AssemblyRegistry.AssemblyRegistered += AssemblyRegistered; + AssemblyRegistry.AssemblyUnregistered += AssemblyUnregistered; + } + + /// + /// Gets or sets the default compiler to use when no compiler are explicitly registered for a type. + /// + public IAssetCompiler? DefaultCompiler { get; set; } + /// - /// A registry containing the asset compilers of the assets. + /// Gets the compiler associated to an type. /// - public sealed class AssetCompilerRegistry + /// The type of the + /// + /// The compiler associated the provided asset type or null if no compiler exists for that type. + public IAssetCompiler? GetCompiler(Type type, Type context) { - private readonly HashSet assembliesToRegister = new HashSet(); - private readonly Logger log = GlobalLogger.GetLogger("AssetsCompiler.AttributeBasedRegistry"); + AssertAssetType(type); - private readonly HashSet registeredAssemblies = new HashSet(); - private readonly Dictionary typeToCompiler = new Dictionary(); - private bool assembliesChanged; + EnsureTypes(); - /// - /// Create an instance of that registry - /// - public AssetCompilerRegistry() + var typeData = new CompilerTypeData(context, type); + + if (!typeToCompiler.TryGetValue(typeData, out var compiler)) { - // Statically find all assemblies related to assets and register them - var assemblies = AssemblyRegistry.Find(AssemblyCommonCategories.Assets); - foreach (var assembly in assemblies) + //support nested context types! + if (context.BaseType != typeof(object)) { - RegisterAssembly(assembly); + return GetCompiler(type, context.BaseType); } - AssemblyRegistry.AssemblyRegistered += AssemblyRegistered; - AssemblyRegistry.AssemblyUnregistered += AssemblyUnregistered; + compiler = DefaultCompiler; } - /// - /// Gets or sets the default compiler to use when no compiler are explicitly registered for a type. - /// - public IAssetCompiler DefaultCompiler { get; set; } - - /// - /// Gets the compiler associated to an type. - /// - /// The type of the - /// - /// The compiler associated the provided asset type or null if no compiler exists for that type. - public IAssetCompiler GetCompiler(Type type, Type context) - { - AssertAssetType(type); + return compiler; + } - EnsureTypes(); + /// + /// Register a compiler for a given type. + /// + /// The type of asset the compiler can compile + /// The compiler to use + /// + public void RegisterCompiler(Type type, IAssetCompiler compiler, Type context) + { + ArgumentNullException.ThrowIfNull(compiler); - var typeData = new CompilerTypeData(context, type); + AssertAssetType(type); - IAssetCompiler compiler; - if (!typeToCompiler.TryGetValue(typeData, out compiler)) - { - //support nested context types! - if (context.BaseType != typeof(object)) - { - return GetCompiler(type, context.BaseType); - } + var typeData = new CompilerTypeData(context, type); - compiler = DefaultCompiler; - } + typeToCompiler[typeData] = compiler; + } - return compiler; + private void UnregisterCompilersFromAssembly(Assembly assembly) + { + foreach (var typeToRemove in typeToCompiler.Where(typeAndCompile => typeAndCompile.Key.Type.Assembly == assembly || typeAndCompile.Value.GetType().Assembly == assembly).Select(e => e.Key).ToList()) + { + typeToCompiler.Remove(typeToRemove); } + } - /// - /// Register a compiler for a given type. - /// - /// The type of asset the compiler can compile - /// The compiler to use - /// - public void RegisterCompiler(Type type, IAssetCompiler compiler, Type context) - { - if (compiler == null) throw new ArgumentNullException(nameof(compiler)); + private static void AssertAssetType(Type assetType) + { + ArgumentNullException.ThrowIfNull(assetType); - AssertAssetType(type); + if (!typeof(Asset).IsAssignableFrom(assetType)) + throw new ArgumentException("Type [{0}] must be assignable to Asset".ToFormat(assetType), nameof(assetType)); + } - var typeData = new CompilerTypeData(context, type); + private void AssemblyRegistered(object? sender, AssemblyRegisteredEventArgs e) + { + // Handle delay-loading assemblies + if (e.Categories.Contains(AssemblyCommonCategories.Assets)) + RegisterAssembly(e.Assembly); + } - typeToCompiler[typeData] = compiler; - } + private void AssemblyUnregistered(object? sender, AssemblyRegisteredEventArgs e) + { + if (e.Categories.Contains(AssemblyCommonCategories.Assets)) + UnregisterAssembly(e.Assembly); + } - private void UnregisterCompilersFromAssembly(Assembly assembly) + private void EnsureTypes() + { + if (assembliesChanged) { - foreach (var typeToRemove in typeToCompiler.Where(typeAndCompile => typeAndCompile.Key.Type.Assembly == assembly || typeAndCompile.Value.GetType().Assembly == assembly).Select(e => e.Key).ToList()) + Assembly[] assembliesToRegisterCopy; + lock (assembliesToRegister) { - typeToCompiler.Remove(typeToRemove); + assembliesToRegisterCopy = [.. assembliesToRegister]; + assembliesToRegister.Clear(); + assembliesChanged = false; + } + + foreach (var assembly in assembliesToRegisterCopy) + { + if (!registeredAssemblies.Contains(assembly)) + { + RegisterCompilersFromAssembly(assembly); + registeredAssemblies.Add(assembly); + } } } + } - private static void AssertAssetType(Type assetType) + private void ProcessAttribute(AssetCompilerAttribute compilerCompilerAttribute, Type type) + { + if (!typeof(ICompilationContext).IsAssignableFrom(compilerCompilerAttribute.CompilationContext)) { - if (assetType == null) - throw new ArgumentNullException(nameof(assetType)); - - if (!typeof(Asset).IsAssignableFrom(assetType)) - throw new ArgumentException("Type [{0}] must be assignable to Asset".ToFormat(assetType), nameof(assetType)); + log.Error($"Invalid compiler context type [{compilerCompilerAttribute.CompilationContext}], must inherit from ICompilerContext"); + return; } - private void AssemblyRegistered(object sender, AssemblyRegisteredEventArgs e) + var assetType = AssemblyRegistry.GetType(compilerCompilerAttribute.TypeName); + if (assetType == null) { - // Handle delay-loading assemblies - if (e.Categories.Contains(AssemblyCommonCategories.Assets)) - RegisterAssembly(e.Assembly); + log.Error($"Unable to find asset [{compilerCompilerAttribute.TypeName}] for compiler [{type}]"); + return; } - private void AssemblyUnregistered(object sender, AssemblyRegisteredEventArgs e) + if (Activator.CreateInstance(type) is not IAssetCompiler compilerInstance) { - if (e.Categories.Contains(AssemblyCommonCategories.Assets)) - UnregisterAssembly(e.Assembly); + log.Error($"Invalid compiler type [{type}], must inherit from IAssetCompiler"); + return; } - private void EnsureTypes() - { - if (assembliesChanged) - { - Assembly[] assembliesToRegisterCopy; - lock (assembliesToRegister) - { - assembliesToRegisterCopy = assembliesToRegister.ToArray(); - assembliesToRegister.Clear(); - assembliesChanged = false; - } + RegisterCompiler(assetType, compilerInstance, compilerCompilerAttribute.CompilationContext); + } - foreach (var assembly in assembliesToRegisterCopy) - { - if (!registeredAssemblies.Contains(assembly)) - { - RegisterCompilersFromAssembly(assembly); - registeredAssemblies.Add(assembly); - } - } - } + private void RegisterAssembly(Assembly assembly) + { + ArgumentNullException.ThrowIfNull(assembly); + lock (assembliesToRegister) + { + assembliesToRegister.Add(assembly); + assembliesChanged = true; } + } - private void ProcessAttribute(AssetCompilerAttribute compilerCompilerAttribute, Type type) + private void RegisterCompilersFromAssembly(Assembly assembly) + { + // Process Asset types. + foreach (var type in GetFullyLoadedTypes(assembly)) { - if (!typeof(ICompilationContext).IsAssignableFrom(compilerCompilerAttribute.CompilationContext)) - { - log.Error($"Invalid compiler context type [{compilerCompilerAttribute.CompilationContext}], must inherit from ICompilerContext"); - return; - } + // Only process Asset types + if (!typeof(IAssetCompiler).IsAssignableFrom(type) || !type.IsClass) + continue; - var assetType = AssemblyRegistry.GetType(compilerCompilerAttribute.TypeName); - if (assetType == null) - { - log.Error($"Unable to find asset [{compilerCompilerAttribute.TypeName}] for compiler [{type}]"); - return; - } + // Asset compiler + var compilerAttribute = type.GetCustomAttribute(); - var compilerInstance = Activator.CreateInstance(type) as IAssetCompiler; - if (compilerInstance == null) + if (compilerAttribute == null) // no compiler attribute in this asset + continue; + + try { - log.Error($"Invalid compiler type [{type}], must inherit from IAssetCompiler"); - return; + ProcessAttribute(compilerAttribute, type); } - - RegisterCompiler(assetType, compilerInstance, compilerCompilerAttribute.CompilationContext); - } - - private void RegisterAssembly(Assembly assembly) - { - if (assembly == null) throw new ArgumentNullException(nameof(assembly)); - lock (assembliesToRegister) + catch (Exception ex) { - assembliesToRegister.Add(assembly); - assembliesChanged = true; + log.Error($"Unable to instantiate compiler [{compilerAttribute.TypeName}]", ex); } } - private void RegisterCompilersFromAssembly(Assembly assembly) + // Taken from https://stackoverflow.com/questions/7889228/how-to-prevent-reflectiontypeloadexception-when-calling-assembly-gettypes + [DebuggerNonUserCode] + IEnumerable GetFullyLoadedTypes(Assembly assembly) { - // Process Asset types. - foreach (var type in GetFullyLoadedTypes(assembly)) + try { - // Only process Asset types - if (!typeof(IAssetCompiler).IsAssignableFrom(type) || !type.IsClass) - continue; - - // Asset compiler - var compilerAttribute = type.GetCustomAttribute(); - - if (compilerAttribute == null) // no compiler attribute in this asset - continue; - - try - { - ProcessAttribute(compilerAttribute, type); - } - catch (Exception ex) - { - log.Error($"Unable to instantiate compiler [{compilerAttribute.TypeName}]", ex); - } + return assembly.GetTypes(); } - - // Taken from https://stackoverflow.com/questions/7889228/how-to-prevent-reflectiontypeloadexception-when-calling-assembly-gettypes - [DebuggerNonUserCode] - IEnumerable GetFullyLoadedTypes(Assembly assembly) + catch (ReflectionTypeLoadException ex) { - try - { - return assembly.GetTypes(); - } - catch (ReflectionTypeLoadException ex) - { - log.Warning($"Could not load all types from assembly {assembly.FullName}", ex); - return ex.Types.Where(t => t != null); - } + log.Warning($"Could not load all types from assembly {assembly.FullName}", ex); + return ex.Types.NotNull(); } } + } + + private void UnregisterAssembly(Assembly assembly) + { + registeredAssemblies.Remove(assembly); + UnregisterCompilersFromAssembly(assembly); + assembliesChanged = true; + } + + private readonly struct CompilerTypeData : IEquatable + { + public readonly Type Context; + public readonly Type Type; - private void UnregisterAssembly(Assembly assembly) + public CompilerTypeData(Type context, Type type) { - registeredAssemblies.Remove(assembly); - UnregisterCompilersFromAssembly(assembly); - assembliesChanged = true; + Context = context; + Type = type; } - private struct CompilerTypeData : IEquatable + /// + public readonly bool Equals(CompilerTypeData other) { - public readonly Type Context; - public readonly Type Type; - - public CompilerTypeData(Type context, Type type) - { - Context = context; - Type = type; - } - - /// - public bool Equals(CompilerTypeData other) - { - return Context == other.Context && Type == other.Type; - } + return Context == other.Context && Type == other.Type; + } - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(obj, null)) return false; - return obj is CompilerTypeData && Equals((CompilerTypeData)obj); - } + /// + public override readonly bool Equals(object? obj) + { + if (ReferenceEquals(obj, null)) return false; + return obj is CompilerTypeData data && Equals(data); + } - /// - public override int GetHashCode() - { - unchecked - { - return ((Context != null ? Context.GetHashCode() : 0) * 397) ^ (Type != null ? Type.GetHashCode() : 0); - } - } + /// + public override readonly int GetHashCode() + { + return HashCode.Combine(Context, Type); + } - public static bool operator ==(CompilerTypeData left, CompilerTypeData right) - { - return left.Equals(right); - } + public static bool operator ==(CompilerTypeData left, CompilerTypeData right) + { + return left.Equals(right); + } - public static bool operator !=(CompilerTypeData left, CompilerTypeData right) - { - return !left.Equals(right); - } + public static bool operator !=(CompilerTypeData left, CompilerTypeData right) + { + return !left.Equals(right); } } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerResult.cs b/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerResult.cs index c0f5880957..32a0991b9f 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerResult.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/AssetCompilerResult.cs @@ -1,33 +1,31 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; - using Stride.Core.BuildEngine; using Stride.Core.Diagnostics; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// Result of a compilation of assets when using +/// +public class AssetCompilerResult : LoggerResult { + private ListBuildStep buildSteps; + /// - /// Result of a compilation of assets when using + /// Initializes a new instance of the class. /// - public class AssetCompilerResult : LoggerResult + /// Name of the module. + public AssetCompilerResult(string? moduleName = null) + : base(moduleName) { - private ListBuildStep buildSteps; - - /// - /// Initializes a new instance of the class. - /// - /// Name of the module. - public AssetCompilerResult(string moduleName = null) : base(moduleName) - { - BuildSteps = new ListBuildStep(); - } - - /// - /// Gets or sets the build steps generated for the build engine. This can be null if is true. - /// - /// The build step. - public ListBuildStep BuildSteps { get { return buildSteps; } set { if (value == null) throw new ArgumentNullException("value", @"The BuildSteps property cannot be set to null"); buildSteps = value; } } + buildSteps = new ListBuildStep(); } + + /// + /// Gets or sets the build steps generated for the build engine. This can be null if is true. + /// + /// The build step. + public ListBuildStep BuildSteps { get { return buildSteps; } set { if (value == null) throw new ArgumentNullException("value", @"The BuildSteps property cannot be set to null"); buildSteps = value; } } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/AssetDependenciesCompiler.cs b/sources/assets/Stride.Core.Assets/Compiler/AssetDependenciesCompiler.cs index c7a3a86d25..e0277205e5 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/AssetDependenciesCompiler.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/AssetDependenciesCompiler.cs @@ -1,154 +1,147 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using Stride.Core.Assets.Analysis; using Stride.Core.BuildEngine; -using Stride.Core.Annotations; -using System.Threading.Tasks; using Stride.Core.Diagnostics; -using Stride.Core.Extensions; -using System.Linq; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// An asset compiler that will compile an asset with all its dependencies. +/// +public class AssetDependenciesCompiler { + public readonly BuildDependencyManager BuildDependencyManager; + /// - /// An asset compiler that will compile an asset with all its dependencies. + /// Raised when a single asset has been compiled. /// - public class AssetDependenciesCompiler - { - public readonly BuildDependencyManager BuildDependencyManager; - - /// - /// Raised when a single asset has been compiled. - /// - public EventHandler AssetCompiled; + public event EventHandler? AssetCompiled; - public AssetDependenciesCompiler(Type compilationContext) - { - if (!typeof(ICompilationContext).IsAssignableFrom(compilationContext)) - throw new InvalidOperationException($"{nameof(compilationContext)} should inherit from ICompilationContext"); - - BuildDependencyManager = new BuildDependencyManager(); - } + public AssetDependenciesCompiler(Type compilationContext) + { + if (!typeof(ICompilationContext).IsAssignableFrom(compilationContext)) + throw new InvalidOperationException($"{nameof(compilationContext)} should inherit from ICompilationContext"); - /// - /// Prepare the list of assets to be built, building all the steps and linking them properly - /// - /// The AssetCompilerContext - /// The assets to prepare for build - /// - public AssetCompilerResult PrepareMany(AssetCompilerContext context, List assetItems) - { - var finalResult = new AssetCompilerResult(); - var compiledItems = new Dictionary(); - foreach (var assetItem in assetItems) - { - var visitedItems = new HashSet(); - Prepare(finalResult, context, assetItem, context.CompilationContext, visitedItems, compiledItems); - } - return finalResult; - } + BuildDependencyManager = new BuildDependencyManager(); + } - /// - /// Prepare a single asset to be built - /// - /// The AssetCompilerContext - /// The asset to build - /// - public AssetCompilerResult Prepare(AssetCompilerContext context, AssetItem assetItem) + /// + /// Prepare the list of assets to be built, building all the steps and linking them properly + /// + /// The AssetCompilerContext + /// The assets to prepare for build + /// + public AssetCompilerResult PrepareMany(AssetCompilerContext context, List assetItems) + { + var finalResult = new AssetCompilerResult(); + var compiledItems = new Dictionary(); + foreach (var assetItem in assetItems) { - var finalResult = new AssetCompilerResult(); var visitedItems = new HashSet(); - var compiledItems = new Dictionary(); Prepare(finalResult, context, assetItem, context.CompilationContext, visitedItems, compiledItems); - return finalResult; } + return finalResult; + } + + /// + /// Prepare a single asset to be built + /// + /// The AssetCompilerContext + /// The asset to build + /// + public AssetCompilerResult Prepare(AssetCompilerContext context, AssetItem assetItem) + { + var finalResult = new AssetCompilerResult(); + var visitedItems = new HashSet(); + var compiledItems = new Dictionary(); + Prepare(finalResult, context, assetItem, context.CompilationContext, visitedItems, compiledItems); + return finalResult; + } + + private void Prepare(AssetCompilerResult finalResult, AssetCompilerContext context, AssetItem assetItem, Type compilationContext, HashSet visitedItems, Dictionary compiledItems, BuildStep? parentBuildStep = null, + BuildDependencyType dependencyType = BuildDependencyType.Runtime) + { + ArgumentNullException.ThrowIfNull(compilationContext); + var assetNode = BuildDependencyManager.FindOrCreateNode(assetItem, compilationContext); + compiledItems.TryGetValue(assetNode.AssetItem.Id, out var assetBuildSteps); - private void Prepare(AssetCompilerResult finalResult, AssetCompilerContext context, AssetItem assetItem, [NotNull] Type compilationContext, HashSet visitedItems, Dictionary compiledItems, BuildStep parentBuildStep = null, - BuildDependencyType dependencyType = BuildDependencyType.Runtime) + // Prevent re-entrancy in the same node + if (visitedItems.Add(assetNode)) { - if (compilationContext == null) throw new ArgumentNullException(nameof(compilationContext)); - var assetNode = BuildDependencyManager.FindOrCreateNode(assetItem, compilationContext); - compiledItems.TryGetValue(assetNode.AssetItem.Id, out var assetBuildSteps); + assetNode.Analyze(context); - // Prevent re-entrancy in the same node - if (visitedItems.Add(assetNode)) + // Invoke the compiler to prepare the build step for this asset if the dependency needs to compile it (Runtime or CompileContent) + if ((dependencyType & ~BuildDependencyType.CompileAsset) != 0 && assetBuildSteps is null) { - assetNode.Analyze(context); + var mainCompiler = BuildDependencyManager.AssetCompilerRegistry.GetCompiler(assetItem.Asset.GetType(), assetNode.CompilationContext); + if (mainCompiler is null) + return; - // Invoke the compiler to prepare the build step for this asset if the dependency needs to compile it (Runtime or CompileContent) - if ((dependencyType & ~BuildDependencyType.CompileAsset) != 0 && assetBuildSteps == null) + var compilerResult = mainCompiler.Prepare(context, assetItem); + + if ((dependencyType & BuildDependencyType.Runtime) == BuildDependencyType.Runtime && compilerResult.HasErrors) //allow Runtime dependencies to fail + { + assetBuildSteps = new ErrorBuildStep(assetItem, compilerResult.Messages); + } + else { - var mainCompiler = BuildDependencyManager.AssetCompilerRegistry.GetCompiler(assetItem.Asset.GetType(), assetNode.CompilationContext); - if (mainCompiler == null) - return; - var compilerResult = mainCompiler.Prepare(context, assetItem); + assetBuildSteps = compilerResult.BuildSteps; + compiledItems.Add(assetNode.AssetItem.Id, assetBuildSteps); - if ((dependencyType & BuildDependencyType.Runtime) == BuildDependencyType.Runtime && compilerResult.HasErrors) //allow Runtime dependencies to fail - { - assetBuildSteps = new ErrorBuildStep(assetItem, compilerResult.Messages); - } - else + // Copy the log to the final result (note: this does not copy or forward the build steps) + compilerResult.CopyTo(finalResult); + if (compilerResult.HasErrors) { - - assetBuildSteps = compilerResult.BuildSteps; - compiledItems.Add(assetNode.AssetItem.Id, assetBuildSteps); - - // Copy the log to the final result (note: this does not copy or forward the build steps) - compilerResult.CopyTo(finalResult); - if (compilerResult.HasErrors) - { - finalResult.Error($"Failed to prepare asset {assetItem.Location}"); - return; - } + finalResult.Error($"Failed to prepare asset {assetItem.Location}"); + return; } + } - // Add the resulting build steps to the final - finalResult.BuildSteps.Add(assetBuildSteps); + // Add the resulting build steps to the final + finalResult.BuildSteps.Add(assetBuildSteps); - AssetCompiled?.Invoke(this, new AssetCompiledArgs(assetItem, compilerResult)); - } + AssetCompiled?.Invoke(this, new AssetCompiledArgs(assetItem, compilerResult)); + } - // Go through the dependencies of the node and prepare them as well - foreach (var reference in assetNode.References) + // Go through the dependencies of the node and prepare them as well + foreach (var reference in assetNode.References) + { + var target = reference.Target; + Prepare(finalResult, context, target.AssetItem, target.CompilationContext, visitedItems, compiledItems, assetBuildSteps, reference.DependencyType); + if (finalResult.HasErrors) { - var target = reference.Target; - Prepare(finalResult, context, target.AssetItem, target.CompilationContext, visitedItems, compiledItems, assetBuildSteps, reference.DependencyType); - if (finalResult.HasErrors) - { - return; - } - } - - // If we didn't prepare any build step for this asset let's exit here. - if (assetBuildSteps == null) return; + } } - // Link the created build steps to their parent step. - if (parentBuildStep != null && assetBuildSteps != null && (dependencyType & BuildDependencyType.CompileContent) == BuildDependencyType.CompileContent) //only if content is required Content.Load - BuildStep.LinkBuildSteps(assetBuildSteps, parentBuildStep); + // If we didn't prepare any build step for this asset let's exit here. + if (assetBuildSteps is null) + return; } - private class ErrorBuildStep : AssetBuildStep - { - private readonly List messages; + // Link the created build steps to their parent step. + if (parentBuildStep is not null && assetBuildSteps is not null && (dependencyType & BuildDependencyType.CompileContent) == BuildDependencyType.CompileContent) //only if content is required Content.Load + BuildStep.LinkBuildSteps(assetBuildSteps, parentBuildStep); + } - public ErrorBuildStep(AssetItem assetItem, IEnumerable messages) - : base(assetItem) - { - this.messages = messages.ToList(); - } + private class ErrorBuildStep : AssetBuildStep + { + private readonly List messages; - public override Task Execute(IExecuteContext executeContext, BuilderContext builderContext) - { - foreach (var message in messages) - executeContext.Logger.Log(message); - return Task.FromResult(ResultStatus.Failed); - } + public ErrorBuildStep(AssetItem assetItem, IEnumerable messages) + : base(assetItem) + { + this.messages = messages.ToList(); + } + + public override Task Execute(IExecuteContext executeContext, BuilderContext builderContext) + { + foreach (var message in messages) + executeContext.Logger.Log(message); + return Task.FromResult(ResultStatus.Failed); } } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/CompilerContext.cs b/sources/assets/Stride.Core.Assets/Compiler/CompilerContext.cs index 950e8cfbc2..0fd6ce4259 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/CompilerContext.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/CompilerContext.cs @@ -1,44 +1,40 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Settings; +namespace Stride.Core.Assets.Compiler; -namespace Stride.Core.Assets.Compiler +/// +/// The context used when compiling an asset in a Package. +/// +public class CompilerContext : IDisposable { /// - /// The context used when compiling an asset in a Package. + /// Initializes a new instance of the class. /// - public class CompilerContext : IDisposable + public CompilerContext() { - /// - /// Initializes a new instance of the class. - /// - public CompilerContext() - { - Properties = new PropertyCollection(); - } + Properties = new PropertyCollection(); + } - /// - /// Properties passed on the command line. - /// - public Dictionary OptionProperties { get; } = new Dictionary(); + /// + /// Properties passed on the command line. + /// + public Dictionary OptionProperties { get; } = []; - /// - /// Gets the attributes attached to this context. - /// - /// The attributes. - public PropertyCollection Properties { get; private set; } + /// + /// Gets the attributes attached to this context. + /// + /// The attributes. + public PropertyCollection Properties { get; private set; } - public CompilerContext Clone() - { - var context = (CompilerContext)MemberwiseClone(); - return context; - } + public CompilerContext Clone() + { + var context = (CompilerContext)MemberwiseClone(); + return context; + } - public void Dispose() - { - } + public void Dispose() + { + GC.SuppressFinalize(this); } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/DummyAssetCommand.cs b/sources/assets/Stride.Core.Assets/Compiler/DummyAssetCommand.cs index bbccacba2b..082e38df1f 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/DummyAssetCommand.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/DummyAssetCommand.cs @@ -1,32 +1,29 @@ -using System.Threading.Tasks; using Stride.Core.BuildEngine; -using Stride.Core.Annotations; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// An that will create a default instance of the content type for a given asset, rather than compiling it. +/// +/// The type of asset for which to generate a default instance of content. +/// The type of content to generate. +public class DummyAssetCommand : AssetCommand where TAsset : Asset where TContent : new() { /// - /// An that will create a default instance of the content type for a given asset, rather than compiling it. + /// Initializes a new instance of the class. /// - /// The type of asset for which to generate a default instance of content. - /// The type of content to generate. - public class DummyAssetCommand : AssetCommand where TAsset : Asset where TContent : new() + /// The asset to compile. + public DummyAssetCommand(AssetItem assetItem) + : base(assetItem.Location, (TAsset)assetItem.Asset, assetItem.Package) { - /// - /// Initializes a new instance of the class. - /// - /// The asset to compile. - public DummyAssetCommand([NotNull] AssetItem assetItem) - : base(assetItem.Location, (TAsset)assetItem.Asset, assetItem.Package) - { - } + } - protected override Task DoCommandOverride(ICommandContext commandContext) - { - var contentManager = new ContentManager(MicrothreadLocalDatabases.ProviderService); - var dummyObject = new TContent(); - contentManager.Save(Url, dummyObject); - return Task.FromResult(ResultStatus.Successful); - } + protected override Task DoCommandOverride(ICommandContext commandContext) + { + var contentManager = new ContentManager(MicrothreadLocalDatabases.ProviderService); + var dummyObject = new TContent(); + contentManager.Save(Url, dummyObject); + return Task.FromResult(ResultStatus.Successful); } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/FailedCommand.cs b/sources/assets/Stride.Core.Assets/Compiler/FailedCommand.cs index 0ea0f82993..b3328da955 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/FailedCommand.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/FailedCommand.cs @@ -1,39 +1,36 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Threading.Tasks; using Stride.Core.BuildEngine; using Stride.Core.Serialization; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +public class FailedCommand : Command { - public class FailedCommand: Command + private readonly string objectThatFailed; + + public FailedCommand(string objectThatFailed) + { + this.objectThatFailed = objectThatFailed; + } + + public override string Title => $"Failed command [Object={objectThatFailed}]"; + + protected override Task DoCommandOverride(ICommandContext commandContext) + { + return Task.FromResult(ResultStatus.Failed); + } + + public override string ToString() + { + return Title; + } + + protected override void ComputeParameterHash(BinarySerializationWriter writer) { - private readonly string objectThatFailed; - - public FailedCommand(string objectThatFailed) - { - this.objectThatFailed = objectThatFailed; - } - - public override string Title => $"Failed command [Object={objectThatFailed}]"; - - protected override Task DoCommandOverride(ICommandContext commandContext) - { - return Task.FromResult(ResultStatus.Failed); - } - - public override string ToString() - { - return Title; - } - - protected override void ComputeParameterHash(BinarySerializationWriter writer) - { - // force execution of the command with a new GUID - var newGuid = Guid.NewGuid(); - writer.Serialize(ref newGuid, ArchiveMode.Serialize); - } + // force execution of the command with a new GUID + var newGuid = Guid.NewGuid(); + writer.Serialize(ref newGuid, ArchiveMode.Serialize); } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/IAssetCompiler.cs b/sources/assets/Stride.Core.Assets/Compiler/IAssetCompiler.cs index 3456299ef3..bb581ed1f4 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/IAssetCompiler.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/IAssetCompiler.cs @@ -1,50 +1,47 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using Stride.Core.Assets.Analysis; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// Main interface for compiling an . +/// +public interface IAssetCompiler { /// - /// Main interface for compiling an . + /// Compiles a list of assets from the specified package. + /// + /// + /// The asset reference. + /// The result of the compilation. + AssetCompilerResult Prepare(AssetCompilerContext context, AssetItem assetItem); + + /// + /// Enumerates all the dependencies required to compile this asset + /// + /// The asset for which dependencies are enumerated + /// The dependencies + IEnumerable GetInputFiles(AssetItem assetItem); + + /// + /// Enumerates all the asset types required to compile this asset + /// + /// The asset for which types are enumerated + /// The dependencies + IEnumerable GetInputTypes(AssetItem assetItem); + + /// + /// Enumerates all the asset types to exclude when compiling this asset /// - public interface IAssetCompiler - { - /// - /// Compiles a list of assets from the specified package. - /// - /// - /// The asset reference. - /// The result of the compilation. - AssetCompilerResult Prepare(AssetCompilerContext context, AssetItem assetItem); - - /// - /// Enumerates all the dependencies required to compile this asset - /// - /// The asset for which dependencies are enumerated - /// The dependencies - IEnumerable GetInputFiles(AssetItem assetItem); - - /// - /// Enumerates all the asset types required to compile this asset - /// - /// The asset for which types are enumerated - /// The dependencies - IEnumerable GetInputTypes(AssetItem assetItem); - - /// - /// Enumerates all the asset types to exclude when compiling this asset - /// - /// The asset for which types are enumerated - /// The types to exclude - /// This method takes higher priority, it will exclude assets included with inclusion methods even in the same compiler - IEnumerable GetInputTypesToExclude(AssetItem assetItem); - - bool AlwaysCheckRuntimeTypes { get; } - - IEnumerable GetRuntimeTypes(AssetItem assetItem); - } + /// The asset for which types are enumerated + /// The types to exclude + /// This method takes higher priority, it will exclude assets included with inclusion methods even in the same compiler + IEnumerable GetInputTypesToExclude(AssetItem assetItem); + + bool AlwaysCheckRuntimeTypes { get; } + + IEnumerable GetRuntimeTypes(AssetItem assetItem); } diff --git a/sources/assets/Stride.Core.Assets/Compiler/ICompilationContext.cs b/sources/assets/Stride.Core.Assets/Compiler/ICompilationContext.cs index 1e7a33611c..e1f36ddfa0 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/ICompilationContext.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/ICompilationContext.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets.Compiler -{ - public interface ICompilationContext - { - } -} + +namespace Stride.Core.Assets.Compiler; + +public interface ICompilationContext; diff --git a/sources/assets/Stride.Core.Assets/Compiler/IPackageCompiler.cs b/sources/assets/Stride.Core.Assets/Compiler/IPackageCompiler.cs index 511a0f5c95..ecae2ea18b 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/IPackageCompiler.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/IPackageCompiler.cs @@ -1,17 +1,17 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets.Compiler + +namespace Stride.Core.Assets.Compiler; + +/// +/// Interface for compiling a package. +/// +public interface IPackageCompiler { /// - /// Interface for compiling a package. + /// Prepares a package with the specified compiler context. /// - public interface IPackageCompiler - { - /// - /// Prepares a package with the specified compiler context. - /// - /// The compiler context. - /// Result of compilation. - AssetCompilerResult Prepare(AssetCompilerContext compilerContext); - } + /// The compiler context. + /// Result of compilation. + AssetCompilerResult Prepare(AssetCompilerContext compilerContext); } diff --git a/sources/assets/Stride.Core.Assets/Compiler/IPackageCompilerSource.cs b/sources/assets/Stride.Core.Assets/Compiler/IPackageCompilerSource.cs index 71637f6c38..2d8046a650 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/IPackageCompilerSource.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/IPackageCompilerSource.cs @@ -1,17 +1,15 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// Enumerate assets that will process. +/// +public interface IPackageCompilerSource { /// - /// Enumerate assets that will process. + /// Enumerates assets. /// - public interface IPackageCompilerSource - { - /// - /// Enumerates assets. - /// - IEnumerable GetAssets(AssetCompilerResult assetCompilerResult); - } + IEnumerable GetAssets(AssetCompilerResult assetCompilerResult); } diff --git a/sources/assets/Stride.Core.Assets/Compiler/ImportStreamCommand.cs b/sources/assets/Stride.Core.Assets/Compiler/ImportStreamCommand.cs index 3088cd8598..c06b23b3b8 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/ImportStreamCommand.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/ImportStreamCommand.cs @@ -1,68 +1,65 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; + using System.ComponentModel; -using System.IO; using Stride.Core.BuildEngine; using Stride.Core.IO; -using System.Threading.Tasks; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +[Description("Import stream")] +public sealed class ImportStreamCommand : SingleFileImportCommand { - [Description("Import stream")] - public sealed class ImportStreamCommand : SingleFileImportCommand - { - /// - public override string Title { get { string title = "Import Stream "; try { title += Path.GetFileName(SourcePath) ?? "[File]"; } catch { title += "[INVALID PATH]"; } return title; } } + /// + public override string Title { get { string title = "Import Stream "; try { title += Path.GetFileName(SourcePath) ?? "[File]"; } catch { title += "[INVALID PATH]"; } return title; } } - public bool DisableCompression { get; set; } + public bool DisableCompression { get; set; } - public bool SaveSourcePath { get; set; } + public bool SaveSourcePath { get; set; } - public ImportStreamCommand() : this(null, null) - { - } + public ImportStreamCommand() : this(null, null) + { + } - public ImportStreamCommand(UFile location, UFile sourcePath) - : base(location, sourcePath) - { - } + public ImportStreamCommand(UFile location, UFile sourcePath) + : base(location, sourcePath) + { + } - protected override Task DoCommandOverride(ICommandContext commandContext) + protected override Task DoCommandOverride(ICommandContext commandContext) + { + // This path for effects xml is now part of this tool, but it should be done in a separate exporter? + using (var inputStream = File.OpenRead(SourcePath)) + using (var outputStream = MicrothreadLocalDatabases.DatabaseFileProvider.OpenStream(Location, VirtualFileMode.Create, VirtualFileAccess.Write)) { - // This path for effects xml is now part of this tool, but it should be done in a separate exporter? - using (var inputStream = File.OpenRead(SourcePath)) - using (var outputStream = MicrothreadLocalDatabases.DatabaseFileProvider.OpenStream(Location, VirtualFileMode.Create, VirtualFileAccess.Write)) - { - inputStream.CopyTo(outputStream); + inputStream.CopyTo(outputStream); - var objectUrl = new ObjectUrl(UrlType.Content, Location); + var objectUrl = new ObjectUrl(UrlType.Content, Location); - if (DisableCompression) - commandContext.AddTag(objectUrl, Builder.DoNotCompressTag); - } + if (DisableCompression) + commandContext.AddTag(objectUrl, Builder.DoNotCompressTag); + } - if (SaveSourcePath) + if (SaveSourcePath) + { + // store absolute path to source + // TODO: the "/path" is hardcoded, used in EffectSystem and ShaderSourceManager. Find a place to share this correctly. + var pathLocation = new UFile(Location.FullPath + "/path"); + using (var outputStreamPath = MicrothreadLocalDatabases.DatabaseFileProvider.OpenStream(pathLocation, VirtualFileMode.Create, VirtualFileAccess.Write)) { - // store absolute path to source - // TODO: the "/path" is hardcoded, used in EffectSystem and ShaderSourceManager. Find a place to share this correctly. - var pathLocation = new UFile(Location.FullPath + "/path"); - using (var outputStreamPath = MicrothreadLocalDatabases.DatabaseFileProvider.OpenStream(pathLocation, VirtualFileMode.Create, VirtualFileAccess.Write)) + using (var sw = new StreamWriter(outputStreamPath)) { - using (var sw = new StreamWriter(outputStreamPath)) - { - sw.Write(SourcePath.FullPath); - } + sw.Write(SourcePath.FullPath); } } - - return Task.FromResult(ResultStatus.Successful); } - public override string ToString() - { - return "Import stream " + (SourcePath ?? "[File]") + " > " + (Location ?? "[Location]"); - } + return Task.FromResult(ResultStatus.Successful); + } + + public override string ToString() + { + return "Import stream " + (SourcePath ?? "[File]") + " > " + (Location ?? "[Location]"); } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/ItemListCompiler.cs b/sources/assets/Stride.Core.Assets/Compiler/ItemListCompiler.cs index 6059d77713..a6bc37c8f5 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/ItemListCompiler.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/ItemListCompiler.cs @@ -1,146 +1,143 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using Stride.Core.Assets.Diagnostics; using Stride.Core.BuildEngine; using Stride.Core.Diagnostics; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// The base class to compile a series of s using associated s. +/// An item list compiler only creates the build steps required to creates some output items. +/// The result of a compilation has then to be executed by the build engine to effectively create the outputs items. +/// +public abstract class ItemListCompiler { + private readonly AssetCompilerRegistry compilerRegistry; + private readonly Type compilationContext; + private int latestPriority; + + /// + /// Raised when a single asset has been compiled. + /// + public event EventHandler? AssetCompiled; + + /// + /// Create an instance of using the provided compiler registry. + /// + /// The registry that contains the compiler to use for each asset type + /// The context in which this list will compile the assets (Asset, Preview, thumbnail etc) + protected ItemListCompiler(AssetCompilerRegistry compilerRegistry, Type compilationContext) + { + if (compilerRegistry == null) throw new ArgumentNullException(nameof(compilerRegistry)); + if (compilationContext == null) throw new ArgumentNullException(nameof(compilationContext)); + this.compilerRegistry = compilerRegistry; + this.compilationContext = compilationContext; + } + /// - /// The base class to compile a series of s using associated s. - /// An item list compiler only creates the build steps required to creates some output items. - /// The result of a compilation has then to be executed by the build engine to effectively create the outputs items. + /// Compile the required build steps necessary to produce the desired outputs items. /// - public abstract class ItemListCompiler + /// The context source. + /// The list of items to compile + /// The current compilation result, containing the build steps and the logging + protected void Prepare(AssetCompilerContext context, IEnumerable assetItems, AssetCompilerResult compilationResult) { - private readonly AssetCompilerRegistry compilerRegistry; - private readonly Type compilationContext; - private int latestPriority; - - /// - /// Raised when a single asset has been compiled. - /// - public EventHandler AssetCompiled; - - /// - /// Create an instance of using the provided compiler registry. - /// - /// The registry that contains the compiler to use for each asset type - /// The context in which this list will compile the assets (Asset, Preview, thumbnail etc) - protected ItemListCompiler(AssetCompilerRegistry compilerRegistry, Type compilationContext) + foreach (var assetItem in assetItems) { - if (compilerRegistry == null) throw new ArgumentNullException(nameof(compilerRegistry)); - if (compilationContext == null) throw new ArgumentNullException(nameof(compilationContext)); - this.compilerRegistry = compilerRegistry; - this.compilationContext = compilationContext; + var itemBuildStep = CompileItem(context, compilationResult, assetItem); + if (itemBuildStep != null) + compilationResult.BuildSteps.Add(itemBuildStep); } + } - /// - /// Compile the required build steps necessary to produce the desired outputs items. - /// - /// The context source. - /// The list of items to compile - /// The current compilation result, containing the build steps and the logging - protected void Prepare(AssetCompilerContext context, IEnumerable assetItems, AssetCompilerResult compilationResult) + /// + /// Compile the required build step necessary to produce the desired output item. + /// + /// The context. + /// The compilation result. + /// The asset item. + public ListBuildStep? CompileItem(AssetCompilerContext context, AssetCompilerResult compilationResult, AssetItem assetItem) + { + // First try to find an asset compiler for this particular asset. + IAssetCompiler? compiler; + try { - foreach (var assetItem in assetItems) - { - var itemBuildStep = CompileItem(context, compilationResult, assetItem); - if (itemBuildStep != null) - compilationResult.BuildSteps.Add(itemBuildStep); - } + compiler = compilerRegistry.GetCompiler(assetItem.Asset.GetType(), compilationContext); + } + catch (Exception ex) + { + compilationResult.Error($"Cannot find a compiler for asset [{assetItem.Id}] from path [{assetItem.Location}]", ex); + return null; } - /// - /// Compile the required build step necessary to produce the desired output item. - /// - /// The context. - /// The compilation result. - /// The asset item. - public ListBuildStep CompileItem(AssetCompilerContext context, AssetCompilerResult compilationResult, AssetItem assetItem) + if (compiler == null) { - // First try to find an asset compiler for this particular asset. - IAssetCompiler compiler; - try - { - compiler = compilerRegistry.GetCompiler(assetItem.Asset.GetType(), compilationContext); - } - catch (Exception ex) - { - compilationResult.Error($"Cannot find a compiler for asset [{assetItem.Id}] from path [{assetItem.Location}]", ex); - return null; - } + return null; + } - if (compiler == null) - { - return null; - } + // Second we are compiling the asset (generating a build step) + try + { + var resultPerAssetType = compiler.Prepare(context, assetItem); + + // Raise the AssetCompiled event. + AssetCompiled?.Invoke(this, new AssetCompiledArgs(assetItem, resultPerAssetType)); - // Second we are compiling the asset (generating a build step) - try + // TODO: See if this can be unified with PackageBuilder.BuildStepProcessed + var assetFullPath = assetItem.FullPath.ToOSPath(); + foreach (var message in resultPerAssetType.Messages) { - var resultPerAssetType = compiler.Prepare(context, assetItem); - - // Raise the AssetCompiled event. - AssetCompiled?.Invoke(this, new AssetCompiledArgs(assetItem, resultPerAssetType)); - - // TODO: See if this can be unified with PackageBuilder.BuildStepProcessed - var assetFullPath = assetItem.FullPath.ToOSPath(); - foreach (var message in resultPerAssetType.Messages) - { - var assetMessage = AssetLogMessage.From(null, assetItem.ToReference(), message, assetFullPath); - // Forward log messages to compilationResult - compilationResult.Log(assetMessage); - - // Forward log messages to build step logger - resultPerAssetType.BuildSteps.Logger.Log(assetMessage); - } - - // Make the build step fail if there was an error during compiling (only when we are compiling the build steps of an asset) - if (resultPerAssetType.BuildSteps is AssetBuildStep && resultPerAssetType.BuildSteps.Logger.HasErrors) - resultPerAssetType.BuildSteps.Add(new CommandBuildStep(new FailedCommand(assetItem.Location))); - - // TODO: Big review of the log infrastructure of CompilerApp & BuildEngine! - // Assign module string to all command build steps - SetAssetLogger(resultPerAssetType.BuildSteps, assetItem.Package, assetItem.ToReference(), assetItem.FullPath.ToOSPath()); - - foreach (var buildStep in resultPerAssetType.BuildSteps) - { - buildStep.Priority = latestPriority++; - } - - // Add the item result build steps the item list result build steps - return resultPerAssetType.BuildSteps; + var assetMessage = AssetLogMessage.From(null, assetItem.ToReference(), message, assetFullPath); + // Forward log messages to compilationResult + compilationResult.Log(assetMessage); + + // Forward log messages to build step logger + resultPerAssetType.BuildSteps.Logger.Log(assetMessage); } - catch (Exception ex) + + // Make the build step fail if there was an error during compiling (only when we are compiling the build steps of an asset) + if (resultPerAssetType.BuildSteps is AssetBuildStep && resultPerAssetType.BuildSteps.Logger.HasErrors) + resultPerAssetType.BuildSteps.Add(new CommandBuildStep(new FailedCommand(assetItem.Location))); + + // TODO: Big review of the log infrastructure of CompilerApp & BuildEngine! + // Assign module string to all command build steps + SetAssetLogger(resultPerAssetType.BuildSteps, assetItem.Package, assetItem.ToReference(), assetItem.FullPath.ToOSPath()); + + foreach (var buildStep in resultPerAssetType.BuildSteps) { - compilationResult.Error($"Unexpected exception while compiling asset [{assetItem.Id}] from path [{assetItem.Location}]", ex); - return null; + buildStep.Priority = latestPriority++; } - } - /// - /// Sets recursively the . - /// - /// The build step. - /// - /// - private void SetAssetLogger(BuildStep buildStep, Package package, IReference assetReference, string assetFullPath) + // Add the item result build steps the item list result build steps + return resultPerAssetType.BuildSteps; + } + catch (Exception ex) { - if (buildStep.TransformExecuteContextLogger == null) - buildStep.TransformExecuteContextLogger = (ref Logger logger) => logger = new AssetLogger(package, assetReference, assetFullPath, logger); + compilationResult.Error($"Unexpected exception while compiling asset [{assetItem.Id}] from path [{assetItem.Location}]", ex); + return null; + } + } - var enumerableBuildStep = buildStep as ListBuildStep; - if (enumerableBuildStep != null && enumerableBuildStep.Steps != null) + /// + /// Sets recursively the . + /// + /// The build step. + /// + /// + private void SetAssetLogger(BuildStep buildStep, Package? package, IReference assetReference, string assetFullPath) + { + if (buildStep.TransformExecuteContextLogger == null) + buildStep.TransformExecuteContextLogger = (ref Logger logger) => logger = new AssetLogger(package, assetReference, assetFullPath, logger); + + var enumerableBuildStep = buildStep as ListBuildStep; + if (enumerableBuildStep != null && enumerableBuildStep.Steps != null) + { + foreach (var child in enumerableBuildStep.Steps) { - foreach (var child in enumerableBuildStep.Steps) - { - SetAssetLogger(child, package, assetReference, assetFullPath); - } + SetAssetLogger(child, package, assetReference, assetFullPath); } } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/PackageAssetEnumerator.cs b/sources/assets/Stride.Core.Assets/Compiler/PackageAssetEnumerator.cs index 0280165c82..a1c0398273 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/PackageAssetEnumerator.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/PackageAssetEnumerator.cs @@ -1,41 +1,39 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Linq; + using Stride.Core.Assets.Analysis; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// Enumerate all assets of this package and its references. +/// +public class PackageAssetEnumerator : IPackageCompilerSource { - /// - /// Enumerate all assets of this package and its references. - /// - public class PackageAssetEnumerator : IPackageCompilerSource + private readonly Package package; + + public PackageAssetEnumerator(Package package) { - private readonly Package package; + this.package = package; + } - public PackageAssetEnumerator(Package package) + /// + public IEnumerable GetAssets(AssetCompilerResult assetCompilerResult) + { + // Check integrity of the packages + var packageAnalysis = new PackageSessionAnalysis(package.Session!, new PackageAnalysisParameters()); + packageAnalysis.Run(assetCompilerResult); + if (assetCompilerResult.HasErrors) { - this.package = package; + yield break; } - /// - public IEnumerable GetAssets(AssetCompilerResult assetCompilerResult) + var packages = package.GetPackagesWithDependencies(); + foreach (var pack in packages) { - // Check integrity of the packages - var packageAnalysis = new PackageSessionAnalysis(package.Session, new PackageAnalysisParameters()); - packageAnalysis.Run(assetCompilerResult); - if (assetCompilerResult.HasErrors) - { - yield break; - } - - var packages = package.GetPackagesWithDependencies(); - foreach (var pack in packages) + foreach (var asset in pack.Assets) { - foreach (var asset in pack.Assets) - { - yield return asset; - } + yield return asset; } } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/PackageCompiler.cs b/sources/assets/Stride.Core.Assets/Compiler/PackageCompiler.cs index aad5f974c2..0e8f755ae9 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/PackageCompiler.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/PackageCompiler.cs @@ -1,77 +1,71 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Stride.Core.BuildEngine; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// A package assets compiler. +/// Creates the build steps necessary to produce the assets of a package. +/// +public class PackageCompiler : IPackageCompiler { + private readonly IPackageCompilerSource packageCompilerSource; + private readonly AssetDependenciesCompiler dependenciesCompiler = new(typeof(AssetCompilationContext)); + + static PackageCompiler() + { + SdkDirectory = GetSdkDirectory(); + } + + private static string GetSdkDirectory() + { + // Compute StrideSdkDir from this assembly + var codeBase = typeof(PackageCompiler).Assembly.Location; + // from ../bin/Debug/net{version} -> ../bin + var path = Path.GetDirectoryName(codeBase)!; + return Path.GetFullPath(Path.Combine(path, $"..{Path.DirectorySeparatorChar}..")); + } + /// - /// A package assets compiler. - /// Creates the build steps necessary to produce the assets of a package. + /// Raised when a single asset has been compiled. /// - public class PackageCompiler : IPackageCompiler + public event EventHandler? AssetCompiled; + + public PackageCompiler(IPackageCompilerSource packageCompilerSource) { - private readonly IPackageCompilerSource packageCompilerSource; - private readonly AssetDependenciesCompiler dependenciesCompiler = new AssetDependenciesCompiler(typeof(AssetCompilationContext)); + this.packageCompilerSource = packageCompilerSource; + } - static PackageCompiler() - { - SdkDirectory = GetSdkDirectory(); - } + /// + /// Gets or sets the SDK directory. Default is bound to env variable StrideSdkDir + /// + /// The SDK directory. + public static string SdkDirectory { get; set; } - private static string GetSdkDirectory() - { - // Compute StrideSdkDir from this assembly - var codeBase = typeof(PackageCompiler).Assembly.Location; - // from ../bin/Debug/net{version} -> ../bin - var path = Path.GetDirectoryName(codeBase); - return Path.GetFullPath(Path.Combine(path, $"..{Path.DirectorySeparatorChar}..")); - } + /// + /// Compile the current package session. + /// That is generate the list of build steps to execute to create the package assets. + /// + public AssetCompilerResult Prepare(AssetCompilerContext compilerContext) + { + ArgumentNullException.ThrowIfNull(compilerContext); - /// - /// Raised when a single asset has been compiled. - /// - public EventHandler AssetCompiled; + var result = new AssetCompilerResult(); - public PackageCompiler(IPackageCompilerSource packageCompilerSource) + var assets = packageCompilerSource.GetAssets(result).ToList(); + if (result.HasErrors) { - this.packageCompilerSource = packageCompilerSource; + return result; } - /// - /// Gets or sets the SDK directory. Default is bound to env variable StrideSdkDir - /// - /// The SDK directory. - public static string SdkDirectory { get; set; } - - /// - /// Compile the current package session. - /// That is generate the list of build steps to execute to create the package assets. - /// - public AssetCompilerResult Prepare(AssetCompilerContext compilerContext) - { - if (compilerContext == null) throw new ArgumentNullException("compilerContext"); - - var result = new AssetCompilerResult(); - - var assets = packageCompilerSource.GetAssets(result).ToList(); - if (result.HasErrors) - { - return result; - } + dependenciesCompiler.AssetCompiled += OnAssetCompiled; + result = dependenciesCompiler.PrepareMany(compilerContext, assets); - dependenciesCompiler.AssetCompiled += OnAssetCompiled; - result = dependenciesCompiler.PrepareMany(compilerContext, assets); - - return result; - } + return result; + } - private void OnAssetCompiled(object sender, AssetCompiledArgs assetCompiledArgs) - { - AssetCompiled?.Invoke(this, assetCompiledArgs); - } + private void OnAssetCompiled(object? sender, AssetCompiledArgs assetCompiledArgs) + { + AssetCompiled?.Invoke(this, assetCompiledArgs); } } diff --git a/sources/assets/Stride.Core.Assets/Compiler/RootPackageAssetEnumerator.cs b/sources/assets/Stride.Core.Assets/Compiler/RootPackageAssetEnumerator.cs index d0eeddc2f9..3cebf86670 100644 --- a/sources/assets/Stride.Core.Assets/Compiler/RootPackageAssetEnumerator.cs +++ b/sources/assets/Stride.Core.Assets/Compiler/RootPackageAssetEnumerator.cs @@ -1,92 +1,89 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Linq; using Stride.Core.Assets.Analysis; using Stride.Core.Extensions; -namespace Stride.Core.Assets.Compiler +namespace Stride.Core.Assets.Compiler; + +/// +/// Only enumerates assets that are marked as roots and their dependencies. +/// +public class RootPackageAssetEnumerator : IPackageCompilerSource { + private readonly Package rootPackage; + private readonly BuildDependencyManager buildDependencyManager; + /// - /// Only enumerates assets that are marked as roots and their dependencies. + /// Initializes a new instance. /// - public class RootPackageAssetEnumerator : IPackageCompilerSource + /// The start package. + /// The extra roots that needs to be collected with their dependencies. + public RootPackageAssetEnumerator(Package package) { - private readonly Package rootPackage; - private readonly BuildDependencyManager buildDependencyManager; + rootPackage = package; + buildDependencyManager = new BuildDependencyManager(); + } - /// - /// Initializes a new instance. - /// - /// The start package. - /// The extra roots that needs to be collected with their dependencies. - public RootPackageAssetEnumerator(Package package) + /// + public IEnumerable GetAssets(AssetCompilerResult assetCompilerResult) + { + // Check integrity of the packages + var packageAnalysis = new PackageSessionAnalysis(rootPackage.Session, new PackageAnalysisParameters()); + packageAnalysis.Run(assetCompilerResult); + if (assetCompilerResult.HasErrors) { - rootPackage = package; - buildDependencyManager = new BuildDependencyManager(); + yield break; } - /// - public IEnumerable GetAssets(AssetCompilerResult assetCompilerResult) + // Compute list of assets to compile and their dependencies + var packagesProcessed = new HashSet(); + var assetsReferenced = new HashSet(); + CollectReferences(rootPackage, assetsReferenced, packagesProcessed); + + foreach (var assetItem in assetsReferenced) { - // Check integrity of the packages - var packageAnalysis = new PackageSessionAnalysis(rootPackage.Session, new PackageAnalysisParameters()); - packageAnalysis.Run(assetCompilerResult); - if (assetCompilerResult.HasErrors) - { - yield break; - } + yield return assetItem; + } + } - // Compute list of assets to compile and their dependencies - var packagesProcessed = new HashSet(); - var assetsReferenced = new HashSet(); - CollectReferences(rootPackage, assetsReferenced, packagesProcessed); + /// + /// Helper method to collect explicit AssetReferences + /// + /// + /// + /// + private static void CollectReferences(Package package, HashSet assetsReferenced, HashSet packagesProcessed) + { + // Check if already processed + if (!packagesProcessed.Add(package)) + return; - foreach (var assetItem in assetsReferenced) + // Determine set of assets to compile + // Start with roots: + // 1. Explicit AssetReferences + foreach (var reference in package.RootAssets) + { + // Locate reference + var asset = package.Session?.FindAsset(reference.Id) ?? package.Session?.FindAsset(reference.Location); + if (asset != null) { - yield return assetItem; + assetsReferenced.Add(asset); } } - /// - /// Helper method to collect explicit AssetReferences - /// - /// - /// - /// - private void CollectReferences(Package package, HashSet assetsReferenced, HashSet packagesProcessed) + // 2. Process referenced packages as well (for their roots) + foreach (var dependency in package.Container.FlattenedDependencies.Select(x => x.Package).NotNull()) { - // Check if already processed - if (!packagesProcessed.Add(package)) - return; - - // Determine set of assets to compile - // Start with roots: - // 1. Explicit AssetReferences - foreach (var reference in package.RootAssets) - { - // Locate reference - var asset = package.Session.FindAsset(reference.Id) ?? package.Session.FindAsset(reference.Location); - if (asset != null) - { - assetsReferenced.Add(asset); - } - } - - // 2. Process referenced packages as well (for their roots) - foreach (var dependency in package.Container.FlattenedDependencies.Select(x => x.Package).NotNull()) - { - CollectReferences(dependency, assetsReferenced, packagesProcessed); - } + CollectReferences(dependency, assetsReferenced, packagesProcessed); + } - // 3. Some types are marked with AlwaysMarkAsRoot - foreach (var assetItem in package.Assets) + // 3. Some types are marked with AlwaysMarkAsRoot + foreach (var assetItem in package.Assets) + { + if (AssetRegistry.IsAssetTypeAlwaysMarkAsRoot(assetItem.Asset.GetType())) { - if (AssetRegistry.IsAssetTypeAlwaysMarkAsRoot(assetItem.Asset.GetType())) - { - assetsReferenced.Add(assetItem); - } + assetsReferenced.Add(assetItem); } } } diff --git a/sources/assets/Stride.Core.Assets/DefaultAssetFactory.cs b/sources/assets/Stride.Core.Assets/DefaultAssetFactory.cs index 8a0cb82278..7ed77c205e 100644 --- a/sources/assets/Stride.Core.Assets/DefaultAssetFactory.cs +++ b/sources/assets/Stride.Core.Assets/DefaultAssetFactory.cs @@ -1,28 +1,26 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// An implementation of the class that uses the default public parameterless constructor +/// of the associated asset type. +/// +/// The type of asset this factory can create. +public class DefaultAssetFactory : AssetFactory where T : Asset { - /// - /// An implementation of the class that uses the default public parameterless constructor - /// of the associated asset type. - /// - /// The type of asset this factory can create. - public class DefaultAssetFactory : AssetFactory where T : Asset + public static T Create() { - public static T Create() - { - return (T)Activator.CreateInstance(typeof(T)); - } + return (T)Activator.CreateInstance(typeof(T))!; + } - /// - public override T New() - { - if (typeof(T).GetConstructor(Type.EmptyTypes) == null) - throw new InvalidOperationException("The associated asset type does not have a public parameterless constructor."); + /// + public override T New() + { + if (typeof(T).GetConstructor(Type.EmptyTypes) == null) + throw new InvalidOperationException("The associated asset type does not have a public parameterless constructor."); - return Create(); - } + return Create(); } } diff --git a/sources/assets/Stride.Core.Assets/Diagnostics/AssetLogMessage.cs b/sources/assets/Stride.Core.Assets/Diagnostics/AssetLogMessage.cs index 3f5c9fd455..1119d87047 100644 --- a/sources/assets/Stride.Core.Assets/Diagnostics/AssetLogMessage.cs +++ b/sources/assets/Stride.Core.Assets/Diagnostics/AssetLogMessage.cs @@ -1,149 +1,140 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; using Stride.Core.Diagnostics; using Stride.Core.Reflection; -using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; using Stride.Core.Yaml; -namespace Stride.Core.Assets.Diagnostics +namespace Stride.Core.Assets.Diagnostics; + +/// +/// Provides a specialized to give specific information about an asset. +/// +public class AssetLogMessage : LogMessage { /// - /// Provides a specialized to give specific information about an asset. + /// Initializes a new instance of the class. /// - public class AssetLogMessage : LogMessage + /// The package. + /// The asset reference. + /// The type. + /// The message code. + /// asset + public AssetLogMessage(Package? package, IReference? assetReference, LogMessageType type, AssetMessageCode messageCode) { - private readonly Package package; - - /// - /// Initializes a new instance of the class. - /// - /// The package. - /// The asset reference. - /// The type. - /// The message code. - /// asset - public AssetLogMessage(Package package, IReference assetReference, LogMessageType type, AssetMessageCode messageCode) - { - this.package = package; - AssetReference = assetReference; - Type = type; - MessageCode = messageCode; - Related = new List(); - Text = AssetMessageStrings.ResourceManager.GetString(messageCode.ToString()) ?? messageCode.ToString(); - } + Package = package; + AssetReference = assetReference; + Type = type; + MessageCode = messageCode; + Related = []; + Text = AssetMessageStrings.ResourceManager.GetString(messageCode.ToString()) ?? messageCode.ToString(); + } - /// - /// Initializes a new instance of the class. - /// - /// The package. - /// The asset reference. - /// The type. - /// The message code. - /// The arguments. - /// asset - public AssetLogMessage(Package package, IReference assetReference, LogMessageType type, AssetMessageCode messageCode, params object[] arguments) - { - this.package = package; - AssetReference = assetReference; - Type = type; - MessageCode = messageCode; - Related = new List(); - var message = AssetMessageStrings.ResourceManager.GetString(messageCode.ToString()) ?? messageCode.ToString(); - Text = string.Format(message, arguments); - } + /// + /// Initializes a new instance of the class. + /// + /// The package. + /// The asset reference. + /// The type. + /// The message code. + /// The arguments. + /// asset + public AssetLogMessage(Package? package, IReference? assetReference, LogMessageType type, AssetMessageCode messageCode, params object?[] arguments) + { + Package = package; + AssetReference = assetReference; + Type = type; + MessageCode = messageCode; + Related = []; + var message = AssetMessageStrings.ResourceManager.GetString(messageCode.ToString()) ?? messageCode.ToString(); + Text = string.Format(message, arguments); + } - /// - /// Initializes a new instance of the class. - /// - /// The package. - /// The asset reference. - /// The type. - /// The message code. - /// The arguments. - /// asset - public AssetLogMessage(Package package, IReference assetReference, LogMessageType type, string text) - { - this.package = package; - AssetReference = assetReference; - Type = type; - Related = new List(); - Text = text; - } + /// + /// Initializes a new instance of the class. + /// + /// The package. + /// The asset reference. + /// The type. + /// asset + public AssetLogMessage(Package? package, IReference? assetReference, LogMessageType type, string text) + { + Package = package; + AssetReference = assetReference; + Type = type; + Related = []; + Text = text; + } - public static AssetLogMessage From(Package package, IReference assetReference, ILogMessage logMessage, string assetPath, int line = 0, int character = 0) + public static AssetLogMessage From(Package? package, IReference? assetReference, ILogMessage logMessage, string assetPath, int line = 0, int character = 0) + { + // Transform to AssetLogMessage + if (logMessage is not AssetLogMessage assetLogMessage) { - // Transform to AssetLogMessage - var assetLogMessage = logMessage as AssetLogMessage; - if (assetLogMessage == null) - { - assetLogMessage = new AssetLogMessage(null, assetReference, logMessage.Type, AssetMessageCode.CompilationMessage, assetReference?.Location, logMessage.Text) - { - Exception = (logMessage as LogMessage)?.Exception - }; - } - - // Set file (and location if available) - assetLogMessage.File = assetPath; - assetLogMessage.Line = line; - assetLogMessage.Character = character; - - // Generate location (if it's a Yaml exception) - var yamlException = (logMessage as LogMessage)?.Exception as YamlException; - if (yamlException != null) + assetLogMessage = new AssetLogMessage(package, assetReference, logMessage.Type, AssetMessageCode.CompilationMessage, assetReference?.Location, logMessage.Text) { - assetLogMessage.Line = yamlException.Start.Line; - assetLogMessage.Character = yamlException.Start.Column; - } + Exception = (logMessage as LogMessage)?.Exception + }; + } + + // Set file (and location if available) + assetLogMessage.File = assetPath; + assetLogMessage.Line = line; + assetLogMessage.Character = character; - return assetLogMessage; + // Generate location (if it's a Yaml exception) + if (logMessage is LogMessage { Exception: YamlException yamlException }) + { + assetLogMessage.Line = yamlException.Start.Line; + assetLogMessage.Character = yamlException.Start.Column; } - public string File { get; set; } + return assetLogMessage; + } - public int Line { get; set; } + public string File { get; set; } - public int Character { get; set; } + public int Line { get; set; } - /// - /// Gets or sets the message code. - /// - /// The message code. - public AssetMessageCode MessageCode { get; set; } + public int Character { get; set; } - /// - /// Gets or sets the asset this message applies to (optional). - /// - /// The asset. - public IReference AssetReference { get; set; } + /// + /// Gets or sets the message code. + /// + /// The message code. + public AssetMessageCode MessageCode { get; set; } + + /// + /// Gets or sets the asset this message applies to (optional). + /// + /// The asset. + public IReference? AssetReference { get; set; } - /// - /// Gets or sets the package. - /// - /// The package. - public Package Package { get { return package; } } + /// + /// Gets or sets the package. + /// + /// The package. + public Package? Package { get; } - /// - /// Gets or sets the member of the asset this message applies to. May be null. - /// - /// The member. - public IMemberDescriptor Member { get; set; } + /// + /// Gets or sets the member of the asset this message applies to. May be null. + /// + /// The member. + public IMemberDescriptor Member { get; set; } - /// - /// Gets or sets the related references. - /// - /// The related. - public List Related { get; private set; } + /// + /// Gets or sets the related references. + /// + /// The related. + public List Related { get; } - public override string ToString() - { - var result = base.ToString(); - if (AssetReference?.Location != null) - result = $"{AssetReference.Location}({Line + 1},{Character + 1}): {result}"; + public override string ToString() + { + var result = base.ToString(); + if (AssetReference?.Location != null) + result = $"{AssetReference.Location}({Line + 1},{Character + 1}): {result}"; - return result; - } + return result; } } diff --git a/sources/assets/Stride.Core.Assets/Diagnostics/AssetLoggerExtensions.cs b/sources/assets/Stride.Core.Assets/Diagnostics/AssetLoggerExtensions.cs index 648212756f..ef1163c7f7 100644 --- a/sources/assets/Stride.Core.Assets/Diagnostics/AssetLoggerExtensions.cs +++ b/sources/assets/Stride.Core.Assets/Diagnostics/AssetLoggerExtensions.cs @@ -1,87 +1,83 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using Stride.Core.Diagnostics; -using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Diagnostics +namespace Stride.Core.Assets.Diagnostics; + +/// +/// Extension to for loggin specific error with assets. +/// +public static class AssetLoggerExtensions { - /// - /// Extension to for loggin specific error with assets. - /// - public static class AssetLoggerExtensions + public static void Error(this ILogger logger, Package? package, IReference? assetReference, AssetMessageCode code, params object?[] arguments) { - public static void Error(this ILogger logger, Package package, IReference assetReference, AssetMessageCode code, params object[] arguments) - { - Error(logger, package, assetReference, code, (IEnumerable)null, arguments); - } + Error(logger, package, assetReference, code, (IEnumerable?)null, arguments); + } - public static void Error(this ILogger logger, Package package, IReference assetReference, AssetMessageCode code, IEnumerable relatedGuids, params object[] arguments) - { - Error(logger, package, assetReference, code, relatedGuids, (Exception)null, arguments); - } + public static void Error(this ILogger logger, Package? package, IReference? assetReference, AssetMessageCode code, IEnumerable? relatedGuids, params object?[] arguments) + { + Error(logger, package, assetReference, code, relatedGuids, null, arguments); + } - public static void Error(this ILogger logger, Package package, IReference assetReference, AssetMessageCode code, IReference[] relatedGuids, Exception exception = null) - { - Error(logger, package, assetReference, code, (IEnumerable)relatedGuids, exception); - } + public static void Error(this ILogger logger, Package? package, IReference? assetReference, AssetMessageCode code, IReference[] relatedGuids, Exception? exception = null) + { + Error(logger, package, assetReference, code, (IEnumerable)relatedGuids, exception); + } - public static void Error(this ILogger logger, Package package, IReference assetReference, AssetMessageCode code, IEnumerable relatedGuids, Exception exception = null) + public static void Error(this ILogger logger, Package? package, IReference? assetReference, AssetMessageCode code, IEnumerable? relatedGuids, Exception? exception = null) + { + var logMessage = new AssetLogMessage(package, assetReference, LogMessageType.Error, code) { Exception = exception }; + if (relatedGuids != null) { - var logMessage = new AssetLogMessage(package, assetReference, LogMessageType.Error, code) { Exception = exception }; - if (relatedGuids != null) - { - logMessage.Related.AddRange(relatedGuids); - } - logger.Log(logMessage); + logMessage.Related.AddRange(relatedGuids); } + logger.Log(logMessage); + } - public static void Error(this ILogger logger, Package package, IReference assetReference, AssetMessageCode code, Exception exception, params object[] arguments) - { - Error(logger, package, assetReference, code, null, exception, arguments); - } + public static void Error(this ILogger logger, Package? package, IReference? assetReference, AssetMessageCode code, Exception exception, params object?[] arguments) + { + Error(logger, package, assetReference, code, null, exception, arguments); + } - public static void Error(this ILogger logger, Package package, IReference assetReference, AssetMessageCode code, IEnumerable relatedGuids, Exception exception, params object[] arguments) + public static void Error(this ILogger logger, Package? package, IReference? assetReference, AssetMessageCode code, IEnumerable? relatedGuids, Exception? exception, params object?[] arguments) + { + var logMessage = new AssetLogMessage(package, assetReference, LogMessageType.Error, code, arguments) { Exception = exception }; + if (relatedGuids != null) { - var logMessage = new AssetLogMessage(package, assetReference, LogMessageType.Error, code, arguments) { Exception = exception }; - if (relatedGuids != null) - { - logMessage.Related.AddRange(relatedGuids); - } - logger.Log(logMessage); + logMessage.Related.AddRange(relatedGuids); } + logger.Log(logMessage); + } - public static void Warning(this ILogger logger, Package package, IReference assetReference, AssetMessageCode code, IReference[] relatedGuids) - { - Warning(logger, package, assetReference, code, (IEnumerable)null); - } + public static void Warning(this ILogger logger, Package? package, IReference? assetReference, AssetMessageCode code) + { + Warning(logger, package, assetReference, code, (IEnumerable?)null); + } - public static void Warning(this ILogger logger, Package package, IReference assetReference, AssetMessageCode code, IEnumerable relatedGuids) + public static void Warning(this ILogger logger, Package? package, IReference? assetReference, AssetMessageCode code, IEnumerable? relatedGuids) + { + var logMessage = new AssetLogMessage(package, assetReference, LogMessageType.Warning, code); + if (relatedGuids != null) { - var logMessage = new AssetLogMessage(package, assetReference, LogMessageType.Warning, code); - if (relatedGuids != null) - { - logMessage.Related.AddRange(relatedGuids); - } - logger.Log(logMessage); + logMessage.Related.AddRange(relatedGuids); } + logger.Log(logMessage); + } - public static void Warning(this ILogger logger, Package package, IReference assetReference, AssetMessageCode code, params object[] arguments) - { - Warning(logger, package, assetReference, code, null, arguments); - } + public static void Warning(this ILogger logger, Package? package, IReference? assetReference, AssetMessageCode code, params object?[] arguments) + { + Warning(logger, package, assetReference, code, null, arguments); + } - public static void Warning(this ILogger logger, Package package, IReference assetReference, AssetMessageCode code, IEnumerable relatedGuids, params object[] arguments) + public static void Warning(this ILogger logger, Package? package, IReference? assetReference, AssetMessageCode code, IEnumerable? relatedGuids, params object?[] arguments) + { + var logMessage = new AssetLogMessage(package, assetReference, LogMessageType.Warning, code, arguments); + if (relatedGuids != null) { - var logMessage = new AssetLogMessage(package, assetReference, LogMessageType.Warning, code, arguments); - if (relatedGuids != null) - { - logMessage.Related.AddRange(relatedGuids); - } - logger.Log(logMessage); + logMessage.Related.AddRange(relatedGuids); } + logger.Log(logMessage); } } diff --git a/sources/assets/Stride.Core.Assets/Diagnostics/AssetMessageCode.cs b/sources/assets/Stride.Core.Assets/Diagnostics/AssetMessageCode.cs index 5a8ffc8011..019f1decf3 100644 --- a/sources/assets/Stride.Core.Assets/Diagnostics/AssetMessageCode.cs +++ b/sources/assets/Stride.Core.Assets/Diagnostics/AssetMessageCode.cs @@ -1,162 +1,162 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets.Diagnostics + +namespace Stride.Core.Assets.Diagnostics; + +/// +/// A message code used by to identify an error/warning. +/// +/// Note that internally AssetMessageStrings.resx should contain an associated error message to this enum. +public enum AssetMessageCode { /// - /// A message code used by to identify an error/warning. - /// - /// Note that internally AssetMessageStrings.resx should contain an associated error message to this enum. - public enum AssetMessageCode - { - /// - /// A raw asset was not found - /// - RawAssetNotFound, - - /// - /// The asset required for the current package was not found - /// - AssetForPackageNotFound, - - /// - /// The asset required for the current package was found in a different package - /// - AssetFoundInDifferentPackage, - - /// - /// The asset reference has been changed for a particular location - /// - AssetReferenceChanged, - - /// - /// The asset loading failed - /// - AssetLoadingFailed, - - /// - /// The asset cannot be deleted - /// - AssetCannotDelete, - - /// - /// The asset cannot be saved - /// - AssetCannotSave, - - /// - /// The package not found - /// - PackageNotFound, - - /// - /// The package filepath is not set for saving - /// - PackageFilePathNotSet, - - /// - /// The package not found - /// - PackageLocationChanged, - - /// - /// The package cannot be saved - /// - PackageCannotSave, - - /// - /// The package dependency is modified - /// - PackageDependencyModified, - - /// - /// The package build profile cannot be null - /// - BuildProfileCannotBeNull, - - /// - /// The package build profile should not have a File extension null - /// - BuildProfileFileExtensionCannotBeNull, - - /// - /// Asset contains invalid circular references - /// - InvalidCircularReferences, - - /// - /// The base not found - /// - BaseNotFound, - - /// - /// The base was changed - /// - BaseChanged, - - /// - /// The base is not the same type as the current asset. - /// - BaseInvalidType, - - /// - /// The asset has been successfully compiled. - /// - CompilationSucceeded, - - /// - /// The asset compilation failed. - /// - CompilationFailed, - - /// - /// The asset compilation has been cancelled. - /// - CompilationCancelled, - - /// - /// The asset has not been compiled because it is already up-to-date. - /// - AssetUpToDate, - - /// - /// The asset has not been compiled because its prerequisites failed to compile. - /// - PrerequisiteFailed, - - /// - /// An unexpected internal error occurred. - /// - InternalCompilerError, - - /// - /// A fatal error that caused the asset compilation to fail. - /// - CompilationFatal, - - /// - /// A message log happened inside the asset compiler. - /// - CompilationMessage, - - /// - /// An error that caused the asset compilation to fail. - /// - CompilationError, - - /// - /// A warning that occurred in the asset compilation. - /// - CompilationWarning, - - /// - /// A default scene was not found in the package. - /// - DefaultSceneNotFound, - - /// - /// Occurs when a asset templating instance is duplicated. - /// - InvalidBasePartInstance, - - } + /// A raw asset was not found + /// + RawAssetNotFound, + + /// + /// The asset required for the current package was not found + /// + AssetForPackageNotFound, + + /// + /// The asset required for the current package was found in a different package + /// + AssetFoundInDifferentPackage, + + /// + /// The asset reference has been changed for a particular location + /// + AssetReferenceChanged, + + /// + /// The asset loading failed + /// + AssetLoadingFailed, + + /// + /// The asset cannot be deleted + /// + AssetCannotDelete, + + /// + /// The asset cannot be saved + /// + AssetCannotSave, + + /// + /// The package not found + /// + PackageNotFound, + + /// + /// The package filepath is not set for saving + /// + PackageFilePathNotSet, + + /// + /// The package not found + /// + PackageLocationChanged, + + /// + /// The package cannot be saved + /// + PackageCannotSave, + + /// + /// The package dependency is modified + /// + PackageDependencyModified, + + /// + /// The package build profile cannot be null + /// + BuildProfileCannotBeNull, + + /// + /// The package build profile should not have a File extension null + /// + BuildProfileFileExtensionCannotBeNull, + + /// + /// Asset contains invalid circular references + /// + InvalidCircularReferences, + + /// + /// The base not found + /// + BaseNotFound, + + /// + /// The base was changed + /// + BaseChanged, + + /// + /// The base is not the same type as the current asset. + /// + BaseInvalidType, + + /// + /// The asset has been successfully compiled. + /// + CompilationSucceeded, + + /// + /// The asset compilation failed. + /// + CompilationFailed, + + /// + /// The asset compilation has been cancelled. + /// + CompilationCancelled, + + /// + /// The asset has not been compiled because it is already up-to-date. + /// + AssetUpToDate, + + /// + /// The asset has not been compiled because its prerequisites failed to compile. + /// + PrerequisiteFailed, + + /// + /// An unexpected internal error occurred. + /// + InternalCompilerError, + + /// + /// A fatal error that caused the asset compilation to fail. + /// + CompilationFatal, + + /// + /// A message log happened inside the asset compiler. + /// + CompilationMessage, + + /// + /// An error that caused the asset compilation to fail. + /// + CompilationError, + + /// + /// A warning that occurred in the asset compilation. + /// + CompilationWarning, + + /// + /// A default scene was not found in the package. + /// + DefaultSceneNotFound, + + /// + /// Occurs when a asset templating instance is duplicated. + /// + InvalidBasePartInstance, + } diff --git a/sources/assets/Stride.Core.Assets/Diagnostics/AssetSerializableLogMessage.cs b/sources/assets/Stride.Core.Assets/Diagnostics/AssetSerializableLogMessage.cs index 47445b0eb4..4d41f6372f 100644 --- a/sources/assets/Stride.Core.Assets/Diagnostics/AssetSerializableLogMessage.cs +++ b/sources/assets/Stride.Core.Assets/Diagnostics/AssetSerializableLogMessage.cs @@ -1,51 +1,48 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; using Stride.Core.Diagnostics; using Stride.Core.IO; -namespace Stride.Core.Assets.Diagnostics +namespace Stride.Core.Assets.Diagnostics; + +[DataContract] +public class AssetSerializableLogMessage : SerializableLogMessage { - [DataContract] - public class AssetSerializableLogMessage : SerializableLogMessage + public AssetSerializableLogMessage() { - public AssetSerializableLogMessage() - { - } + } - public AssetSerializableLogMessage(AssetLogMessage logMessage) - : base(logMessage) + public AssetSerializableLogMessage(AssetLogMessage logMessage) + : base(logMessage) + { + if (logMessage.AssetReference != null) { - if (logMessage.AssetReference != null) - { - AssetId = logMessage.AssetReference.Id; - AssetUrl = logMessage.AssetReference.Location; - } + AssetId = logMessage.AssetReference.Id; + AssetUrl = logMessage.AssetReference.Location; } + } - public AssetSerializableLogMessage(AssetId assetId, UFile assetUrl, LogMessageType type, string text, ExceptionInfo exceptionInfo = null) - : base("", type, text, exceptionInfo) - { - AssetId = assetId; - AssetUrl = assetUrl; - } + public AssetSerializableLogMessage(AssetId assetId, UFile assetUrl, LogMessageType type, string text, ExceptionInfo exceptionInfo = null) + : base("", type, text, exceptionInfo) + { + AssetId = assetId; + AssetUrl = assetUrl; + } - public AssetId AssetId { get; set; } + public AssetId AssetId { get; set; } - public UFile AssetUrl { get; set; } + public UFile AssetUrl { get; set; } - public string File { get; set; } + public string File { get; set; } - public int Line { get; set; } + public int Line { get; set; } - public int Character { get; set; } + public int Character { get; set; } - public override string ToString() - { - return $"{AssetUrl}({Line},{Character}): {base.ToString()}"; - } + public override string ToString() + { + return $"{AssetUrl}({Line},{Character}): {base.ToString()}"; } } diff --git a/sources/assets/Stride.Core.Assets/DirectoryHelper.cs b/sources/assets/Stride.Core.Assets/DirectoryHelper.cs index ed90fbcbea..f4c712b3f4 100644 --- a/sources/assets/Stride.Core.Assets/DirectoryHelper.cs +++ b/sources/assets/Stride.Core.Assets/DirectoryHelper.cs @@ -1,39 +1,36 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.IO; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Helper class that contains methods to retrieve and manipulate SDK locations. +/// +public static class DirectoryHelper { + private const string StrideSolution = @"build\Stride.sln"; + /// - /// Helper class that contains methods to retrieve and manipulate SDK locations. + /// Gets the path to the file corresponding to the given package name in the given directory. /// - public static class DirectoryHelper + /// The directory where the package file is located. + /// The name of the package. + /// The path to the file corresponding to the given package name in the given directory. + public static string GetPackageFile(string directory, string packageName) { - private const string StrideSolution = @"build\Stride.sln"; - - /// - /// Gets the path to the file corresponding to the given package name in the given directory. - /// - /// The directory where the package file is located. - /// The name of the package. - /// The path to the file corresponding to the given package name in the given directory. - public static string GetPackageFile(string directory, string packageName) - { - if (directory == null) throw new ArgumentNullException(nameof(directory)); - return Path.Combine(directory, packageName + Package.PackageFileExtension); - } + ArgumentNullException.ThrowIfNull(directory); + return Path.Combine(directory, packageName + Package.PackageFileExtension); + } - /// - /// Indicates whether the given directory is the root directory of the repository, when executing from a development build. - /// - /// The directory to check. - /// True if the given directory is the root directory of the repository, false otherwise. - public static bool IsRootDevDirectory(string directory) - { - if (directory == null) throw new ArgumentNullException(nameof(directory)); - var strideSolution = Path.Combine(directory, StrideSolution); - return File.Exists(strideSolution); - } + /// + /// Indicates whether the given directory is the root directory of the repository, when executing from a development build. + /// + /// The directory to check. + /// True if the given directory is the root directory of the repository, false otherwise. + public static bool IsRootDevDirectory(string directory) + { + ArgumentNullException.ThrowIfNull(directory); + var strideSolution = Path.Combine(directory, StrideSolution); + return File.Exists(strideSolution); } } diff --git a/sources/assets/Stride.Core.Assets/DirtyFlagChangedDelegate.cs b/sources/assets/Stride.Core.Assets/DirtyFlagChangedDelegate.cs index d9518995bb..71f5fa7703 100644 --- a/sources/assets/Stride.Core.Assets/DirtyFlagChangedDelegate.cs +++ b/sources/assets/Stride.Core.Assets/DirtyFlagChangedDelegate.cs @@ -1,12 +1,12 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets -{ - /// - /// A delegate used for events raised when the dirty flag of an object has changed - /// - /// The object that had its dirty flag changed. - /// The old value of the dirty flag. - /// The new value of the dirty flag. - public delegate void DirtyFlagChangedDelegate(T sender, bool oldValue, bool newValue); -} + +namespace Stride.Core.Assets; + +/// +/// A delegate used for events raised when the dirty flag of an object has changed +/// +/// The object that had its dirty flag changed. +/// The old value of the dirty flag. +/// The new value of the dirty flag. +public delegate void DirtyFlagChangedDelegate(T sender, bool oldValue, bool newValue); diff --git a/sources/assets/Stride.Core.Assets/DynamicYaml/DynamicYamlExtensions.cs b/sources/assets/Stride.Core.Assets/DynamicYaml/DynamicYamlExtensions.cs index 3eb07e2b96..57e89f37b6 100644 --- a/sources/assets/Stride.Core.Assets/DynamicYaml/DynamicYamlExtensions.cs +++ b/sources/assets/Stride.Core.Assets/DynamicYaml/DynamicYamlExtensions.cs @@ -1,50 +1,40 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.IO; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +// TODO: this works only for asset now. Allow to select the YamlSerializer to use to make it work for other scenario +public static class DynamicYamlExtensions { - // TODO: this works only for asset now. Allow to select the YamlSerializer to use to make it work for other scenario - public static class DynamicYamlExtensions + public static T ConvertTo(IDynamicYamlNode yamObject) + { + using var memoryStream = new MemoryStream(); + // convert Yaml nodes to string + using var streamWriter = new StreamWriter(memoryStream); + var yamlStream = new YamlStream { new YamlDocument(yamObject.Node) }; + yamlStream.Save(streamWriter, true, AssetYamlSerializer.Default.GetSerializerSettings().PreferredIndent); + + streamWriter.Flush(); + memoryStream.Position = 0; + + // convert string to object + return (T)AssetYamlSerializer.Default.Deserialize(memoryStream, typeof(T)); + } + + public static IDynamicYamlNode ConvertFrom(T dataObject) { - public static T ConvertTo(IDynamicYamlNode yamObject) - { - using (var memoryStream = new MemoryStream()) - { - // convert Yaml nodes to string - using (var streamWriter = new StreamWriter(memoryStream)) - { - var yamlStream = new YamlStream { new YamlDocument(yamObject.Node) }; - yamlStream.Save(streamWriter, true, AssetYamlSerializer.Default.GetSerializerSettings().PreferredIndent); - - streamWriter.Flush(); - memoryStream.Position = 0; - - // convert string to object - return (T)AssetYamlSerializer.Default.Deserialize(memoryStream, typeof(T)); - } - } - } - - public static IDynamicYamlNode ConvertFrom(T dataObject) - { - using (var stream = new MemoryStream()) - { - // convert data to string - AssetYamlSerializer.Default.Serialize(stream, dataObject); - - stream.Position = 0; - - // convert string to Yaml nodes - using (var reader = new StreamReader(stream)) - { - var yamlStream = new YamlStream(); - yamlStream.Load(reader); - return (IDynamicYamlNode)DynamicYamlObject.ConvertToDynamic(yamlStream.Documents[0].RootNode); - } - } - } + using var stream = new MemoryStream(); + // convert data to string + AssetYamlSerializer.Default.Serialize(stream, dataObject); + + stream.Position = 0; + + // convert string to Yaml nodes + using var reader = new StreamReader(stream); + var yamlStream = new YamlStream(); + yamlStream.Load(reader); + return (IDynamicYamlNode)DynamicYamlObject.ConvertToDynamic(yamlStream.Documents[0].RootNode); } } diff --git a/sources/assets/Stride.Core.Assets/EmptyAssetUpgrader.cs b/sources/assets/Stride.Core.Assets/EmptyAssetUpgrader.cs index dfef1e0818..bcac270a03 100644 --- a/sources/assets/Stride.Core.Assets/EmptyAssetUpgrader.cs +++ b/sources/assets/Stride.Core.Assets/EmptyAssetUpgrader.cs @@ -1,18 +1,15 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; +namespace Stride.Core.Assets; -namespace Stride.Core.Assets +/// +/// Empty asset upgrader (useful when we want to bump version without changing anything). +/// +public class EmptyAssetUpgrader : AssetUpgraderBase { - /// - /// Empty asset upgrader (useful when we want to bump version without changing anything). - /// - public class EmptyAssetUpgrader : AssetUpgraderBase + /// + protected override void UpgradeAsset(AssetMigrationContext context, PackageVersion currentVersion, PackageVersion targetVersion, dynamic asset, PackageLoadingAssetFile assetFile, OverrideUpgraderHint overrideHint) { - /// - protected override void UpgradeAsset(AssetMigrationContext context, PackageVersion currentVersion, PackageVersion targetVersion, dynamic asset, PackageLoadingAssetFile assetFile, OverrideUpgraderHint overrideHint) - { - } } } diff --git a/sources/assets/Stride.Core.Assets/FileVersionManager.cs b/sources/assets/Stride.Core.Assets/FileVersionManager.cs index fcd5bfcbe4..dbd9e38571 100644 --- a/sources/assets/Stride.Core.Assets/FileVersionManager.cs +++ b/sources/assets/Stride.Core.Assets/FileVersionManager.cs @@ -1,252 +1,232 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Threading; using Stride.Core.BuildEngine; -using Stride.Core.Annotations; using Stride.Core.Diagnostics; using Stride.Core.IO; using Stride.Core.Storage; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public class FileVersionManager { - public class FileVersionManager + private static readonly object TrackerLock = new(); + private static FileVersionManager? instance; + private readonly FileVersionTracker tracker; + private readonly Thread asyncRunner; + private readonly AutoResetEvent asyncRequestAvailable; + private readonly ConcurrentQueue asyncRequests; + private readonly HashSet requestsToProcess = []; + private bool isDisposing; + private bool isDisposed; + private long requestsInFlight; + + private FileVersionManager() { - private static readonly object TrackerLock = new object(); - private static FileVersionManager instance; - private readonly FileVersionTracker tracker; - private readonly Thread asyncRunner; - private readonly AutoResetEvent asyncRequestAvailable; - private readonly ConcurrentQueue asyncRequests; - private readonly HashSet requestsToProcess = new HashSet(); - private bool isDisposing; - private bool isDisposed; - private long requestsInFlight; - - private FileVersionManager() - { - // Environment.SpecialFolder.ApplicationData - asyncRequestAvailable = new AutoResetEvent(false); - asyncRequests = new ConcurrentQueue(); - - // Loads the file version cache - tracker = FileVersionTracker.GetDefault(); - asyncRunner = new Thread(SafeAction.Wrap(ComputeFileHashAsyncRunner)) { Name = "File Version Manager", IsBackground = true }; - asyncRunner.Start(); - } + // Environment.SpecialFolder.ApplicationData + asyncRequestAvailable = new AutoResetEvent(false); + asyncRequests = new ConcurrentQueue(); + + // Loads the file version cache + tracker = FileVersionTracker.GetDefault(); + asyncRunner = new Thread(SafeAction.Wrap(ComputeFileHashAsyncRunner)) { Name = "File Version Manager", IsBackground = true }; + asyncRunner.Start(); + } - /// - /// Returns the amount of items scheduled left to process - /// - /// - /// It may already be out of date as soon as it returns. - /// Do not rely on this to check for completion, use the callbacks instead. - /// - public long PeekAsyncRequestsLeft + /// + /// Returns the amount of items scheduled left to process + /// + /// + /// It may already be out of date as soon as it returns. + /// Do not rely on this to check for completion, use the callbacks instead. + /// + public long PeekAsyncRequestsLeft + { + get { - get - { - return Interlocked.Read(ref requestsInFlight); - } + return Interlocked.Read(ref requestsInFlight); } + } - [NotNull] - public static FileVersionManager Instance + public static FileVersionManager Instance + { + get { - get + lock (TrackerLock) { - lock (TrackerLock) - { - if (instance != null) - return instance; - - instance = new FileVersionManager(); - AppDomain.CurrentDomain.ProcessExit += CurrentDomainProcessExit; + if (instance != null) return instance; - } + + instance = new FileVersionManager(); + AppDomain.CurrentDomain.ProcessExit += CurrentDomainProcessExit; + return instance; } } + } - public static void Shutdown() + public static void Shutdown() + { + lock (TrackerLock) { - lock (TrackerLock) - { - if (instance == null) - return; - instance.Dispose(); - instance = null; - } + if (instance == null) + return; + instance.Dispose(); + instance = null; } + } - public ObjectId ComputeFileHash(UFile path) - { - if (!File.Exists(path)) - return ObjectId.Empty; + public ObjectId ComputeFileHash(UFile path) + { + if (!File.Exists(path)) + return ObjectId.Empty; - return tracker.ComputeFileHash(path); - } + return tracker.ComputeFileHash(path); + } - public void ComputeFileHashAsync([NotNull] UFile path, Action fileHashCallback = null, CancellationToken? cancellationToken = null) - { - if (path == null) throw new ArgumentNullException(nameof(path)); + public void ComputeFileHashAsync(UFile path, Action? fileHashCallback = null, CancellationToken? cancellationToken = null) + { + ArgumentNullException.ThrowIfNull(path); - lock (asyncRequests) - { - asyncRequests.Enqueue(new AsyncRequest(path, fileHashCallback, cancellationToken)); - Interlocked.Increment(ref requestsInFlight); - } - asyncRequestAvailable.Set(); + lock (asyncRequests) + { + asyncRequests.Enqueue(new AsyncRequest(path, fileHashCallback, cancellationToken)); + Interlocked.Increment(ref requestsInFlight); } + asyncRequestAvailable.Set(); + } - public void ComputeFileHashAsync([NotNull] IEnumerable paths, Action fileHashCallback = null, CancellationToken? cancellationToken = null) - { - if (paths == null) throw new ArgumentNullException(nameof(paths)); + public void ComputeFileHashAsync(IEnumerable paths, Action? fileHashCallback = null, CancellationToken? cancellationToken = null) + { + ArgumentNullException.ThrowIfNull(paths); - int itemCount = 0; - lock (asyncRequests) + int itemCount = 0; + lock (asyncRequests) + { + foreach (var path in paths) { - foreach (var path in paths) - { - asyncRequests.Enqueue(new AsyncRequest(path, fileHashCallback, cancellationToken)); - itemCount++; - } + asyncRequests.Enqueue(new AsyncRequest(path, fileHashCallback, cancellationToken)); + itemCount++; } - Interlocked.Add(ref requestsInFlight, itemCount); - asyncRequestAvailable.Set(); } + Interlocked.Add(ref requestsInFlight, itemCount); + asyncRequestAvailable.Set(); + } - private void ComputeFileHashAsyncRunner() + private void ComputeFileHashAsyncRunner() + { + while (!isDisposing) { - while (!isDisposing) + if (asyncRequestAvailable.WaitOne()) { - if (asyncRequestAvailable.WaitOne()) + lock (asyncRequests) { - lock (asyncRequests) + // Dequeue as much as possible in a single row + while (asyncRequests.TryDequeue(out var asyncRequest)) { - // Dequeue as much as possible in a single row - while (true) - { - AsyncRequest asyncRequest; - if (asyncRequests.TryDequeue(out asyncRequest)) - { - requestsToProcess.Add(asyncRequest); - } - else - { - break; - } - } + requestsToProcess.Add(asyncRequest); } } + } + + // Early exit + if (isDisposing) + { + return; + } - // Early exit + foreach (var request in requestsToProcess) + { if (isDisposing) { return; } - foreach (var request in requestsToProcess) + if (request.CancellationToken.HasValue && request.CancellationToken.Value.IsCancellationRequested) { - if (isDisposing) - { - return; - } - - if (request.CancellationToken.HasValue && request.CancellationToken.Value.IsCancellationRequested) - { - continue; - } - - var hash = ComputeFileHash(request.File); + continue; + } - if (request.CancellationToken.HasValue && request.CancellationToken.Value.IsCancellationRequested) - { - continue; - } + var hash = ComputeFileHash(request.File); - if (isDisposing) - { - return; - } + if (request.CancellationToken.HasValue && request.CancellationToken.Value.IsCancellationRequested) + { + continue; + } - request.FileHashCallback?.Invoke(request.File, hash); + if (isDisposing) + { + return; } - Interlocked.Add(ref requestsInFlight, -requestsToProcess.Count); - // Once we have processed the list, we can clear it - requestsToProcess.Clear(); + + request.FileHashCallback?.Invoke(request.File, hash); } + Interlocked.Add(ref requestsInFlight, -requestsToProcess.Count); + // Once we have processed the list, we can clear it + requestsToProcess.Clear(); } + } - private static void CurrentDomainProcessExit(object sender, EventArgs e) - { - Shutdown(); - } + private static void CurrentDomainProcessExit(object? sender, EventArgs e) + { + Shutdown(); + } - private void Dispose() - { - if (isDisposed) - return; + private void Dispose() + { + if (isDisposed) + return; - // Set to true and let the async runner thread terminates - isDisposing = true; - asyncRequestAvailable.Set(); + // Set to true and let the async runner thread terminates + isDisposing = true; + asyncRequestAvailable.Set(); - asyncRunner.Join(); + asyncRunner.Join(); - isDisposed = true; - } + isDisposed = true; + } - private struct AsyncRequest : IEquatable + private readonly struct AsyncRequest : IEquatable + { + public AsyncRequest(UFile file, Action? fileHashCallback, CancellationToken? cancellationToken) { - public AsyncRequest(UFile file, Action fileHashCallback, CancellationToken? cancellationToken) - { - File = file; - FileHashCallback = fileHashCallback; - CancellationToken = cancellationToken; - } + File = file; + FileHashCallback = fileHashCallback; + CancellationToken = cancellationToken; + } - public readonly UFile File; + public readonly UFile File; - public readonly Action FileHashCallback; + public readonly Action? FileHashCallback; - public readonly CancellationToken? CancellationToken; + public readonly CancellationToken? CancellationToken; - public bool Equals(AsyncRequest other) - { - return Equals(File, other.File) && - Equals(FileHashCallback, other.FileHashCallback) && - CancellationToken.Equals(other.CancellationToken); - } + public readonly bool Equals(AsyncRequest other) + { + return Equals(File, other.File) && + Equals(FileHashCallback, other.FileHashCallback) && + CancellationToken.Equals(other.CancellationToken); + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is AsyncRequest && Equals((AsyncRequest)obj); - } + public override readonly bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is AsyncRequest asyncRequest && Equals(asyncRequest); + } - public override int GetHashCode() - { - unchecked - { - var hashCode = File?.GetHashCode() ?? 0; - hashCode = (hashCode*397) ^ (FileHashCallback?.GetHashCode() ?? 0); - hashCode = (hashCode*397) ^ CancellationToken.GetHashCode(); - return hashCode; - } - } + public override readonly int GetHashCode() + { + return HashCode.Combine(File, FileHashCallback, CancellationToken); + } - public static bool operator ==(AsyncRequest left, AsyncRequest right) - { - return left.Equals(right); - } + public static bool operator ==(AsyncRequest left, AsyncRequest right) + { + return left.Equals(right); + } - public static bool operator !=(AsyncRequest left, AsyncRequest right) - { - return !left.Equals(right); - } + public static bool operator !=(AsyncRequest left, AsyncRequest right) + { + return !left.Equals(right); } } } diff --git a/sources/assets/Stride.Core.Assets/IAssetComposite.cs b/sources/assets/Stride.Core.Assets/IAssetComposite.cs index 19ea5e5f9d..eaefb0df8b 100644 --- a/sources/assets/Stride.Core.Assets/IAssetComposite.cs +++ b/sources/assets/Stride.Core.Assets/IAssetComposite.cs @@ -1,26 +1,22 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; +namespace Stride.Core.Assets; -namespace Stride.Core.Assets +/// +/// An interface that defines the composition declared by an asset inheriting from another asset. +/// +public interface IAssetComposite { /// - /// An interface that defines the composition declared by an asset inheriting from another asset. + /// Collects the part assets. /// - public interface IAssetComposite - { - /// - /// Collects the part assets. - /// - IEnumerable CollectParts(); + IEnumerable CollectParts(); - /// - /// Checks if this container contains the part with the specified id. - /// - /// Unique identifier of the asset part - /// true if this asset contains the part with the specified id; otherwise false - bool ContainsPart(Guid id); - } + /// + /// Checks if this container contains the part with the specified id. + /// + /// Unique identifier of the asset part + /// true if this asset contains the part with the specified id; otherwise false + bool ContainsPart(Guid id); } diff --git a/sources/assets/Stride.Core.Assets/IAssetFactory.cs b/sources/assets/Stride.Core.Assets/IAssetFactory.cs index 2b559270fe..61415d8881 100644 --- a/sources/assets/Stride.Core.Assets/IAssetFactory.cs +++ b/sources/assets/Stride.Core.Assets/IAssetFactory.cs @@ -1,29 +1,26 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; + using Stride.Core.Reflection; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// An interface that represents an asset factory. +/// +/// The type of asset this factory can create. +[AssemblyScan] +public interface IAssetFactory where T : Asset { /// - /// An interface that represents an asset factory. + /// Retrieve the asset type associated to this factory. /// - /// The type of asset this factory can create. - [AssemblyScan] - public interface IAssetFactory where T : Asset - { - /// - /// Retrieve the asset type associated to this factory. - /// - /// The asset type associated to this factory. - [NotNull] - Type AssetType { get; } + /// The asset type associated to this factory. + Type AssetType { get; } - /// - /// Creates a new instance of the asset type associated to this factory. - /// - /// A new instance of the asset type associated to this factory. - T New(); - } + /// + /// Creates a new instance of the asset type associated to this factory. + /// + /// A new instance of the asset type associated to this factory. + T New(); } diff --git a/sources/assets/Stride.Core.Assets/IAssetFinder.cs b/sources/assets/Stride.Core.Assets/IAssetFinder.cs index da67f4f126..74d4efdfff 100644 --- a/sources/assets/Stride.Core.Assets/IAssetFinder.cs +++ b/sources/assets/Stride.Core.Assets/IAssetFinder.cs @@ -1,35 +1,30 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Annotations; using Stride.Core.IO; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public interface IAssetFinder { - public interface IAssetFinder - { - /// - /// Finds an asset by its identifier. - /// - /// The identifier of the asset. - /// The corresponding if found; otherwise, null. - [CanBeNull] - AssetItem FindAsset(AssetId assetId); + /// + /// Finds an asset by its identifier. + /// + /// The identifier of the asset. + /// The corresponding if found; otherwise, null. + AssetItem? FindAsset(AssetId assetId); - /// - /// Finds an asset by its location. - /// - /// The location of the asset. - /// The corresponding if found; otherwise, null. - [CanBeNull] - AssetItem FindAsset([NotNull] UFile location); + /// + /// Finds an asset by its location. + /// + /// The location of the asset. + /// The corresponding if found; otherwise, null. + AssetItem? FindAsset(UFile location); - /// - /// Finds an asset from a proxy object. - /// - /// The proxy object which is represent the targeted asset. - /// The corresponding if found; otherwise, null. - [CanBeNull] - AssetItem FindAssetFromProxyObject([CanBeNull] object proxyObject); - } + /// + /// Finds an asset from a proxy object. + /// + /// The proxy object which is represent the targeted asset. + /// The corresponding if found; otherwise, null. + AssetItem? FindAssetFromProxyObject(object? proxyObject); } diff --git a/sources/assets/Stride.Core.Assets/IAssetImporter.cs b/sources/assets/Stride.Core.Assets/IAssetImporter.cs index 4434cf3fd3..6b9b1965e0 100644 --- a/sources/assets/Stride.Core.Assets/IAssetImporter.cs +++ b/sources/assets/Stride.Core.Assets/IAssetImporter.cs @@ -1,85 +1,79 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.IO; using Stride.Core.Reflection; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Imports a raw asset into the asset system. +/// +[AssemblyScan] +public interface IAssetImporter { /// - /// Imports a raw asset into the asset system. + /// Gets an unique identifier to identify the importer. See remarks. /// - [AssemblyScan] - public interface IAssetImporter - { - /// - /// Gets an unique identifier to identify the importer. See remarks. - /// - /// The identifier. - /// This identifier is used to recover the importer used for a particular asset. This - /// Guid must be unique and stored statically in the definition of an importer. It is used to - /// reimport an existing asset with the same importer. - /// - Guid Id { get; } + /// The identifier. + /// This identifier is used to recover the importer used for a particular asset. This + /// Guid must be unique and stored statically in the definition of an importer. It is used to + /// reimport an existing asset with the same importer. + /// + Guid Id { get; } - /// - /// Gets the name of this importer. - /// - /// The name. - string Name { get; } + /// + /// Gets the name of this importer. + /// + /// The name. + string Name { get; } - /// - /// Gets the description of this importer. - /// - /// The description. - string Description { get; } + /// + /// Gets the description of this importer. + /// + /// The description. + string Description { get; } - /// - /// Gets the order of precedence between the importers, so that an importer can override another one. - /// - /// The order. - int Order { get; } + /// + /// Gets the order of precedence between the importers, so that an importer can override another one. + /// + /// The order. + int Order { get; } - /// - /// Gets the supported file extensions (separated by ',' for multiple extensions) by this importer. This is used for display purpose only. The method is used for matching extensions. - /// - /// Returns a list of supported file extensions handled by this importer. - [NotNull] - string SupportedFileExtensions { get; } + /// + /// Gets the supported file extensions (separated by ',' for multiple extensions) by this importer. This is used for display purpose only. The method is used for matching extensions. + /// + /// Returns a list of supported file extensions handled by this importer. + string SupportedFileExtensions { get; } - /// - /// Determines whether this importer is supporting the specified file. - /// - /// The file path. - /// true if this importer is supporting the specified file; otherwise, false. - bool IsSupportingFile([NotNull] string filePath); + /// + /// Determines whether this importer is supporting the specified file. + /// + /// The file path. + /// true if this importer is supporting the specified file; otherwise, false. + bool IsSupportingFile(string filePath); - /// - /// Gets the types of asset that are mainly generated by this importer. - /// - [ItemNotNull] - IEnumerable RootAssetTypes { get; } + /// + /// Gets the types of asset that are mainly generated by this importer. + /// + IEnumerable RootAssetTypes { get; } - /// - /// Gets the additional types of asset that can be generated by this importer in complement of the root assets - /// - [ItemNotNull] - IEnumerable AdditionalAssetTypes { get; } + /// + /// Gets the additional types of asset that can be generated by this importer in complement of the root assets + /// + IEnumerable AdditionalAssetTypes { get; } - /// - /// Gets the default parameters for this importer. - /// - /// - /// The supported types. - AssetImporterParameters GetDefaultParameters(bool isForReImport); + /// + /// Gets the default parameters for this importer. + /// + /// + /// The supported types. + AssetImporterParameters GetDefaultParameters(bool isForReImport); - /// - /// Imports a raw assets from the specified path into the specified package. - /// - /// The path to a raw asset on the disk. - /// The parameters. It is mandatory to call and pass the parameters instance here - IEnumerable Import(UFile rawAssetPath, AssetImporterParameters importParameters); - } + /// + /// Imports a raw assets from the specified path into the specified package. + /// + /// The path to a raw asset on the disk. + /// The parameters. It is mandatory to call and pass the parameters instance here + IEnumerable Import(UFile rawAssetPath, AssetImporterParameters importParameters); } diff --git a/sources/assets/Stride.Core.Assets/IAssetPartDesign.cs b/sources/assets/Stride.Core.Assets/IAssetPartDesign.cs index d715878a08..e0224992de 100644 --- a/sources/assets/Stride.Core.Assets/IAssetPartDesign.cs +++ b/sources/assets/Stride.Core.Assets/IAssetPartDesign.cs @@ -1,33 +1,27 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; -using Stride.Core.Annotations; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// An interface representing a design-time part in an . +/// +public interface IAssetPartDesign { - /// - /// An interface representing a design-time part in an . - /// - public interface IAssetPartDesign - { - [CanBeNull] - BasePart Base { get; set; } + BasePart? Base { get; set; } - [NotNull] - IIdentifiable Part { get; } - } + IIdentifiable Part { get; } +} +/// +/// An interface representing a design-time part in an . +/// +/// The underlying type of part. +public interface IAssetPartDesign : IAssetPartDesign + where TAssetPart : IIdentifiable +{ /// - /// An interface representing a design-time part in an . + /// The actual part. /// - /// The underlying type of part. - public interface IAssetPartDesign : IAssetPartDesign - where TAssetPart : IIdentifiable - { - /// - /// The actual part. - /// - [NotNull] - new TAssetPart Part { get; } - } + new TAssetPart Part { get; } } diff --git a/sources/assets/Stride.Core.Assets/IAssetUpgrader.cs b/sources/assets/Stride.Core.Assets/IAssetUpgrader.cs index cf80a0729d..4d9242e7df 100644 --- a/sources/assets/Stride.Core.Assets/IAssetUpgrader.cs +++ b/sources/assets/Stride.Core.Assets/IAssetUpgrader.cs @@ -1,12 +1,11 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; + using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public interface IAssetUpgrader { - public interface IAssetUpgrader - { - void Upgrade(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, YamlMappingNode yamlAssetNode, PackageLoadingAssetFile assetFile); - } + void Upgrade(AssetMigrationContext context, string dependencyName, PackageVersion currentVersion, PackageVersion targetVersion, YamlMappingNode yamlAssetNode, PackageLoadingAssetFile assetFile); } diff --git a/sources/assets/Stride.Core.Assets/IAssetWithSource.cs b/sources/assets/Stride.Core.Assets/IAssetWithSource.cs index c11728382e..b1abf1d859 100644 --- a/sources/assets/Stride.Core.Assets/IAssetWithSource.cs +++ b/sources/assets/Stride.Core.Assets/IAssetWithSource.cs @@ -3,16 +3,15 @@ using Stride.Core.IO; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public interface IAssetWithSource { - public interface IAssetWithSource - { - /// - /// The source file of this asset. - /// - /// - /// The source file of this asset. - /// - UFile Source { get; set; } - } + /// + /// The source file of this asset. + /// + /// + /// The source file of this asset. + /// + UFile Source { get; set; } } diff --git a/sources/assets/Stride.Core.Assets/IFileSynchronizable.cs b/sources/assets/Stride.Core.Assets/IFileSynchronizable.cs index 968f2d837c..9fa8dd1c2f 100644 --- a/sources/assets/Stride.Core.Assets/IFileSynchronizable.cs +++ b/sources/assets/Stride.Core.Assets/IFileSynchronizable.cs @@ -1,25 +1,25 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.IO; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Identify an object that is associated with an anchor file on the disk where all the members of this +/// instance are relative to the of this instance. +/// +public interface IFileSynchronizable { /// - /// Identify an object that is associated with an anchor file on the disk where all the members of this - /// instance are relative to the of this instance. + /// Gets the full path on disk where this instance is stored. /// - public interface IFileSynchronizable - { - /// - /// Gets the full path on disk where this instance is stored. - /// - /// The full path. - UFile FullPath { get; } + /// The full path. + UFile FullPath { get; } - /// - /// Gets or sets a value indicating whether this instance is dirty. - /// - /// true if this instance is dirty; otherwise, false. - bool IsDirty { get; set; } - } + /// + /// Gets or sets a value indicating whether this instance is dirty. + /// + /// true if this instance is dirty; otherwise, false. + bool IsDirty { get; set; } } diff --git a/sources/assets/Stride.Core.Assets/IO/FileExtensionCollection.cs b/sources/assets/Stride.Core.Assets/IO/FileExtensionCollection.cs index cf2504b749..bd2c8d099f 100644 --- a/sources/assets/Stride.Core.Assets/IO/FileExtensionCollection.cs +++ b/sources/assets/Stride.Core.Assets/IO/FileExtensionCollection.cs @@ -1,103 +1,98 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; + using System.Text; using System.Text.RegularExpressions; -namespace Stride.Core.Assets.IO +namespace Stride.Core.Assets.IO; + +/// +/// A class describing a collection of file extensions, with a facultative description string. +/// +public sealed class FileExtensionCollection { /// - /// A class describing a collection of file extensions, with a facultative description string. + /// Initializes a new instance of the class. /// - public sealed class FileExtensionCollection + /// The extensions to add in this collection. Extensions must be separated with the semi-colon character (;). + public FileExtensionCollection(string extensions) + : this(null, extensions) { - /// - /// Initializes a new instance of the class. - /// - /// The extensions to add in this collection. Extensions must be separated with the semi-colon character (;). - public FileExtensionCollection(string extensions) - : this(null, extensions) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The description of this file extension collection. Can be null. - /// The extensions to add in this collection. Extensions must be separated with the semi-colon character (;). - /// Additional extensions to add in this collection. Extensions inside each string must be separated with the semi-colon character (;). - public FileExtensionCollection(string description, string extensions, params string[] additionalExtensions) + /// + /// Initializes a new instance of the class. + /// + /// The description of this file extension collection. Can be null. + /// The extensions to add in this collection. Extensions must be separated with the semi-colon character (;). + /// Additional extensions to add in this collection. Extensions inside each string must be separated with the semi-colon character (;). + public FileExtensionCollection(string description, string extensions, params string[] additionalExtensions) + { + var sb = new StringBuilder(extensions); + if (additionalExtensions != null) { - var sb = new StringBuilder(extensions); - if (additionalExtensions != null) + foreach (var nextExtensions in additionalExtensions) { - foreach (var nextExtensions in additionalExtensions) - { - sb.Append(';'); - sb.Append(nextExtensions); - } + sb.Append(';'); + sb.Append(nextExtensions); } - var allExtensions = sb.ToString(); - SingleExtensions = SplitExtensions(allExtensions); - ConcatenatedExtensions = string.Join(";", SingleExtensions); - Description = description; } + var allExtensions = sb.ToString(); + SingleExtensions = SplitExtensions(allExtensions); + ConcatenatedExtensions = string.Join(";", SingleExtensions); + Description = description; + } - /// - /// Gets the description of this file extension collection. - /// - public string Description { get; } + /// + /// Gets the description of this file extension collection. + /// + public string Description { get; } - /// - /// Gets each extension in this collection individually splitted in an . - /// - public IEnumerable SingleExtensions { get; } + /// + /// Gets each extension in this collection individually splitted in an . + /// + public IEnumerable SingleExtensions { get; } - /// - /// Gets a string containing all extensions separated with the semi-colon character (;). - /// - public string ConcatenatedExtensions { get; } + /// + /// Gets a string containing all extensions separated with the semi-colon character (;). + /// + public string ConcatenatedExtensions { get; } - /// - /// Indicates whether the given extension matches any of the extension in this collection. - /// - /// The extension to match. Can contain wildcards. - /// True if the given extension matches, false otherwise. - public bool Contains(string extension) - { - var normalized = NormalizeExtension(extension); - var pattern = new Regex(normalized.Replace(".", "[.]").Replace("*", ".*")); - return SingleExtensions.Any(x => pattern.IsMatch(x) || new Regex(x.Replace(".", "[.]").Replace("*", ".*")).IsMatch(normalized)); - } + /// + /// Indicates whether the given extension matches any of the extension in this collection. + /// + /// The extension to match. Can contain wildcards. + /// True if the given extension matches, false otherwise. + public bool Contains(string extension) + { + var normalized = NormalizeExtension(extension); + var pattern = new Regex(normalized.Replace(".", "[.]").Replace("*", ".*")); + return SingleExtensions.Any(x => pattern.IsMatch(x) || new Regex(x.Replace(".", "[.]").Replace("*", ".*")).IsMatch(normalized)); + } - private static List SplitExtensions(string extensions) - { - return extensions.Split(new[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries).Select(NormalizeExtension).ToList(); - } + private static List SplitExtensions(string extensions) + { + return extensions.Split([';', ','], StringSplitOptions.RemoveEmptyEntries).Select(NormalizeExtension).ToList(); + } - private static string NormalizeExtension(string extension) - { - if (extension == null) - throw new ArgumentNullException(nameof(extension)); + private static string NormalizeExtension(string extension) + { + ArgumentNullException.ThrowIfNull(extension); - if (extension.Contains(";") || extension.Contains(",")) - throw new ArgumentException("Expecting a single extension"); + if (extension.Contains(';') || extension.Contains(',')) + throw new ArgumentException("Expecting a single extension"); - if (extension.StartsWith("*.", StringComparison.Ordinal)) - { - extension = extension[1..]; - } - if (extension.Any(x => x != '*' & Path.GetInvalidFileNameChars().Contains(x))) - throw new ArgumentException("Extension contains invalid characters"); + if (extension.StartsWith("*.", StringComparison.Ordinal)) + { + extension = extension[1..]; + } + if (extension.Any(x => x != '*' & Path.GetInvalidFileNameChars().Contains(x))) + throw new ArgumentException("Extension contains invalid characters"); - if (!extension.StartsWith('.')) - { - extension = $".{extension}"; - } - return extension.ToLowerInvariant(); + if (!extension.StartsWith('.')) + { + extension = $".{extension}"; } + return extension.ToLowerInvariant(); } } diff --git a/sources/assets/Stride.Core.Assets/IO/FileUtility.cs b/sources/assets/Stride.Core.Assets/IO/FileUtility.cs index 4c5617b7da..b41fe23333 100644 --- a/sources/assets/Stride.Core.Assets/IO/FileUtility.cs +++ b/sources/assets/Stride.Core.Assets/IO/FileUtility.cs @@ -1,136 +1,131 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// File Utilities methods. +/// +public class FileUtility { /// - /// File Utilities methods. + /// Determines whether the specified file is locked. /// - public class FileUtility - { - /// - /// Determines whether the specified file is locked. - /// - /// The file path. - /// true if the specified file is locked; otherwise, false. - /// is null - public static bool IsFileLocked(string filePath) => IsFileLocked(new FileInfo(filePath)); + /// The file path. + /// true if the specified file is locked; otherwise, false. + /// is null + public static bool IsFileLocked(string filePath) => IsFileLocked(new FileInfo(filePath)); - /// - /// Determines whether the specified file is locked. - /// - /// The file. - /// true if the specified file is locked; otherwise, false. - /// is null - public static bool IsFileLocked(FileInfo file) + /// + /// Determines whether the specified file is locked. + /// + /// The file. + /// true if the specified file is locked; otherwise, false. + /// is null + public static bool IsFileLocked(FileInfo file) + { + ArgumentNullException.ThrowIfNull(file); + try { - if (file == null) throw new ArgumentNullException(nameof(file)); - try - { - using (file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None)) - { - } - } - catch (IOException) + using (file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { - return true; } - return false; } - - /// - /// Converts a relative path to an absolute path using the current working directoy. - /// - /// The file path. - /// An absolute path. - public static string GetAbsolutePath(string filePath) + catch (IOException) { - return filePath == null ? null : Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, filePath)); + return true; } + return false; + } + + /// + /// Converts a relative path to an absolute path using the current working directoy. + /// + /// The file path. + /// An absolute path. + public static string? GetAbsolutePath(string filePath) + { + return filePath == null ? null : Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, filePath)); + } - /// - /// Normalizes the file extension by adding a '.' prefix and making it lowercase. - /// - /// The file extension. - /// A normalized file extension. - public static string NormalizeFileExtension(string fileExtension) + /// + /// Normalizes the file extension by adding a '.' prefix and making it lowercase. + /// + /// The file extension. + /// A normalized file extension. + public static string NormalizeFileExtension(string fileExtension) + { + if (string.IsNullOrEmpty(fileExtension)) { - if (String.IsNullOrEmpty(fileExtension)) - { - return fileExtension; - } + return fileExtension; + } - fileExtension = fileExtension.ToLowerInvariant(); - if (fileExtension.StartsWith('.')) - { - return fileExtension; - } - return $".{fileExtension}"; + fileExtension = fileExtension.ToLowerInvariant(); + if (fileExtension.StartsWith('.')) + { + return fileExtension; } + return $".{fileExtension}"; + } - /// - /// Gets the file extensions normalized separated by ',' ';'. - /// - /// The file extensions separated by ',' ';'. - /// An array of file extensions. - public static HashSet GetFileExtensionsAsSet(string fileExtensions) + /// + /// Gets the file extensions normalized separated by ',' ';'. + /// + /// The file extensions separated by ',' ';'. + /// An array of file extensions. + public static HashSet GetFileExtensionsAsSet(string fileExtensions) + { + ArgumentNullException.ThrowIfNull(fileExtensions); + var fileExtensionArray = fileExtensions.Split([',', ';']).Select(fileExt => fileExt.Trim().ToLowerInvariant()).ToList(); + var filteredExtensions = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var fileExtension in fileExtensionArray.Select(NormalizeFileExtension)) { - if (fileExtensions == null) throw new ArgumentNullException(nameof(fileExtensions)); - var fileExtensionArray = fileExtensions.Split(new[] { ',', ';' }).Select(fileExt => fileExt.Trim().ToLowerInvariant()).ToList(); - var filteredExtensions = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var fileExtension in fileExtensionArray.Select(NormalizeFileExtension)) + if (fileExtension == string.Empty) { - if (fileExtension == string.Empty) - { - continue; - } - filteredExtensions.Add(fileExtension); + continue; } - - return filteredExtensions; + filteredExtensions.Add(fileExtension); } + return filteredExtensions; + } - /// - /// Gets the file extensions normalized separated by ',' ';'. - /// - /// The file extensions separated by ',' ';'. - /// An array of file extensions. - public static string[] GetFileExtensions(string fileExtensions) - { - return GetFileExtensionsAsSet(fileExtensions).ToArray(); - } - public static IEnumerable EnumerateDirectories(string rootDirectory, SearchDirection direction) - { - if (rootDirectory == null) throw new ArgumentNullException(nameof(rootDirectory)); + /// + /// Gets the file extensions normalized separated by ',' ';'. + /// + /// The file extensions separated by ',' ';'. + /// An array of file extensions. + public static string[] GetFileExtensions(string fileExtensions) + { + return [.. GetFileExtensionsAsSet(fileExtensions)]; + } + + public static IEnumerable EnumerateDirectories(string rootDirectory, SearchDirection direction) + { + ArgumentNullException.ThrowIfNull(rootDirectory); - var directory = new DirectoryInfo(rootDirectory); - if (Directory.Exists(rootDirectory)) + var directory = new DirectoryInfo(rootDirectory); + if (Directory.Exists(rootDirectory)) + { + if (direction == SearchDirection.Down) { - if (direction == SearchDirection.Down) + yield return directory; + foreach (var subDirectory in directory.EnumerateDirectories("*", SearchOption.AllDirectories)) { - yield return directory; - foreach (var subDirectory in directory.EnumerateDirectories("*", SearchOption.AllDirectories)) - { - yield return subDirectory; - } + yield return subDirectory; } - else + } + else + { + do { - do - { - yield return directory; - directory = directory.Parent; - } - while (directory != null); + yield return directory; + directory = directory.Parent; } + while (directory != null); } } } diff --git a/sources/assets/Stride.Core.Assets/IO/SearchDirection.cs b/sources/assets/Stride.Core.Assets/IO/SearchDirection.cs index ac11dd866b..b4c03dc961 100644 --- a/sources/assets/Stride.Core.Assets/IO/SearchDirection.cs +++ b/sources/assets/Stride.Core.Assets/IO/SearchDirection.cs @@ -1,20 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets + +namespace Stride.Core.Assets; + +/// +/// A direction to search for files in directories +/// +public enum SearchDirection { /// - /// A direction to search for files in directories + /// Search files in all sub-directories. /// - public enum SearchDirection - { - /// - /// Search files in all sub-directories. - /// - Down, + Down, - /// - /// Searchg files going upward in the directory hierarchy. - /// - Up, - } + /// + /// Searchg files going upward in the directory hierarchy. + /// + Up, } diff --git a/sources/assets/Stride.Core.Assets/IProjectAsset.cs b/sources/assets/Stride.Core.Assets/IProjectAsset.cs index 77098fd551..3bce952417 100644 --- a/sources/assets/Stride.Core.Assets/IProjectAsset.cs +++ b/sources/assets/Stride.Core.Assets/IProjectAsset.cs @@ -1,13 +1,9 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; -namespace Stride.Core.Assets -{ - /// - /// An asset that is stored in a project file (such as .csproj). - /// - public interface IProjectAsset - { - } -} +namespace Stride.Core.Assets; + +/// +/// An asset that is stored in a project file (such as .csproj). +/// +public interface IProjectAsset; diff --git a/sources/assets/Stride.Core.Assets/IProjectFileGeneratorAsset.cs b/sources/assets/Stride.Core.Assets/IProjectFileGeneratorAsset.cs index 8c51f59129..79eb379039 100644 --- a/sources/assets/Stride.Core.Assets/IProjectFileGeneratorAsset.cs +++ b/sources/assets/Stride.Core.Assets/IProjectFileGeneratorAsset.cs @@ -1,14 +1,14 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets + +namespace Stride.Core.Assets; + +/// +/// An asset that generates another file. +/// +public interface IProjectFileGeneratorAsset : IProjectAsset { - /// - /// An asset that generates another file. - /// - public interface IProjectFileGeneratorAsset : IProjectAsset - { - string Generator { get; } + string Generator { get; } - void SaveGeneratedAsset(AssetItem assetItem); - } + void SaveGeneratedAsset(AssetItem assetItem); } diff --git a/sources/assets/Stride.Core.Assets/Module.cs b/sources/assets/Stride.Core.Assets/Module.cs index ff494bf476..a12bb9123e 100644 --- a/sources/assets/Stride.Core.Assets/Module.cs +++ b/sources/assets/Stride.Core.Assets/Module.cs @@ -1,27 +1,23 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Assets.Analysis; -using Stride.Core.Assets.Templates; using Stride.Core.Assets.Tracking; -using Stride.Core; using Stride.Core.Reflection; using Stride.Core.Yaml; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +internal class Module { - internal class Module + [ModuleInitializer] + public static void Initialize() { - [ModuleInitializer] - public static void Initialize() - { - // Shadow object is always enabled when we are using assets, so we force it here - ShadowObject.Enable = true; + // Shadow object is always enabled when we are using assets, so we force it here + ShadowObject.Enable = true; - // Make sure that this assembly is registered - AssemblyRegistry.Register(typeof(Module).Assembly, AssemblyCommonCategories.Assets); + // Make sure that this assembly is registered + AssemblyRegistry.Register(typeof(Module).Assembly, AssemblyCommonCategories.Assets); - AssetYamlSerializer.Default.PrepareMembers += SourceHashesHelper.AddSourceHashesMember; - } + AssetYamlSerializer.Default.PrepareMembers += SourceHashesHelper.AddSourceHashesMember; } } diff --git a/sources/assets/Stride.Core.Assets/OverrideUpgraderHint.cs b/sources/assets/Stride.Core.Assets/OverrideUpgraderHint.cs index 7a72718232..9aa3b34402 100644 --- a/sources/assets/Stride.Core.Assets/OverrideUpgraderHint.cs +++ b/sources/assets/Stride.Core.Assets/OverrideUpgraderHint.cs @@ -1,25 +1,25 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets + +namespace Stride.Core.Assets; + +/// +/// Defines a context for overrides when upgrading an asset. +/// +public enum OverrideUpgraderHint { /// - /// Defines a context for overrides when upgrading an asset. + /// The upgrader is performed on an asset that may be used as the base for another asset /// - public enum OverrideUpgraderHint - { - /// - /// The upgrader is performed on an asset that may be used as the base for another asset - /// - Unknown, + Unknown, - /// - /// The upgrader is performed on an asset that has at least one base asset (for asset templating) - /// - Derived, + /// + /// The upgrader is performed on an asset that has at least one base asset (for asset templating) + /// + Derived, - /// - /// The upgrader is performed on the base asset of an asset being upgraded ( or ) - /// - Base - } + /// + /// The upgrader is performed on the base asset of an asset being upgraded ( or ) + /// + Base } diff --git a/sources/assets/Stride.Core.Assets/Package.Constants.cs b/sources/assets/Stride.Core.Assets/Package.Constants.cs index a6bbce8911..f2cf5e5e83 100644 --- a/sources/assets/Stride.Core.Assets/Package.Constants.cs +++ b/sources/assets/Stride.Core.Assets/Package.Constants.cs @@ -1,12 +1,12 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets + +namespace Stride.Core.Assets; + +public partial class Package { - public partial class Package - { - /// - /// The file extension used for . - /// - public const string PackageFileExtension = ".sdpkg"; - } + /// + /// The file extension used for . + /// + public const string PackageFileExtension = ".sdpkg"; } diff --git a/sources/assets/Stride.Core.Assets/Package.cs b/sources/assets/Stride.Core.Assets/Package.cs index e8b51dcc39..1f185f8b6c 100644 --- a/sources/assets/Stride.Core.Assets/Package.cs +++ b/sources/assets/Stride.Core.Assets/Package.cs @@ -1,18 +1,12 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.ComponentModel; using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; using Stride.Core.Assets.Analysis; using Stride.Core.Assets.Diagnostics; using Stride.Core.Assets.Templates; using Stride.Core.Assets.Yaml; -using Stride.Core; using Stride.Core.Annotations; using Stride.Core.Diagnostics; using Stride.Core.Extensions; @@ -21,1358 +15,1328 @@ using Stride.Core.Serialization; using Stride.Core.Yaml; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public enum PackageState { - public enum PackageState + /// + /// Package has been deserialized. References and assets are not ready. + /// + Raw, + + /// + /// Dependencies have all been resolved and are also in state. + /// + DependenciesReady, + + /// + /// Package upgrade has been failed (either error or denied by user). + /// Dependencies are ready, but not assets. + /// Should be manually switched back to DependenciesReady to try upgrade again. + /// + UpgradeFailed, + + /// + /// Assembly references and assets have all been loaded. + /// + AssetsReady, +} + +/// +/// A package managing assets. +/// +[DataContract("Package")] +[NonIdentifiableCollectionItems] +[AssetDescription(PackageFileExtension)] +[DebuggerDisplay("Name: {Meta.Name}, Version: {Meta.Version}, Assets [{Assets.Count}]")] +[AssetFormatVersion("Assets", PackageFileVersion, "0.0.0.4")] +[AssetUpgrader("Assets", "0.0.0.4", "3.1.0.0", typeof(MovePackageInsideProject))] +public sealed partial class Package : IFileSynchronizable, IAssetFinder +{ + private const string PackageFileVersion = "3.1.0.0"; + + internal readonly List FilesToDelete = []; + + private UFile packagePath; + internal UFile PreviousPackagePath; + private bool isDirty; + private readonly Lazy settings; + + /// + /// Occurs when package dirty changed occurred. + /// + public event DirtyFlagChangedDelegate PackageDirtyChanged; + + /// + /// Occurs when an asset dirty changed occurred. + /// + public event DirtyFlagChangedDelegate AssetDirtyChanged; + + /// + /// Initializes a new instance of the class. + /// + public Package() { - /// - /// Package has been deserialized. References and assets are not ready. - /// - Raw, - - /// - /// Dependencies have all been resolved and are also in state. - /// - DependenciesReady, - - /// - /// Package upgrade has been failed (either error or denied by user). - /// Dependencies are ready, but not assets. - /// Should be manually switched back to DependenciesReady to try upgrade again. - /// - UpgradeFailed, - - /// - /// Assembly references and assets have all been loaded. - /// - AssetsReady, + // Initializse package with default versions (same code as in Asset..ctor()) + var defaultPackageVersion = AssetRegistry.GetCurrentFormatVersions(GetType()); + if (defaultPackageVersion is not null) + { + SerializedVersion = new Dictionary(defaultPackageVersion); + } + + Assets = new PackageAssetCollection(this); + Bundles = new BundleCollection(this); + IsDirty = true; + settings = new Lazy(() => new PackageUserSettings(this)); } + // Note: Please keep this code in sync with Asset class /// - /// A package managing assets. + /// Gets or sets the version number for this asset, used internally when migrating assets. /// - [DataContract("Package")] + /// The version. + [DataMember(-8000, DataMemberMode.Assign)] + [DataStyle(DataStyle.Compact)] + [Display(Browsable = false)] + [DefaultValue(null)] + [NonOverridable] [NonIdentifiableCollectionItems] - [AssetDescription(PackageFileExtension)] - [DebuggerDisplay("Name: {Meta.Name}, Version: {Meta.Version}, Assets [{Assets.Count}]")] - [AssetFormatVersion("Assets", PackageFileVersion, "0.0.0.4")] - [AssetUpgrader("Assets", "0.0.0.4", "3.1.0.0", typeof(MovePackageInsideProject))] - public sealed partial class Package : IFileSynchronizable, IAssetFinder - { - private const string PackageFileVersion = "3.1.0.0"; + public Dictionary? SerializedVersion { get; set; } + + /// + /// Gets or sets a value indicating whether this package is a system package. + /// + /// true if this package is a system package; otherwise, false. + [DataMemberIgnore] + public bool IsSystem => Container is not SolutionProject; + + /// + /// Gets or sets the metadata associated with this package. + /// + /// The meta. + [DataMember(10)] + public PackageMeta Meta { get; set; } = new PackageMeta(); - internal readonly List FilesToDelete = new List(); + /// + /// Gets the asset directories to lookup. + /// + /// The asset directories. + [DataMember(40, DataMemberMode.Assign)] + public AssetFolderCollection AssetFolders { get; set; } = []; - private UFile packagePath; - internal UFile PreviousPackagePath; - private bool isDirty; - private readonly Lazy settings; + /// + /// Gets the resource directories to lookup. + /// + /// The resource directories. + [DataMember(45, DataMemberMode.Assign)] + public List ResourceFolders { get; set; } = []; - /// - /// Occurs when package dirty changed occurred. - /// - public event DirtyFlagChangedDelegate PackageDirtyChanged; + /// + /// Gets the output group directories. + /// + /// The output group directories. + [DataMember(50, DataMemberMode.Assign)] + public Dictionary OutputGroupDirectories { get; set; } = []; - /// - /// Occurs when an asset dirty changed occurred. - /// - public event DirtyFlagChangedDelegate AssetDirtyChanged; + /// + /// Gets or sets the list of folders that are explicitly created but contains no assets. + /// + [DataMember(70)] + public List ExplicitFolders { get; } = []; - /// - /// Initializes a new instance of the class. - /// - public Package() - { - // Initializse package with default versions (same code as in Asset..ctor()) - var defaultPackageVersion = AssetRegistry.GetCurrentFormatVersions(GetType()); - if (defaultPackageVersion != null) - { - SerializedVersion = new Dictionary(defaultPackageVersion); - } + /// + /// Gets the bundles defined for this package. + /// + /// The bundles. + [DataMember(80)] + public BundleCollection Bundles { get; private set; } - Assets = new PackageAssetCollection(this); - Bundles = new BundleCollection(this); - IsDirty = true; - settings = new Lazy(() => new PackageUserSettings(this)); - } + /// + /// Gets the template folders. + /// + /// The template folders. + [DataMember(90)] + public List TemplateFolders { get; } = []; - // Note: Please keep this code in sync with Asset class - /// - /// Gets or sets the version number for this asset, used internally when migrating assets. - /// - /// The version. - [DataMember(-8000, DataMemberMode.Assign)] - [DataStyle(DataStyle.Compact)] - [Display(Browsable = false)] - [DefaultValue(null)] - [NonOverridable] - [NonIdentifiableCollectionItems] - public Dictionary SerializedVersion { get; set; } - - /// - /// Gets or sets a value indicating whether this package is a system package. - /// - /// true if this package is a system package; otherwise, false. - [DataMemberIgnore] - public bool IsSystem => !(Container is SolutionProject); - - /// - /// Gets or sets the metadata associated with this package. - /// - /// The meta. - [DataMember(10)] - public PackageMeta Meta { get; set; } = new PackageMeta(); - - /// - /// Gets the asset directories to lookup. - /// - /// The asset directories. - [DataMember(40, DataMemberMode.Assign)] - public AssetFolderCollection AssetFolders { get; set; } = new AssetFolderCollection(); - - /// - /// Gets the resource directories to lookup. - /// - /// The resource directories. - [DataMember(45, DataMemberMode.Assign)] - public List ResourceFolders { get; set; } = new List(); - - /// - /// Gets the output group directories. - /// - /// The output group directories. - [DataMember(50, DataMemberMode.Assign)] - public Dictionary OutputGroupDirectories { get; set; } = new Dictionary(); - - /// - /// Gets or sets the list of folders that are explicitly created but contains no assets. - /// - [DataMember(70)] - public List ExplicitFolders { get; } = new List(); - - /// - /// Gets the bundles defined for this package. - /// - /// The bundles. - [DataMember(80)] - public BundleCollection Bundles { get; private set; } - - /// - /// Gets the template folders. - /// - /// The template folders. - [DataMember(90)] - public List TemplateFolders { get; } = new List(); - - /// - /// Asset references that needs to be compiled even if not directly or indirectly referenced (useful for explicit code references). - /// - [DataMember(100)] - public RootAssetCollection RootAssets { get; private set; } = new RootAssetCollection(); - - /// - /// Gets the loaded templates from the - /// - /// The templates. - [DataMemberIgnore] - public List Templates { get; } = new List(); - - /// - /// Gets the assets stored in this package. - /// - /// The assets. - [DataMemberIgnore] - public PackageAssetCollection Assets { get; } - - /// - /// Gets the temporary assets list loaded from disk before they are going into . - /// - /// The temporary assets. - [DataMemberIgnore] - // TODO: turn that internal! - public List TemporaryAssets { get; } = new List(); - - /// - /// Gets the path to the package file. May be null if the package was not loaded or saved. - /// - /// The package path. - [DataMemberIgnore] - public UFile FullPath + /// + /// Asset references that needs to be compiled even if not directly or indirectly referenced (useful for explicit code references). + /// + [DataMember(100)] + public RootAssetCollection RootAssets { get; private set; } = []; + + /// + /// Gets the loaded templates from the + /// + /// The templates. + [DataMemberIgnore] + public List Templates { get; } = []; + + /// + /// Gets the assets stored in this package. + /// + /// The assets. + [DataMemberIgnore] + public PackageAssetCollection Assets { get; } + + /// + /// Gets the temporary assets list loaded from disk before they are going into . + /// + /// The temporary assets. + [DataMemberIgnore] + // TODO: turn that internal! + public List TemporaryAssets { get; } = []; + + /// + /// Gets the path to the package file. May be null if the package was not loaded or saved. + /// + /// The package path. + [DataMemberIgnore] + public UFile FullPath + { + get { - get - { - return packagePath; - } - set - { - SetPackagePath(value, true); - } + return packagePath; } - - /// - /// Gets or sets a value indicating whether this instance has been modified since last saving. - /// - /// true if this instance is dirty; otherwise, false. - [DataMemberIgnore] - public bool IsDirty + set { - get - { - return isDirty; - } - set - { - var oldValue = isDirty; - isDirty = value; - OnPackageDirtyChanged(this, oldValue, value); - } + SetPackagePath(value, true); } + } - [DataMemberIgnore] - public PackageState State { get; set; } - - /// - /// Gets the top directory of this package on the local disk. - /// - /// The top directory. - [DataMemberIgnore] - public UDirectory RootDirectory => FullPath?.GetParent(); - - [DataMemberIgnore] - public PackageContainer Container { get; internal set; } - - /// - /// Gets the session. - /// - /// The session. - /// Cannot attach a package to more than one session - [DataMemberIgnore] - public PackageSession Session => Container?.Session; - - /// - /// Gets the package user settings. Usually stored in a .user file alongside the package. Lazily loaded on first time. - /// - /// - /// The package user settings. - /// - [DataMemberIgnore] - public PackageUserSettings UserSettings => settings.Value; - - /// - /// Gets the list of assemblies loaded by this package. - /// - /// - /// The loaded assemblies. - /// - [DataMemberIgnore] - public List LoadedAssemblies { get; } = new List(); - - [DataMemberIgnore] - public string RootNamespace { get; private set; } - - [DataMemberIgnore] - public bool IsImplicitProject + /// + /// Gets or sets a value indicating whether this instance has been modified since last saving. + /// + /// true if this instance is dirty; otherwise, false. + [DataMemberIgnore] + public bool IsDirty + { + get { - get - { - // To keep in sync with LoadProject() .csproj - // Note: Meta is ignored since it is supposedly "read-only" from csproj - return (AssetFolders.Count == 1 && AssetFolders.First().Path == "Assets" - && ResourceFolders.Count == 1 && ResourceFolders.First() == "Resources" - && OutputGroupDirectories.Count == 0 - && ExplicitFolders.Count == 0 - && Bundles.Count == 0 - && RootAssets.Count == 0 - && TemplateFolders.Count == 0); - } + return isDirty; } - - /// - /// Adds an existing project to this package. - /// - /// The path to msproj. - /// LoggerResult. - public LoggerResult AddExistingProject(UFile pathToMsproj) + set { - var logger = new LoggerResult(); - AddExistingProject(pathToMsproj, logger); - return logger; + var oldValue = isDirty; + isDirty = value; + OnPackageDirtyChanged(this, oldValue, value); } + } + + [DataMemberIgnore] + public PackageState State { get; set; } - /// - /// Adds an existing project to this package. - /// - /// The path to msproj. - /// The logger. - public void AddExistingProject(UFile pathToMsproj, LoggerResult logger) + /// + /// Gets the top directory of this package on the local disk. + /// + /// The top directory. + [DataMemberIgnore] + public UDirectory? RootDirectory => FullPath?.GetParent(); + + [DataMemberIgnore] + public PackageContainer Container { get; internal set; } + + /// + /// Gets the session. + /// + /// The session. + /// Cannot attach a package to more than one session + [DataMemberIgnore] + public PackageSession? Session => Container?.Session; + + /// + /// Gets the package user settings. Usually stored in a .user file alongside the package. Lazily loaded on first time. + /// + /// + /// The package user settings. + /// + [DataMemberIgnore] + public PackageUserSettings UserSettings => settings.Value; + + /// + /// Gets the list of assemblies loaded by this package. + /// + /// + /// The loaded assemblies. + /// + [DataMemberIgnore] + public List LoadedAssemblies { get; } = []; + + [DataMemberIgnore] + public string? RootNamespace { get; private set; } + + [DataMemberIgnore] + public bool IsImplicitProject + { + get { - if (pathToMsproj == null) throw new ArgumentNullException(nameof(pathToMsproj)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (!pathToMsproj.IsAbsolute) throw new ArgumentException(@"Expecting relative path", nameof(pathToMsproj)); + // To keep in sync with LoadProject() .csproj + // Note: Meta is ignored since it is supposedly "read-only" from csproj + return AssetFolders.Count == 1 && AssetFolders[0].Path == "Assets" + && ResourceFolders.Count == 1 && ResourceFolders[0] == "Resources" + && OutputGroupDirectories.Count == 0 + && ExplicitFolders.Count == 0 + && Bundles.Count == 0 + && RootAssets.Count == 0 + && TemplateFolders.Count == 0; + } + } + + /// + /// Adds an existing project to this package. + /// + /// The path to msproj. + /// LoggerResult. + public LoggerResult AddExistingProject(UFile pathToMsproj) + { + var logger = new LoggerResult(); + AddExistingProject(pathToMsproj, logger); + return logger; + } + + /// + /// Adds an existing project to this package. + /// + /// The path to msproj. + /// The logger. + public void AddExistingProject(UFile pathToMsproj, LoggerResult logger) + { + ArgumentNullException.ThrowIfNull(pathToMsproj); + ArgumentNullException.ThrowIfNull(logger); + if (!pathToMsproj.IsAbsolute) throw new ArgumentException("Expecting relative path", nameof(pathToMsproj)); + try + { + // Load a project without specifying a platform to make sure we get the correct platform type + var msProject = VSProjectHelper.LoadProject(pathToMsproj, platform: "NoPlatform"); try { - // Load a project without specifying a platform to make sure we get the correct platform type - var msProject = VSProjectHelper.LoadProject(pathToMsproj, platform: "NoPlatform"); - try + var projectType = VSProjectHelper.GetProjectTypeFromProject(msProject); + if (!projectType.HasValue) { - - var projectType = VSProjectHelper.GetProjectTypeFromProject(msProject); - if (!projectType.HasValue) - { - logger.Error("This project is not a project created with the editor"); - } - else - { - var platformType = VSProjectHelper.GetPlatformTypeFromProject(msProject) ?? PlatformType.Shared; - var projectReference = new ProjectReference(VSProjectHelper.GetProjectGuid(msProject), pathToMsproj.MakeRelative(RootDirectory), projectType.Value); - - // TODO CSPROJ=XKPKG - throw new NotImplementedException(); - // Add the ProjectReference only for the compatible profiles (same platform or no platform) - //foreach (var profile in Profiles.Where(profile => platformType == profile.Platform)) - //{ - // profile.ProjectReferences.Add(projectReference); - //} - } + logger.Error("This project is not a project created with the editor"); } - finally + else { - msProject.ProjectCollection.UnloadAllProjects(); - msProject.ProjectCollection.Dispose(); + var platformType = VSProjectHelper.GetPlatformTypeFromProject(msProject) ?? PlatformType.Shared; + var projectReference = new ProjectReference(VSProjectHelper.GetProjectGuid(msProject), pathToMsproj.MakeRelative(RootDirectory), projectType.Value); + + // TODO CSPROJ=XKPKG + throw new NotImplementedException(); + // Add the ProjectReference only for the compatible profiles (same platform or no platform) + //foreach (var profile in Profiles.Where(profile => platformType == profile.Platform)) + //{ + // profile.ProjectReferences.Add(projectReference); + //} } } - catch (Exception ex) + finally { - logger.Error($"Unexpected exception while loading project [{pathToMsproj}]", ex); + msProject.ProjectCollection.UnloadAllProjects(); + msProject.ProjectCollection.Dispose(); } } - - /// - /// Looks for the asset amongst the current package and its dependencies. - public AssetItem FindAsset(AssetId assetId) + catch (Exception ex) { - return this.GetPackagesWithDependencies().Select(p => p.Assets.Find(assetId)).NotNull().FirstOrDefault(); + logger.Error($"Unexpected exception while loading project [{pathToMsproj}]", ex); } + } - /// - /// Looks for the asset amongst the current package and its dependencies. - public AssetItem FindAsset(UFile location) - { - return this.GetPackagesWithDependencies().Select(p => p.Assets.Find(location)).NotNull().FirstOrDefault(); - } + /// + /// Looks for the asset amongst the current package and its dependencies. + public AssetItem? FindAsset(AssetId assetId) + { + return this.GetPackagesWithDependencies().Select(p => p.Assets.Find(assetId)).NotNull().FirstOrDefault(); + } - /// - /// Looks for the asset amongst the current package and its dependencies. - public AssetItem FindAssetFromProxyObject(object proxyObject) - { - var attachedReference = AttachedReferenceManager.GetAttachedReference(proxyObject); - return attachedReference != null ? this.FindAsset(attachedReference) : null; - } + /// + /// Looks for the asset amongst the current package and its dependencies. + public AssetItem? FindAsset(UFile location) + { + return this.GetPackagesWithDependencies().Select(p => p.Assets.Find(location)).NotNull().FirstOrDefault(); + } - public UDirectory GetDefaultAssetFolder() - { - var folder = AssetFolders.FirstOrDefault(); - return folder?.Path ?? ("Assets"); - } + /// + /// Looks for the asset amongst the current package and its dependencies. + public AssetItem? FindAssetFromProxyObject(object? proxyObject) + { + var attachedReference = AttachedReferenceManager.GetAttachedReference(proxyObject); + return attachedReference is not null ? this.FindAsset(attachedReference) : null; + } + + public UDirectory GetDefaultAssetFolder() + { + var folder = AssetFolders.FirstOrDefault(); + return folder?.Path ?? ("Assets"); + } - /// - /// Deep clone this package. - /// - /// The package cloned. - public Package Clone() + /// + /// Deep clone this package. + /// + /// The package cloned. + public Package Clone() + { + // Use a new ShadowRegistry to copy override parameters + // Clone this asset + var package = AssetCloner.Clone(this); + package.FullPath = FullPath; + foreach (var asset in Assets) { - // Use a new ShadowRegistry to copy override parameters - // Clone this asset - var package = AssetCloner.Clone(this); - package.FullPath = FullPath; - foreach (var asset in Assets) + var newAsset = asset.Asset; + var assetItem = new AssetItem(asset.Location, newAsset) { - var newAsset = asset.Asset; - var assetItem = new AssetItem(asset.Location, newAsset) - { - SourceFolder = asset.SourceFolder, - AlternativePath = asset.AlternativePath, - }; - package.Assets.Add(assetItem); - } - return package; + SourceFolder = asset.SourceFolder, + AlternativePath = asset.AlternativePath, + }; + package.Assets.Add(assetItem); } + return package; + } - /// - /// Sets the package path. - /// - /// The new path. - /// if set to true assets will be copied relatively to the new location. - public void SetPackagePath(UFile newPath, bool copyAssets = true) + /// + /// Sets the package path. + /// + /// The new path. + /// if set to true assets will be copied relatively to the new location. + public void SetPackagePath(UFile newPath, bool copyAssets = true) + { + var previousPath = packagePath; + var previousRootDirectory = RootDirectory; + packagePath = newPath; + if (packagePath?.IsAbsolute == false) { - var previousPath = packagePath; - var previousRootDirectory = RootDirectory; - packagePath = newPath; - if (packagePath != null && !packagePath.IsAbsolute) - { - packagePath = UPath.Combine(Environment.CurrentDirectory, packagePath); - } + packagePath = UPath.Combine(Environment.CurrentDirectory, packagePath); + } - if (copyAssets && packagePath != previousPath) + if (copyAssets && packagePath != previousPath) + { + // Update source folders + var currentRootDirectory = RootDirectory; + if (previousRootDirectory is not null && currentRootDirectory is not null) { - // Update source folders - var currentRootDirectory = RootDirectory; - if (previousRootDirectory != null && currentRootDirectory != null) + foreach (var sourceFolder in AssetFolders) { - foreach (var sourceFolder in AssetFolders) + if (sourceFolder.Path.IsAbsolute) { - if (sourceFolder.Path.IsAbsolute) - { - var relativePath = sourceFolder.Path.MakeRelative(previousRootDirectory); - sourceFolder.Path = UPath.Combine(currentRootDirectory, relativePath); - } + var relativePath = sourceFolder.Path.MakeRelative(previousRootDirectory); + sourceFolder.Path = UPath.Combine(currentRootDirectory, relativePath); } } + } - foreach (var asset in Assets) - { - asset.IsDirty = true; - } - IsDirty = true; + foreach (var asset in Assets) + { + asset.IsDirty = true; } + IsDirty = true; } + } - internal void OnPackageDirtyChanged(Package package, bool oldValue, bool newValue) - { - if (package == null) throw new ArgumentNullException(nameof(package)); - PackageDirtyChanged?.Invoke(package, oldValue, newValue); - } + internal void OnPackageDirtyChanged(Package package, bool oldValue, bool newValue) + { + ArgumentNullException.ThrowIfNull(package); + PackageDirtyChanged?.Invoke(package, oldValue, newValue); + } + + internal void OnAssetDirtyChanged(AssetItem asset, bool oldValue, bool newValue) + { + ArgumentNullException.ThrowIfNull(asset); + AssetDirtyChanged?.Invoke(asset, oldValue, newValue); + } + + public static bool SaveSingleAsset(AssetItem asset, ILogger log) + { + // Make sure AssetItem.SourceFolder/Project are generated if they were null + asset.UpdateSourceFolders(); + return SaveSingleAsset_NoUpdateSourceFolder(asset, log); + } - internal void OnAssetDirtyChanged(AssetItem asset, bool oldValue, bool newValue) + internal static bool SaveSingleAsset_NoUpdateSourceFolder(AssetItem asset, ILogger log) + { + var assetPath = asset.FullPath; + + try { - if (asset == null) throw new ArgumentNullException(nameof(asset)); - AssetDirtyChanged?.Invoke(asset, oldValue, newValue); + // Handle the ProjectSourceCodeAsset differently then regular assets in regards of Path + if (asset.Asset is IProjectAsset projectAsset) + { + assetPath = asset.FullPath; + } + + // Inject a copy of the base into the current asset when saving + AssetFileSerializer.Save((string)assetPath, (object)asset.Asset, (AttachedYamlAssetMetadata)asset.YamlMetadata, log); + + // Save generated asset (if necessary) + if (asset.Asset is IProjectFileGeneratorAsset codeGeneratorAsset) + { + codeGeneratorAsset.SaveGeneratedAsset(asset); + } + + asset.IsDirty = false; } - - public static bool SaveSingleAsset(AssetItem asset, ILogger log) + catch (Exception ex) { - // Make sure AssetItem.SourceFolder/Project are generated if they were null - asset.UpdateSourceFolders(); - return SaveSingleAsset_NoUpdateSourceFolder(asset, log); + log.Error(asset.Package, asset.ToReference(), AssetMessageCode.AssetCannotSave, ex, assetPath); + return false; } + return true; + } - internal static bool SaveSingleAsset_NoUpdateSourceFolder(AssetItem asset, ILogger log) - { - var assetPath = asset.FullPath; + /// + /// Gets the package identifier from file. + /// + /// The file path. + /// Guid. + /// + /// log + /// or + /// filePath + /// + public static Guid GetPackageIdFromFile(string filePath) + { + ArgumentNullException.ThrowIfNull(filePath); - try + var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); + bool hasPackage = false; + using (var reader = new StreamReader(stream)) + { + string? line; + while ((line = reader.ReadLine()) is not null) { - // Handle the ProjectSourceCodeAsset differently then regular assets in regards of Path - var projectAsset = asset.Asset as IProjectAsset; - if (projectAsset != null) + if (line.StartsWith("!Package", StringComparison.Ordinal)) { - assetPath = asset.FullPath; + hasPackage = true; } - // Inject a copy of the base into the current asset when saving - AssetFileSerializer.Save((string)assetPath, (object)asset.Asset, (AttachedYamlAssetMetadata)asset.YamlMetadata, log); - - // Save generated asset (if necessary) - var codeGeneratorAsset = asset.Asset as IProjectFileGeneratorAsset; - if (codeGeneratorAsset != null) + if (hasPackage && line.StartsWith("Id:", StringComparison.Ordinal)) { - codeGeneratorAsset.SaveGeneratedAsset(asset); + var id = line["Id:".Length..].Trim(); + return Guid.Parse(id); } - - asset.IsDirty = false; } - catch (Exception ex) - { - log.Error(asset.Package, asset.ToReference(), AssetMessageCode.AssetCannotSave, ex, assetPath); - return false; - } - return true; } + throw new IOException($"File {filePath} doesn't appear to be a valid package"); + } - /// - /// Gets the package identifier from file. - /// - /// The file path. - /// Guid. - /// - /// log - /// or - /// filePath - /// - public static Guid GetPackageIdFromFile(string filePath) + /// + /// Loads only the package description but not assets or plugins. + /// + /// The log to receive error messages. + /// The file path. + /// The load parameters argument. + /// A package. + /// log + /// or + /// filePath + public static Package? Load(ILogger log, string filePath, PackageLoadParameters? loadParametersArg = null) + { + var package = LoadProject(log, filePath)?.Package; + + if (package?.LoadAssembliesAndAssets(log, loadParametersArg) == false) { - if (filePath == null) throw new ArgumentNullException(nameof(filePath)); + package = null; + } - var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); - bool hasPackage = false; - using (var reader = new StreamReader(stream)) - { - string line; - while ((line = reader.ReadLine()) != null) - { - if (line.StartsWith("!Package", StringComparison.Ordinal)) - { - hasPackage = true; - } + return package; + } - if (hasPackage && line.StartsWith("Id:", StringComparison.Ordinal)) - { - var id = line["Id:".Length..].Trim(); - return Guid.Parse(id); - } - } - } - throw new IOException($"File {filePath} doesn't appear to be a valid package"); - } + /// + /// Performs first part of the loading sequence, by deserializing the package but without processing anything yet. + /// + /// The log. + /// The file path. + /// + /// + /// log + /// or + /// filePath + /// + internal static Package LoadRaw(ILogger log, string filePath) + { + ArgumentNullException.ThrowIfNull(log); + ArgumentNullException.ThrowIfNull(filePath); - /// - /// Loads only the package description but not assets or plugins. - /// - /// The log to receive error messages. - /// The file path. - /// The load parameters argument. - /// A package. - /// log - /// or - /// filePath - public static Package Load(ILogger log, string filePath, PackageLoadParameters loadParametersArg = null) + filePath = FileUtility.GetAbsolutePath(filePath); + + if (!File.Exists(filePath)) { - var package = LoadProject(log, filePath)?.Package; + throw new FileNotFoundException($"Package file [{filePath}] was not found"); + } - if (package != null) - { - if (!package.LoadAssembliesAndAssets(log, loadParametersArg)) - package = null; - } + try + { + var packageFile = new PackageLoadingAssetFile(filePath, Path.GetDirectoryName(filePath)) { CachedFileSize = filePath.Length }; + var context = new AssetMigrationContext(null, null, filePath, log); + AssetMigration.MigrateAssetIfNeeded(context, packageFile, "Assets"); + + var loadResult = packageFile.AssetContent is not null + ? AssetFileSerializer.Load(new MemoryStream(packageFile.AssetContent), filePath, log) + : AssetFileSerializer.Load(filePath, log); + var package = loadResult.Asset; + package.FullPath = packageFile.FilePath; + package.PreviousPackagePath = packageFile.OriginalFilePath; + package.IsDirty = packageFile.AssetContent is not null || loadResult.AliasOccurred; return package; } - - /// - /// Performs first part of the loading sequence, by deserializing the package but without processing anything yet. - /// - /// The log. - /// The file path. - /// - /// - /// log - /// or - /// filePath - /// - internal static Package LoadRaw(ILogger log, string filePath) + catch (Exception ex) { - if (log == null) throw new ArgumentNullException(nameof(log)); - if (filePath == null) throw new ArgumentNullException(nameof(filePath)); + throw new InvalidOperationException($"Error while pre-loading package [{filePath}]", ex); + } + } - filePath = FileUtility.GetAbsolutePath(filePath); + public static PackageContainer LoadProject(ILogger log, string filePath) + { + if (SupportedProgrammingLanguages.IsProjectExtensionSupported(Path.GetExtension(filePath).ToLowerInvariant())) + { + var projectPath = filePath; + var packagePath = Path.ChangeExtension(filePath, Package.PackageFileExtension); + var packageExists = File.Exists(packagePath); - if (!File.Exists(filePath)) + // Xenko to Stride migration + if (!packageExists) { - throw new FileNotFoundException($"Package file [{filePath}] was not found"); + var oldPackagePath = Path.ChangeExtension(filePath, ".xkpkg"); + if (File.Exists(oldPackagePath)) + { + packageExists = true; + XenkoToStrideRenameHelper.RenameStrideFile(oldPackagePath, XenkoToStrideRenameHelper.StrideContentType.Package); + } } - try - { - var packageFile = new PackageLoadingAssetFile(filePath, Path.GetDirectoryName(filePath)) { CachedFileSize = filePath.Length }; - var context = new AssetMigrationContext(null, null, filePath, log); - AssetMigration.MigrateAssetIfNeeded(context, packageFile, "Assets"); - - var loadResult = packageFile.AssetContent != null - ? AssetFileSerializer.Load(new MemoryStream(packageFile.AssetContent), filePath, log) - : AssetFileSerializer.Load(filePath, log); - var package = loadResult.Asset; - package.FullPath = packageFile.FilePath; - package.PreviousPackagePath = packageFile.OriginalFilePath; - package.IsDirty = packageFile.AssetContent != null || loadResult.AliasOccurred; - - return package; - } - catch (Exception ex) - { - throw new InvalidOperationException($"Error while pre-loading package [{filePath}]", ex); - } + var package = packageExists + ? LoadRaw(log, packagePath) + : new Package + { + Meta = { Name = Path.GetFileNameWithoutExtension(packagePath) }, + AssetFolders = { new AssetFolder("Assets") }, + ResourceFolders = { "Resources" }, + FullPath = packagePath, + IsDirty = false, + }; + return new SolutionProject(package, Guid.NewGuid(), projectPath) { IsImplicitProject = !packageExists }; } - - public static PackageContainer LoadProject(ILogger log, string filePath) + else { - if (SupportedProgrammingLanguages.IsProjectExtensionSupported(Path.GetExtension(filePath).ToLowerInvariant())) - { - var projectPath = filePath; - var packagePath = Path.ChangeExtension(filePath, Package.PackageFileExtension); - var packageExists = File.Exists(packagePath); - - // Xenko to Stride migration - if (!packageExists) - { - var oldPackagePath = Path.ChangeExtension(filePath, ".xkpkg"); - if (File.Exists(oldPackagePath)) - { - packageExists = true; - XenkoToStrideRenameHelper.RenameStrideFile(oldPackagePath, XenkoToStrideRenameHelper.StrideContentType.Package); - } - } + var package = LoadRaw(log, filePath); - var package = packageExists - ? LoadRaw(log, packagePath) - : new Package - { - Meta = { Name = Path.GetFileNameWithoutExtension(packagePath) }, - AssetFolders = { new AssetFolder("Assets") }, - ResourceFolders = { "Resources" }, - FullPath = packagePath, - IsDirty = false, - }; - return new SolutionProject(package, Guid.NewGuid(), projectPath) { IsImplicitProject = !packageExists }; + // Find the .csproj next to .sdpkg (if any) + // Note that we use package.FullPath since we must first perform package upgrade from 3.0 to 3.1+ (might move package in .csproj folder) + var projectPath = Path.ChangeExtension(package.FullPath.ToOSPath(), ".csproj"); + if (File.Exists(projectPath)) + { + return new SolutionProject(package, Guid.NewGuid(), projectPath); } else { - var package = LoadRaw(log, filePath); - - // Find the .csproj next to .sdpkg (if any) - // Note that we use package.FullPath since we must first perform package upgrade from 3.0 to 3.1+ (might move package in .csproj folder) - var projectPath = Path.ChangeExtension(package.FullPath.ToOSPath(), ".csproj"); - if (File.Exists(projectPath)) + // Try to get version from NuGet folder + var path = new UFile(filePath); + var nuspecPath = UPath.Combine(path.GetFullDirectory().GetParent(), new UFile(path.GetFileNameWithoutExtension() + ".nuspec")); + if (path.GetFullDirectory().GetDirectoryName() == "stride" && File.Exists(nuspecPath) + && PackageVersion.TryParse(path.GetFullDirectory().GetParent().GetDirectoryName(), out var packageVersion)) { - return new SolutionProject(package, Guid.NewGuid(), projectPath); - } - else - { - // Try to get version from NuGet folder - var path = new UFile(filePath); - var nuspecPath = UPath.Combine(path.GetFullDirectory().GetParent(), new UFile(path.GetFileNameWithoutExtension() + ".nuspec")); - if (path.GetFullDirectory().GetDirectoryName() == "stride" && File.Exists(nuspecPath) - && PackageVersion.TryParse(path.GetFullDirectory().GetParent().GetDirectoryName(), out var packageVersion)) - { - package.Meta.Version = packageVersion; - } - return new StandalonePackage(package); + package.Meta.Version = packageVersion; } + return new StandalonePackage(package); } } + } - private static PackageVersion TryGetPackageVersion(string projectPath) + private static PackageVersion? TryGetPackageVersion(string projectPath) + { + try { + // Load a project without specifying a platform to make sure we get the correct platform type + var msProject = VSProjectHelper.LoadProject(projectPath, platform: "NoPlatform"); try { - // Load a project without specifying a platform to make sure we get the correct platform type - var msProject = VSProjectHelper.LoadProject(projectPath, platform: "NoPlatform"); - try - { - - var packageVersion = msProject.GetPropertyValue("PackageVersion"); - return !string.IsNullOrEmpty(packageVersion) ? new PackageVersion(packageVersion) : null; - } - finally - { - msProject.ProjectCollection.UnloadAllProjects(); - msProject.ProjectCollection.Dispose(); - } + var packageVersion = msProject.GetPropertyValue("PackageVersion"); + return !string.IsNullOrEmpty(packageVersion) ? new PackageVersion(packageVersion) : null; } - catch + finally { - return null; + msProject.ProjectCollection.UnloadAllProjects(); + msProject.ProjectCollection.Dispose(); } } - - /// - /// Second part of the package loading process, when references, assets and package analysis is done. - /// - /// The package. - /// The log. - /// The load parameters argument. - /// - internal bool LoadAssembliesAndAssets(ILogger log, PackageLoadParameters loadParametersArg) + catch { - return LoadAssemblies(log, loadParametersArg) && LoadAssets(log, loadParametersArg); + return null; } + } - /// - /// Load only assembly references - /// - /// The package. - /// The log. - /// The load parameters argument. - /// - internal bool LoadAssemblies(ILogger log, PackageLoadParameters loadParametersArg) - { - var loadParameters = loadParametersArg ?? PackageLoadParameters.Default(); - - try - { - // Load assembly references - if (loadParameters.LoadAssemblyReferences) - { - LoadAssemblyReferencesForPackage(log, loadParameters); - } - return true; - } - catch (Exception ex) - { - log.Error($"Error while pre-loading package [{FullPath}]", ex); + /// + /// Second part of the package loading process, when references, assets and package analysis is done. + /// + /// The log. + /// The load parameters argument. + /// + internal bool LoadAssembliesAndAssets(ILogger log, PackageLoadParameters? loadParametersArg) + { + return LoadAssemblies(log, loadParametersArg) && LoadAssets(log, loadParametersArg); + } - return false; - } - } + /// + /// Load only assembly references + /// + /// The log. + /// The load parameters argument. + /// + internal bool LoadAssemblies(ILogger log, PackageLoadParameters? loadParametersArg) + { + var loadParameters = loadParametersArg ?? PackageLoadParameters.Default(); - /// - /// Load assets and perform package analysis. - /// - /// The package. - /// The log. - /// The load parameters argument. - /// - internal bool LoadAssets(ILogger log, PackageLoadParameters loadParametersArg) + try { - var loadParameters = loadParametersArg ?? PackageLoadParameters.Default(); - - try + // Load assembly references + if (loadParameters.LoadAssemblyReferences) { - // Load assets - if (loadParameters.AutoLoadTemporaryAssets) - { - LoadTemporaryAssets(log, loadParameters.AssetFiles, loadParameters.CancelToken, loadParameters.TemporaryAssetsInMsbuild, loadParameters.TemporaryAssetFilter); - } - - // Convert UPath to absolute - if (loadParameters.ConvertUPathToAbsolute) - { - var analysis = new PackageAnalysis(this, new PackageAnalysisParameters() - { - ConvertUPathTo = UPathType.Absolute, - IsProcessingUPaths = true, // This is done already by Package.Load - SetDirtyFlagOnAssetWhenFixingAbsoluteUFile = true // When loading tag attributes that have an absolute file - }); - analysis.Run(log); - } - - // Load templates - LoadTemplates(log); - - return true; + LoadAssemblyReferencesForPackage(log, loadParameters); } - catch (Exception ex) - { - log.Error($"Error while pre-loading package [{FullPath}]", ex); + return true; + } + catch (Exception ex) + { + log.Error($"Error while pre-loading package [{FullPath}]", ex); - return false; - } + return false; } + } + + /// + /// Load assets and perform package analysis. + /// + /// The log. + /// The load parameters argument. + /// + internal bool LoadAssets(ILogger log, PackageLoadParameters? loadParametersArg) + { + var loadParameters = loadParametersArg ?? PackageLoadParameters.Default(); - public void ValidateAssets(bool alwaysGenerateNewAssetId, bool removeUnloadableObjects, ILogger log) + try { - if (TemporaryAssets.Count == 0) + // Load assets + if (loadParameters.AutoLoadTemporaryAssets) { - return; + LoadTemporaryAssets(log, loadParameters.AssetFiles, loadParameters.TemporaryAssetsInMsbuild, loadParameters.TemporaryAssetFilter, loadParameters.CancelToken ?? default); } - try + // Convert UPath to absolute + if (loadParameters.ConvertUPathToAbsolute) { - // Make sure we are suspending notifications before updating all assets - Assets.SuspendCollectionChanged(); - - Assets.Clear(); - - // Get generated output items - var outputItems = new List(); - - // Create a resolver from the package - var resolver = AssetResolver.FromPackage(this); - resolver.AlwaysCreateNewId = alwaysGenerateNewAssetId; - - // Clean assets - AssetCollision.Clean(this, TemporaryAssets, outputItems, resolver, true, removeUnloadableObjects); - - // Add them back to the package - foreach (var item in outputItems) + var analysis = new PackageAnalysis(this, new PackageAnalysisParameters() { - Assets.Add(item); - - // Fix collection item ids - AssetCollectionItemIdHelper.GenerateMissingItemIds(item.Asset); - CollectionItemIdsAnalysis.FixupItemIds(item, log); + ConvertUPathTo = UPathType.Absolute, + IsProcessingUPaths = true, // This is done already by Package.Load + SetDirtyFlagOnAssetWhenFixingAbsoluteUFile = true // When loading tag attributes that have an absolute file + }); + analysis.Run(log); + } - // Fix duplicate identifiable objects - var hasBeenModified = IdentifiableObjectAnalysis.Visit(item.Asset, true, log); - if (hasBeenModified) - item.IsDirty = true; - } + // Load templates + LoadTemplates(log); - // Don't delete SourceCodeAssets as their files are handled by the package upgrader - var dirtyAssets = outputItems.Where(o => o.IsDirty && !(o.Asset is SourceCodeAsset)) - .Join(TemporaryAssets, o => o.Id, t => t.Id, (o, t) => t) - .ToList(); - // Dirty assets (except in system package) should be mark as deleted so that are properly saved again later. - if (!IsSystem && dirtyAssets.Count > 0) - { - IsDirty = true; + return true; + } + catch (Exception ex) + { + log.Error($"Error while pre-loading package [{FullPath}]", ex); - lock (FilesToDelete) - { - FilesToDelete.AddRange(dirtyAssets.Select(a => a.FullPath)); - } - } + return false; + } + } - TemporaryAssets.Clear(); - } - finally - { - // Restore notification on assets - Assets.ResumeCollectionChanged(); - } + public void ValidateAssets(bool alwaysGenerateNewAssetId, bool removeUnloadableObjects, ILogger log) + { + if (TemporaryAssets.Count == 0) + { + return; } - /// - /// Refreshes this package from the disk by loading or reloading all assets. - /// - /// The log. - /// The asset files (loaded from if null). - /// The cancel token. - /// Specifies if we need to evaluate MSBuild files for assets. - /// A function that will filter assets loading - /// A logger that contains error messages while refreshing. - /// Package RootDirectory is null - /// or - /// Package RootDirectory [{0}] does not exist.ToFormat(RootDirectory) - public void LoadTemporaryAssets(ILogger log, List assetFiles = null, CancellationToken? cancelToken = null, bool listAssetsInMsbuild = true, Func filterFunc = null) + try { - if (log == null) throw new ArgumentNullException(nameof(log)); + // Make sure we are suspending notifications before updating all assets + Assets.SuspendCollectionChanged(); - // If FullPath is null, then we can't load assets from disk, just return - if (FullPath == null) - { - log.Warning("Fullpath not set on this package"); - return; - } + Assets.Clear(); - // Clears the assets already loaded and reload them - TemporaryAssets.Clear(); + // Get generated output items + var outputItems = new List(); - // List all package files on disk - if (assetFiles == null) - { - assetFiles = ListAssetFiles(log, this, listAssetsInMsbuild, false, cancelToken); - // Sort them by size (to improve concurrency during load) - assetFiles.Sort(PackageLoadingAssetFile.FileSizeComparer.Default); - } + // Create a resolver from the package + var resolver = AssetResolver.FromPackage(this); + resolver.AlwaysCreateNewId = alwaysGenerateNewAssetId; - var progressMessage = $"Loading Assets from Package [{FullPath.GetFileName()}]"; + // Clean assets + AssetCollision.Clean(this, TemporaryAssets, outputItems, resolver, true, removeUnloadableObjects); - // Display this message at least once if the logger does not log progress (And it shouldn't in this case) - var loggerResult = log as LoggerResult; - if (loggerResult == null || !loggerResult.IsLoggingProgressAsInfo) + // Add them back to the package + foreach (var item in outputItems) { - log.Verbose(progressMessage); - } + Assets.Add(item); + + // Fix collection item ids + AssetCollectionItemIdHelper.GenerateMissingItemIds(item.Asset); + CollectionItemIdsAnalysis.FixupItemIds(item, log); + // Fix duplicate identifiable objects + var hasBeenModified = IdentifiableObjectAnalysis.Visit(item.Asset, true, log); + if (hasBeenModified) + item.IsDirty = true; + } - // Update step counter for log progress - var tasks = new List(); - for (int i = 0; i < assetFiles.Count; i++) + // Don't delete SourceCodeAssets as their files are handled by the package upgrader + var dirtyAssets = outputItems.Where(static o => o.IsDirty && o.Asset is not SourceCodeAsset) + .Join(TemporaryAssets, o => o.Id, t => t.Id, (_, t) => t) + .ToList(); + // Dirty assets (except in system package) should be mark as deleted so that are properly saved again later. + if (!IsSystem && dirtyAssets.Count > 0) { - var assetFile = assetFiles[i]; + IsDirty = true; - if (filterFunc != null && !filterFunc(assetFile)) + lock (FilesToDelete) { - continue; + FilesToDelete.AddRange(dirtyAssets.Select(a => a.FullPath)); } + } + + TemporaryAssets.Clear(); + } + finally + { + // Restore notification on assets + Assets.ResumeCollectionChanged(); + } + } - // Update the loading progress - loggerResult?.Progress(progressMessage, i, assetFiles.Count); + /// + /// Refreshes this package from the disk by loading or reloading all assets. + /// + /// The log. + /// The asset files (loaded from if null). + /// Specifies if we need to evaluate MSBuild files for assets. + /// A function that will filter assets loading + /// The cancellation token. + /// A logger that contains error messages while refreshing. + /// Package RootDirectory is null + /// or + /// Package RootDirectory [{0}] does not exist.ToFormat(RootDirectory) + public void LoadTemporaryAssets(ILogger log, List? assetFiles = null, bool listAssetsInMsbuild = true, Func? filterFunc = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(log); - var task = cancelToken.HasValue ? - System.Threading.Tasks.Task.Factory.StartNew(() => LoadAsset(new AssetMigrationContext(this, assetFile.ToReference(), assetFile.FilePath.ToOSPath(), log), assetFile), cancelToken.Value) : - System.Threading.Tasks.Task.Factory.StartNew(() => LoadAsset(new AssetMigrationContext(this, assetFile.ToReference(), assetFile.FilePath.ToOSPath(), log), assetFile)); + // If FullPath is null, then we can't load assets from disk, just return + if (FullPath is null) + { + log.Warning("Fullpath not set on this package"); + return; + } - tasks.Add(task); - } + // Clears the assets already loaded and reload them + TemporaryAssets.Clear(); - if (cancelToken.HasValue) - { - System.Threading.Tasks.Task.WaitAll(tasks.ToArray(), cancelToken.Value); - } - else - { - System.Threading.Tasks.Task.WaitAll(tasks.ToArray()); - } + // List all package files on disk + if (assetFiles is null) + { + assetFiles = ListAssetFiles(this, listAssetsInMsbuild, false); + // Sort them by size (to improve concurrency during load) + assetFiles.Sort(PackageLoadingAssetFile.FileSizeComparer.Default); + } - // DEBUG - // StaticLog.Info("[{0}] Assets files loaded in {1}", assetFiles.Count, clock.ElapsedMilliseconds); + var progressMessage = $"Loading Assets from Package [{FullPath.GetFileName()}]"; - if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested) - { - log.Warning("Skipping loading assets. PackageSession.Load cancelled"); - } + // Display this message at least once if the logger does not log progress (And it shouldn't in this case) + var loggerResult = log as LoggerResult; + if (loggerResult?.IsLoggingProgressAsInfo != true) + { + log.Verbose(progressMessage); } - private void LoadAsset(AssetMigrationContext context, PackageLoadingAssetFile assetFile) + // Update step counter for log progress + var tasks = new List(); + for (int i = 0; i < assetFiles.Count; i++) { - var fileUPath = assetFile.FilePath; - var sourceFolder = assetFile.SourceFolder; + var assetFile = assetFiles[i]; - // Check if asset has been deleted by an upgrader - if (assetFile.Deleted) + if (filterFunc is not null && !filterFunc(assetFile)) { - IsDirty = true; - - lock (FilesToDelete) - { - FilesToDelete.Add(assetFile.FilePath); - } - - // Don't create temporary assets for files deleted during package upgrading - return; + continue; } - // An exception can occur here, so we make sure that loading a single asset is not going to break - // the loop - try - { - AssetMigration.MigrateAssetIfNeeded(context, assetFile, "Stride"); + // Update the loading progress + loggerResult?.Progress(progressMessage, i, assetFiles.Count); - // Try to load only if asset is not already in the package or assetRef.Asset is null - var assetPath = assetFile.AssetLocation; + var task = Task.Factory.StartNew(() => LoadAsset(new AssetMigrationContext(this, assetFile.ToReference(), assetFile.FilePath.ToOSPath(), log), assetFile), cancellationToken); - var assetFullPath = fileUPath.ToOSPath(); - var assetContent = assetFile.AssetContent; + tasks.Add(task); + } - var asset = LoadAsset(context.Log, Meta.Name, assetFullPath, assetPath.ToOSPath(), assetContent, out var aliasOccurred, out var yamlMetadata); + Task.WaitAll([.. tasks], cancellationToken); - // Create asset item - var assetItem = new AssetItem(assetPath, asset, this) - { - IsDirty = assetContent != null || aliasOccurred, - SourceFolder = sourceFolder.MakeRelative(RootDirectory), - AlternativePath = assetFile.Link != null ? assetFullPath : null, - }; - yamlMetadata.CopyInto(assetItem.YamlMetadata); + // DEBUG + // StaticLog.Info("[{0}] Assets files loaded in {1}", assetFiles.Count, clock.ElapsedMilliseconds); - // Set the modified time to the time loaded from disk - if (!assetItem.IsDirty) - assetItem.ModifiedTime = File.GetLastWriteTime(assetFullPath); + if (cancellationToken.IsCancellationRequested) + { + log.Warning("Skipping loading assets. PackageSession.Load cancelled"); + } + } - // TODO: Let's review that when we rework import process - // Not fixing asset import anymore, as it was only meant for upgrade - // However, it started to make asset dirty, for ex. when we create a new texture, choose a file and reload the scene later - // since there was no importer id and base. - //FixAssetImport(assetItem); + private void LoadAsset(AssetMigrationContext context, PackageLoadingAssetFile assetFile) + { + var fileUPath = assetFile.FilePath; + var sourceFolder = assetFile.SourceFolder; - // Add to temporary assets - lock (TemporaryAssets) - { - TemporaryAssets.Add(assetItem); - } - } - catch (Exception ex) - { - int row = 1; - int column = 1; - var yamlException = ex as YamlException; - if (yamlException != null) - { - row = yamlException.Start.Line + 1; - column = yamlException.Start.Column; - } + // Check if asset has been deleted by an upgrader + if (assetFile.Deleted) + { + IsDirty = true; - var assetReference = new AssetReference(AssetId.Empty, fileUPath.FullPath); - context.Log.Error(this, assetReference, AssetMessageCode.AssetLoadingFailed, ex, fileUPath, ex.Message); + lock (FilesToDelete) + { + FilesToDelete.Add(assetFile.FilePath); } + + // Don't create temporary assets for files deleted during package upgrading + return; } - /// - /// Loads the assembly references that were not loaded before. - /// - /// The log. - /// The load parameters argument. - public void UpdateAssemblyReferences(ILogger log, PackageLoadParameters loadParametersArg = null) + // An exception can occur here, so we make sure that loading a single asset is not going to break + // the loop + try { - if (State < PackageState.DependenciesReady) - return; + AssetMigration.MigrateAssetIfNeeded(context, assetFile, "Stride"); - var loadParameters = loadParametersArg ?? PackageLoadParameters.Default(); - LoadAssemblyReferencesForPackage(log, loadParameters); - } + // Try to load only if asset is not already in the package or assetRef.Asset is null + var assetPath = assetFile.AssetLocation; - private static Asset LoadAsset(ILogger log, string packageName, string assetFullPath, string assetPath, byte[] assetContent, out bool assetDirty, out AttachedYamlAssetMetadata yamlMetadata) - { - var loadResult = assetContent != null - ? AssetFileSerializer.Load(new MemoryStream(assetContent), assetFullPath, log) - : AssetFileSerializer.Load(assetFullPath, log); + var assetFullPath = fileUPath.ToOSPath(); + var assetContent = assetFile.AssetContent; - assetDirty = loadResult.AliasOccurred; - yamlMetadata = loadResult.YamlMetadata; + var asset = LoadAsset(context.Log, Meta.Name, assetFullPath, assetPath.ToOSPath(), assetContent, out var aliasOccurred, out var yamlMetadata); - // Set location on source code asset - var sourceCodeAsset = loadResult.Asset as SourceCodeAsset; - if (sourceCodeAsset != null) + // Create asset item + var assetItem = new AssetItem(assetPath, asset, this) + { + IsDirty = assetContent is not null || aliasOccurred, + SourceFolder = sourceFolder.MakeRelative(RootDirectory), + AlternativePath = assetFile.Link is not null ? assetFullPath : null, + }; + yamlMetadata.CopyInto(assetItem.YamlMetadata); + + // Set the modified time to the time loaded from disk + if (!assetItem.IsDirty) + assetItem.ModifiedTime = File.GetLastWriteTime(assetFullPath); + + // TODO: Let's review that when we rework import process + // Not fixing asset import anymore, as it was only meant for upgrade + // However, it started to make asset dirty, for ex. when we create a new texture, choose a file and reload the scene later + // since there was no importer id and base. + //FixAssetImport(assetItem); + + // Add to temporary assets + lock (TemporaryAssets) + { + TemporaryAssets.Add(assetItem); + } + } + catch (Exception ex) + { + if (ex is YamlException yamlException) { - // Use an id generated from the location instead of the default id - sourceCodeAsset.Id = SourceCodeAsset.GenerateIdFromLocation(packageName, assetPath); + var row = yamlException.Start.Line + 1; + var column = yamlException.Start.Column; } - return loadResult.Asset; + var assetReference = new AssetReference(AssetId.Empty, fileUPath.FullPath); + context.Log.Error(this, assetReference, AssetMessageCode.AssetLoadingFailed, ex, fileUPath, ex.Message); } + } + + /// + /// Loads the assembly references that were not loaded before. + /// + /// The log. + /// The load parameters argument. + public void UpdateAssemblyReferences(ILogger log, PackageLoadParameters? loadParametersArg = null) + { + if (State < PackageState.DependenciesReady) + return; + + var loadParameters = loadParametersArg ?? PackageLoadParameters.Default(); + LoadAssemblyReferencesForPackage(log, loadParameters); + } - private void LoadAssemblyReferencesForPackage(ILogger log, PackageLoadParameters loadParameters) + private static Asset LoadAsset(ILogger log, string packageName, string assetFullPath, string assetPath, byte[] assetContent, out bool assetDirty, out AttachedYamlAssetMetadata yamlMetadata) + { + var loadResult = assetContent is not null + ? AssetFileSerializer.Load(new MemoryStream(assetContent), assetFullPath, log) + : AssetFileSerializer.Load(assetFullPath, log); + + assetDirty = loadResult.AliasOccurred; + yamlMetadata = loadResult.YamlMetadata; + + // Set location on source code asset + if (loadResult.Asset is SourceCodeAsset sourceCodeAsset) { - if (log == null) throw new ArgumentNullException(nameof(log)); - if (loadParameters == null) throw new ArgumentNullException(nameof(loadParameters)); - var assemblyContainer = loadParameters.AssemblyContainer ?? AssemblyContainer.Default; + // Use an id generated from the location instead of the default id + sourceCodeAsset.Id = SourceCodeAsset.GenerateIdFromLocation(packageName, assetPath); + } + + return loadResult.Asset; + } + + private void LoadAssemblyReferencesForPackage(ILogger log, PackageLoadParameters loadParameters) + { + ArgumentNullException.ThrowIfNull(log); + ArgumentNullException.ThrowIfNull(loadParameters); + var assemblyContainer = loadParameters.AssemblyContainer ?? AssemblyContainer.Default; - // Load from package - if (Container is StandalonePackage standalonePackage) + // Load from package + if (Container is StandalonePackage standalonePackage) + { + foreach (var assemblyPath in standalonePackage.Assemblies) { - foreach (var assemblyPath in standalonePackage.Assemblies) - { - LoadAssemblyReferenceInternal(log, loadParameters, assemblyContainer, null, assemblyPath); - } + LoadAssemblyReferenceInternal(log, loadParameters, assemblyContainer, null, assemblyPath); } + } - // Load from csproj - if (Container is SolutionProject project && project.FullPath != null && project.Type == ProjectType.Library) - { - // Check if already loaded - // TODO: More advanced cases: unload removed references, etc... - var projectReference = new ProjectReference(project.Id, project.FullPath, Core.Assets.ProjectType.Library); + // Load from csproj + if (Container is SolutionProject project && project.FullPath is not null && project.Type == ProjectType.Library) + { + // Check if already loaded + // TODO: More advanced cases: unload removed references, etc... + var projectReference = new ProjectReference(project.Id, project.FullPath, Core.Assets.ProjectType.Library); - LoadAssemblyReferenceInternal(log, loadParameters, assemblyContainer, projectReference, project.TargetPath); - } + LoadAssemblyReferenceInternal(log, loadParameters, assemblyContainer, projectReference, project.TargetPath); } + } - private void LoadAssemblyReferenceInternal(ILogger log, PackageLoadParameters loadParameters, AssemblyContainer assemblyContainer, ProjectReference projectReference, string assemblyPath) + private void LoadAssemblyReferenceInternal(ILogger log, PackageLoadParameters loadParameters, AssemblyContainer assemblyContainer, ProjectReference? projectReference, string assemblyPath) + { + try { - try - { - // Check if already loaded - if (projectReference != null && LoadedAssemblies.Any(x => x.ProjectReference == projectReference)) - return; - else if (LoadedAssemblies.Any(x => string.Compare(x.Path, assemblyPath, true) == 0)) - return; + // Check if already loaded + if (projectReference is not null && LoadedAssemblies.Any(x => x.ProjectReference == projectReference)) + return; + else if (LoadedAssemblies.Any(x => string.Compare(x.Path, assemblyPath, true) == 0)) + return; - var forwardingLogger = new ForwardingLoggerResult(log); + var forwardingLogger = new ForwardingLoggerResult(log); - // If csproj, we might need to compile it - if (projectReference != null) + // If csproj, we might need to compile it + if (projectReference is not null) + { + var fullProjectLocation = projectReference.Location.ToOSPath(); + if (loadParameters.AutoCompileProjects || string.IsNullOrWhiteSpace(assemblyPath)) { - var fullProjectLocation = projectReference.Location.ToOSPath(); - if (loadParameters.AutoCompileProjects || string.IsNullOrWhiteSpace(assemblyPath)) + assemblyPath = VSProjectHelper.GetOrCompileProjectAssembly(fullProjectLocation, forwardingLogger, "Build", loadParameters.AutoCompileProjects, loadParameters.BuildConfiguration, extraProperties: loadParameters.ExtraCompileProperties, onlyErrors: true); + if (string.IsNullOrWhiteSpace(assemblyPath)) { - assemblyPath = VSProjectHelper.GetOrCompileProjectAssembly(Session?.SolutionPath, fullProjectLocation, forwardingLogger, "Build", loadParameters.AutoCompileProjects, loadParameters.BuildConfiguration, extraProperties: loadParameters.ExtraCompileProperties, onlyErrors: true); - if (string.IsNullOrWhiteSpace(assemblyPath)) - { - log.Error($"Unable to locate assembly reference for project [{fullProjectLocation}]"); - return; - } + log.Error($"Unable to locate assembly reference for project [{fullProjectLocation}]"); + return; } } + } - var loadedAssembly = new PackageLoadedAssembly(projectReference, assemblyPath); - LoadedAssemblies.Add(loadedAssembly); - - if (!File.Exists(assemblyPath) || forwardingLogger.HasErrors) - { - log.Error($"Unable to build assembly reference [{assemblyPath}]"); - return; - } + var loadedAssembly = new PackageLoadedAssembly(projectReference, assemblyPath); + LoadedAssemblies.Add(loadedAssembly); - // Check if assembly is already loaded in appdomain (for Stride core assemblies that are not plugins) - var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => string.Compare(x.GetName().Name, Path.GetFileNameWithoutExtension(assemblyPath), StringComparison.InvariantCultureIgnoreCase) == 0); + if (!File.Exists(assemblyPath) || forwardingLogger.HasErrors) + { + log.Error($"Unable to build assembly reference [{assemblyPath}]"); + return; + } - // Otherwise, load assembly from its file - if (assembly == null) - { - assembly = assemblyContainer.LoadAssemblyFromPath(assemblyPath, log); + // Check if assembly is already loaded in appdomain (for Stride core assemblies that are not plugins) + var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => string.Equals(x.GetName().Name, Path.GetFileNameWithoutExtension(assemblyPath), StringComparison.InvariantCultureIgnoreCase)); - if (assembly == null) - { - log.Error($"Unable to load assembly reference [{assemblyPath}]"); - } + // Otherwise, load assembly from its file + if (assembly is null) + { + assembly = assemblyContainer.LoadAssemblyFromPath(assemblyPath, log); - // Note: we should investigate so that this can also be done for Stride core assemblies (right now they use module initializers) - if (assembly != null) - { - // Register assembly in the registry - AssemblyRegistry.Register(assembly, AssemblyCommonCategories.Assets); - } + if (assembly is null) + { + log.Error($"Unable to load assembly reference [{assemblyPath}]"); } - loadedAssembly.Assembly = assembly; - } - catch (Exception ex) - { - log.Error($"Unexpected error while loading assembly reference [{assemblyPath}]", ex); + // Note: we should investigate so that this can also be done for Stride core assemblies (right now they use module initializers) + if (assembly is not null) + { + // Register assembly in the registry + AssemblyRegistry.Register(assembly, AssemblyCommonCategories.Assets); + } } + + loadedAssembly.Assembly = assembly; + } + catch (Exception ex) + { + log.Error($"Unexpected error while loading assembly reference [{assemblyPath}]", ex); } + } - /// - /// In case was null, generates it. - /// - internal void UpdateSourceFolders(IReadOnlyCollection assets) + /// + /// In case was null, generates it. + /// + internal void UpdateSourceFolders(IReadOnlyCollection assets) + { + // If there are not assets, we don't need to update or create an asset folder + if (assets.Count == 0) { - // If there are not assets, we don't need to update or create an asset folder - if (assets.Count == 0) - { - return; - } + return; + } - // Use by default the first asset folders if not defined on the asset item - var defaultFolder = AssetFolders.Count > 0 ? AssetFolders.First().Path : UDirectory.This; - var assetFolders = new HashSet(GetDistinctAssetFolderPaths()); - foreach (var asset in assets) + // Use by default the first asset folders if not defined on the asset item + var defaultFolder = AssetFolders.Count > 0 ? AssetFolders[0].Path : UDirectory.This; + var assetFolders = new HashSet(GetDistinctAssetFolderPaths()); + foreach (var asset in assets) + { + if (asset.Asset is IProjectAsset) { - if (asset.Asset is IProjectAsset) + if (asset.SourceFolder is null) { - if (asset.SourceFolder == null) - { - asset.SourceFolder = string.Empty; - asset.IsDirty = true; - } + asset.SourceFolder = string.Empty; + asset.IsDirty = true; } - else + } + else + { + if (asset.SourceFolder is null) { - if (asset.SourceFolder == null) - { - asset.SourceFolder = defaultFolder.IsAbsolute ? defaultFolder.MakeRelative(RootDirectory) : defaultFolder; - asset.IsDirty = true; - } + asset.SourceFolder = defaultFolder.IsAbsolute ? defaultFolder.MakeRelative(RootDirectory) : defaultFolder; + asset.IsDirty = true; + } - var assetFolderAbsolute = UPath.Combine(RootDirectory, asset.SourceFolder); - if (!assetFolders.Contains(assetFolderAbsolute)) - { - assetFolders.Add(assetFolderAbsolute); - AssetFolders.Add(new AssetFolder(assetFolderAbsolute)); - IsDirty = true; - } + var assetFolderAbsolute = UPath.Combine(RootDirectory, asset.SourceFolder); + if (!assetFolders.Add(assetFolderAbsolute)) + { + AssetFolders.Add(new AssetFolder(assetFolderAbsolute)); + IsDirty = true; } } } + } - /// - /// Loads the templates. - /// - /// The log result. - private void LoadTemplates(ILogger log) + /// + /// Loads the templates. + /// + /// The log result. + private void LoadTemplates(ILogger log) + { + foreach (var templateDir in TemplateFolders) { - foreach (var templateDir in TemplateFolders) + foreach (var filePath in templateDir.Files) { - foreach (var filePath in templateDir.Files) + try { - try - { - var file = new FileInfo(filePath); - if (!file.Exists) - { - log.Warning($"Template [{file}] does not exist "); - continue; - } - - var templateDescription = YamlSerializer.Load(file.FullName); - templateDescription.FullPath = file.FullName; - Templates.Add(templateDescription); - } - catch (Exception ex) + var file = new FileInfo(filePath); + if (!file.Exists) { - log.Error($"Error while loading template from [{filePath}]", ex); + log.Warning($"Template [{file}] does not exist "); + continue; } + + var templateDescription = YamlSerializer.Load(file.FullName); + templateDescription.FullPath = file.FullName; + Templates.Add(templateDescription); + } + catch (Exception ex) + { + log.Error($"Error while loading template from [{filePath}]", ex); } } } + } - private List GetDistinctAssetFolderPaths() + private List GetDistinctAssetFolderPaths() + { + var existingAssetFolders = new List(); + foreach (var folder in AssetFolders) { - var existingAssetFolders = new List(); - foreach (var folder in AssetFolders) + var folderPath = RootDirectory is not null ? UPath.Combine(RootDirectory, folder.Path) : folder.Path; + if (!existingAssetFolders.Contains(folderPath)) { - var folderPath = RootDirectory != null ? UPath.Combine(RootDirectory, folder.Path) : folder.Path; - if (!existingAssetFolders.Contains(folderPath)) - { - existingAssetFolders.Add(folderPath); - } + existingAssetFolders.Add(folderPath); } - return existingAssetFolders; } + return existingAssetFolders; + } - public static List ListAssetFiles(ILogger log, Package package, bool listAssetsInMsbuild, bool listUnregisteredAssets, CancellationToken? cancelToken) - { - var listFiles = new List(); + public static List ListAssetFiles(Package package, bool listAssetsInMsbuild, bool listUnregisteredAssets) + { + var listFiles = new List(); - // TODO Check how to handle refresh correctly as a public API - if (package.RootDirectory == null) - { - throw new InvalidOperationException("Package RootDirectory is null"); - } + // TODO Check how to handle refresh correctly as a public API + if (package.RootDirectory is null) + { + throw new InvalidOperationException("Package RootDirectory is null"); + } - if (!Directory.Exists(package.RootDirectory)) - { - return listFiles; - } + if (!Directory.Exists(package.RootDirectory)) + { + return listFiles; + } - // Iterate on each source folders - foreach (var sourceFolder in package.GetDistinctAssetFolderPaths()) + // Iterate on each source folders + foreach (var sourceFolder in package.GetDistinctAssetFolderPaths()) + { + // Lookup all files + foreach (var directory in FileUtility.EnumerateDirectories(sourceFolder, SearchDirection.Down)) { - // Lookup all files - foreach (var directory in FileUtility.EnumerateDirectories(sourceFolder, SearchDirection.Down)) + foreach (var filePath in directory.GetFiles()) { - var files = directory.GetFiles(); - - foreach (var filePath in files) + // Don't load package via this method + if (filePath.FullName.EndsWith(PackageFileExtension, StringComparison.OrdinalIgnoreCase)) { - // Don't load package via this method - if (filePath.FullName.EndsWith(PackageFileExtension, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - // Make an absolute path from the root of this package - var fileUPath = new UFile(filePath.FullName); - if (fileUPath.GetFileExtension() == null) - { - continue; - } - - // If this kind of file an asset file? - var ext = fileUPath.GetFileExtension(); - // Adjust extensions for Stride rename - ext = ext.Replace(".xk", ".sd"); + continue; + } - //make sure to add default shaders in this case, since we don't have a csproj for them - if (AssetRegistry.IsProjectCodeGeneratorAssetFileExtension(ext) && (!(package.Container is SolutionProject) || package.IsSystem)) - { - listFiles.Add(new PackageLoadingAssetFile(fileUPath, sourceFolder) { CachedFileSize = filePath.Length }); - continue; - } + // Make an absolute path from the root of this package + var fileUPath = new UFile(filePath.FullName); + if (fileUPath.GetFileExtension() is null) + { + continue; + } - //project source code assets follow the csproj pipeline - var isAsset = listUnregisteredAssets - ? ext.StartsWith(".sd", StringComparison.InvariantCultureIgnoreCase) - : AssetRegistry.IsAssetFileExtension(ext); - if (!isAsset || AssetRegistry.IsProjectAssetFileExtension(ext)) - { - continue; - } + // If this kind of file an asset file? + var ext = fileUPath.GetFileExtension(); + // Adjust extensions for Stride rename + ext = ext?.Replace(".xk", ".sd"); - var loadingAsset = new PackageLoadingAssetFile(fileUPath, sourceFolder) { CachedFileSize = filePath.Length }; - listFiles.Add(loadingAsset); + //make sure to add default shaders in this case, since we don't have a csproj for them + if (AssetRegistry.IsProjectCodeGeneratorAssetFileExtension(ext) && (package.Container is not SolutionProject || package.IsSystem)) + { + listFiles.Add(new PackageLoadingAssetFile(fileUPath, sourceFolder) { CachedFileSize = filePath.Length }); + continue; } - } - } - //find also assets in the csproj - if (listAssetsInMsbuild) - { - FindAssetsInProject(listFiles, package); - } + //project source code assets follow the csproj pipeline + var isAsset = listUnregisteredAssets + ? ext?.StartsWith(".sd", StringComparison.InvariantCultureIgnoreCase) ?? false + : AssetRegistry.IsAssetFileExtension(ext); + if (!isAsset || AssetRegistry.IsProjectAssetFileExtension(ext)) + { + continue; + } - // Adjust extensions for Stride rename - foreach (var loadingAsset in listFiles) - { - var originalExt = loadingAsset.FilePath.GetFileExtension() ?? ""; - var ext = originalExt.Replace(".xk", ".sd"); - if (ext != originalExt) - { - loadingAsset.FilePath = new UFile(loadingAsset.FilePath.FullPath.Replace(".xk", ".sd")); + var loadingAsset = new PackageLoadingAssetFile(fileUPath, sourceFolder) { CachedFileSize = filePath.Length }; + listFiles.Add(loadingAsset); } } + } - return listFiles; + //find also assets in the csproj + if (listAssetsInMsbuild) + { + FindAssetsInProject(listFiles, package); } - public static List<(UFile FilePath, UFile Link)> FindAssetsInProject(string projectFullPath, out string nameSpace) + // Adjust extensions for Stride rename + foreach (var loadingAsset in listFiles) { - var realFullPath = new UFile(projectFullPath); - var project = VSProjectHelper.LoadProject(realFullPath); - var dir = new UDirectory(realFullPath.GetFullDirectory()); - - nameSpace = project.GetPropertyValue("RootNamespace"); - if (nameSpace == string.Empty) - nameSpace = null; - - var result = project.Items.Where(x => (x.ItemType == "Compile" || x.ItemType == "None") && string.IsNullOrEmpty(x.GetMetadataValue("AutoGen"))) - // Build full path for Include and Link - .Select(x => (FilePath: UPath.Combine(dir, new UFile(x.EvaluatedInclude)), Link: x.HasMetadata("Link") ? UPath.Combine(dir, new UFile(x.GetMetadataValue("Link"))) : null)) - // For items outside project, let's pretend they are link - .Select(x => (FilePath: x.FilePath, Link: x.Link ?? (!dir.Contains(x.FilePath) ? x.FilePath.GetFileName() : null))) - // Test both Stride and Xenko extensions - .Where(x => - AssetRegistry.IsProjectAssetFileExtension(x.FilePath.GetFileExtension()) - || AssetRegistry.IsProjectAssetFileExtension(x.FilePath.GetFileExtension()?.Replace(".xk", ".sd"))) - // avoid duplicates otherwise it might save a single file as separte file with renaming - // had issues with case such as Effect.sdsl being registered twice (with glob pattern) and being saved as Effect.sdsl and Effect (2).sdsl - .Distinct() - .ToList(); + var originalExt = loadingAsset.FilePath.GetFileExtension() ?? ""; + var ext = originalExt.Replace(".xk", ".sd"); + if (ext != originalExt) + { + loadingAsset.FilePath = new UFile(loadingAsset.FilePath.FullPath.Replace(".xk", ".sd")); + } + } - project.ProjectCollection.UnloadAllProjects(); - project.ProjectCollection.Dispose(); + return listFiles; + } - return result; - } + public static List<(UFile FilePath, UFile? Link)> FindAssetsInProject(string projectFullPath, out string? nameSpace) + { + var realFullPath = new UFile(projectFullPath); + var project = VSProjectHelper.LoadProject(realFullPath); + var dir = new UDirectory(realFullPath.GetFullDirectory()); + + nameSpace = project.GetPropertyValue("RootNamespace"); + if (nameSpace?.Length == 0) + nameSpace = null; + + var result = project.Items.Where(x => (x.ItemType == "Compile" || x.ItemType == "None") && string.IsNullOrEmpty(x.GetMetadataValue("AutoGen"))) + // Build full path for Include and Link + .Select(x => (FilePath: UPath.Combine(dir, new UFile(x.EvaluatedInclude)), Link: x.HasMetadata("Link") ? UPath.Combine(dir, new UFile(x.GetMetadataValue("Link"))) : null)) + // For items outside project, let's pretend they are link + .Select(x => (x.FilePath, Link: x.Link ?? (!dir.Contains(x.FilePath) ? x.FilePath.GetFileName() : null))) + // Test both Stride and Xenko extensions + .Where(x => + AssetRegistry.IsProjectAssetFileExtension(x.FilePath.GetFileExtension()) + || AssetRegistry.IsProjectAssetFileExtension(x.FilePath.GetFileExtension()?.Replace(".xk", ".sd"))) + // avoid duplicates otherwise it might save a single file as separte file with renaming + // had issues with case such as Effect.sdsl being registered twice (with glob pattern) and being saved as Effect.sdsl and Effect (2).sdsl + .Distinct() + .ToList(); + + project.ProjectCollection.UnloadAllProjects(); + project.ProjectCollection.Dispose(); + + return result; + } - private static void FindAssetsInProject(ICollection list, Package package) - { - if (package.IsSystem) return; + private static void FindAssetsInProject(ICollection list, Package package) + { + if (package.IsSystem) return; - var project = package.Container as SolutionProject; - if (project == null || project.FullPath == null) - return; + if (package.Container is not SolutionProject project || project.FullPath is null) + return; - string defaultNamespace; - var projectAssets = FindAssetsInProject(project.FullPath, out defaultNamespace); - package.RootNamespace = defaultNamespace; - var projectDirectory = new UDirectory(project.FullPath.GetFullDirectory()); + var projectAssets = FindAssetsInProject(project.FullPath, out var defaultNamespace); + package.RootNamespace = defaultNamespace; + var projectDirectory = new UDirectory(project.FullPath.GetFullDirectory()); - foreach (var projectAsset in projectAssets) - { - list.Add(new PackageLoadingAssetFile(projectAsset.FilePath, projectDirectory) { Link = projectAsset.Link }); - } + foreach (var (FilePath, Link) in projectAssets) + { + list.Add(new PackageLoadingAssetFile(FilePath, projectDirectory) { Link = Link }); } + } - private class MovePackageInsideProject : AssetUpgraderBase + private class MovePackageInsideProject : AssetUpgraderBase + { + protected override void UpgradeAsset(AssetMigrationContext context, PackageVersion currentVersion, PackageVersion targetVersion, dynamic asset, PackageLoadingAssetFile assetFile, OverrideUpgraderHint overrideHint) { - protected override void UpgradeAsset(AssetMigrationContext context, PackageVersion currentVersion, PackageVersion targetVersion, dynamic asset, PackageLoadingAssetFile assetFile, OverrideUpgraderHint overrideHint) + if (asset.Profiles is not null) { - if (asset.Profiles != null) + foreach (var profile in asset.Profiles) { - var profiles = asset.Profiles; - - foreach (var profile in profiles) + if (profile.Platform == "Shared") { - if (profile.Platform == "Shared") + if (profile.ProjectReferences?.Count == 1) { - if (profile.ProjectReferences != null && profile.ProjectReferences.Count == 1) - { - var projectLocation = (UFile)(string)profile.ProjectReferences[0].Location; - assetFile.FilePath = UPath.Combine(assetFile.OriginalFilePath.GetFullDirectory(), (UFile)(projectLocation.GetFullPathWithoutExtension() + PackageFileExtension)); - asset.Meta.Name = projectLocation.GetFileNameWithoutExtension(); - } + var projectLocation = (UFile)(string)profile.ProjectReferences[0].Location; + assetFile.FilePath = UPath.Combine(assetFile.OriginalFilePath.GetFullDirectory(), (UFile)(projectLocation.GetFullPathWithoutExtension() + PackageFileExtension)); + asset.Meta.Name = projectLocation.GetFileNameWithoutExtension(); + } - if (profile.AssetFolders != null) + if (profile.AssetFolders is not null) + { + for (int i = 0; i < profile.AssetFolders.Count; ++i) { - for (int i = 0; i < profile.AssetFolders.Count; ++i) - { - var assetPath = UPath.Combine(assetFile.OriginalFilePath.GetFullDirectory(), (UDirectory)(string)profile.AssetFolders[i].Path); - assetPath = assetPath.MakeRelative(assetFile.FilePath.GetFullDirectory()); - profile.AssetFolders[i].Path = (string)assetPath; - } + var assetPath = UPath.Combine(assetFile.OriginalFilePath.GetFullDirectory(), (UDirectory)(string)profile.AssetFolders[i].Path); + assetPath = assetPath.MakeRelative(assetFile.FilePath.GetFullDirectory()); + profile.AssetFolders[i].Path = (string)assetPath; } + } - if (profile.ResourceFolders != null) + if (profile.ResourceFolders is not null) + { + for (int i = 0; i < profile.ResourceFolders.Count; ++i) { - for (int i = 0; i < profile.ResourceFolders.Count; ++i) - { - var resourcePath = UPath.Combine(assetFile.OriginalFilePath.GetFullDirectory(), (UDirectory)(string)profile.ResourceFolders[i]); - resourcePath = resourcePath.MakeRelative(assetFile.FilePath.GetFullDirectory()); - profile.ResourceFolders[i] = (string)resourcePath; - } + var resourcePath = UPath.Combine(assetFile.OriginalFilePath.GetFullDirectory(), (UDirectory)(string)profile.ResourceFolders[i]); + resourcePath = resourcePath.MakeRelative(assetFile.FilePath.GetFullDirectory()); + profile.ResourceFolders[i] = (string)resourcePath; } - - asset.AssetFolders = profile.AssetFolders; - asset.ResourceFolders = profile.ResourceFolders; - asset.OutputGroupDirectories = profile.OutputGroupDirectories; } - } - asset.Profiles = DynamicYamlEmpty.Default; + asset.AssetFolders = profile.AssetFolders; + asset.ResourceFolders = profile.ResourceFolders; + asset.OutputGroupDirectories = profile.OutputGroupDirectories; + } } + + asset.Profiles = DynamicYamlEmpty.Default; } } } diff --git a/sources/assets/Stride.Core.Assets/PackageAssetCollection.cs b/sources/assets/Stride.Core.Assets/PackageAssetCollection.cs index 3c20eab00f..0867460564 100644 --- a/sources/assets/Stride.Core.Assets/PackageAssetCollection.cs +++ b/sources/assets/Stride.Core.Assets/PackageAssetCollection.cs @@ -1,422 +1,408 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Reflection; -using System.Threading; -using Stride.Core; using Stride.Core.Diagnostics; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// A collection of that contains only absolute location without any drive information. This class cannot be inherited. +/// +[DebuggerTypeProxy(typeof(CollectionDebugView))] +[DebuggerDisplay("Count = {Count}")] +public sealed class PackageAssetCollection : ICollection, IReadOnlyCollection, ICollection, INotifyCollectionChanged { - /// - /// A collection of that contains only absolute location without any drive information. This class cannot be inherited. - /// - [DebuggerTypeProxy(typeof(CollectionDebugView))] - [DebuggerDisplay("Count = {Count}")] - public sealed class PackageAssetCollection : ICollection, IReadOnlyCollection, ICollection, INotifyCollectionChanged - { - private readonly Package package; - private object syncRoot; + private object? syncRoot; - // Maps Asset.Location to Asset.Id - private readonly Dictionary mapPathToId; + // Maps Asset.Location to Asset.Id + private readonly Dictionary mapPathToId; - // Maps Asset.Id to Asset.Location - private readonly Dictionary mapIdToPath; + // Maps Asset.Id to Asset.Location + private readonly Dictionary mapIdToPath; - // Maps Id to AssetItem - private readonly Dictionary mapIdToAsset; + // Maps Id to AssetItem + private readonly Dictionary mapIdToAsset; - // All registered items - private readonly HashSet registeredItems; + // All registered items + private readonly HashSet registeredItems; - private bool collectionChangedSuspended; + private bool collectionChangedSuspended; - /// - /// Occurs when the collection changes. - /// - public event NotifyCollectionChangedEventHandler CollectionChanged; + /// + /// Occurs when the collection changes. + /// + public event NotifyCollectionChangedEventHandler? CollectionChanged; - /// - /// Initializes a new instance of the class. - /// - /// The package that will contain assets. - public PackageAssetCollection(Package package) - { - if (package == null) throw new ArgumentNullException("package"); - this.package = package; - mapPathToId = new Dictionary(); - mapIdToPath = new Dictionary(); - mapIdToAsset = new Dictionary(); - registeredItems = new HashSet(new ReferenceEqualityComparer()); - } + /// + /// Initializes a new instance of the class. + /// + /// The package that will contain assets. + public PackageAssetCollection(Package package) + { + ArgumentNullException.ThrowIfNull(package); + Package = package; + mapPathToId = []; + mapIdToPath = []; + mapIdToAsset = []; + registeredItems = new HashSet(new ReferenceEqualityComparer()); + } + + /// + /// Gets the package this collection is attached to. + /// + /// The package. + public Package Package { get; } - /// - /// Gets the package this collection is attached to. - /// - /// The package. - public Package Package + /// + /// Gets or sets a value indicating whether this instance is dirty. Sets this flag when moving assets between packages + /// or removing assets. + /// + /// true if this instance is dirty; otherwise, false. + public bool IsDirty { get; set; } + + /// + /// Determines whether this instance contains an asset with the specified identifier. + /// + /// The asset identifier. + /// true if this instance contains an asset with the specified identifier; otherwise, false. + public bool ContainsById(AssetId assetId) + { + return mapIdToAsset.ContainsKey(assetId); + } + + /// + /// Finds an asset by its location. + /// + /// The location of the assets. + /// AssetItem. + public AssetItem? Find(string location) + { + if (!mapPathToId.TryGetValue(location, out var id)) { - get - { - return package; - } + return null; } + return Find(id); + } + + /// + /// Finds an asset by its location. + /// + /// The asset unique identifier. + /// AssetItem. + public AssetItem? Find(AssetId assetId) + { + mapIdToAsset.TryGetValue(assetId, out var value); + return value; + } - /// - /// Gets or sets a value indicating whether this instance is dirty. Sets this flag when moving assets between packages - /// or removing assets. - /// - /// true if this instance is dirty; otherwise, false. - public bool IsDirty { get; set; } - - /// - /// Determines whether this instance contains an asset with the specified identifier. - /// - /// The asset identifier. - /// true if this instance contains an asset with the specified identifier; otherwise, false. - public bool ContainsById(AssetId assetId) + /// + /// Adds an to this instance. + /// + /// The item to add to this instance. + public void Add(AssetItem item) + { + // Item is already added. Just skip it + if (registeredItems.Contains(item)) { - return mapIdToAsset.ContainsKey(assetId); + return; } - /// - /// Finds an asset by its location. - /// - /// The location of the assets. - /// AssetItem. - public AssetItem Find(string location) + // Verify item integrity + CheckCanAdd(item); + + // Set the parent of the item to the package + item.Package = Package; + + // Add the item + var asset = item.Asset; + asset.IsIdLocked = true; + + // Note: we ignore name collisions if asset is not referenceable + var referenceable = item.Asset.GetType().GetCustomAttribute()?.Referenceable ?? true; + + // Maintain all internal maps + if (referenceable) + mapPathToId.Add(item.Location, item.Id); + mapIdToPath.Add(item.Id, item.Location); + mapIdToAsset.Add(item.Id, item); + registeredItems.Add(item); + + // Handle notification - insert + if (!collectionChangedSuspended) { - AssetId id; - if (!mapPathToId.TryGetValue(location, out id)) - { - return null; - } - return Find(id); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } + } - /// - /// Finds an asset by its location. - /// - /// The asset unique identifier. - /// AssetItem. - public AssetItem Find(AssetId assetId) + /// + /// Removes all items from this instance. + /// + public void Clear() + { + // Clean parent + foreach (var registeredItem in registeredItems) { - AssetItem value; - mapIdToAsset.TryGetValue(assetId, out value); - return value; + RemoveInternal(registeredItem); } - /// - /// Adds an to this instance. - /// - /// The item to add to this instance. - public void Add(AssetItem item) + // Unregister all items / clear all internal maps + registeredItems.Clear(); + mapIdToPath.Clear(); + mapPathToId.Clear(); + mapIdToAsset.Clear(); + + // Handle notification - clear items + if (!collectionChangedSuspended) { - // Item is already added. Just skip it - if (registeredItems.Contains(item)) - { - return; - } + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } - // Verify item integrity - CheckCanAdd(item); + /// + /// Checks this collection contains the specified asset reference, throws an exception if not found. + /// + /// The asset item. + /// assetItem + /// Asset [{0}] was not found.ToFormat(assetItem) + public bool Contains(AssetItem assetItem) + { + ArgumentNullException.ThrowIfNull(assetItem); + return registeredItems.Contains(assetItem); + } - // Set the parent of the item to the package - item.Package = package; + /// + /// Copies items to the specified array. + /// + /// The array. + /// Index of the array. + public void CopyTo(AssetItem[] array, int arrayIndex) + { + registeredItems.CopyTo(array, arrayIndex); + } - // Add the item - var asset = item.Asset; - asset.IsIdLocked = true; + /// + /// Removes an from this instance. + /// + /// The object to remove from the . + /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + public bool Remove(AssetItem item) + { + if (registeredItems.Remove(item)) + { + // Remove from all internal maps + registeredItems.Remove(item); + mapIdToAsset.Remove(item.Id); + mapIdToPath.Remove(item.Id); // Note: we ignore name collisions if asset is not referenceable var referenceable = item.Asset.GetType().GetCustomAttribute()?.Referenceable ?? true; - - // Maintain all internal maps if (referenceable) - mapPathToId.Add(item.Location, item.Id); - mapIdToPath.Add(item.Id, item.Location); - mapIdToAsset.Add(item.Id, item); - registeredItems.Add(item); + mapPathToId.Remove(item.Location); + + RemoveInternal(item); - // Handle notification - insert + // Handle notification - replace if (!collectionChangedSuspended) { - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)); } + return true; } + return false; + } - /// - /// Removes all items from this instance. - /// - public void Clear() + /// + /// Removes an from this instance. + /// + /// The item identifier. + /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + public bool RemoveById(AssetId itemId) + { + var item = Find(itemId); + if (item == null) { - // Clean parent - foreach (var registeredItem in registeredItems) - { - RemoveInternal(registeredItem); - } + return false; + } + return Remove(item); + } - // Unregister all items / clear all internal maps - registeredItems.Clear(); - mapIdToPath.Clear(); - mapPathToId.Clear(); - mapIdToAsset.Clear(); + /// + /// Suspends the collection changed that can happen on this collection. + /// + public void SuspendCollectionChanged() + { + collectionChangedSuspended = true; + } - // Handle notification - clear items - if (!collectionChangedSuspended) - { - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - } + /// + /// Resumes the collection changed that happened on this collection and fire a + /// + public void ResumeCollectionChanged() + { + collectionChangedSuspended = false; - /// - /// Checks this collection contains the specified asset reference, throws an exception if not found. - /// - /// The asset item. - /// assetItem - /// Asset [{0}] was not found.ToFormat(assetItem) - public bool Contains(AssetItem assetItem) - { - if (assetItem == null) throw new ArgumentNullException("assetItem"); - return registeredItems.Contains(assetItem); - } + // Handle notification - clear items + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + private static void RemoveInternal(AssetItem item) + { + item.Asset.IsIdLocked = false; + } - /// - /// Copies items to the specified array. - /// - /// The array. - /// Index of the array. - public void CopyTo(AssetItem[] array, int arrayIndex) + /// + /// Gets the number of elements contained in this instance. + /// + /// The number of elements contained in this instance. + public int Count + { + get { - registeredItems.CopyTo(array, arrayIndex); + return registeredItems.Count; } + } - /// - /// Removes an from this instance. - /// - /// The object to remove from the . - /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - public bool Remove(AssetItem item) + /// + /// Gets a value indicating whether this collection is read-only. Default is false. + /// + /// false + public bool IsReadOnly + { + get { - if (registeredItems.Remove(item)) - { - // Remove from all internal maps - registeredItems.Remove(item); - mapIdToAsset.Remove(item.Id); - mapIdToPath.Remove(item.Id); - - // Note: we ignore name collisions if asset is not referenceable - var referenceable = item.Asset.GetType().GetCustomAttribute()?.Referenceable ?? true; - if (referenceable) - mapPathToId.Remove(item.Location); - - RemoveInternal(item); - - // Handle notification - replace - if (!collectionChangedSuspended) - { - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)); - } - return true; - } return false; } + } - /// - /// Removes an from this instance. - /// - /// The item identifier. - /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - public bool RemoveById(AssetId itemId) + /// + /// Checks if the specified item can be add to this collection. + /// + /// The item. + /// + /// item;Cannot add an empty asset item reference + /// or + /// item;Cannot add an item with an empty asset + /// or + /// item;Cannot add an asset with an empty Id + /// or + /// item;Location cannot be null when adding an asset reference + /// + /// + /// An asset with the same location is already registered [{0}].ToFormat(location.Path);item + /// or + /// An asset with the same id [{0}] is already registered with the location [{1}].ToFormat(item.Id, location.Path);item + /// or + /// Asset location [{0}] cannot contain drive information.ToFormat(location);item + /// or + /// Asset location [{0}] must be relative and not absolute (not start with '/').ToFormat(location);item + /// or + /// Asset location [{0}] cannot start with relative '..'.ToFormat(location);item + /// + public void CheckCanAdd(AssetItem item) + { + // TODO better handle interaction + if (item == null) { - var item = Find(itemId); - if (item == null) - { - return false; - } - return Remove(item); + throw new ArgumentNullException(nameof(item), "Cannot add an empty asset item reference"); } - /// - /// Suspends the collection changed that can happen on this collection. - /// - public void SuspendCollectionChanged() + if (registeredItems.Contains(item)) { - collectionChangedSuspended = true; + throw new ArgumentException("Asset already exist in this collection", nameof(item)); } - /// - /// Resumes the collection changed that happened on this collection and fire a - /// - public void ResumeCollectionChanged() + if (item.Id == AssetId.Empty) { - collectionChangedSuspended = false; - - // Handle notification - clear items - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + throw new ArgumentException("Cannot add an asset with an empty Id", nameof(item)); } - private void RemoveInternal(AssetItem item) + if (item.Package != null && item.Package != Package) { - item.Asset.IsIdLocked = false; + throw new ArgumentException("Cannot add an asset that is already added to another package", nameof(item)); } - /// - /// Gets the number of elements contained in this instance. - /// - /// The number of elements contained in this instance. - public int Count + // Note: we ignore name collisions if asset is not referenceable + var referenceable = item.Asset.GetType().GetCustomAttribute()?.Referenceable ?? true; + + var location = item.Location; + if (referenceable && mapPathToId.ContainsKey(location)) { - get - { - return registeredItems.Count; - } + throw new ArgumentException("An asset [{0}] with the same location [{1}] is already registered ".ToFormat(mapPathToId[location], location.GetDirectoryAndFileName()), nameof(item)); } - /// - /// Gets a value indicating whether this collection is read-only. Default is false. - /// - /// false - public bool IsReadOnly + if (mapIdToPath.ContainsKey(item.Id)) { - get - { - return false; - } + throw new ArgumentException("An asset with the same id [{0}] is already registered with the location [{1}]".ToFormat(item.Id, location.GetDirectoryAndFileName()), nameof(item)); } - /// - /// Checks if the specified item can be add to this collection. - /// - /// The item. - /// - /// item;Cannot add an empty asset item reference - /// or - /// item;Cannot add an item with an empty asset - /// or - /// item;Cannot add an asset with an empty Id - /// or - /// item;Location cannot be null when adding an asset reference - /// - /// - /// An asset with the same location is already registered [{0}].ToFormat(location.Path);item - /// or - /// An asset with the same id [{0}] is already registered with the location [{1}].ToFormat(item.Id, location.Path);item - /// or - /// Asset location [{0}] cannot contain drive information.ToFormat(location);item - /// or - /// Asset location [{0}] must be relative and not absolute (not start with '/').ToFormat(location);item - /// or - /// Asset location [{0}] cannot start with relative '..'.ToFormat(location);item - /// - public void CheckCanAdd(AssetItem item) + if (location.HasDrive) { - // TODO better handle interaction - if (item == null) - { - throw new ArgumentNullException("item", "Cannot add an empty asset item reference"); - } - - if (registeredItems.Contains(item)) - { - throw new ArgumentException("Asset already exist in this collection", "item"); - } - - if (item.Id == AssetId.Empty) - { - throw new ArgumentException("Cannot add an asset with an empty Id", "item"); - } - - if (item.Package != null && item.Package != package) - { - throw new ArgumentException("Cannot add an asset that is already added to another package", "item"); - } - - // Note: we ignore name collisions if asset is not referenceable - var referenceable = item.Asset.GetType().GetCustomAttribute()?.Referenceable ?? true; - - var location = item.Location; - if (referenceable && mapPathToId.ContainsKey(location)) - { - throw new ArgumentException("An asset [{0}] with the same location [{1}] is already registered ".ToFormat(mapPathToId[location], location.GetDirectoryAndFileName()), "item"); - } - - if (mapIdToPath.ContainsKey(item.Id)) - { - throw new ArgumentException("An asset with the same id [{0}] is already registered with the location [{1}]".ToFormat(item.Id, location.GetDirectoryAndFileName()), "item"); - } - - if (location.HasDrive) - { - throw new ArgumentException("Asset location [{0}] cannot contain drive information".ToFormat(location), "item"); - } + throw new ArgumentException("Asset location [{0}] cannot contain drive information".ToFormat(location), nameof(item)); + } - if (location.IsAbsolute) - { - throw new ArgumentException("Asset location [{0}] must be relative and not absolute (not start with '/')".ToFormat(location), "item"); - } + if (location.IsAbsolute) + { + throw new ArgumentException("Asset location [{0}] must be relative and not absolute (not start with '/')".ToFormat(location), nameof(item)); + } - if (location.GetDirectory() != null && location.GetDirectory().StartsWith("..", StringComparison.Ordinal)) - { - throw new ArgumentException("Asset location [{0}] cannot start with relative '..'".ToFormat(location), "item"); - } + if (location.GetDirectory()?.StartsWith("..", StringComparison.Ordinal) == true) + { + throw new ArgumentException("Asset location [{0}] cannot start with relative '..'".ToFormat(location), nameof(item)); + } - // Double check that this asset is not already stored in another package for this session - if (Package.Session != null) + // Double check that this asset is not already stored in another package for this session + if (Package.Session != null) + { + foreach (var otherPackage in Package.Session.Packages) { - foreach (var otherPackage in Package.Session.Packages) + if (otherPackage != Package) { - if (otherPackage != Package) + if (otherPackage.Assets.ContainsById(item.Id)) { - if (otherPackage.Assets.ContainsById(item.Id)) - { - throw new ArgumentException("Cannot add the asset [{0}] that is already in different package [{1}] in the current session".ToFormat(item.Id, otherPackage.Meta.Name)); - } + throw new ArgumentException("Cannot add the asset [{0}] that is already in different package [{1}] in the current session".ToFormat(item.Id, otherPackage.Meta.Name)); } } } } + } - public IEnumerator GetEnumerator() - { - return registeredItems.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + public IEnumerator GetEnumerator() + { + return registeredItems.GetEnumerator(); + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - void ICollection.CopyTo(Array array, int index) + void ICollection.CopyTo(Array array, int index) + { + foreach (var item in this) { - foreach (var item in this) - { - array.SetValue(item, index++); - } + array.SetValue(item, index++); } + } - object ICollection.SyncRoot + object ICollection.SyncRoot + { + get { - get - { - if (syncRoot == null) - Interlocked.CompareExchange(ref syncRoot, new object(), (object)null); - return this.syncRoot; - } + if (syncRoot == null) + Interlocked.CompareExchange(ref syncRoot, new object(), null); + return syncRoot; } + } - bool ICollection.IsSynchronized + bool ICollection.IsSynchronized + { + get { - get - { - return false; - } + return false; } } } diff --git a/sources/assets/Stride.Core.Assets/PackageCollection.cs b/sources/assets/Stride.Core.Assets/PackageCollection.cs index 56771cc71e..1871ce8011 100644 --- a/sources/assets/Stride.Core.Assets/PackageCollection.cs +++ b/sources/assets/Stride.Core.Assets/PackageCollection.cs @@ -1,155 +1,136 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; -using System.Linq; using Stride.Core.Diagnostics; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public interface IReadOnlyPackageCollection : IReadOnlyCollection, INotifyCollectionChanged { - public interface IReadOnlyPackageCollection : IReadOnlyCollection, INotifyCollectionChanged - { - Package Find(Dependency dependency); + Package? Find(Dependency dependency); - Package Find(PackageDependency packageDependency); + Package? Find(PackageDependency packageDependency); - Package Find(string name, PackageVersionRange versionRange); - } + Package? Find(string name, PackageVersionRange versionRange); +} + +/// +/// A collection of . +/// +[DebuggerTypeProxy(typeof(CollectionDebugView))] +[DebuggerDisplay("Count = {Count}")] +[DataContract("PackageCollection")] +public sealed class PackageCollection : ICollection, INotifyCollectionChanged, IReadOnlyPackageCollection +{ + private readonly List packages; /// - /// A collection of . + /// Occurs when the collection changes. /// - [DebuggerTypeProxy(typeof(CollectionDebugView))] - [DebuggerDisplay("Count = {Count}")] - [DataContract("PackageCollection")] - public sealed class PackageCollection : ICollection, INotifyCollectionChanged, IReadOnlyPackageCollection + public event NotifyCollectionChangedEventHandler? CollectionChanged; + + /// + /// Initializes a new instance of the class. + /// + public PackageCollection() { - private readonly List packages; + packages = []; + } - /// - /// Occurs when the collection changes. - /// - public event NotifyCollectionChangedEventHandler CollectionChanged; + public int Count => packages.Count; - /// - /// Initializes a new instance of the class. - /// - public PackageCollection() - { - packages = new List(); - } + public bool IsReadOnly => false; - public int Count - { - get - { - return packages.Count; - } - } + public IEnumerator GetEnumerator() + { + return packages.GetEnumerator(); + } - public bool IsReadOnly - { - get - { - return false; - } - } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - public IEnumerator GetEnumerator() + /// + /// Finds the a package already in this collection from the specified dependency. + /// + /// The package dependency. + /// Package. + public Package? Find(Dependency dependency) + { + ArgumentNullException.ThrowIfNull(dependency); + return dependency.Type switch { - return packages.GetEnumerator(); - } + DependencyType.Package => Find(dependency.Name, new PackageVersionRange(dependency.Version)), + DependencyType.Project => packages.FirstOrDefault(package => package.Meta.Name == dependency.Name && package.Container is SolutionProject),// Project versions might not be properly loaded so we check only by name + _ => throw new ArgumentException($"Unhandled value: {dependency.Type}"), + }; + } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// Finds the a package already in this collection from the specified dependency. - /// - /// The package dependency. - /// Package. - public Package Find(Dependency dependency) - { - if (dependency == null) throw new ArgumentNullException(nameof(dependency)); - switch (dependency.Type) - { - case DependencyType.Package: - return Find(dependency.Name, new PackageVersionRange(dependency.Version)); - case DependencyType.Project: - // Project versions might not be properly loaded so we check only by name - return packages.FirstOrDefault(package => package.Meta.Name == dependency.Name && package.Container is SolutionProject); - default: - throw new ArgumentException($"Unhandled value: {dependency.Type}"); - } - } - - /// - /// Finds the a package already in this collection from the specified dependency. - /// - /// The package dependency. - /// Package. - public Package Find(PackageDependency packageDependency) - { - if (packageDependency == null) throw new ArgumentNullException("packageDependency"); - return Find(packageDependency.Name, packageDependency.Version); - } - - /// - /// Finds a package with the specified name and . - /// - /// The name. - /// The version range. - /// Package. - public Package Find(string name, PackageVersionRange versionRange) - { - if (name == null) throw new ArgumentNullException("name"); - if (versionRange == null) throw new ArgumentNullException("versionRange"); - var filter = versionRange.ToFilter(); - return packages.FirstOrDefault(package => package.Meta.Name == name && filter(package)); - } + /// + /// Finds the a package already in this collection from the specified dependency. + /// + /// The package dependency. + /// Package. + public Package? Find(PackageDependency packageDependency) + { + ArgumentNullException.ThrowIfNull(packageDependency); + return Find(packageDependency.Name, packageDependency.Version); + } - public void Add(Package item) - { - if (item == null) throw new ArgumentNullException("item"); - packages.Add(item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); - } + /// + /// Finds a package with the specified name and . + /// + /// The name. + /// The version range. + /// Package. + public Package? Find(string name, PackageVersionRange versionRange) + { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(versionRange); + var filter = versionRange.ToFilter(); + return packages.FirstOrDefault(package => package.Meta.Name == name && filter(package)); + } - public void Clear() - { - var oldPackages = new List(packages); - packages.Clear(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, packages, oldPackages)); - } + public void Add(Package item) + { + ArgumentNullException.ThrowIfNull(item); + packages.Add(item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + } - public bool Contains(Package item) - { - if (item == null) throw new ArgumentNullException("item"); - return packages.Contains(item); - } + public void Clear() + { + var oldPackages = new List(packages); + packages.Clear(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, packages, oldPackages)); + } - public void CopyTo(Package[] array, int arrayIndex) - { - packages.CopyTo(array, arrayIndex); - } + public bool Contains(Package item) + { + ArgumentNullException.ThrowIfNull(item); + return packages.Contains(item); + } - public bool Remove(Package item) - { - if (item == null) throw new ArgumentNullException("item"); - var success = packages.Remove(item); - if (success) - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)); - return success; - } - - private void OnCollectionChanged(NotifyCollectionChangedEventArgs e) - { - CollectionChanged?.Invoke(this, e); - } + public void CopyTo(Package[] array, int arrayIndex) + { + packages.CopyTo(array, arrayIndex); + } + + public bool Remove(Package item) + { + ArgumentNullException.ThrowIfNull(item); + var success = packages.Remove(item); + if (success) + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)); + return success; + } + + private void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + CollectionChanged?.Invoke(this, e); } } diff --git a/sources/assets/Stride.Core.Assets/PackageDependency.cs b/sources/assets/Stride.Core.Assets/PackageDependency.cs index ef02431591..dadfabf805 100644 --- a/sources/assets/Stride.Core.Assets/PackageDependency.cs +++ b/sources/assets/Stride.Core.Assets/PackageDependency.cs @@ -1,10 +1,8 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections.ObjectModel; using System.ComponentModel; -using Stride.Core; -using Stride.Core.Annotations; namespace Stride.Core.Assets { @@ -57,7 +55,7 @@ public PackageDependency(string name, PackageVersionRange version) /// [DefaultValue(null)] [DataMember(10)] - public string Name { get; set; } + public string? Name { get; set; } /// /// Gets or sets the version. @@ -65,26 +63,25 @@ public PackageDependency(string name, PackageVersionRange version) /// The version. [DefaultValue(null)] [DataMember(20)] - public PackageVersionRange Version { get; set; } + public PackageVersionRange? Version { get; set; } /// /// Clones this instance. /// /// PackageDependency. - [NotNull] public PackageDependency Clone() { return new PackageDependency(Name, Version); } - public bool Equals(PackageDependency other) + public bool Equals(PackageDependency? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return string.Equals(Name, other.Name) && Equals(Version, other.Version); } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; diff --git a/sources/assets/Stride.Core.Assets/PackageExtensions.cs b/sources/assets/Stride.Core.Assets/PackageExtensions.cs index 6f1303ccd1..26dc7e5925 100644 --- a/sources/assets/Stride.Core.Assets/PackageExtensions.cs +++ b/sources/assets/Stride.Core.Assets/PackageExtensions.cs @@ -1,113 +1,103 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; + using Stride.Core.Extensions; using Stride.Core.IO; -using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Extensions for +/// +public static class PackageExtensions { /// - /// Extensions for + /// Finds an asset from all the packages by its asset reference. + /// It will first try by id, then location. /// - public static class PackageExtensions + /// The package. + /// The reference to the asset. + /// An or null if not found. + public static AssetItem? FindAsset(this Package package, IReference reference) { - /// - /// Finds an asset from all the packages by its asset reference. - /// It will first try by id, then location. - /// - /// The package. - /// The reference to the asset. - /// An or null if not found. - public static AssetItem FindAsset(this Package package, IReference reference) - { - return package.FindAsset(reference.Id) ?? package.FindAsset(reference.Location); - } + return package.FindAsset(reference.Id) ?? package.FindAsset(reference.Location); + } - internal static IEnumerable GetPackagesWithDependencies(this Package currentPackage) + internal static IEnumerable GetPackagesWithDependencies(this Package currentPackage) + { + // Let's do a depth first search + if (currentPackage == null) { - // Let's do a depth first search - if (currentPackage == null) - { - yield break; - } - - yield return currentPackage; - - var session = currentPackage.Session; - - foreach (var dependency in currentPackage.Container.FlattenedDependencies) - { - if (dependency.Package != null) - yield return dependency.Package; - } + yield break; } - /// - /// Finds the package dependencies for the specified . See remarks. - /// - /// The root package. - /// if set to true [include root package]. - /// if set to true [is recursive]. - /// if set to true [ignores local packages and keeps only store packages]. - /// List<Package>. - /// rootPackage - /// Root package must be part of a session;rootPackage - public static PackageCollection FindDependencies(this Package rootPackage, bool includeRootPackage = false) - { - if (rootPackage == null) throw new ArgumentNullException("rootPackage"); - var packages = new PackageCollection(); - - if (includeRootPackage) - { - packages.Add(rootPackage); - } - - FillPackageDependencies(rootPackage, packages); + yield return currentPackage; - return packages; - } + _ = currentPackage.Session; - /// - /// Determines whether the specified packages contains an asset by its guid. - /// - /// The packages. - /// The asset unique identifier. - /// true if the specified packages contains asset; otherwise, false. - public static bool ContainsAsset(this IEnumerable packages, AssetId assetId) + foreach (var dependency in currentPackage.Container.FlattenedDependencies) { - return packages.Any(package => package.Assets.ContainsById(assetId)); + if (dependency.Package != null) + yield return dependency.Package; } + } + + /// + /// Finds the package dependencies for the specified . See remarks. + /// + /// The root package. + /// if set to true [include root package]. + /// if set to true [is recursive]. + /// if set to true [ignores local packages and keeps only store packages]. + /// List<Package>. + /// rootPackage + /// Root package must be part of a session;rootPackage + public static PackageCollection FindDependencies(this Package rootPackage, bool includeRootPackage = false) + { + ArgumentNullException.ThrowIfNull(rootPackage); + var packages = new PackageCollection(); - /// - /// Determines whether the specified packages contains an asset by its location. - /// - /// The packages. - /// The location. - /// true if the specified packages contains asset; otherwise, false. - public static bool ContainsAsset(this IEnumerable packages, UFile location) + if (includeRootPackage) { - return packages.Any(package => package.Assets.Find(location) != null); + packages.Add(rootPackage); } - private static void FillPackageDependencies(Package rootPackage, ICollection packagesFound) - { - var session = rootPackage.Session; + FillPackageDependencies(rootPackage, packages); + + return packages; + } + + /// + /// Determines whether the specified packages contains an asset by its guid. + /// + /// The packages. + /// The asset unique identifier. + /// true if the specified packages contains asset; otherwise, false. + public static bool ContainsAsset(this IEnumerable packages, AssetId assetId) + { + return packages.Any(package => package.Assets.ContainsById(assetId)); + } - if (session == null) - { - throw new InvalidOperationException("Cannot query package with dependencies when it is not attached to a session"); - } + /// + /// Determines whether the specified packages contains an asset by its location. + /// + /// The packages. + /// The location. + /// true if the specified packages contains asset; otherwise, false. + public static bool ContainsAsset(this IEnumerable packages, UFile location) + { + return packages.Any(package => package.Assets.Find(location) != null); + } - foreach (var dependency in rootPackage.Container.FlattenedDependencies.Select(x => x.Package).NotNull()) - { - if (!packagesFound.Contains(dependency)) - packagesFound.Add(dependency); - } + private static void FillPackageDependencies(Package rootPackage, PackageCollection packagesFound) + { + var session = rootPackage.Session + ?? throw new InvalidOperationException("Cannot query package with dependencies when it is not attached to a session"); + foreach (var dependency in rootPackage.Container.FlattenedDependencies.Select(x => x.Package).NotNull()) + { + if (!packagesFound.Contains(dependency)) + packagesFound.Add(dependency); } } } diff --git a/sources/assets/Stride.Core.Assets/PackageLoadParameters.cs b/sources/assets/Stride.Core.Assets/PackageLoadParameters.cs index fc3c8e8a22..e05d78cae5 100644 --- a/sources/assets/Stride.Core.Assets/PackageLoadParameters.cs +++ b/sources/assets/Stride.Core.Assets/PackageLoadParameters.cs @@ -1,154 +1,150 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Threading; using Stride.Core.Diagnostics; using Stride.Core.IO; using Stride.Core.Reflection; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Parameters used for loading a package. +/// +public sealed class PackageLoadParameters { + private static readonly PackageLoadParameters DefaultParameters = new(); + + /// + /// Gets the default parameters. + /// + /// PackageLoadParameters. + public static PackageLoadParameters Default() + { + return DefaultParameters.Clone(); + } + + /// + /// Indicates if the given value of should trigger an upgrade. + /// + /// The value to evaluate. + /// True if it should trigger an upgrade, false otherwise. + public static bool ShouldUpgrade(PackageUpgradeRequestedAnswer answer) + { + return answer == PackageUpgradeRequestedAnswer.Upgrade || answer == PackageUpgradeRequestedAnswer.UpgradeAll; + } + + /// + /// Initializes a new instance of the class. + /// + public PackageLoadParameters() + { + LoadMissingDependencies = true; + LoadAssemblyReferences = true; + AutoCompileProjects = true; + AutoLoadTemporaryAssets = true; + ConvertUPathToAbsolute = true; + BuildConfiguration = "Debug"; + } + + /// + /// Gets or sets a value indicating whether [load missing dependencies]. + /// + /// true if [load missing dependencies]; otherwise, false. + public bool LoadMissingDependencies { get; set; } + + /// + /// Gets or sets a value indicating whether [load assembly references]. + /// + /// true if [load assembly references]; otherwise, false. + public bool LoadAssemblyReferences { get; set; } + + /// + /// Gets or sets a value indicating whether to automatically compile projects that don't have their assembly generated. + /// + /// true if [automatic compile projects]; otherwise, false. + public bool AutoCompileProjects { get; set; } + + /// + /// Gets or sets the build configuration used to . + /// + /// The build configuration. + public string BuildConfiguration { get; set; } + + /// + /// Gets or sets the extra compile properties, used when is true. + /// + /// + /// The extra compile parameters. + /// + public Dictionary ExtraCompileProperties { get; set; } + + /// + /// Gets or sets the asset files to load, if you want to not rely on the default . + /// + /// + /// The load asset files. + /// + public List AssetFiles { get; set; } + + /// + /// Gets or sets a value indicating whether to automatically load assets. Default is true + /// + /// true if [automatic load assets]; otherwise, false. + public bool AutoLoadTemporaryAssets { get; set; } + + /// + /// Gets or sets a value indicating whether to convert all to absolute paths when loading. Default + /// is true + /// + /// true if [convert u path to absolute]; otherwise, false. + public bool ConvertUPathToAbsolute { get; set; } + + /// + /// Gets or sets the cancelation token. + /// + /// The cancel token. + public CancellationToken? CancelToken { get; set; } + + /// + /// Gets or sets the assembly container used to load assemblies referenced by the package. If null, will use the + /// + /// + /// The assembly container. + public AssemblyContainer AssemblyContainer { get; set; } + + /// + /// Gets or sets the generate new asset ids. + /// + /// The generate new asset ids. + /// Only makes sense for . + public bool GenerateNewAssetIds { get; set; } + + /// + /// If true, unloadable objects will be removed, similar to . + /// + public bool RemoveUnloadableObjects { get; set; } + + /// + /// Occurs when one or more package upgrades are required for a single package. Returning false will cancel upgrades on this package. + /// + public Func, PackageUpgradeRequestedAnswer> PackageUpgradeRequested; + + /// + /// Occurs when an asset is about to be loaded, if false is returned the asset will be ignored and not loaded. + /// + public Func TemporaryAssetFilter; + + /// + /// Gets a boolean telling if MSBuild files should be evaluated when listing assets. + /// + public bool TemporaryAssetsInMsbuild { get; set; } = true; + /// - /// Parameters used for loading a package. + /// Clones this instance. /// - public sealed class PackageLoadParameters + /// PackageLoadParameters. + public PackageLoadParameters Clone() { - private static readonly PackageLoadParameters DefaultParameters = new PackageLoadParameters(); - - /// - /// Gets the default parameters. - /// - /// PackageLoadParameters. - public static PackageLoadParameters Default() - { - return DefaultParameters.Clone(); - } - - /// - /// Indicates if the given value of should trigger an upgrade. - /// - /// The value to evaluate. - /// True if it should trigger an upgrade, false otherwise. - public static bool ShouldUpgrade(PackageUpgradeRequestedAnswer answer) - { - return answer == PackageUpgradeRequestedAnswer.Upgrade || answer == PackageUpgradeRequestedAnswer.UpgradeAll; - } - - /// - /// Initializes a new instance of the class. - /// - public PackageLoadParameters() - { - LoadMissingDependencies = true; - LoadAssemblyReferences = true; - AutoCompileProjects = true; - AutoLoadTemporaryAssets = true; - ConvertUPathToAbsolute = true; - BuildConfiguration = "Debug"; - } - - /// - /// Gets or sets a value indicating whether [load missing dependencies]. - /// - /// true if [load missing dependencies]; otherwise, false. - public bool LoadMissingDependencies { get; set; } - - /// - /// Gets or sets a value indicating whether [load assembly references]. - /// - /// true if [load assembly references]; otherwise, false. - public bool LoadAssemblyReferences { get; set; } - - /// - /// Gets or sets a value indicating whether to automatically compile projects that don't have their assembly generated. - /// - /// true if [automatic compile projects]; otherwise, false. - public bool AutoCompileProjects { get; set; } - - /// - /// Gets or sets the build configuration used to . - /// - /// The build configuration. - public string BuildConfiguration { get; set; } - - /// - /// Gets or sets the extra compile properties, used when is true. - /// - /// - /// The extra compile parameters. - /// - public Dictionary ExtraCompileProperties { get; set; } - - /// - /// Gets or sets the asset files to load, if you want to not rely on the default . - /// - /// - /// The load asset files. - /// - public List AssetFiles { get; set; } - - /// - /// Gets or sets a value indicating whether to automatically load assets. Default is true - /// - /// true if [automatic load assets]; otherwise, false. - public bool AutoLoadTemporaryAssets { get; set; } - - /// - /// Gets or sets a value indicating whether to convert all to absolute paths when loading. Default - /// is true - /// - /// true if [convert u path to absolute]; otherwise, false. - public bool ConvertUPathToAbsolute { get; set; } - - /// - /// Gets or sets the cancelation token. - /// - /// The cancel token. - public CancellationToken? CancelToken { get; set; } - - /// - /// Gets or sets the assembly container used to load assemblies referenced by the package. If null, will use the - /// - /// - /// The assembly container. - public AssemblyContainer AssemblyContainer { get; set; } - - /// - /// Gets or sets the generate new asset ids. - /// - /// The generate new asset ids. - /// Only makes sense for . - public bool GenerateNewAssetIds { get; set; } - - /// - /// If true, unloadable objects will be removed, similar to . - /// - public bool RemoveUnloadableObjects { get; set; } - - /// - /// Occurs when one or more package upgrades are required for a single package. Returning false will cancel upgrades on this package. - /// - public Func, PackageUpgradeRequestedAnswer> PackageUpgradeRequested; - - /// - /// Occurs when an asset is about to be loaded, if false is returned the asset will be ignored and not loaded. - /// - public Func TemporaryAssetFilter; - - /// - /// Gets a boolean telling if MSBuild files should be evaluated when listing assets. - /// - public bool TemporaryAssetsInMsbuild { get; set; } = true; - - /// - /// Clones this instance. - /// - /// PackageLoadParameters. - public PackageLoadParameters Clone() - { - return (PackageLoadParameters)MemberwiseClone(); - } + return (PackageLoadParameters)MemberwiseClone(); } } diff --git a/sources/assets/Stride.Core.Assets/PackageLoadedAssembly.cs b/sources/assets/Stride.Core.Assets/PackageLoadedAssembly.cs index b659136cc0..050d66c032 100644 --- a/sources/assets/Stride.Core.Assets/PackageLoadedAssembly.cs +++ b/sources/assets/Stride.Core.Assets/PackageLoadedAssembly.cs @@ -1,42 +1,42 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System.Reflection; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Represents an assembly that is loaded at runtime by the package. +/// +public class PackageLoadedAssembly { /// - /// Represents an assembly that is loaded at runtime by the package. + /// Gets the project reference for this assembly. /// - public class PackageLoadedAssembly - { - /// - /// Gets the project reference for this assembly. - /// - /// - /// The project reference. - /// - public ProjectReference ProjectReference { get; private set; } + /// + /// The project reference. + /// + public ProjectReference ProjectReference { get; } - /// - /// Gets the path of the assembly. - /// - /// - /// The path. - /// - public string Path { get; set; } + /// + /// Gets the path of the assembly. + /// + /// + /// The path. + /// + public string Path { get; set; } - /// - /// Gets or sets the loaded assembly. Could be null if not properly loaded. - /// - /// - /// The assembly. - /// - public Assembly Assembly { get; set; } + /// + /// Gets or sets the loaded assembly. Could be null if not properly loaded. + /// + /// + /// The assembly. + /// + public Assembly Assembly { get; set; } - public PackageLoadedAssembly(ProjectReference projectReference, string path) - { - ProjectReference = projectReference; - Path = path; - } + public PackageLoadedAssembly(ProjectReference projectReference, string path) + { + ProjectReference = projectReference; + Path = path; } } diff --git a/sources/assets/Stride.Core.Assets/PackageLoadingAssetFile.cs b/sources/assets/Stride.Core.Assets/PackageLoadingAssetFile.cs index 77ab25a6ec..524ffc90cc 100644 --- a/sources/assets/Stride.Core.Assets/PackageLoadingAssetFile.cs +++ b/sources/assets/Stride.Core.Assets/PackageLoadingAssetFile.cs @@ -1,149 +1,142 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using Stride.Core.Annotations; + using Stride.Core.IO; using Stride.Core.Serialization.Contents; using Stride.Core.Yaml; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Represents an asset before being loaded. Used mostly for asset upgrading. +/// +public class PackageLoadingAssetFile { + public readonly UDirectory SourceFolder; + + public UFile OriginalFilePath { get; set; } + + public UFile FilePath { get; set; } + + public long CachedFileSize; + + // If asset has been created or upgraded in place during package upgrade phase, it will be stored here + public byte[] AssetContent { get; set; } + + public bool Deleted; + + public UFile? AssetLocation => (Link ?? FilePath).MakeRelative(SourceFolder).GetDirectoryAndFileNameWithoutExtension(); + + public UFile? Link { get; set; } + /// - /// Represents an asset before being loaded. Used mostly for asset upgrading. + /// Initializes a new instance of the class. /// - public class PackageLoadingAssetFile + /// The file path. + /// The source folder. + public PackageLoadingAssetFile(UFile filePath, UDirectory sourceFolder) { - public readonly UDirectory SourceFolder; - - [NotNull] public UFile OriginalFilePath { get; set; } - [NotNull] public UFile FilePath { get; set; } - - public long CachedFileSize; + FilePath = filePath; + OriginalFilePath = FilePath; + SourceFolder = sourceFolder; + } - // If asset has been created or upgraded in place during package upgrade phase, it will be stored here - public byte[] AssetContent { get; set; } + /// + /// Initializes a new instance of the class. + /// + /// The package this asset will be part of. + /// The relative file path (from default asset folder). + /// The source folder (optional, can be null). + /// filePath must be relative + public PackageLoadingAssetFile(Package package, UFile filePath, UDirectory sourceFolder) + { + if (filePath.IsAbsolute) + throw new ArgumentException("filePath must be relative", filePath); - public bool Deleted; + SourceFolder = UPath.Combine(package.RootDirectory!, sourceFolder ?? package.GetDefaultAssetFolder()); + FilePath = UPath.Combine(SourceFolder, filePath); + OriginalFilePath = FilePath; + } - public UFile AssetLocation => (Link ?? FilePath).MakeRelative(SourceFolder).GetDirectoryAndFileNameWithoutExtension(); + public IReference ToReference() + { + return new AssetReference(AssetId.Empty, AssetLocation); + } - public UFile Link { get; set; } + public YamlAsset? AsYamlAsset() + { + // The asset file might have been been marked as deleted during the run of asset upgrader. In this case let's just return null. + if (Deleted) + return null; - /// - /// Initializes a new instance of the class. - /// - /// The file path. - /// The source folder. - public PackageLoadingAssetFile([NotNull] UFile filePath, UDirectory sourceFolder) + try { - FilePath = filePath; - OriginalFilePath = FilePath; - SourceFolder = sourceFolder; + return new YamlAsset(this); } - - /// - /// Initializes a new instance of the class. - /// - /// The package this asset will be part of. - /// The relative file path (from default asset folder). - /// The source folder (optional, can be null). - /// filePath must be relative - public PackageLoadingAssetFile(Package package, [NotNull] UFile filePath, UDirectory sourceFolder) + catch (SyntaxErrorException) { - if (filePath.IsAbsolute) - throw new ArgumentException("filePath must be relative", filePath); - - SourceFolder = UPath.Combine(package.RootDirectory, sourceFolder ?? package.GetDefaultAssetFolder()); - FilePath = UPath.Combine(SourceFolder, filePath); - OriginalFilePath = FilePath; + return null; } - - public IReference ToReference() + catch (YamlException) { - return new AssetReference(AssetId.Empty, AssetLocation); + return null; } + } - public YamlAsset AsYamlAsset() - { - // The asset file might have been been marked as deleted during the run of asset upgrader. In this case let's just return null. - if (Deleted) - return null; - - try - { - return new YamlAsset(this); - } - catch (SyntaxErrorException) - { - return null; - } - catch (YamlException) - { - return null; - } - } + public Stream OpenStream() + { + if (Deleted) + throw new InvalidOperationException(); - public Stream OpenStream() - { - if (Deleted) - throw new InvalidOperationException(); + if (AssetContent != null) + return new MemoryStream(AssetContent); - if (AssetContent != null) - return new MemoryStream(AssetContent); + return new FileStream(FilePath.FullPath, FileMode.Open, FileAccess.Read, FileShare.Read); + } - return new FileStream(FilePath.FullPath, FileMode.Open, FileAccess.Read, FileShare.Read); - } + /// + public override string ToString() + { + var result = FilePath.MakeRelative(SourceFolder).ToString(); + if (AssetContent != null) + result += " (Modified)"; + else if (Deleted) + result += " (Deleted)"; - /// - public override string ToString() - { - var result = FilePath.MakeRelative(SourceFolder).ToString(); - if (AssetContent != null) - result += " (Modified)"; - else if (Deleted) - result += " (Deleted)"; + return result; + } - return result; + public sealed class YamlAsset : DynamicYaml, IDisposable + { + public YamlAsset(PackageLoadingAssetFile packageLoadingAssetFile) : base(GetSafeStream(packageLoadingAssetFile)) + { + Asset = packageLoadingAssetFile; } - public class YamlAsset : DynamicYaml, IDisposable + public PackageLoadingAssetFile Asset { get; } + + public void Dispose() { - private readonly PackageLoadingAssetFile packageLoadingAssetFile; - - public YamlAsset(PackageLoadingAssetFile packageLoadingAssetFile) : base(GetSafeStream(packageLoadingAssetFile)) - { - this.packageLoadingAssetFile = packageLoadingAssetFile; - } - - public PackageLoadingAssetFile Asset => packageLoadingAssetFile; - - public void Dispose() - { - // Save asset back to AssetContent - using (var memoryStream = new MemoryStream()) - { - WriteTo(memoryStream, AssetYamlSerializer.Default.GetSerializerSettings()); - packageLoadingAssetFile.AssetContent = memoryStream.ToArray(); - } - } - - private static Stream GetSafeStream(PackageLoadingAssetFile packageLoadingAssetFile) - { - if (packageLoadingAssetFile == null) throw new ArgumentNullException(nameof(packageLoadingAssetFile)); - return packageLoadingAssetFile.OpenStream(); - } + // Save asset back to AssetContent + using var memoryStream = new MemoryStream(); + WriteTo(memoryStream, AssetYamlSerializer.Default.GetSerializerSettings()); + Asset.AssetContent = memoryStream.ToArray(); } - public class FileSizeComparer : Comparer + private static Stream GetSafeStream(PackageLoadingAssetFile packageLoadingAssetFile) { - public new static readonly FileSizeComparer Default = new FileSizeComparer(); + ArgumentNullException.ThrowIfNull(packageLoadingAssetFile); + return packageLoadingAssetFile.OpenStream(); + } + } - public override int Compare(PackageLoadingAssetFile x, PackageLoadingAssetFile y) - { - return y.CachedFileSize.CompareTo(x.CachedFileSize); - } + public class FileSizeComparer : Comparer + { + public static new readonly FileSizeComparer Default = new(); + + public override int Compare(PackageLoadingAssetFile? x, PackageLoadingAssetFile? y) + { + return Comparer.Default.Compare(x?.CachedFileSize, y?.CachedFileSize); } } } diff --git a/sources/assets/Stride.Core.Assets/PackageMeta.cs b/sources/assets/Stride.Core.Assets/PackageMeta.cs index fb63497064..da35e104a0 100644 --- a/sources/assets/Stride.Core.Assets/PackageMeta.cs +++ b/sources/assets/Stride.Core.Assets/PackageMeta.cs @@ -1,207 +1,204 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.ComponentModel; -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Metadata for a accessible from . +/// +[DataContract("PackageMeta")] +public sealed class PackageMeta { /// - /// Metadata for a accessible from . + /// Initializes a new instance of the class. + /// + public PackageMeta() + { + Authors = []; + Owners = []; + } + + /// + /// Gets or sets the identifier name of this package. + /// + /// The name. + [DataMember(10)] + [DefaultValue(null)] + public string? Name { get; set; } + + /// + /// Gets or sets the version of this package. + /// + /// The version. + [DataMember(20)] + [DefaultValue(null)] + public PackageVersion? Version { get; set; } + + /// + /// Gets or sets the title. + /// + /// The title. + [DataMember(30)] + [DefaultValue(null)] + public string? Title { get; set; } + + /// + /// Gets the authors. + /// + /// The authors. + [DataMember(40)] + public List Authors { get; private set; } + + /// + /// Gets the owners. + /// + /// The owners. + [DataMember(50)] + public List Owners { get; private set; } + + /// + /// Gets or sets the icon URL. + /// + /// The icon URL. + [DataMember(60)] + [DefaultValue(null)] + public Uri? IconUrl { get; set; } + + /// + /// Gets or sets the license URL. + /// + /// The license URL. + [DataMember(70)] + [DefaultValue(null)] + public Uri? LicenseUrl { get; set; } + + /// + /// Gets or sets the project URL. + /// + /// The project URL. + [DataMember(80)] + [DefaultValue(null)] + public Uri? ProjectUrl { get; set; } + + /// + /// Gets or sets a value indicating whether it requires license acceptance. + /// + /// true if it requires license acceptance; otherwise, false. + [DataMember(90)] + [DefaultValue(false)] + public bool RequireLicenseAcceptance { get; set; } + + /// + /// Gets or sets the description of this package. + /// + /// The description. + [DataMember(100)] + [DefaultValue(null)] + public string? Description { get; set; } + + /// + /// Gets or sets the summary of this package. + /// + /// The summary. + [DataMember(110)] + [DefaultValue(null)] + public string? Summary { get; set; } + + /// + /// Gets or sets the release notes of this package. + /// + /// The release notes. + [DataMember(120)] + [DefaultValue(null)] + public string? ReleaseNotes { get; set; } + + /// + /// Gets or sets the language supported by this package. + /// + /// The language. + [DataMember(130)] + [DefaultValue(null)] + public string? Language { get; set; } + + /// + /// Gets or sets the tags associated to this package. + /// + /// The tags. + [DataMember(140)] + [DefaultValue(null)] + public string? Tags { get; set; } + + /// + /// Gets or sets the copyright. + /// + /// The copyright. + [DataMember(150)] + [DefaultValue(null)] + public string? Copyright { get; set; } + + /// + /// Gets or sets the default namespace for this package. + /// + /// The default namespace. + [DataMember(155)] + [DefaultValue(null)] + public string? RootNamespace { get; set; } + + /// + /// Gets the package dependencies. + /// + /// The package dependencies. + [DataMember(160)] + public PackageDependencyCollection Dependencies { get; private set; } + + /// + /// Gets the report abuse URL. Only valid for store packages. + /// + /// The report abuse URL. + [DataMemberIgnore] + public Uri ReportAbuseUrl { get; internal set; } + + /// + /// Gets the download count. Only valid for store packages. + /// + /// The download count. + [DataMemberIgnore] + public long DownloadCount { get; internal set; } + + /// + /// Gets a value indicating whether this is listed. /// - [DataContract("PackageMeta")] - public sealed class PackageMeta + /// true if listed; otherwise, false. + [DataMemberIgnore] + public bool Listed { get; internal set; } + + /// + /// Gets the published time. + /// + /// The published. + [DataMemberIgnore] + public DateTimeOffset? Published { get; internal set; } + + /// + /// Creates a new with default values. + /// + /// Name of the package. + /// PackageMeta. + /// packageName + public static PackageMeta NewDefault(string packageName) { - /// - /// Initializes a new instance of the class. - /// - public PackageMeta() - { - Authors = new List(); - Owners = new List(); - } - - /// - /// Gets or sets the identifier name of this package. - /// - /// The name. - [DataMember(10)] - [DefaultValue(null)] - public string Name { get; set; } - - /// - /// Gets or sets the version of this package. - /// - /// The version. - [DataMember(20)] - [DefaultValue(null)] - public PackageVersion Version { get; set; } - - /// - /// Gets or sets the title. - /// - /// The title. - [DataMember(30)] - [DefaultValue(null)] - public string Title { get; set; } - - /// - /// Gets the authors. - /// - /// The authors. - [DataMember(40)] - public List Authors { get; private set; } - - /// - /// Gets the owners. - /// - /// The owners. - [DataMember(50)] - public List Owners { get; private set; } - - /// - /// Gets or sets the icon URL. - /// - /// The icon URL. - [DataMember(60)] - [DefaultValue(null)] - public Uri IconUrl { get; set; } - - /// - /// Gets or sets the license URL. - /// - /// The license URL. - [DataMember(70)] - [DefaultValue(null)] - public Uri LicenseUrl { get; set; } - - /// - /// Gets or sets the project URL. - /// - /// The project URL. - [DataMember(80)] - [DefaultValue(null)] - public Uri ProjectUrl { get; set; } - - /// - /// Gets or sets a value indicating whether it requires license acceptance. - /// - /// true if it requires license acceptance; otherwise, false. - [DataMember(90)] - [DefaultValue(false)] - public bool RequireLicenseAcceptance { get; set; } - - /// - /// Gets or sets the description of this package. - /// - /// The description. - [DataMember(100)] - [DefaultValue(null)] - public string Description { get; set; } - - /// - /// Gets or sets the summary of this package. - /// - /// The summary. - [DataMember(110)] - [DefaultValue(null)] - public string Summary { get; set; } - - /// - /// Gets or sets the release notes of this package. - /// - /// The release notes. - [DataMember(120)] - [DefaultValue(null)] - public string ReleaseNotes { get; set; } - - /// - /// Gets or sets the language supported by this package. - /// - /// The language. - [DataMember(130)] - [DefaultValue(null)] - public string Language { get; set; } - - /// - /// Gets or sets the tags associated to this package. - /// - /// The tags. - [DataMember(140)] - [DefaultValue(null)] - public string Tags { get; set; } - - /// - /// Gets or sets the copyright. - /// - /// The copyright. - [DataMember(150)] - [DefaultValue(null)] - public string Copyright { get; set; } - - /// - /// Gets or sets the default namespace for this package. - /// - /// The default namespace. - [DataMember(155)] - [DefaultValue(null)] - public string RootNamespace { get; set; } - - /// - /// Gets the package dependencies. - /// - /// The package dependencies. - [DataMember(160)] - public PackageDependencyCollection Dependencies { get; private set; } - - /// - /// Gets the report abuse URL. Only valid for store packages. - /// - /// The report abuse URL. - [DataMemberIgnore] - public Uri ReportAbuseUrl { get; internal set; } - - /// - /// Gets the download count. Only valid for store packages. - /// - /// The download count. - [DataMemberIgnore] - public long DownloadCount { get; internal set; } - - /// - /// Gets a value indicating whether this is listed. - /// - /// true if listed; otherwise, false. - [DataMemberIgnore] - public bool Listed { get; internal set; } - - /// - /// Gets the published time. - /// - /// The published. - [DataMemberIgnore] - public DateTimeOffset? Published { get; internal set; } - - /// - /// Creates a new with default values. - /// - /// Name of the package. - /// PackageMeta. - /// packageName - public static PackageMeta NewDefault(string packageName) - { - if (string.IsNullOrWhiteSpace(packageName)) throw new ArgumentNullException("packageName"); - - var meta = new PackageMeta() - { - Name = packageName, - Version = new PackageVersion("1.0.0"), - Description = "Modify description of this package here", - }; - meta.Authors.Add("Modify Author of this package here"); - - return meta; - } + if (string.IsNullOrWhiteSpace(packageName)) throw new ArgumentNullException(nameof(packageName)); + + var meta = new PackageMeta() + { + Name = packageName, + Version = new PackageVersion("1.0.0"), + Description = "Modify description of this package here", + }; + meta.Authors.Add("Modify Author of this package here"); + + return meta; } } diff --git a/sources/assets/Stride.Core.Assets/PackageReferenceBase.cs b/sources/assets/Stride.Core.Assets/PackageReferenceBase.cs index 66cd0acf5d..21308c86d0 100644 --- a/sources/assets/Stride.Core.Assets/PackageReferenceBase.cs +++ b/sources/assets/Stride.Core.Assets/PackageReferenceBase.cs @@ -1,20 +1,17 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Common class used by . +/// +[DataContract("PackageReferenceBase")] +public abstract class PackageReferenceBase { /// - /// Common class used by . + /// Asset references that needs to be compiled even if not directly or indirectly referenced (useful for explicit code references). /// - [DataContract("PackageReferenceBase")] - public abstract class PackageReferenceBase - { - /// - /// Asset references that needs to be compiled even if not directly or indirectly referenced (useful for explicit code references). - /// - [DataMember(100)] - public RootAssetCollection RootAssets { get; private set; } = new RootAssetCollection(); - } + [DataMember(100)] + public RootAssetCollection RootAssets { get; private set; } = []; } diff --git a/sources/assets/Stride.Core.Assets/PackageSaveParameters.cs b/sources/assets/Stride.Core.Assets/PackageSaveParameters.cs index 400e7195b8..3035ca4cc0 100644 --- a/sources/assets/Stride.Core.Assets/PackageSaveParameters.cs +++ b/sources/assets/Stride.Core.Assets/PackageSaveParameters.cs @@ -1,27 +1,25 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets -{ - public class PackageSaveParameters - { - private static readonly PackageSaveParameters DefaultParameters = new PackageSaveParameters(); +namespace Stride.Core.Assets; - public static PackageSaveParameters Default() - { - return DefaultParameters.Clone(); - } +public class PackageSaveParameters +{ + private static readonly PackageSaveParameters DefaultParameters = new(); - /// - /// Clones this instance. - /// - /// PackageLoadParameters. - public PackageSaveParameters Clone() - { - return (PackageSaveParameters)MemberwiseClone(); - } + public static PackageSaveParameters Default() + { + return DefaultParameters.Clone(); + } - public Func AssetFilter; + /// + /// Clones this instance. + /// + /// PackageLoadParameters. + public PackageSaveParameters Clone() + { + return (PackageSaveParameters)MemberwiseClone(); } + + public Func? AssetFilter; } diff --git a/sources/assets/Stride.Core.Assets/PackageSession.Dependencies.cs b/sources/assets/Stride.Core.Assets/PackageSession.Dependencies.cs index 6a05d5ce1e..53ec77e9e5 100644 --- a/sources/assets/Stride.Core.Assets/PackageSession.Dependencies.cs +++ b/sources/assets/Stride.Core.Assets/PackageSession.Dependencies.cs @@ -1,11 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; + using NuGet.Commands; using NuGet.DependencyResolver; using NuGet.ProjectModel; @@ -13,453 +8,453 @@ using Stride.Core.IO; using Stride.Core.Packages; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +partial class PackageSession { - partial class PackageSession + private async Task PreLoadPackageDependencies(ILogger log, SolutionProject project, PackageLoadParameters loadParameters) { - private async Task PreLoadPackageDependencies(ILogger log, SolutionProject project, PackageLoadParameters loadParameters) - { - if (log == null) throw new ArgumentNullException(nameof(log)); - if (project == null) throw new ArgumentNullException(nameof(project)); - if (loadParameters == null) throw new ArgumentNullException(nameof(loadParameters)); + ArgumentNullException.ThrowIfNull(log); + ArgumentNullException.ThrowIfNull(project); + ArgumentNullException.ThrowIfNull(loadParameters); - bool packageDependencyErrors = false; + bool packageDependencyErrors = false; - var package = project.Package; + var package = project.Package; - // TODO: Remove and recheck Dependencies Ready if some secondary packages are removed? - if (package.State >= PackageState.DependenciesReady) - return; + // TODO: Remove and recheck Dependencies Ready if some secondary packages are removed? + if (package.State >= PackageState.DependenciesReady) + return; - log.Verbose($"Process dependencies for {project.Name}..."); + log.Verbose($"Process dependencies for {project.Name}..."); - var packageReferences = new Dictionary(); + var packageReferences = new Dictionary(); - // Check if there is any package upgrade to do - var pendingPackageUpgrades = new List(); - pendingPackageUpgradesPerPackage.Add(package, pendingPackageUpgrades); + // Check if there is any package upgrade to do + var pendingPackageUpgrades = new List(); + pendingPackageUpgradesPerPackage.Add(package, pendingPackageUpgrades); - // Load some informations about the project + // Load some informations about the project + try + { + var extraProperties = new Dictionary(); + if (loadParameters.ExtraCompileProperties != null) + { + foreach (var extraProperty in loadParameters.ExtraCompileProperties) + extraProperties.Add(extraProperty.Key, extraProperty.Value); + } + extraProperties.Add("SkipInvalidConfigurations", "true"); + var msProject = VSProjectHelper.LoadProject(project.FullPath, loadParameters.BuildConfiguration, extraProperties: extraProperties); try { - var extraProperties = new Dictionary(); - if (loadParameters.ExtraCompileProperties != null) + var packageVersion = msProject.GetPropertyValue("PackageVersion"); + if (!string.IsNullOrEmpty(packageVersion)) + package.Meta.Version = new PackageVersion(packageVersion); + + project.TargetPath = msProject.GetPropertyValue("TargetPath"); + project.AssemblyProcessorSerializationHashFile = msProject.GetProperty("StrideAssemblyProcessorSerializationHashFile")?.EvaluatedValue; + if (project.AssemblyProcessorSerializationHashFile != null) + project.AssemblyProcessorSerializationHashFile = Path.Combine(Path.GetDirectoryName(project.FullPath), project.AssemblyProcessorSerializationHashFile); + package.Meta.Name = (msProject.GetProperty("PackageId") ?? msProject.GetProperty("AssemblyName"))?.EvaluatedValue ?? package.Meta.Name; + + var outputType = msProject.GetPropertyValue("OutputType"); + project.Type = outputType.Equals("winexe", StringComparison.InvariantCultureIgnoreCase) + || outputType.Equals("exe", StringComparison.InvariantCultureIgnoreCase) + || outputType.Equals("appcontainerexe", StringComparison.InvariantCultureIgnoreCase) // UWP + || msProject.GetPropertyValue("AndroidApplication").Equals("true", StringComparison.InvariantCultureIgnoreCase) // Android + ? ProjectType.Executable + : ProjectType.Library; + + // Note: Platform might be incorrect if Stride is not restored yet (it won't include Stride targets) + // Also, if already set, don't try to query it again + if (project.Type == ProjectType.Executable && project.Platform == PlatformType.Shared) + project.Platform = VSProjectHelper.GetPlatformTypeFromProject(msProject) ?? PlatformType.Shared; + + foreach (var packageReference in msProject.GetItems("PackageReference").ToList()) { - foreach (var extraProperty in loadParameters.ExtraCompileProperties) - extraProperties.Add(extraProperty.Key, extraProperty.Value); + if (packageReference.HasMetadata("Version") && PackageVersionRange.TryParse(packageReference.GetMetadataValue("Version"), out var packageRange)) + packageReferences[packageReference.EvaluatedInclude] = packageRange; } - extraProperties.Add("SkipInvalidConfigurations", "true"); - var msProject = VSProjectHelper.LoadProject(project.FullPath, loadParameters.BuildConfiguration, extraProperties: extraProperties); - try - { - var packageVersion = msProject.GetPropertyValue("PackageVersion"); - if (!string.IsNullOrEmpty(packageVersion)) - package.Meta.Version = new PackageVersion(packageVersion); - - project.TargetPath = msProject.GetPropertyValue("TargetPath"); - project.AssemblyProcessorSerializationHashFile = msProject.GetProperty("StrideAssemblyProcessorSerializationHashFile")?.EvaluatedValue; - if (project.AssemblyProcessorSerializationHashFile != null) - project.AssemblyProcessorSerializationHashFile = Path.Combine(Path.GetDirectoryName(project.FullPath), project.AssemblyProcessorSerializationHashFile); - package.Meta.Name = (msProject.GetProperty("PackageId") ?? msProject.GetProperty("AssemblyName"))?.EvaluatedValue ?? package.Meta.Name; - - var outputType = msProject.GetPropertyValue("OutputType"); - project.Type = outputType.ToLowerInvariant() == "winexe" || outputType.ToLowerInvariant() == "exe" - || outputType.ToLowerInvariant() == "appcontainerexe" // UWP - || msProject.GetPropertyValue("AndroidApplication").ToLowerInvariant() == "true" // Android - ? ProjectType.Executable - : ProjectType.Library; - - // Note: Platform might be incorrect if Stride is not restored yet (it won't include Stride targets) - // Also, if already set, don't try to query it again - if (project.Type == ProjectType.Executable && project.Platform == PlatformType.Shared) - project.Platform = VSProjectHelper.GetPlatformTypeFromProject(msProject) ?? PlatformType.Shared; - - foreach (var packageReference in msProject.GetItems("PackageReference").ToList()) - { - if (packageReference.HasMetadata("Version") && PackageVersionRange.TryParse(packageReference.GetMetadataValue("Version"), out var packageRange)) - packageReferences[packageReference.EvaluatedInclude] = packageRange; - } - // Need to go recursively - foreach (var projectReference in msProject.GetItems("ProjectReference").ToList()) + // Need to go recursively + foreach (var projectReference in msProject.GetItems("ProjectReference").ToList()) + { + var projectFile = new UFile(Path.Combine(Path.GetDirectoryName(project.FullPath), projectReference.EvaluatedInclude)); + if (File.Exists(projectFile)) { - var projectFile = new UFile(Path.Combine(Path.GetDirectoryName(project.FullPath), projectReference.EvaluatedInclude)); - if (File.Exists(projectFile)) + var referencedProject = Projects.OfType().FirstOrDefault(x => x.FullPath == new UFile(projectFile)); + if (referencedProject != null) { - var referencedProject = Projects.OfType().FirstOrDefault(x => x.FullPath == new UFile(projectFile)); - if (referencedProject != null) - { - await PreLoadPackageDependencies(log, referencedProject, loadParameters); + await PreLoadPackageDependencies(log, referencedProject, loadParameters); - // Get package upgrader from dependency (a project might depend on another project rather than referencing Stride directly) - // A better system would be to evaluate nuget flattened dependencies WITHOUT doing the actual restore (dry-run). - // However I am not sure it's easy/possible to do it (using API) without doing a full restore/download, which we don't want to do - // with old version (it might be uninstalled already and we want to avoid re-downloading it again) - if (pendingPackageUpgradesPerPackage.TryGetValue(referencedProject.Package, out var dependencyPackageUpgraders)) + // Get package upgrader from dependency (a project might depend on another project rather than referencing Stride directly) + // A better system would be to evaluate nuget flattened dependencies WITHOUT doing the actual restore (dry-run). + // However I am not sure it's easy/possible to do it (using API) without doing a full restore/download, which we don't want to do + // with old version (it might be uninstalled already and we want to avoid re-downloading it again) + if (pendingPackageUpgradesPerPackage.TryGetValue(referencedProject.Package, out var dependencyPackageUpgraders)) + { + foreach (var dependencyPackageUpgrader in dependencyPackageUpgraders) { - foreach (var dependencyPackageUpgrader in dependencyPackageUpgraders) + // Make sure this upgrader is not already added + if (!pendingPackageUpgrades.Any(x => x.DependencyPackage == dependencyPackageUpgrader.DependencyPackage)) { - // Make sure this upgrader is not already added - if (!pendingPackageUpgrades.Any(x => x.DependencyPackage == dependencyPackageUpgrader.DependencyPackage)) - { - // Note: it's important to clone because once upgraded, each instance will have its Dependency.Version tested/updated - pendingPackageUpgrades.Add(dependencyPackageUpgrader.Clone()); - } + // Note: it's important to clone because once upgraded, each instance will have its Dependency.Version tested/updated + pendingPackageUpgrades.Add(dependencyPackageUpgrader.Clone()); } } } } } } - finally - { - msProject.ProjectCollection.UnloadAllProjects(); - msProject.ProjectCollection.Dispose(); - } } - catch (Exception ex) + finally { - log.Error($"Unexpected exception while loading project [{project.FullPath.ToOSPath()}]", ex); + msProject.ProjectCollection.UnloadAllProjects(); + msProject.ProjectCollection.Dispose(); } + } + catch (Exception ex) + { + log.Error($"Unexpected exception while loading project [{project.FullPath.ToOSPath()}]", ex); + } - foreach (var packageReference in packageReferences) + foreach (var packageReference in packageReferences) + { + var dependencyName = packageReference.Key; + var dependencyVersion = packageReference.Value; + + var packageUpgrader = AssetRegistry.GetPackageUpgrader(dependencyName); + if (packageUpgrader != null) { - var dependencyName = packageReference.Key; - var dependencyVersion = packageReference.Value; + // Check if this upgrader has already been added due to another package reference + if (pendingPackageUpgrades.Any(pendingPackageUpgrade => pendingPackageUpgrade.PackageUpgrader == packageUpgrader)) + continue; - var packageUpgrader = AssetRegistry.GetPackageUpgrader(dependencyName); - if (packageUpgrader != null) + // Check if upgrade is necessary + if (dependencyVersion.MinVersion >= packageUpgrader.Attribute.UpdatedVersionRange.MinVersion) { - // Check if this upgrader has already been added due to another package reference - if (pendingPackageUpgrades.Any(pendingPackageUpgrade => pendingPackageUpgrade.PackageUpgrader == packageUpgrader)) - continue; - - // Check if upgrade is necessary - if (dependencyVersion.MinVersion >= packageUpgrader.Attribute.UpdatedVersionRange.MinVersion) - { - continue; - } + continue; + } - // Check if upgrade is allowed - if (dependencyVersion.MinVersion < packageUpgrader.Attribute.PackageMinimumVersion) - { - // Throw an exception, because the package update is not allowed and can't be done - throw new InvalidOperationException($"Upgrading project [{project.Name}] to use [{dependencyName}] from version [{dependencyVersion}] to [{packageUpgrader.Attribute.UpdatedVersionRange.MinVersion}] is not supported (supported only from version [{packageUpgrader.Attribute.PackageMinimumVersion}]"); - } + // Check if upgrade is allowed + if (dependencyVersion.MinVersion < packageUpgrader.Attribute.PackageMinimumVersion) + { + // Throw an exception, because the package update is not allowed and can't be done + throw new InvalidOperationException($"Upgrading project [{project.Name}] to use [{dependencyName}] from version [{dependencyVersion}] to [{packageUpgrader.Attribute.UpdatedVersionRange.MinVersion}] is not supported (supported only from version [{packageUpgrader.Attribute.PackageMinimumVersion}]"); + } - log.Info($"Upgrading project [{project.Name}] to use [{dependencyName}] from version [{dependencyVersion}] to [{packageUpgrader.Attribute.UpdatedVersionRange.MinVersion}] will be required"); + log.Info($"Upgrading project [{project.Name}] to use [{dependencyName}] from version [{dependencyVersion}] to [{packageUpgrader.Attribute.UpdatedVersionRange.MinVersion}] will be required"); - pendingPackageUpgrades.Add(new PendingPackageUpgrade(packageUpgrader, new PackageDependency(dependencyName, dependencyVersion), null)); - } + pendingPackageUpgrades.Add(new PendingPackageUpgrade(packageUpgrader, new PackageDependency(dependencyName, dependencyVersion), null)); } + } - if (pendingPackageUpgrades.Count > 0) + if (pendingPackageUpgrades.Count > 0) + { + var upgradeAllowed = packageUpgradeAllowed != false ? PackageUpgradeRequestedAnswer.Upgrade : PackageUpgradeRequestedAnswer.DoNotUpgrade; + + // Need upgrades, let's ask user confirmation + if (loadParameters.PackageUpgradeRequested != null && !packageUpgradeAllowed.HasValue) { - var upgradeAllowed = packageUpgradeAllowed != false ? PackageUpgradeRequestedAnswer.Upgrade : PackageUpgradeRequestedAnswer.DoNotUpgrade; + upgradeAllowed = loadParameters.PackageUpgradeRequested(package, pendingPackageUpgrades); + if (upgradeAllowed == PackageUpgradeRequestedAnswer.UpgradeAll) + packageUpgradeAllowed = true; + if (upgradeAllowed == PackageUpgradeRequestedAnswer.DoNotUpgradeAny) + packageUpgradeAllowed = false; + } - // Need upgrades, let's ask user confirmation - if (loadParameters.PackageUpgradeRequested != null && !packageUpgradeAllowed.HasValue) - { - upgradeAllowed = loadParameters.PackageUpgradeRequested(package, pendingPackageUpgrades); - if (upgradeAllowed == PackageUpgradeRequestedAnswer.UpgradeAll) - packageUpgradeAllowed = true; - if (upgradeAllowed == PackageUpgradeRequestedAnswer.DoNotUpgradeAny) - packageUpgradeAllowed = false; - } + if (!PackageLoadParameters.ShouldUpgrade(upgradeAllowed)) + { + log.Error($"Necessary package migration for [{package.Meta.Name}] has not been allowed"); + return; + } - if (!PackageLoadParameters.ShouldUpgrade(upgradeAllowed)) - { - log.Error($"Necessary package migration for [{package.Meta.Name}] has not been allowed"); - return; - } + // Perform pre assembly load upgrade + foreach (var pendingPackageUpgrade in pendingPackageUpgrades) + { + var expectedVersion = pendingPackageUpgrade.PackageUpgrader.Attribute.UpdatedVersionRange?.MinVersion?.ToString(); - // Perform pre assembly load upgrade - foreach (var pendingPackageUpgrade in pendingPackageUpgrades) + // Update NuGet references + try { - var expectedVersion = pendingPackageUpgrade.PackageUpgrader.Attribute.UpdatedVersionRange.MinVersion.ToString(); + var projectFile = project.FullPath; + var msbuildProject = VSProjectHelper.LoadProject(projectFile.ToOSPath()); + var isProjectDirty = false; - // Update NuGet references - try + foreach (var packageReference in msbuildProject.GetItems("PackageReference").ToList()) { - var projectFile = project.FullPath; - var msbuildProject = VSProjectHelper.LoadProject(projectFile.ToOSPath()); - var isProjectDirty = false; - - foreach (var packageReference in msbuildProject.GetItems("PackageReference").ToList()) + if (packageReference.EvaluatedInclude == pendingPackageUpgrade.Dependency.Name && packageReference.GetMetadataValue("Version") != expectedVersion) { - if (packageReference.EvaluatedInclude == pendingPackageUpgrade.Dependency.Name && packageReference.GetMetadataValue("Version") != expectedVersion) - { - packageReference.SetMetadataValue("Version", expectedVersion); - isProjectDirty = true; - } + packageReference.SetMetadataValue("Version", expectedVersion); + isProjectDirty = true; } + } - if (isProjectDirty) - msbuildProject.Save(); + if (isProjectDirty) + msbuildProject.Save(); - msbuildProject.ProjectCollection.UnloadAllProjects(); - msbuildProject.ProjectCollection.Dispose(); - } - catch (Exception e) - { - log.Warning($"Unable to load project [{project.FullPath.GetFileName()}]", e); - } + msbuildProject.ProjectCollection.UnloadAllProjects(); + msbuildProject.ProjectCollection.Dispose(); + } + catch (Exception e) + { + log.Warning($"Unable to load project [{project.FullPath.GetFileName()}]", e); + } - var packageUpgrader = pendingPackageUpgrade.PackageUpgrader; - var dependencyPackage = pendingPackageUpgrade.DependencyPackage; - if (!packageUpgrader.UpgradeBeforeAssembliesLoaded(loadParameters, package.Session, log, package, pendingPackageUpgrade.Dependency, dependencyPackage)) - { - log.Error($"Error while upgrading package [{package.Meta.Name}] for [{dependencyPackage.Meta.Name}] from version [{pendingPackageUpgrade.Dependency.Version}] to [{dependencyPackage.Meta.Version}]"); - return; - } + var packageUpgrader = pendingPackageUpgrade.PackageUpgrader; + var dependencyPackage = pendingPackageUpgrade.DependencyPackage; + if (!packageUpgrader.UpgradeBeforeAssembliesLoaded(loadParameters, package.Session, log, package, pendingPackageUpgrade.Dependency, dependencyPackage)) + { + log.Error($"Error while upgrading package [{package.Meta.Name}] for [{dependencyPackage.Meta.Name}] from version [{pendingPackageUpgrade.Dependency.Version}] to [{dependencyPackage.Meta.Version}]"); + return; } } + } - // Now that our references are upgraded, let's do a real nuget restore (download files) - log.Verbose($"Restore NuGet packages for {project.Name}..."); - if (loadParameters.AutoCompileProjects) - await VSProjectHelper.RestoreNugetPackages(log, project.FullPath); + // Now that our references are upgraded, let's do a real nuget restore (download files) + log.Verbose($"Restore NuGet packages for {project.Name}..."); + if (loadParameters.AutoCompileProjects) + await VSProjectHelper.RestoreNugetPackages(log, project.FullPath); - // If platform was unknown (due to missing nuget packages during first pass), check it again - if (project.Type == ProjectType.Executable && project.Platform == PlatformType.Shared) + // If platform was unknown (due to missing nuget packages during first pass), check it again + if (project.Type == ProjectType.Executable && project.Platform == PlatformType.Shared) + { + try { + var msProject = VSProjectHelper.LoadProject(project.FullPath, extraProperties: new Dictionary { { "SkipInvalidConfigurations", "true" } }); try { - var msProject = VSProjectHelper.LoadProject(project.FullPath, extraProperties: new Dictionary { { "SkipInvalidConfigurations", "true" } }); - try - { - project.Platform = VSProjectHelper.GetPlatformTypeFromProject(msProject) ?? PlatformType.Shared; - } - finally - { - msProject.ProjectCollection.UnloadAllProjects(); - msProject.ProjectCollection.Dispose(); - } + project.Platform = VSProjectHelper.GetPlatformTypeFromProject(msProject) ?? PlatformType.Shared; } - catch (Exception ex) + finally { - log.Error($"Unexpected exception while loading project [{project.FullPath.ToOSPath()}]", ex); + msProject.ProjectCollection.UnloadAllProjects(); + msProject.ProjectCollection.Dispose(); } } + catch (Exception ex) + { + log.Error($"Unexpected exception while loading project [{project.FullPath.ToOSPath()}]", ex); + } + } + + UpdateDependencies(project, true, true); - UpdateDependencies(project, true, true); + // 1. Load store package + foreach (var projectDependency in project.FlattenedDependencies) + { + // Make all the assemblies known to the container to ensure that later assembly loads succeed + foreach (var assembly in projectDependency.Assemblies) + AssemblyContainer.RegisterDependency(assembly); - // 1. Load store package - foreach (var projectDependency in project.FlattenedDependencies) + var loadedPackage = packages.Find(projectDependency); + if (loadedPackage == null) { - // Make all the assemblies known to the container to ensure that later assembly loads succeed - foreach (var assembly in projectDependency.Assemblies) - AssemblyContainer.RegisterDependency(assembly); + string? file = null; + switch (projectDependency.Type) + { + case DependencyType.Project: + if (SupportedProgrammingLanguages.IsProjectExtensionSupported(Path.GetExtension(projectDependency.MSBuildProject).ToLowerInvariant())) + file = UPath.Combine(project.FullPath.GetFullDirectory(), (UFile)projectDependency.MSBuildProject); + break; + case DependencyType.Package: + file = PackageStore.Instance.GetPackageFileName(projectDependency.Name, new PackageVersionRange(projectDependency.Version), constraintProvider); + break; + } - var loadedPackage = packages.Find(projectDependency); - if (loadedPackage == null) + if (file != null && File.Exists(file)) { - string file = null; - switch (projectDependency.Type) - { - case DependencyType.Project: - if (SupportedProgrammingLanguages.IsProjectExtensionSupported(Path.GetExtension(projectDependency.MSBuildProject).ToLowerInvariant())) - file = UPath.Combine(project.FullPath.GetFullDirectory(), (UFile)projectDependency.MSBuildProject); - break; - case DependencyType.Package: - file = PackageStore.Instance.GetPackageFileName(projectDependency.Name, new PackageVersionRange(projectDependency.Version), constraintProvider); - break; - } + // Load package + var loadedProject = LoadProject(log, file, loadParameters); + loadedProject.Package.Meta.Name = projectDependency.Name; + loadedProject.Package.Meta.Version = projectDependency.Version; + Projects.Add(loadedProject); - if (file != null && File.Exists(file)) + if (loadedProject is StandalonePackage standalonePackage) { - // Load package - var loadedProject = LoadProject(log, file, loadParameters); - loadedProject.Package.Meta.Name = projectDependency.Name; - loadedProject.Package.Meta.Version = projectDependency.Version; - Projects.Add(loadedProject); - - if (loadedProject is StandalonePackage standalonePackage) - { - standalonePackage.Assemblies.AddRange(projectDependency.Assemblies); - } - - loadedPackage = loadedProject.Package; + standalonePackage.Assemblies.AddRange(projectDependency.Assemblies); } - } - if (loadedPackage != null) - projectDependency.Package = loadedPackage; + loadedPackage = loadedProject.Package; + } } - // 2. Load local packages - /*foreach (var packageReference in package.LocalDependencies) + if (loadedPackage != null) + projectDependency.Package = loadedPackage; + } + + // 2. Load local packages + /*foreach (var packageReference in package.LocalDependencies) + { + // Check that the package was not already loaded, otherwise return the same instance + if (Packages.ContainsById(packageReference.Id)) { - // Check that the package was not already loaded, otherwise return the same instance - if (Packages.ContainsById(packageReference.Id)) - { - continue; - } + continue; + } - // Expand the string of the location - var newLocation = packageReference.Location; + // Expand the string of the location + var newLocation = packageReference.Location; - var subPackageFilePath = package.RootDirectory != null ? UPath.Combine(package.RootDirectory, newLocation) : newLocation; + var subPackageFilePath = package.RootDirectory != null ? UPath.Combine(package.RootDirectory, newLocation) : newLocation; - // Recursive load - var loadedPackage = PreLoadPackage(log, subPackageFilePath.FullPath, false, loadedPackages, loadParameters); + // Recursive load + var loadedPackage = PreLoadPackage(log, subPackageFilePath.FullPath, false, loadedPackages, loadParameters); - if (loadedPackage == null || loadedPackage.State < PackageState.DependenciesReady) - packageDependencyErrors = true; - }*/ + if (loadedPackage == null || loadedPackage.State < PackageState.DependenciesReady) + packageDependencyErrors = true; + }*/ - // 3. Update package state - if (!packageDependencyErrors) - { - package.State = PackageState.DependenciesReady; - } + // 3. Update package state + if (!packageDependencyErrors) + { + package.State = PackageState.DependenciesReady; } + } - public static void UpdateDependencies(SolutionProject project, bool directDependencies, bool flattenedDependencies) + public static void UpdateDependencies(SolutionProject project, bool directDependencies, bool flattenedDependencies) + { + if (flattenedDependencies) + project.FlattenedDependencies.Clear(); + if (directDependencies) + project.DirectDependencies.Clear(); + var projectAssetsJsonPath = Path.Combine(project.FullPath.GetFullDirectory(), @"obj", LockFileFormat.AssetsFileName); + if (File.Exists(projectAssetsJsonPath)) { + var format = new LockFileFormat(); + var projectAssets = format.Read(projectAssetsJsonPath); + + // Update dependencies if (flattenedDependencies) - project.FlattenedDependencies.Clear(); - if (directDependencies) - project.DirectDependencies.Clear(); - var projectAssetsJsonPath = Path.Combine(project.FullPath.GetFullDirectory(), @"obj", LockFileFormat.AssetsFileName); - if (File.Exists(projectAssetsJsonPath)) { - var format = new LockFileFormat(); - var projectAssets = format.Read(projectAssetsJsonPath); + var libPaths = new Dictionary<(string?, NuGet.Versioning.NuGetVersion?), LockFileLibrary>(); + foreach (var lib in projectAssets.Libraries) + { + libPaths.Add((lib.Name, lib.Version), lib); + } - // Update dependencies - if (flattenedDependencies) + foreach (var targetLibrary in projectAssets.Targets.Last().Libraries) { - var libPaths = new Dictionary, LockFileLibrary>(); - foreach (var lib in projectAssets.Libraries) - { - libPaths.Add(ValueTuple.Create(lib.Name, lib.Version), lib); - } + if (!libPaths.TryGetValue((targetLibrary.Name, targetLibrary.Version), out var library)) + continue; - foreach (var targetLibrary in projectAssets.Targets.Last().Libraries) - { - if (!libPaths.TryGetValue(ValueTuple.Create(targetLibrary.Name, targetLibrary.Version), out var library)) - continue; + var projectDependency = new Dependency(library.Name, library.Version.ToPackageVersion(), library.Type == "project" ? DependencyType.Project : DependencyType.Package) { MSBuildProject = library.Type == "project" ? library.MSBuildProject : null }; - var projectDependency = new Dependency(library.Name, library.Version.ToPackageVersion(), library.Type == "project" ? DependencyType.Project : DependencyType.Package) { MSBuildProject = library.Type == "project" ? library.MSBuildProject : null }; + if (library.Type == "package") + { + // Find library path by testing with each PackageFolders + var libraryPath = projectAssets.PackageFolders + .Select(packageFolder => Path.Combine(packageFolder.Path, library.Path.Replace('/', Path.DirectorySeparatorChar))) + .FirstOrDefault(x => Directory.Exists(x)); - if (library.Type == "package") + if (libraryPath != null) { - // Find library path by testing with each PackageFolders - var libraryPath = projectAssets.PackageFolders - .Select(packageFolder => Path.Combine(packageFolder.Path, library.Path.Replace('/', Path.DirectorySeparatorChar))) - .FirstOrDefault(x => Directory.Exists(x)); - - if (libraryPath != null) + // Build list of assemblies + foreach (var a in targetLibrary.RuntimeAssemblies) { - // Build list of assemblies - foreach (var a in targetLibrary.RuntimeAssemblies) + if (!a.Path.EndsWith("_._", StringComparison.Ordinal) && !a.Path.Contains("/native/")) { - if (!a.Path.EndsWith("_._", StringComparison.Ordinal) && !a.Path.Contains("/native/")) - { - var assemblyFile = Path.Combine(libraryPath, a.Path.Replace('/', Path.DirectorySeparatorChar)); - projectDependency.Assemblies.Add(assemblyFile); - } + var assemblyFile = Path.Combine(libraryPath, a.Path.Replace('/', Path.DirectorySeparatorChar)); + projectDependency.Assemblies.Add(assemblyFile); } - foreach (var a in targetLibrary.RuntimeTargets) + } + foreach (var a in targetLibrary.RuntimeTargets) + { + if (!a.Path.EndsWith("_._", StringComparison.Ordinal) && !a.Path.Contains("/native/")) { - if (!a.Path.EndsWith("_._", StringComparison.Ordinal) && !a.Path.Contains("/native/")) - { - var assemblyFile = Path.Combine(libraryPath, a.Path.Replace('/', Path.DirectorySeparatorChar)); - projectDependency.Assemblies.Add(assemblyFile); - } + var assemblyFile = Path.Combine(libraryPath, a.Path.Replace('/', Path.DirectorySeparatorChar)); + projectDependency.Assemblies.Add(assemblyFile); } } } - - project.FlattenedDependencies.Add(projectDependency); - // Try to resolve package if already loaded - projectDependency.Package = project.Session.Packages.Find(projectDependency); } + + project.FlattenedDependencies.Add(projectDependency); + // Try to resolve package if already loaded + projectDependency.Package = project.Session.Packages.Find(projectDependency); } + } - if (directDependencies) + if (directDependencies) + { + foreach (var projectReference in projectAssets.PackageSpec.RestoreMetadata.TargetFrameworks.First().ProjectReferences) { - foreach (var projectReference in projectAssets.PackageSpec.RestoreMetadata.TargetFrameworks.First().ProjectReferences) - { - var projectName = new UFile(projectReference.ProjectUniqueName).GetFileNameWithoutExtension(); - project.DirectDependencies.Add(new DependencyRange(projectName, null, DependencyType.Project) { MSBuildProject = projectReference.ProjectPath }); - } + var projectName = new UFile(projectReference.ProjectUniqueName).GetFileNameWithoutExtension(); + project.DirectDependencies.Add(new DependencyRange(projectName, null, DependencyType.Project) { MSBuildProject = projectReference.ProjectPath }); + } - foreach (var dependency in projectAssets.PackageSpec.TargetFrameworks.First().Dependencies) - { - if (dependency.AutoReferenced) - continue; - project.DirectDependencies.Add(new DependencyRange(dependency.Name, dependency.LibraryRange.VersionRange.ToPackageVersionRange(), DependencyType.Package)); - } + foreach (var dependency in projectAssets.PackageSpec.TargetFrameworks.First().Dependencies) + { + if (dependency.AutoReferenced) + continue; + project.DirectDependencies.Add(new DependencyRange(dependency.Name, dependency.LibraryRange.VersionRange.ToPackageVersionRange(), DependencyType.Package)); } } } + } + + private static ExternalProjectReference ToExternalProjectReference(PackageSpec project) + { + return new ExternalProjectReference( + project.Name, + project, + msbuildProjectPath: null, + projectReferences: []); + } - private static ExternalProjectReference ToExternalProjectReference(PackageSpec project) + private static List GetProjectReferences(RestoreRequest _request, RemoteWalkContext context) + { + // External references + var updatedExternalProjects = new List(); + + if (_request.ExternalProjects.Count == 0) { - return new ExternalProjectReference( - project.Name, - project, - msbuildProjectPath: null, - projectReferences: Enumerable.Empty()); + // If no projects exist add the current project.json file to the project + // list so that it can be resolved. + updatedExternalProjects.Add(ToExternalProjectReference(_request.Project)); } - - private static List GetProjectReferences(RestoreRequest _request, RemoteWalkContext context) + else if (_request.ExternalProjects.Count > 0) { - // External references - var updatedExternalProjects = new List(); - - if (_request.ExternalProjects.Count == 0) + // There should be at most one match in the external projects. + var rootProjectMatches = _request.ExternalProjects.Where(proj => + string.Equals( + _request.Project.Name, + proj.PackageSpecProjectName, + StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (rootProjectMatches.Count > 1) { - // If no projects exist add the current project.json file to the project - // list so that it can be resolved. - updatedExternalProjects.Add(ToExternalProjectReference(_request.Project)); + throw new InvalidOperationException($"Ambiguous project name '{_request.Project.Name}'."); } - else if (_request.ExternalProjects.Count > 0) - { - // There should be at most one match in the external projects. - var rootProjectMatches = _request.ExternalProjects.Where(proj => - string.Equals( - _request.Project.Name, - proj.PackageSpecProjectName, - StringComparison.OrdinalIgnoreCase)) - .ToList(); - - if (rootProjectMatches.Count > 1) - { - throw new InvalidOperationException($"Ambiguous project name '{_request.Project.Name}'."); - } - var rootProject = rootProjectMatches.SingleOrDefault(); + var rootProject = rootProjectMatches.SingleOrDefault(); - if (rootProject != null) - { - // Replace the project spec with the passed in package spec, - // for installs which are done in memory first this will be - // different from the one on disk - updatedExternalProjects.AddRange(_request.ExternalProjects - .Where(project => - !project.UniqueName.Equals(rootProject.UniqueName, StringComparison.Ordinal))); - - var updatedReference = new ExternalProjectReference( - rootProject.UniqueName, - _request.Project, - rootProject.MSBuildProjectPath, - rootProject.ExternalProjectReferences); - - updatedExternalProjects.Add(updatedReference); - } - } - else + if (rootProject != null) { - // External references were passed, but the top level project wasn't found. - // This is always due to an internal issue and typically caused by errors - // building the project closure. - throw new InvalidOperationException($"Missing external reference metadata for {_request.Project.Name}"); + // Replace the project spec with the passed in package spec, + // for installs which are done in memory first this will be + // different from the one on disk + updatedExternalProjects.AddRange(_request.ExternalProjects + .Where(project => + !project.UniqueName.Equals(rootProject.UniqueName, StringComparison.Ordinal))); + + var updatedReference = new ExternalProjectReference( + rootProject.UniqueName, + _request.Project, + rootProject.MSBuildProjectPath, + rootProject.ExternalProjectReferences); + + updatedExternalProjects.Add(updatedReference); } - - return updatedExternalProjects; } + else + { + // External references were passed, but the top level project wasn't found. + // This is always due to an internal issue and typically caused by errors + // building the project closure. + throw new InvalidOperationException($"Missing external reference metadata for {_request.Project.Name}"); + } + + return updatedExternalProjects; } } diff --git a/sources/assets/Stride.Core.Assets/PackageSession.Extensions.cs b/sources/assets/Stride.Core.Assets/PackageSession.Extensions.cs index 4c61e4ce32..ef71f55348 100644 --- a/sources/assets/Stride.Core.Assets/PackageSession.Extensions.cs +++ b/sources/assets/Stride.Core.Assets/PackageSession.Extensions.cs @@ -1,73 +1,64 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Linq; + using Stride.Core.Assets.Analysis; -using Stride.Core.IO; -using Stride.Core.Serialization; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Extension methods for . +/// +public static class PackageSessionExtensions { /// - /// Extension methods for . + /// Create a that can be used to compile an by analyzing and resolving its dependencies. /// - public static class PackageSessionExtensions + /// The package packageSession that can be used to compile the asset item. + public static Package CreateCompilePackageFromAsset(this PackageSession session, AssetItem originalAssetItem) { - /// - /// Create a that can be used to compile an by analyzing and resolving its dependencies. - /// - /// The package packageSession that can be used to compile the asset item. - public static Package CreateCompilePackageFromAsset(this PackageSession session, AssetItem originalAssetItem) - { - // create the compile root package and package session - var assetPackageCloned = new Package(); - //the following line is necessary to attach a session to the package - // ReSharper disable once UnusedVariable - var compilePackageSession = new PackageSession(assetPackageCloned); + // create the compile root package and package session + var assetPackageCloned = new Package(); + //the following line is necessary to attach a session to the package + // ReSharper disable once UnusedVariable + _ = new PackageSession(assetPackageCloned); - AddAssetToCompilePackage(session, originalAssetItem, assetPackageCloned); + AddAssetToCompilePackage(session, originalAssetItem, assetPackageCloned); - return assetPackageCloned; - } - - public static void AddAssetToCompilePackage(this PackageSession session, AssetItem originalAssetItem, Package assetPackageCloned) - { - if (originalAssetItem == null) throw new ArgumentNullException("originalAssetItem"); + return assetPackageCloned; + } - // Find the asset from the session - var assetItem = originalAssetItem.Package.FindAsset(originalAssetItem.Id); - if (assetItem == null) - { - throw new ArgumentException("Cannot find the specified AssetItem instance in the session"); - } + public static void AddAssetToCompilePackage(this PackageSession session, AssetItem originalAssetItem, Package assetPackageCloned) + { + ArgumentNullException.ThrowIfNull(originalAssetItem); - // Calculate dependencies - // Search only for references - var dependencies = session.DependencyManager.ComputeDependencies(assetItem.Id, AssetDependencySearchOptions.Out | AssetDependencySearchOptions.Recursive, ContentLinkType.Reference); - if (dependencies == null) - throw new InvalidOperationException("The asset doesn't exist in the dependency manager anymore"); + // Find the asset from the session + var assetItem = originalAssetItem.Package?.FindAsset(originalAssetItem.Id) + ?? throw new ArgumentException("Cannot find the specified AssetItem instance in the session"); - var assetItemRootCloned = dependencies.Item.Clone(); + // Calculate dependencies + // Search only for references + var dependencies = session.DependencyManager.ComputeDependencies(assetItem.Id, AssetDependencySearchOptions.Out | AssetDependencySearchOptions.Recursive, ContentLinkType.Reference) + ?? throw new InvalidOperationException("The asset doesn't exist in the dependency manager anymore"); + var assetItemRootCloned = dependencies.Item.Clone(); - // Store the fullpath to the sourcefolder, this avoid us to clone hierarchy of packages - assetItemRootCloned.SourceFolder = assetItem.FullPath.GetParent(); + // Store the fullpath to the sourcefolder, this avoid us to clone hierarchy of packages + assetItemRootCloned.SourceFolder = assetItem.FullPath.GetParent(); - if (assetPackageCloned.Assets.Find(assetItemRootCloned.Id) == null) - assetPackageCloned.Assets.Add(assetItemRootCloned); + if (assetPackageCloned.Assets.Find(assetItemRootCloned.Id) == null) + assetPackageCloned.Assets.Add(assetItemRootCloned); - // For each asset item dependency, clone it in the new package - foreach (var assetLink in dependencies.LinksOut) + // For each asset item dependency, clone it in the new package + foreach (var assetLink in dependencies.LinksOut) + { + // Only add assets not already added (in case of circular dependencies) + if (assetPackageCloned.Assets.Find(assetLink.Item.Id) == null) { - // Only add assets not already added (in case of circular dependencies) - if (assetPackageCloned.Assets.Find(assetLink.Item.Id) == null) - { - // create a copy of the asset item and add it to the appropriate compile package - var itemCloned = assetLink.Item.Clone(); + // create a copy of the asset item and add it to the appropriate compile package + var itemCloned = assetLink.Item.Clone(); - // Store the fullpath to the sourcefolder, this avoid us to clone hierarchy of packages - itemCloned.SourceFolder = assetLink.Item.FullPath.GetParent(); - assetPackageCloned.Assets.Add(itemCloned); - } + // Store the fullpath to the sourcefolder, this avoid us to clone hierarchy of packages + itemCloned.SourceFolder = assetLink.Item.FullPath.GetParent(); + assetPackageCloned.Assets.Add(itemCloned); } } } diff --git a/sources/assets/Stride.Core.Assets/PackageSession.cs b/sources/assets/Stride.Core.Assets/PackageSession.cs index e0369b5449..c0f05c915d 100644 --- a/sources/assets/Stride.Core.Assets/PackageSession.cs +++ b/sources/assets/Stride.Core.Assets/PackageSession.cs @@ -1,15 +1,11 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading.Tasks; using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.Assets.Analysis; using Stride.Core.Assets.Diagnostics; using Stride.Core.Assets.Tracking; @@ -22,1610 +18,1584 @@ using static Stride.Core.Assets.PackageSession; using ILogger = Stride.Core.Diagnostics.ILogger; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +record AssetLoadingInfo(PackageSession session, ILogger log, Package package, PackageLoadParameters loadParameters, List pendingPackageUpgrades, PackageLoadParameters newLoadParameters); + +public abstract class PackageContainer { - record AssetLoadingInfo(PackageSession session, ILogger log, Package package, PackageLoadParameters loadParameters, List pendingPackageUpgrades, PackageLoadParameters newLoadParameters); - public abstract class PackageContainer + public PackageContainer(Package package) { - public PackageContainer([NotNull] Package package) - { - Package = package; - Package.Container = this; - } + Package = package; + Package.Container = this; + } + + /// + /// Gets the session. + /// + public PackageSession? Session { get; private set; } - /// - /// Gets the session. - /// - [CanBeNull] - public PackageSession Session { get; private set; } + public Package Package { get; } - [NotNull] - public Package Package { get; } + public ObservableCollection DirectDependencies { get; } = []; - public ObservableCollection DirectDependencies { get; } = new ObservableCollection(); + public ObservableCollection FlattenedDependencies { get; } = []; - public ObservableCollection FlattenedDependencies { get; } = new ObservableCollection(); + /// + /// Saves this package and all dirty assets. See remarks. + /// + /// The log. + /// log + /// When calling this method directly, it does not handle moving assets between packages. + /// Call instead. + public void Save(ILogger log, PackageSaveParameters? saveParameters = null) + { + ArgumentNullException.ThrowIfNull(log); - /// - /// Saves this package and all dirty assets. See remarks. - /// - /// The log. - /// log - /// When calling this method directly, it does not handle moving assets between packages. - /// Call instead. - public void Save(ILogger log, PackageSaveParameters saveParameters = null) + if (Package.FullPath is null) { - if (log == null) throw new ArgumentNullException(nameof(log)); + log.Error(Package, null, AssetMessageCode.PackageCannotSave, "null"); + return; + } - if (Package.FullPath == null) - { - log.Error(Package, null, AssetMessageCode.PackageCannotSave, "null"); - return; - } + saveParameters ??= PackageSaveParameters.Default(); - saveParameters = saveParameters ?? PackageSaveParameters.Default(); + // Use relative paths when saving + var analysis = new PackageAnalysis(Package, new PackageAnalysisParameters() + { + SetDirtyFlagOnAssetWhenFixingUFile = false, + ConvertUPathTo = UPathType.Relative, + IsProcessingUPaths = true, + }); + analysis.Run(log); + + var assetsFiltered = false; + try + { + // Update source folders + Package.UpdateSourceFolders(Package.Assets); - // Use relative paths when saving - var analysis = new PackageAnalysis(Package, new PackageAnalysisParameters() + if (Package.IsDirty) { - SetDirtyFlagOnAssetWhenFixingUFile = false, - ConvertUPathTo = UPathType.Relative, - IsProcessingUPaths = true, - }); - analysis.Run(log); - - var assetsFiltered = false; - try - { - // Update source folders - Package.UpdateSourceFolders(Package.Assets); + List filesToDeleteLocal; + var filesToDelete = Package.FilesToDelete; + lock (filesToDelete) + { + filesToDeleteLocal = [.. filesToDelete]; + filesToDelete.Clear(); + } - if (Package.IsDirty) + try { - List filesToDeleteLocal; - var filesToDelete = Package.FilesToDelete; - lock (filesToDelete) + SavePackage(); + + // Move the package if the path has changed + if (Package.PreviousPackagePath is not null && Package.PreviousPackagePath != Package.FullPath) { - filesToDeleteLocal = filesToDelete.ToList(); - filesToDelete.Clear(); + filesToDeleteLocal.Add(Package.PreviousPackagePath); } + Package.PreviousPackagePath = Package.FullPath; - try - { - SavePackage(); + Package.IsDirty = false; + } + catch (Exception ex) + { + log.Error(Package, null, AssetMessageCode.PackageCannotSave, ex, Package.FullPath); + return; + } - // Move the package if the path has changed - if (Package.PreviousPackagePath != null && Package.PreviousPackagePath != Package.FullPath) + // Delete obsolete files + foreach (var file in filesToDeleteLocal) + { + if (File.Exists(file.FullPath)) + { + try { - filesToDeleteLocal.Add(Package.PreviousPackagePath); + File.Delete(file.FullPath); } - Package.PreviousPackagePath = Package.FullPath; - - Package.IsDirty = false; - } - catch (Exception ex) - { - log.Error(Package, null, AssetMessageCode.PackageCannotSave, ex, Package.FullPath); - return; - } - - // Delete obsolete files - foreach (var file in filesToDeleteLocal) - { - if (File.Exists(file.FullPath)) + catch (Exception ex) { - try - { - File.Delete(file.FullPath); - } - catch (Exception ex) - { - log.Error(Package, null, AssetMessageCode.AssetCannotDelete, ex, file.FullPath); - } + log.Error(Package, null, AssetMessageCode.AssetCannotDelete, ex, file.FullPath); } } } + } - //batch projects - var vsProjs = new Dictionary(); + //batch projects + var vsProjs = new Dictionary(); - foreach (var asset in Package.Assets) + foreach (var asset in Package.Assets) + { + if (asset.IsDirty) { - if (asset.IsDirty) + if (saveParameters.AssetFilter?.Invoke(asset) ?? true) { - if (saveParameters.AssetFilter?.Invoke(asset) ?? true) - { - Package.SaveSingleAsset_NoUpdateSourceFolder(asset, log); - } - else - { - assetsFiltered = true; - } + Package.SaveSingleAsset_NoUpdateSourceFolder(asset, log); + } + else + { + assetsFiltered = true; } + } + + // Add new files to .csproj + if (asset.Asset is IProjectAsset projectAsset) + { + var projectFullPath = (asset.Package.Container as SolutionProject)?.FullPath; + var projectInclude = asset.GetProjectInclude(); - // Add new files to .csproj - var projectAsset = asset.Asset as IProjectAsset; - if (projectAsset != null) + if (!vsProjs.TryGetValue(projectFullPath, out var project)) { - var projectFullPath = (asset.Package.Container as SolutionProject)?.FullPath; - var projectInclude = asset.GetProjectInclude(); + project = VSProjectHelper.LoadProject(projectFullPath); + vsProjs.Add(projectFullPath, project); + } - Microsoft.Build.Evaluation.Project project; - if (!vsProjs.TryGetValue(projectFullPath, out project)) + //check if the item is already there, this is possible when saving the first time when creating from a template + // Note: if project has auto items, no need to add it + if (project.Items.All(x => x.EvaluatedInclude != projectInclude) + && (string.Compare(project.GetPropertyValue("EnableDefaultCompileItems"), "true", true, CultureInfo.InvariantCulture) != 0)) + { + if (projectAsset is IProjectFileGeneratorAsset generatorAsset) { - project = VSProjectHelper.LoadProject(projectFullPath); - vsProjs.Add(projectFullPath, project); + var generatedInclude = asset.GetGeneratedInclude(); + + project.AddItem("None", projectInclude, + [ + new KeyValuePair("Generator", generatorAsset.Generator), + new KeyValuePair("LastGenOutput", new UFile(generatedInclude).GetFileName()) + ]); + + project.AddItem("Compile", generatedInclude, + [ + new KeyValuePair("AutoGen", "True"), + new KeyValuePair("DesignTime", "True"), + new KeyValuePair("DesignTimeSharedInput", "True"), + new KeyValuePair("DependentUpon", new UFile(projectInclude).GetFileName()) + ]); } - - //check if the item is already there, this is possible when saving the first time when creating from a template - // Note: if project has auto items, no need to add it - if (project.Items.All(x => x.EvaluatedInclude != projectInclude) - && (string.Compare(project.GetPropertyValue("EnableDefaultCompileItems"), "true", true, CultureInfo.InvariantCulture) != 0)) + else { - var generatorAsset = projectAsset as IProjectFileGeneratorAsset; - if (generatorAsset != null) - { - var generatedInclude = asset.GetGeneratedInclude(); - - project.AddItem("None", projectInclude, - new List> - { - new KeyValuePair("Generator", generatorAsset.Generator), - new KeyValuePair("LastGenOutput", new UFile(generatedInclude).GetFileName()) - }); - - project.AddItem("Compile", generatedInclude, - new List> - { - new KeyValuePair("AutoGen", "True"), - new KeyValuePair("DesignTime", "True"), - new KeyValuePair("DesignTimeSharedInput", "True"), - new KeyValuePair("DependentUpon", new UFile(projectInclude).GetFileName()) - }); - } - else - { - project.AddItem("Compile", projectInclude); - } + project.AddItem("Compile", projectInclude); } } } - - foreach (var project in vsProjs.Values) - { - project.Save(); - project.ProjectCollection.UnloadAllProjects(); - project.ProjectCollection.Dispose(); - } - - // If some assets were filtered out, Assets is still dirty - Package.Assets.IsDirty = assetsFiltered; } - finally + + foreach (var project in vsProjs.Values) { - // Rollback all relative UFile to absolute paths - analysis.Parameters.ConvertUPathTo = UPathType.Absolute; - analysis.Run(); + project.Save(); + project.ProjectCollection.UnloadAllProjects(); + project.ProjectCollection.Dispose(); } - } - protected virtual void SavePackage() - { - AssetFileSerializer.Save(Package.FullPath, Package, null); + // If some assets were filtered out, Assets is still dirty + Package.Assets.IsDirty = assetsFiltered; } - - internal void SetSessionInternal(PackageSession session) + finally { - Session = session; + // Rollback all relative UFile to absolute paths + analysis.Parameters.ConvertUPathTo = UPathType.Absolute; + analysis.Run(); } } - public class StandalonePackage : PackageContainer + protected virtual void SavePackage() { - public StandalonePackage([NotNull] Package package) - : base(package) - { - } - - /// - /// Optional list of assemblies to load, typically filled using NuGet. - /// - public List Assemblies { get; } = new List(); + AssetFileSerializer.Save(Package.FullPath, Package, null); + } - public override string ToString() => $"Package: {Package.Meta.Name}"; + internal void SetSessionInternal(PackageSession? session) + { + Session = session; } +} - public enum DependencyType +public class StandalonePackage : PackageContainer +{ + public StandalonePackage(Package package) + : base(package) { - Package, - Project, } - public class Dependency + /// + /// Optional list of assemblies to load, typically filled using NuGet. + /// + public List Assemblies { get; } = []; + + public override string ToString() => $"Package: {Package.Meta.Name}"; +} + +public enum DependencyType +{ + Package, + Project, +} + +public class Dependency +{ + public Dependency(string name, PackageVersion version, DependencyType type) { - public Dependency(string name, PackageVersion version, DependencyType type) - { - Name = name; - Version = version; - Type = type; - } + Name = name; + Version = version; + Type = type; + } - public Dependency(Package package) : this(package.Meta.Name, package.Meta.Version, DependencyType.Package) - { - Package = package; - } + public Dependency(Package package) : this(package.Meta.Name, package.Meta.Version, DependencyType.Package) + { + Package = package; + } - public string Name { get; set; } + public string Name { get; set; } - public string MSBuildProject { get; set; } + public string MSBuildProject { get; set; } - public PackageVersion Version { get; set; } + public PackageVersion Version { get; set; } - public DependencyType Type { get; set; } + public DependencyType Type { get; set; } - public Package Package { get; set; } + public Package Package { get; set; } - public List Assemblies { get; } = new List(); + public List Assemblies { get; } = []; - public override string ToString() - { - return $"{Name} {Version} ({Type})"; - } + public override string ToString() + { + return $"{Name} {Version} ({Type})"; } +} - public class DependencyRange +public class DependencyRange +{ + public DependencyRange(string name, PackageVersionRange versionRange, DependencyType type) { - public DependencyRange(string name, PackageVersionRange versionRange, DependencyType type) - { - Name = name; - VersionRange = versionRange; - Type = type; - } + Name = name; + VersionRange = versionRange; + Type = type; + } - public string Name { get; set; } + public string Name { get; set; } - public string MSBuildProject { get; set; } + public string MSBuildProject { get; set; } - public PackageVersionRange VersionRange { get; set; } + public PackageVersionRange VersionRange { get; set; } - public DependencyType Type { get; set; } - } + public DependencyType Type { get; set; } +} - public class SolutionProject : PackageContainer +public class SolutionProject : PackageContainer +{ + protected SolutionProject(Package package) : base(package) { - protected SolutionProject([NotNull] Package package) : base(package) - { - DirectDependencies.CollectionChanged += DirectDependencies_CollectionChanged; - } + DirectDependencies.CollectionChanged += DirectDependencies_CollectionChanged; + } - public SolutionProject([NotNull] Package package, Guid projectGuid, string fullPath) - : this(package) - { - VSProject = new VisualStudio.Project(projectGuid, VisualStudio.KnownProjectTypeGuid.CSharp, Path.GetFileNameWithoutExtension(fullPath), fullPath, Guid.Empty, - Enumerable.Empty(), - Enumerable.Empty(), - Enumerable.Empty()); - } + public SolutionProject(Package package, Guid projectGuid, string fullPath) + : this(package) + { + VSProject = new VisualStudio.Project(projectGuid, VisualStudio.KnownProjectTypeGuid.CSharp, Path.GetFileNameWithoutExtension(fullPath), fullPath, Guid.Empty, + [], + [], + []); + } - public SolutionProject([NotNull] Package package, VisualStudio.Project vsProject) - : this(package) - { - VSProject = vsProject; - } + public SolutionProject(Package package, VisualStudio.Project vsProject) + : this(package) + { + VSProject = vsProject; + } - public VisualStudio.Project VSProject { get; set; } + public VisualStudio.Project VSProject { get; set; } - public Guid Id => VSProject.Guid; + public Guid Id => VSProject.Guid; - public ProjectType Type { get; set; } + public ProjectType Type { get; set; } - public PlatformType Platform { get; set; } + public PlatformType Platform { get; set; } - public bool IsImplicitProject { get; set; } + public bool IsImplicitProject { get; set; } - public bool TrackDirectDependencies { get; set; } + public bool TrackDirectDependencies { get; set; } - public string Name => VSProject.Name; + public string Name => VSProject.Name; - public string TargetPath { get; set; } + public string TargetPath { get; set; } - public string AssemblyProcessorSerializationHashFile { get; set; } + public string AssemblyProcessorSerializationHashFile { get; set; } - public UFile FullPath => VSProject.FullPath; + public UFile FullPath => VSProject.FullPath; - private void DirectDependencies_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - // Ignore collection being filled during dependency resolution - if (Package.State < PackageState.DependenciesReady) - return; + private void DirectDependencies_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + // Ignore collection being filled during dependency resolution + if (Package.State < PackageState.DependenciesReady) + return; - var projectFile = FullPath; - var msbuildProject = VSProjectHelper.LoadProject(projectFile.ToOSPath()); - var isProjectDirty = false; + var projectFile = FullPath; + var msbuildProject = VSProjectHelper.LoadProject(projectFile.ToOSPath()); + var isProjectDirty = false; - if (e.OldItems != null && e.OldItems.Count > 0) + if (e.OldItems?.Count > 0) + { + foreach (DependencyRange dependency in e.OldItems) { - foreach (DependencyRange dependency in e.OldItems) + switch (dependency.Type) { - switch (dependency.Type) - { - case DependencyType.Package: - { - var matchingReferences = msbuildProject.GetItems("PackageReference").Where(packageReference => packageReference.EvaluatedInclude == dependency.Name && packageReference.GetMetadataValue("Version") == dependency.VersionRange.ToString()).ToList(); - isProjectDirty = matchingReferences.Count > 0; - msbuildProject.RemoveItems(matchingReferences); - break; - } - case DependencyType.Project: - { - var matchingReferences = msbuildProject.GetItems("ProjectReference").Where(projectReference => (UFile)projectReference.EvaluatedInclude == ((UFile)dependency.MSBuildProject).MakeRelative(projectFile.GetFullDirectory())).ToList(); - isProjectDirty = matchingReferences.Count > 0; - msbuildProject.RemoveItems(matchingReferences); - break; - } - } + case DependencyType.Package: + { + var matchingReferences = msbuildProject.GetItems("PackageReference").Where(packageReference => packageReference.EvaluatedInclude == dependency.Name && packageReference.GetMetadataValue("Version") == dependency.VersionRange.ToString()).ToList(); + isProjectDirty = matchingReferences.Count > 0; + msbuildProject.RemoveItems(matchingReferences); + break; + } + case DependencyType.Project: + { + var matchingReferences = msbuildProject.GetItems("ProjectReference").Where(projectReference => (UFile)projectReference.EvaluatedInclude == ((UFile)dependency.MSBuildProject).MakeRelative(projectFile.GetFullDirectory())).ToList(); + isProjectDirty = matchingReferences.Count > 0; + msbuildProject.RemoveItems(matchingReferences); + break; + } } } + } - if (e.NewItems != null && e.NewItems.Count > 0) + if (e.NewItems?.Count > 0) + { + foreach (DependencyRange dependency in e.NewItems) { - foreach (DependencyRange dependency in e.NewItems) + switch (dependency.Type) { - switch (dependency.Type) - { - case DependencyType.Package: - msbuildProject.AddItem("PackageReference", dependency.Name, new[] { new KeyValuePair("Version", dependency.VersionRange.ToString()) }) - // Make sure Version is a XML attribute rather than a XML child. - .ForEach(packageReference => packageReference.Metadata.ForEach(metadata => metadata.Xml.ExpressedAsAttribute = true)); - isProjectDirty = true; - break; - case DependencyType.Project: - msbuildProject.AddItem("ProjectReference", ((UFile)dependency.MSBuildProject).MakeRelative(projectFile.GetFullDirectory()).ToOSPath()); - isProjectDirty = true; - break; - } + case DependencyType.Package: + msbuildProject.AddItem("PackageReference", dependency.Name, [new KeyValuePair("Version", dependency.VersionRange.ToString())]) + // Make sure Version is a XML attribute rather than a XML child. + .ForEach(packageReference => packageReference.Metadata.ForEach(metadata => metadata.Xml.ExpressedAsAttribute = true)); + isProjectDirty = true; + break; + case DependencyType.Project: + msbuildProject.AddItem("ProjectReference", ((UFile)dependency.MSBuildProject).MakeRelative(projectFile.GetFullDirectory()).ToOSPath()); + isProjectDirty = true; + break; } } - - if (isProjectDirty) - msbuildProject.Save(); - - msbuildProject.ProjectCollection.UnloadAllProjects(); - msbuildProject.ProjectCollection.Dispose(); } - protected override void SavePackage() - { - // Check if our project is still implicit one - // Note: we only allow transition from implicit to explicit (otherwise we would have to delete file, etc.) - if (IsImplicitProject && Package.IsDirty && !Package.IsImplicitProject) - IsImplicitProject = false; + if (isProjectDirty) + msbuildProject.Save(); - if (!IsImplicitProject) - base.SavePackage(); - } + msbuildProject.ProjectCollection.UnloadAllProjects(); + msbuildProject.ProjectCollection.Dispose(); + } - public override string ToString() => $"Project: {Name}"; + protected override void SavePackage() + { + // Check if our project is still implicit one + // Note: we only allow transition from implicit to explicit (otherwise we would have to delete file, etc.) + if (IsImplicitProject && Package.IsDirty && !Package.IsImplicitProject) + IsImplicitProject = false; + + if (!IsImplicitProject) + base.SavePackage(); } - public sealed class ProjectCollection : ObservableCollection + public override string ToString() => $"Project: {Name}"; +} + +public sealed class ProjectCollection : ObservableCollection; + +/// +/// A session for editing a package. +/// +public sealed partial class PackageSession : IDisposable, IAssetFinder +{ + /// + /// The visual studio version property used for newly created project solution files + /// + public static readonly Version DefaultVisualStudioVersion = new("16.0.0.0"); + + internal static readonly string SolutionHeader = + """ + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio 16 + VisualStudioVersion = {0} + MinimumVisualStudioVersion = {0} + """.ToFormat(DefaultVisualStudioVersion); + + private readonly Dictionary> pendingPackageUpgradesPerPackage = []; + private readonly ConstraintProvider constraintProvider = new(); + private readonly PackageCollection packages; + private readonly Dictionary packagesCopy; + private readonly object dependenciesLock = new(); + private SolutionProject currentProject; + private AssetDependencyManager dependencies; + private AssetSourceTracker sourceTracker; + private bool? packageUpgradeAllowed; + public event DirtyFlagChangedDelegate AssetDirtyChanged; + private TaskCompletionSource saveCompletion; + + internal VisualStudio.Solution VSSolution; + + /// + /// Initializes a new instance of the class. + /// + public PackageSession() { + VSSolution = new VisualStudio.Solution(); + VSSolution.Headers.Add(SolutionHeader); + + Projects = []; + Projects.CollectionChanged += ProjectsCollectionChanged; + + packages = []; + packagesCopy = []; + AssemblyContainer = new AssemblyContainer(); + packages.CollectionChanged += PackagesCollectionChanged; } /// - /// A session for editing a package. + /// Initializes a new instance of the class. /// - public sealed partial class PackageSession : IDisposable, IAssetFinder + public PackageSession(Package package) : this() { - /// - /// The visual studio version property used for newly created project solution files - /// - public static readonly Version DefaultVisualStudioVersion = new Version("16.0.0.0"); - - internal static readonly string SolutionHeader = @"Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 16 -VisualStudioVersion = {0} -MinimumVisualStudioVersion = {0}".ToFormat(DefaultVisualStudioVersion); - - private Dictionary> pendingPackageUpgradesPerPackage = new Dictionary>(); - private readonly ConstraintProvider constraintProvider = new ConstraintProvider(); - private readonly PackageCollection packages; - private readonly Dictionary packagesCopy; - private readonly object dependenciesLock = new object(); - private SolutionProject currentProject; - private AssetDependencyManager dependencies; - private AssetSourceTracker sourceTracker; - private bool? packageUpgradeAllowed; - public event DirtyFlagChangedDelegate AssetDirtyChanged; - private TaskCompletionSource saveCompletion; - - internal VisualStudio.Solution VSSolution; - - /// - /// Initializes a new instance of the class. - /// - public PackageSession() - { - VSSolution = new VisualStudio.Solution(); - VSSolution.Headers.Add(SolutionHeader); + Projects.Add(new StandalonePackage(package)); + } - Projects = new ProjectCollection(); - Projects.CollectionChanged += ProjectsCollectionChanged; + public bool IsDirty { get; set; } - packages = new PackageCollection(); - packagesCopy = new Dictionary(); - AssemblyContainer = new AssemblyContainer(); - packages.CollectionChanged += PackagesCollectionChanged; - } + /// + /// Gets the packages referenced by the solution. + /// + /// The packages. + public IReadOnlyPackageCollection Packages => packages; - /// - /// Initializes a new instance of the class. - /// - public PackageSession(Package package) : this() - { - Projects.Add(new StandalonePackage(package)); - } + /// + /// The projects referenced by the solution. + /// + public ProjectCollection Projects { get; } - public bool IsDirty { get; set; } - - /// - /// Gets the packages referenced by the solution. - /// - /// The packages. - public IReadOnlyPackageCollection Packages => packages; - - /// - /// The projects referenced by the solution. - /// - public ProjectCollection Projects { get; } - - /// - /// Gets the user packages (excluding system packages). - /// - /// The user packages. - public IEnumerable LocalPackages => Packages.Where(package => !package.IsSystem); - - /// - /// Gets a task that completes when the session is finished saving. - /// - [NotNull] - public Task SaveCompletion => saveCompletion?.Task ?? Task.CompletedTask; - - /// - /// Gets or sets the solution path (sln) in case the session was loaded from a solution. - /// - /// The solution path. - public UFile SolutionPath - { - get => VSSolution.FullPath; - set => VSSolution.FullPath = value; - } + /// + /// Gets the user packages (excluding system packages). + /// + /// The user packages. + public IEnumerable LocalPackages => Packages.Where(package => !package.IsSystem); + + /// + /// Gets a task that completes when the session is finished saving. + /// + public Task SaveCompletion => saveCompletion?.Task ?? Task.CompletedTask; + + /// + /// Gets or sets the solution path (sln) in case the session was loaded from a solution. + /// + /// The solution path. + public UFile SolutionPath + { + get => VSSolution.FullPath; + set => VSSolution.FullPath = value; + } - public AssemblyContainer AssemblyContainer { get; } + public AssemblyContainer AssemblyContainer { get; } - /// - /// The targeted visual studio version (if specified by the loaded package) - /// - public Version VisualStudioVersion { get; set; } + /// + /// The targeted visual studio version (if specified by the loaded package) + /// + public Version? VisualStudioVersion { get; set; } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - dependencies?.Dispose(); - sourceTracker?.Dispose(); + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + dependencies?.Dispose(); + sourceTracker?.Dispose(); - var loadedAssemblies = Packages.SelectMany(x => x.LoadedAssemblies).ToList(); - for (int index = loadedAssemblies.Count - 1; index >= 0; index--) - { - var loadedAssembly = loadedAssemblies[index]; - if (loadedAssembly == null) - continue; + var loadedAssemblies = Packages.SelectMany(x => x.LoadedAssemblies).ToList(); + for (int index = loadedAssemblies.Count - 1; index >= 0; index--) + { + var loadedAssembly = loadedAssemblies[index]; + if (loadedAssembly is null) + continue; - // Unregisters assemblies that have been registered in Package.Load => Package.LoadAssemblyReferencesForPackage - AssemblyRegistry.Unregister(loadedAssembly.Assembly); + // Unregisters assemblies that have been registered in Package.Load => Package.LoadAssemblyReferencesForPackage + AssemblyRegistry.Unregister(loadedAssembly.Assembly); - // Unload binary serialization - DataSerializerFactory.UnregisterSerializationAssembly(loadedAssembly.Assembly); + // Unload binary serialization + DataSerializerFactory.UnregisterSerializationAssembly(loadedAssembly.Assembly); - // Unload assembly - AssemblyContainer.UnloadAssembly(loadedAssembly.Assembly); - } + // Unload assembly + AssemblyContainer.UnloadAssembly(loadedAssembly.Assembly); } + } - /// - /// Gets a value indicating whether this instance has dependency manager. - /// - /// true if this instance has dependency manager; otherwise, false. - public bool HasDependencyManager + /// + /// Gets a value indicating whether this instance has dependency manager. + /// + /// true if this instance has dependency manager; otherwise, false. + public bool HasDependencyManager + { + get { - get + lock (dependenciesLock) { - lock (dependenciesLock) - { - return dependencies != null; - } + return dependencies is not null; } } + } - /// - /// Gets or sets the selected current package. - /// - /// The selected current package. - /// Expecting a package that is already registered in this session - public SolutionProject CurrentProject + /// + /// Gets or sets the selected current package. + /// + /// The selected current package. + /// Expecting a package that is already registered in this session + public SolutionProject CurrentProject + { + get { - get - { - return currentProject; - } - set + return currentProject; + } + set + { + if (value is not null) { - if (value != null) + if (!Projects.Contains(value)) { - if (!Projects.Contains(value)) - { - throw new InvalidOperationException("Expecting a package that is already registered in this session"); - } + throw new InvalidOperationException("Expecting a package that is already registered in this session"); } - currentProject = value; } + currentProject = value; } + } - /// - /// Gets the packages referenced by the current package. - /// - /// IEnumerable<Package>. - public IEnumerable GetPackagesFromCurrent() + /// + /// Gets the packages referenced by the current package. + /// + /// IEnumerable<Package>. + public IEnumerable GetPackagesFromCurrent() + { + if (CurrentProject.Package is null) { - if (CurrentProject.Package == null) - { - yield return CurrentProject.Package; - } - - foreach (var dependency in CurrentProject.FlattenedDependencies) - { - var loadedPackage = packages.Find(dependency); - // In case the package is not found (when working with session not fully loaded/resolved with all deps) - if (loadedPackage == null) - { - yield return loadedPackage; - } - } + yield return CurrentProject.Package; } - /// - /// Gets the dependency manager. - /// - /// AssetDependencyManager. - public AssetDependencyManager DependencyManager + foreach (var dependency in CurrentProject.FlattenedDependencies) { - get + var loadedPackage = packages.Find(dependency); + // In case the package is not found (when working with session not fully loaded/resolved with all deps) + if (loadedPackage is null) { - lock (dependenciesLock) - { - return dependencies ?? (dependencies = new AssetDependencyManager(this)); - } + yield return loadedPackage; } } + } - public AssetSourceTracker SourceTracker + /// + /// Gets the dependency manager. + /// + /// AssetDependencyManager. + public AssetDependencyManager DependencyManager + { + get { - get + lock (dependenciesLock) { - lock (dependenciesLock) - { - return sourceTracker ?? (sourceTracker = new AssetSourceTracker(this)); - } + return dependencies ??= new AssetDependencyManager(this); } } + } - /// - /// Adds an existing package to the current session. - /// - /// The project or package path. - /// The session result. - /// The load parameters argument. - /// packagePath - /// Invalid relative path. Expecting an absolute package path;packagePath - /// Unable to find package - public PackageContainer AddExistingProject(UFile projectPath, ILogger logger, PackageLoadParameters loadParametersArg = null) + public AssetSourceTracker SourceTracker + { + get { - if (projectPath == null) throw new ArgumentNullException(nameof(projectPath)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (!projectPath.IsAbsolute) throw new ArgumentException(@"Invalid relative path. Expecting an absolute project path", nameof(projectPath)); - if (!File.Exists(projectPath)) throw new FileNotFoundException("Unable to find project", projectPath); - - var loadParameters = loadParametersArg ?? PackageLoadParameters.Default(); - - Package package; - PackageContainer project; - try + lock (dependenciesLock) { - // Enable reference analysis caching during loading - AssetReferenceAnalysis.EnableCaching = true; - - project = LoadProject(logger, projectPath.ToOSPath(), loadParametersArg); - Projects.Add(project); + return sourceTracker ??= new AssetSourceTracker(this); + } + } + } - package = project.Package; + /// + /// Adds an existing package to the current session. + /// + /// The project or package path. + /// The session result. + /// The load parameters argument. + /// packagePath + /// Invalid relative path. Expecting an absolute package path;packagePath + /// Unable to find package + public PackageContainer AddExistingProject(UFile projectPath, ILogger logger, PackageLoadParameters? loadParametersArg = null) + { + ArgumentNullException.ThrowIfNull(projectPath); + ArgumentNullException.ThrowIfNull(logger); + if (!projectPath.IsAbsolute) throw new ArgumentException("Invalid relative path. Expecting an absolute project path", nameof(projectPath)); + if (!File.Exists(projectPath)) throw new FileNotFoundException("Unable to find project", projectPath); - // Load all missing references/dependencies - LoadMissingDependencies(logger, loadParameters); + var loadParameters = loadParametersArg ?? PackageLoadParameters.Default(); - // Process everything except current one (it needs different load parameters) - var dependencyLoadParameters = loadParameters.Clone(); - dependencyLoadParameters.GenerateNewAssetIds = false; - LoadMissingAssets(logger, Packages.Where(x => x != package).ToList(), dependencyLoadParameters); + Package package; + PackageContainer project; + try + { + // Enable reference analysis caching during loading + AssetReferenceAnalysis.EnableCaching = true; - LoadMissingAssets(logger, new[] { package }, loadParameters); + project = LoadProject(logger, projectPath.ToOSPath(), loadParametersArg); + Projects.Add(project); - // Run analysis after - // TODO CSPROJ=XKPKG - //foreach (var packageToAdd in packagesLoaded) - //{ - // var analysis = new PackageAnalysis(packageToAdd, GetPackageAnalysisParametersForLoad()); - // analysis.Run(logger); - //} - } - finally - { - // Disable reference analysis caching after loading - AssetReferenceAnalysis.EnableCaching = false; - } - return project; - } + package = project.Package; - /// - /// Adds an existing package to the current session and runs the package analysis before adding it. - /// - /// The package to add - /// The logger - public void AddExistingPackage(Package package, ILogger logger) - { - if (package == null) throw new ArgumentNullException(nameof(package)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); + // Load all missing references/dependencies + LoadMissingDependencies(logger, loadParameters); - if (Packages.Contains(package)) - { - return; - } + // Process everything except current one (it needs different load parameters) + var dependencyLoadParameters = loadParameters.Clone(); + dependencyLoadParameters.GenerateNewAssetIds = false; + LoadMissingAssets(logger, Packages.Where(x => x != package).ToList(), dependencyLoadParameters); - // Preset the session on the package to allow the session to look for existing asset - packages.Add(package); + LoadMissingAssets(logger, [package], loadParameters); // Run analysis after - var analysis = new PackageAnalysis(package, GetPackageAnalysisParametersForLoad()); - analysis.Run(logger); - + // TODO CSPROJ=XKPKG + //foreach (var packageToAdd in packagesLoaded) + //{ + // var analysis = new PackageAnalysis(packageToAdd, GetPackageAnalysisParametersForLoad()); + // analysis.Run(logger); + //} } - - /// - /// Looks for the asset amongst all the packages of this session. - public AssetItem FindAsset(AssetId assetId) + finally { - return Packages.Select(p => p.Assets.Find(assetId)).NotNull().FirstOrDefault(); + // Disable reference analysis caching after loading + AssetReferenceAnalysis.EnableCaching = false; } + return project; + } - /// - /// Looks for the asset amongst all the packages of this session. - public AssetItem FindAsset(UFile location) - { - return Packages.Select(p => p.Assets.Find(location)).NotNull().FirstOrDefault(); - } + /// + /// Adds an existing package to the current session and runs the package analysis before adding it. + /// + /// The package to add + /// The logger + public void AddExistingPackage(Package package, ILogger logger) + { + ArgumentNullException.ThrowIfNull(package); + ArgumentNullException.ThrowIfNull(logger); - /// - /// Looks for the asset amongst all the packages of this session. - public AssetItem FindAssetFromProxyObject(object proxyObject) + if (Packages.Contains(package)) { - var reference = AttachedReferenceManager.GetAttachedReference(proxyObject); - return reference != null ? (FindAsset(reference.Id) ?? FindAsset(reference.Url)) : null; + return; } - private PackageContainer LoadProject(ILogger log, string filePath, PackageLoadParameters loadParameters = null) - { - var project = Package.LoadProject(log, filePath); + // Preset the session on the package to allow the session to look for existing asset + packages.Add(package); - // Upgrade from 3.0: figure out if this was the package project - var vsProject = VSSolution.Projects.FirstOrDefault(x => x.FullPath == filePath); - var vsPackage = vsProject?.GetParentProject(VSSolution); - if (vsPackage != null && PackageSessionHelper.IsPackage(vsPackage, out var packagePathRelative)) - { - var packagePath = Path.Combine(Path.GetDirectoryName(VSSolution.FullPath), packagePathRelative); - if (File.Exists(packagePath)) - { - var packageProject = Package.LoadProject(log, packagePath); - if ((packageProject as SolutionProject)?.FullPath == new UFile(filePath)) - { - project = packageProject; - PackageSessionHelper.RemovePackageSections(vsPackage); - } - } - } + // Run analysis after + var analysis = new PackageAnalysis(package, GetPackageAnalysisParametersForLoad()); + analysis.Run(logger); + } - var package = project.Package; + /// + /// Looks for the asset amongst all the packages of this session. + public AssetItem? FindAsset(AssetId assetId) + { + return Packages.Select(p => p.Assets.Find(assetId)).NotNull().FirstOrDefault(); + } - // If the package doesn't have a meta name, fix it here (This is supposed to be done in the above disabled analysis - but we still need to do it!) - if (string.IsNullOrWhiteSpace(package.Meta.Name) && package.FullPath != null) - { - package.Meta.Name = package.FullPath.GetFileNameWithoutExtension(); - package.IsDirty = true; - } + /// + /// Looks for the asset amongst all the packages of this session. + public AssetItem? FindAsset(UFile location) + { + return Packages.Select(p => p.Assets.Find(location)).NotNull().FirstOrDefault(); + } - // Package has been loaded, register it in constraints so that we force each subsequent loads to use this one (or fails if version doesn't match) - if (package.Meta.Version != null) + /// + /// Looks for the asset amongst all the packages of this session. + public AssetItem? FindAssetFromProxyObject(object? proxyObject) + { + var reference = AttachedReferenceManager.GetAttachedReference(proxyObject); + return reference is not null ? (FindAsset(reference.Id) ?? FindAsset(reference.Url)) : null; + } + + private PackageContainer LoadProject(ILogger log, string filePath, PackageLoadParameters? loadParameters = null) + { + var project = Package.LoadProject(log, filePath); + + // Upgrade from 3.0: figure out if this was the package project + var vsProject = VSSolution.Projects.FirstOrDefault(x => x.FullPath == filePath); + var vsPackage = vsProject?.GetParentProject(VSSolution); + if (vsPackage is not null && PackageSessionHelper.IsPackage(vsPackage, out var packagePathRelative)) + { + var packagePath = Path.Combine(Path.GetDirectoryName(VSSolution.FullPath), packagePathRelative); + if (File.Exists(packagePath)) { - constraintProvider.AddConstraint(package.Meta.Name, new PackageVersionRange(package.Meta.Version)); + var packageProject = Package.LoadProject(log, packagePath); + if ((packageProject as SolutionProject)?.FullPath == new UFile(filePath)) + { + project = packageProject; + PackageSessionHelper.RemovePackageSections(vsPackage); + } } + } + + var package = project.Package; - return project; + // If the package doesn't have a meta name, fix it here (This is supposed to be done in the above disabled analysis - but we still need to do it!) + if (string.IsNullOrWhiteSpace(package.Meta.Name) && package.FullPath is not null) + { + package.Meta.Name = package.FullPath.GetFileNameWithoutExtension(); + package.IsDirty = true; } - /// - /// Loads a package from specified file path. - /// - /// The file path to a package file. - /// The session result. - /// The load parameters. - /// filePath - /// File [{0}] must exist.ToFormat(filePath);filePath - public static void Load(string filePath, PackageSessionResult sessionResult, PackageLoadParameters loadParameters = null) + // Package has been loaded, register it in constraints so that we force each subsequent loads to use this one (or fails if version doesn't match) + if (package.Meta.Version is not null) { - if (filePath == null) throw new ArgumentNullException(nameof(filePath)); - if (sessionResult == null) throw new ArgumentNullException(nameof(sessionResult)); + constraintProvider.AddConstraint(package.Meta.Name, new PackageVersionRange(package.Meta.Version)); + } - // Make sure with have valid parameters - loadParameters = loadParameters ?? PackageLoadParameters.Default(); + return project; + } - // Make sure to use a full path. - filePath = FileUtility.GetAbsolutePath(filePath); + /// + /// Loads a package from specified file path. + /// + /// The file path to a package file. + /// The session result. + /// The load parameters. + /// filePath + /// File [{0}] must exist.ToFormat(filePath);filePath + public static void Load(string filePath, PackageSessionResult sessionResult, PackageLoadParameters? loadParameters = null) + { + ArgumentNullException.ThrowIfNull(filePath); + ArgumentNullException.ThrowIfNull(sessionResult); - if (!File.Exists(filePath)) throw new ArgumentException($@"File [{filePath}] must exist", nameof(filePath)); + // Make sure with have valid parameters + loadParameters ??= PackageLoadParameters.Default(); - try - { - // Enable reference analysis caching during loading - AssetReferenceAnalysis.EnableCaching = true; + // Make sure to use a full path. + filePath = FileUtility.GetAbsolutePath(filePath); - using (var profile = Profiler.Begin(PackageSessionProfilingKeys.Loading)) - { - sessionResult.Clear(); - sessionResult.Progress("Loading..", 0, 1); + if (!File.Exists(filePath)) throw new ArgumentException($"File [{filePath}] must exist", nameof(filePath)); - var session = new PackageSession(); + try + { + // Enable reference analysis caching during loading + AssetReferenceAnalysis.EnableCaching = true; - var cancelToken = loadParameters.CancelToken; - SolutionProject firstProject = null; + using var profile = Profiler.Begin(PackageSessionProfilingKeys.Loading); + sessionResult.Clear(); + sessionResult.Progress("Loading..", 0, 1); - // If we have a solution, load all packages - if (Path.GetExtension(filePath).ToLowerInvariant() == ".sln") - { - // The session should save back its changes to the solution - var solution = session.VSSolution = VisualStudio.Solution.FromFile(filePath); - - // Keep header - var versionHeader = solution.Properties.FirstOrDefault(x => x.Name == "VisualStudioVersion"); - Version version; - if (versionHeader != null && Version.TryParse(versionHeader.Value, out version)) - session.VisualStudioVersion = version; - else - session.VisualStudioVersion = null; + var session = new PackageSession(); - // Note: using ToList() because upgrade from old package system might change Projects list - foreach (var vsProject in solution.Projects.ToList()) - { - if (vsProject.TypeGuid == VisualStudio.KnownProjectTypeGuid.CSharp || vsProject.TypeGuid == VisualStudio.KnownProjectTypeGuid.CSharpNewSystem) - { - var project = (SolutionProject)session.LoadProject(sessionResult, vsProject.FullPath, loadParameters); - project.VSProject = vsProject; - session.Projects.Add(project); - - if (firstProject == null) - firstProject = project; - - // Output the session only if there is no cancellation - if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested) - { - return; - } - } - } + var cancelToken = loadParameters.CancelToken; + SolutionProject? firstProject = null; - session.LoadMissingDependencies(sessionResult, loadParameters); - } - else if (SupportedProgrammingLanguages.IsProjectExtensionSupported(Path.GetExtension(filePath).ToLowerInvariant()) - || Path.GetExtension(filePath).ToLowerInvariant() == Package.PackageFileExtension) + // If we have a solution, load all packages + if (string.Equals(Path.GetExtension(filePath), ".sln", StringComparison.InvariantCultureIgnoreCase)) + { + // The session should save back its changes to the solution + var solution = session.VSSolution = VisualStudio.Solution.FromFile(filePath); + + // Keep header + var versionHeader = solution.Properties.FirstOrDefault(x => x.Name == "VisualStudioVersion"); + if (versionHeader is not null && Version.TryParse(versionHeader.Value, out var version)) + session.VisualStudioVersion = version; + else + session.VisualStudioVersion = null; + + // Note: using ToList() because upgrade from old package system might change Projects list + foreach (var vsProject in solution.Projects.ToList()) + { + if (vsProject.TypeGuid == VisualStudio.KnownProjectTypeGuid.CSharp || vsProject.TypeGuid == VisualStudio.KnownProjectTypeGuid.CSharpNewSystem) { - var project = session.LoadProject(sessionResult, filePath, loadParameters); + var project = (SolutionProject)session.LoadProject(sessionResult, vsProject.FullPath, loadParameters); + project.VSProject = vsProject; session.Projects.Add(project); - firstProject = project as SolutionProject; - } - else - { - var supportedExtensions = SupportedProgrammingLanguages.Languages - .Select(lang => lang.Extension) - .ToArray(); - - sessionResult.Error($"Unsupported file extension (only .sln, {string.Join(", ", supportedExtensions)} and .sdpkg are supported)"); - return; - } - - // Load all missing references/dependencies - session.LoadMissingReferences(sessionResult, loadParameters); - // Fix relative references - var analysis = new PackageSessionAnalysis(session, GetPackageAnalysisParametersForLoad()); - var analysisResults = analysis.Run(); - analysisResults.CopyTo(sessionResult); + firstProject ??= project; - // Run custom package session analysis - foreach (var type in AssetRegistry.GetPackageSessionAnalysisTypes()) - { - var pkgAnalysis = (PackageSessionAnalysisBase)Activator.CreateInstance(type); - pkgAnalysis.Session = session; - var results = pkgAnalysis.Run(); - results.CopyTo(sessionResult); + // Output the session only if there is no cancellation + if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested) + { + return; + } } + } - // Output the session only if there is no cancellation - if (!cancelToken.HasValue || !cancelToken.Value.IsCancellationRequested) - { - sessionResult.Session = session; + session.LoadMissingDependencies(sessionResult, loadParameters); + } + else if (SupportedProgrammingLanguages.IsProjectExtensionSupported(Path.GetExtension(filePath).ToLowerInvariant()) + || Path.GetExtension(filePath).Equals(Package.PackageFileExtension, StringComparison.InvariantCultureIgnoreCase)) + { + var project = session.LoadProject(sessionResult, filePath, loadParameters); + session.Projects.Add(project); + firstProject = project as SolutionProject; + } + else + { + var supportedExtensions = SupportedProgrammingLanguages.Languages + .Select(lang => lang.Extension) + .ToArray(); - // Defer the initialization of the dependency manager - //session.DependencyManager.InitializeDeferred(); - } + sessionResult.Error($"Unsupported file extension (only .sln, {string.Join(", ", supportedExtensions)} and .sdpkg are supported)"); + return; + } - // Setup the current package when loading it - if (firstProject != null) - session.CurrentProject = firstProject; + // Load all missing references/dependencies + session.LoadMissingReferences(sessionResult, loadParameters); - // The session is not dirty when loading it - session.IsDirty = false; - } + // Fix relative references + var analysis = new PackageSessionAnalysis(session, GetPackageAnalysisParametersForLoad()); + var analysisResults = analysis.Run(); + analysisResults.CopyTo(sessionResult); + + // Run custom package session analysis + foreach (var type in AssetRegistry.GetPackageSessionAnalysisTypes()) + { + var pkgAnalysis = (PackageSessionAnalysisBase)Activator.CreateInstance(type)!; + pkgAnalysis.Session = session; + var results = pkgAnalysis.Run(); + results.CopyTo(sessionResult); } - finally + + // Output the session only if there is no cancellation + if (!cancelToken.HasValue || !cancelToken.Value.IsCancellationRequested) { - // Disable reference analysis caching after loading - AssetReferenceAnalysis.EnableCaching = false; + sessionResult.Session = session; + + // Defer the initialization of the dependency manager + //session.DependencyManager.InitializeDeferred(); } - } - /// - /// Loads a package from specified file path. - /// - /// The file path to a package file. - /// The load parameters. - /// A package. - /// filePath - public static PackageSessionResult Load(string filePath, PackageLoadParameters loadParameters = null) - { - var result = new PackageSessionResult(); - Load(filePath, result, loadParameters); - return result; - } + // Setup the current package when loading it + if (firstProject is not null) + session.CurrentProject = firstProject; - /// - /// Make sure packages have their dependencies and assets loaded. - /// - /// The log. - /// The load parameters. - public void LoadMissingReferences(ILogger log, PackageLoadParameters loadParameters = null) - { - LoadMissingDependencies(log, loadParameters); - LoadMissingAssets(log, Packages.ToList(), loadParameters); + // The session is not dirty when loading it + session.IsDirty = false; } - - /// - /// Make sure packages have their dependencies loaded. - /// - /// The log. - /// The load parameters argument. - public void LoadMissingDependencies(ILogger log, PackageLoadParameters loadParametersArg = null) + finally { - var loadParameters = loadParametersArg ?? PackageLoadParameters.Default(); + // Disable reference analysis caching after loading + AssetReferenceAnalysis.EnableCaching = false; + } + } - var cancelToken = loadParameters.CancelToken; + /// + /// Loads a package from specified file path. + /// + /// The file path to a package file. + /// The load parameters. + /// A package. + /// filePath + public static PackageSessionResult Load(string filePath, PackageLoadParameters? loadParameters = null) + { + var result = new PackageSessionResult(); + Load(filePath, result, loadParameters); + return result; + } - // Note: list can grow as dependencies get loaded - for (int i = 0; i < Projects.Count; ++i) - { - var project = Projects[i]; + /// + /// Make sure packages have their dependencies and assets loaded. + /// + /// The log. + /// The load parameters. + public void LoadMissingReferences(ILogger log, PackageLoadParameters? loadParameters = null) + { + LoadMissingDependencies(log, loadParameters); + LoadMissingAssets(log, [.. Packages], loadParameters); + } - // Output the session only if there is no cancellation - if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested) - { - return; - } + /// + /// Make sure packages have their dependencies loaded. + /// + /// The log. + /// The load parameters argument. + public void LoadMissingDependencies(ILogger log, PackageLoadParameters? loadParametersArg = null) + { + var loadParameters = loadParametersArg ?? PackageLoadParameters.Default(); - if (project is SolutionProject solutionProject) - PreLoadPackageDependencies(log, solutionProject, loadParameters).Wait(); - else if (project.Package.State < PackageState.DependenciesReady) // not handling standalone packages yet - project.Package.State = PackageState.DependenciesReady; - } - } + var cancelToken = loadParameters.CancelToken; - /// - /// Make sure packages have their assets loaded. - /// - /// The log. - /// The packages to try to load missing assets from. - /// The load parameters argument. - public void LoadMissingAssets(ILogger log, IEnumerable packages, PackageLoadParameters loadParametersArg = null) + // Note: list can grow as dependencies get loaded + for (int i = 0; i < Projects.Count; ++i) { - var loadParameters = loadParametersArg ?? PackageLoadParameters.Default(); + var project = Projects[i]; - var cancelToken = loadParameters.CancelToken; - List assetLoadInfos = []; - // Make a copy of Packages as it can be modified by PreLoadPackageDependencies - foreach (var package in packages) + // Output the session only if there is no cancellation + if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested) { - // Output the session only if there is no cancellation - if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested) - { - return; - } - if(TryLoadAssemblies(this, log, package, loadParameters,out var assetInfo)) - assetLoadInfos.Add(assetInfo); + return; } - foreach (AssetLoadingInfo assetInfo in assetLoadInfos) + + if (project is SolutionProject solutionProject) + PreLoadPackageDependencies(log, solutionProject, loadParameters).Wait(); + else if (project.Package.State < PackageState.DependenciesReady) // not handling standalone packages yet + project.Package.State = PackageState.DependenciesReady; + } + } + + /// + /// Make sure packages have their assets loaded. + /// + /// The log. + /// The packages to try to load missing assets from. + /// The load parameters argument. + public void LoadMissingAssets(ILogger log, IEnumerable packages, PackageLoadParameters? loadParametersArg = null) + { + var loadParameters = loadParametersArg ?? PackageLoadParameters.Default(); + + var cancelToken = loadParameters.CancelToken; + List assetLoadInfos = []; + // Make a copy of Packages as it can be modified by PreLoadPackageDependencies + foreach (var package in packages) + { + // Output the session only if there is no cancellation + if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested) { - LoadAssets(assetInfo.session,assetInfo.log,assetInfo.package, assetInfo.loadParameters,assetInfo.pendingPackageUpgrades,assetInfo.newLoadParameters); + return; } + if (TryLoadAssemblies(this, log, package, loadParameters, out var assetInfo)) + assetLoadInfos.Add(assetInfo); + } + foreach (AssetLoadingInfo assetInfo in assetLoadInfos) + { + LoadAssets(assetInfo.session, assetInfo.log, assetInfo.package, assetInfo.loadParameters, assetInfo.pendingPackageUpgrades, assetInfo.newLoadParameters); } + } - /// - /// Saves all packages and assets. - /// - /// The in which to report result. - /// The parameters for the save operation. - public void Save(ILogger log, PackageSaveParameters saveParameters = null) + /// + /// Saves all packages and assets. + /// + /// The in which to report result. + /// The parameters for the save operation. + public void Save(ILogger log, PackageSaveParameters? saveParameters = null) + { + //var clock = Stopwatch.StartNew(); + var loggerResult = new ForwardingLoggerResult(log); + using var profile = Profiler.Begin(PackageSessionProfilingKeys.Saving); + var packagesSaved = false; + var packagesDirty = false; + try { - //var clock = Stopwatch.StartNew(); - var loggerResult = new ForwardingLoggerResult(log); - using (var profile = Profiler.Begin(PackageSessionProfilingKeys.Saving)) + saveCompletion = new TaskCompletionSource(); + + saveParameters ??= PackageSaveParameters.Default(); + var assetsOrPackagesToRemove = BuildAssetsOrPackagesToRemove(); + + // Compute packages that have been renamed + // TODO: Disable for now, as not sure if we want to delete a previous package + //foreach (var package in packagesCopy) + //{ + // var newPackage = packages.Find(package.Id); + // if (newPackage is not null && package.PackagePath is not null && newPackage.PackagePath != package.PackagePath) + // { + // assetsOrPackagesToRemove[package.PackagePath] = package; + // } + //} + + // If package are not modified, return immediately + if (!CheckModifiedPackages() && assetsOrPackagesToRemove.Count == 0) { - var packagesSaved = false; - var packagesDirty = false; - try - { - saveCompletion = new TaskCompletionSource(); - - saveParameters = saveParameters ?? PackageSaveParameters.Default(); - var assetsOrPackagesToRemove = BuildAssetsOrPackagesToRemove(); - - // Compute packages that have been renamed - // TODO: Disable for now, as not sure if we want to delete a previous package - //foreach (var package in packagesCopy) - //{ - // var newPackage = packages.Find(package.Id); - // if (newPackage != null && package.PackagePath != null && newPackage.PackagePath != package.PackagePath) - // { - // assetsOrPackagesToRemove[package.PackagePath] = package; - // } - //} - - // If package are not modified, return immediately - if (!CheckModifiedPackages() && assetsOrPackagesToRemove.Count == 0) - { - return; - } + return; + } + + // Suspend tracking when saving as we don't want to receive + // all notification events + dependencies?.BeginSavingSession(); + sourceTracker?.BeginSavingSession(); - // Suspend tracking when saving as we don't want to receive - // all notification events - dependencies?.BeginSavingSession(); - sourceTracker?.BeginSavingSession(); + // Return immediately if there is any error + if (loggerResult.HasErrors) + return; - // Return immediately if there is any error - if (loggerResult.HasErrors) - return; - - //batch projects - var vsProjs = new Dictionary(); + //batch projects + var vsProjs = new Dictionary(); - // Delete previous files - foreach (var fileIt in assetsOrPackagesToRemove) + // Delete previous files + foreach (var (assetPath, assetItem) in assetsOrPackagesToRemove) + { + try + { + //If we are within a csproj we need to remove the file from there as well + var projectFullPath = (assetItem.Package?.Container as SolutionProject)?.FullPath; + if (projectFullPath is not null && assetItem.Asset is IProjectAsset projectAsset) { - var assetPath = fileIt.Key; - var assetItemOrPackage = fileIt.Value; + var projectInclude = assetItem.GetProjectInclude(); - var assetItem = assetItemOrPackage as AssetItem; - try + if (!vsProjs.TryGetValue(projectFullPath, out var project)) { - //If we are within a csproj we need to remove the file from there as well - var projectFullPath = (assetItem.Package.Container as SolutionProject)?.FullPath; - if (projectFullPath != null) - { - var projectAsset = assetItem.Asset as IProjectAsset; - if (projectAsset != null) - { - var projectInclude = assetItem.GetProjectInclude(); - - Microsoft.Build.Evaluation.Project project; - if (!vsProjs.TryGetValue(projectFullPath, out project)) - { - project = VSProjectHelper.LoadProject(projectFullPath.ToOSPath()); - vsProjs.Add(projectFullPath, project); - } - var projectItem = project.Items.FirstOrDefault(x => (x.ItemType == "Compile" || x.ItemType == "None") && x.EvaluatedInclude == projectInclude); - if (projectItem != null && !projectItem.IsImported) - { - project.RemoveItem(projectItem); - } - - //delete any generated file as well - var generatorAsset = assetItem.Asset as IProjectFileGeneratorAsset; - if (generatorAsset != null) - { - var generatedAbsolutePath = assetItem.GetGeneratedAbsolutePath().ToOSPath(); - - File.Delete(generatedAbsolutePath); - - var generatedInclude = assetItem.GetGeneratedInclude(); - var generatedItem = project.Items.FirstOrDefault(x => (x.ItemType == "Compile" || x.ItemType == "None") && x.EvaluatedInclude == generatedInclude); - if (generatedItem != null) - { - project.RemoveItem(generatedItem); - } - } - } - } - - File.Delete(assetPath); + project = VSProjectHelper.LoadProject(projectFullPath.ToOSPath()); + vsProjs.Add(projectFullPath, project); } - catch (Exception ex) + var projectItem = project.Items.FirstOrDefault(x => (x.ItemType == "Compile" || x.ItemType == "None") && x.EvaluatedInclude == projectInclude); + if (projectItem?.IsImported == false) { - if (assetItem != null) - { - loggerResult.Error(assetItem.Package, assetItem.ToReference(), AssetMessageCode.AssetCannotDelete, ex, assetPath); - } - else - { - var package = assetItemOrPackage as Package; - if (package != null) - { - loggerResult.Error(package, null, AssetMessageCode.AssetCannotDelete, ex, assetPath); - } - } + project.RemoveItem(projectItem); } - } - - foreach (var project in vsProjs.Values) - { - project.Save(); - project.ProjectCollection.UnloadAllProjects(); - project.ProjectCollection.Dispose(); - } - // Save all dirty assets - packagesCopy.Clear(); - foreach (var package in LocalPackages) - { - // Save the package to disk and all its assets - package.Container.Save(loggerResult, saveParameters); + //delete any generated file as well + if (assetItem.Asset is IProjectFileGeneratorAsset generatorAsset) + { + var generatedAbsolutePath = assetItem.GetGeneratedAbsolutePath().ToOSPath(); - // Check if everything was saved (might not be the case if things are filtered out) - if (package.IsDirty || package.Assets.IsDirty) - packagesDirty = true; + File.Delete(generatedAbsolutePath); - // Clone the package (but not all assets inside, just the structure) - var packageClone = package.Clone(); - packagesCopy.Add(package, packageClone); + var generatedInclude = assetItem.GetGeneratedInclude(); + var generatedItem = project.Items.FirstOrDefault(x => (x.ItemType == "Compile" || x.ItemType == "None") && x.EvaluatedInclude == generatedInclude); + if (generatedItem is not null) + { + project.RemoveItem(generatedItem); + } + } } - packagesSaved = true; + File.Delete(assetPath); } - finally + catch (Exception ex) { - sourceTracker?.EndSavingSession(); - dependencies?.EndSavingSession(); - - // Once all packages and assets have been saved, we can save the solution (as we need to have fullpath to - // be setup for the packages) - if (packagesSaved) - { - VSSolution.Save(); - } - saveCompletion?.SetResult(0); - saveCompletion = null; + loggerResult.Error(assetItem.Package, assetItem.ToReference(), AssetMessageCode.AssetCannotDelete, ex, assetPath); } - - //System.Diagnostics.Trace.WriteLine("Elapsed saved: " + clock.ElapsedMilliseconds); - IsDirty = packagesDirty; } - } - private Dictionary BuildAssetsOrPackagesToRemove() - { - // Grab all previous assets - var previousAssets = new Dictionary(); - foreach (var assetItem in packagesCopy.SelectMany(package => package.Value.Assets)) + foreach (var project in vsProjs.Values) { - previousAssets[assetItem.Id] = assetItem; + project.Save(); + project.ProjectCollection.UnloadAllProjects(); + project.ProjectCollection.Dispose(); } - // Grab all new assets - var newAssets = new Dictionary(); - foreach (var assetItem in LocalPackages.SelectMany(package => package.Assets)) + // Save all dirty assets + packagesCopy.Clear(); + foreach (var package in LocalPackages) { - newAssets[assetItem.Id] = assetItem; - } + // Save the package to disk and all its assets + package.Container.Save(loggerResult, saveParameters); - // Compute all assets that were removed - var assetsOrPackagesToRemove = new Dictionary(); - foreach (var assetIt in previousAssets) - { - var asset = assetIt.Value; + // Check if everything was saved (might not be the case if things are filtered out) + if (package.IsDirty || package.Assets.IsDirty) + packagesDirty = true; - AssetItem newAsset; - if (!newAssets.TryGetValue(assetIt.Key, out newAsset) || newAsset.Location != asset.Location) - { - assetsOrPackagesToRemove[asset.FullPath] = asset; - } + // Clone the package (but not all assets inside, just the structure) + var packageClone = package.Clone(); + packagesCopy.Add(package, packageClone); } - return assetsOrPackagesToRemove; - } - /// - /// Loads the assembly references that were not loaded before. - /// - /// The log. - public void UpdateAssemblyReferences(LoggerResult log) - { - foreach (var package in LocalPackages) - { - package.UpdateAssemblyReferences(log); - } + packagesSaved = true; } - - private bool CheckModifiedPackages() + finally { - if (IsDirty) - { - return true; - } + sourceTracker?.EndSavingSession(); + dependencies?.EndSavingSession(); - foreach (var package in LocalPackages) + // Once all packages and assets have been saved, we can save the solution (as we need to have fullpath to + // be setup for the packages) + if (packagesSaved) { - if (package.IsDirty || package.Assets.IsDirty) - { - return true; - } - if (package.Assets.Any(item => item.IsDirty)) - { - return true; - } + VSSolution.Save(); } - return false; + saveCompletion?.SetResult(0); + saveCompletion = null; } - private void ProjectsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - RegisterProject((PackageContainer)e.NewItems[0]); - break; - case NotifyCollectionChangedAction.Remove: - UnRegisterProject((PackageContainer)e.OldItems[0]); - break; - - case NotifyCollectionChangedAction.Replace: - packagesCopy.Clear(); - - foreach (var oldProject in e.OldItems.OfType()) - { - UnRegisterProject(oldProject); - } + //System.Diagnostics.Trace.WriteLine("Elapsed saved: " + clock.ElapsedMilliseconds); + IsDirty = packagesDirty; + } - foreach (var projectToCopy in Projects) - { - RegisterProject(projectToCopy); - } - break; - } + private Dictionary BuildAssetsOrPackagesToRemove() + { + // Grab all previous assets + var previousAssets = new Dictionary(); + foreach (var assetItem in packagesCopy.SelectMany(package => package.Value.Assets)) + { + previousAssets[assetItem.Id] = assetItem; } - private void RegisterProject(PackageContainer project) + // Grab all new assets + var newAssets = new Dictionary(); + foreach (var assetItem in LocalPackages.SelectMany(package => package.Assets)) { - if (project.Session != null) - { - throw new InvalidOperationException("Cannot attach a project to more than one session"); - } + newAssets[assetItem.Id] = assetItem; + } - project.SetSessionInternal(this); + // Compute all assets that were removed + var assetsOrPackagesToRemove = new Dictionary(); + foreach (var assetIt in previousAssets) + { + var asset = assetIt.Value; - if (project is SolutionProject solutionProject) + if (!newAssets.TryGetValue(assetIt.Key, out var newAsset) || newAsset.Location != asset.Location) { - // Note: when loading, package might already be there - // TODO CSPROJ=XKPKG: skip it in a proper way? (context info) - if (!VSSolution.Projects.Contains(solutionProject.VSProject)) - { - // Special case: let's put executable windows project first, so that Visual Studio use them as startup project (first project in .sln) - var insertPosition = (solutionProject.Type == ProjectType.Executable && solutionProject.Platform == PlatformType.Windows) - ? 0 - : VSSolution.Projects.Count; - VSSolution.Projects.Insert(insertPosition, solutionProject.VSProject); - } + assetsOrPackagesToRemove[asset.FullPath] = asset; } + } + return assetsOrPackagesToRemove; + } - packages.Add(project.Package); + /// + /// Loads the assembly references that were not loaded before. + /// + /// The log. + public void UpdateAssemblyReferences(LoggerResult log) + { + foreach (var package in LocalPackages) + { + package.UpdateAssemblyReferences(log); + } + } + + private bool CheckModifiedPackages() + { + if (IsDirty) + { + return true; } - private void UnRegisterProject(PackageContainer project) + foreach (var package in LocalPackages) { - if (project.Session != this) + if (package.IsDirty || package.Assets.IsDirty) { - throw new InvalidOperationException("Cannot detach a project that was not attached to this session"); + return true; } - - if (project.Package != null) - packages.Remove(project.Package); - if (project is SolutionProject solutionProject) + if (package.Assets.Any(item => item.IsDirty)) { - VSSolution.Projects.Remove(solutionProject.VSProject); + return true; } - - project.SetSessionInternal(null); } + return false; + } - private void PackagesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void ProjectsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - RegisterPackage((Package)e.NewItems[0]); - break; - case NotifyCollectionChangedAction.Remove: - UnRegisterPackage((Package)e.OldItems[0]); - break; + case NotifyCollectionChangedAction.Add: + { + if (e.NewItems?[0] is PackageContainer project) + RegisterProject(project); + } + break; + case NotifyCollectionChangedAction.Remove: + { + if (e.OldItems?[0] is PackageContainer project) + UnRegisterProject(project); + } + break; - case NotifyCollectionChangedAction.Replace: - packagesCopy.Clear(); + case NotifyCollectionChangedAction.Replace: + packagesCopy.Clear(); - foreach (var oldPackage in e.OldItems.OfType()) - { - UnRegisterPackage(oldPackage); - } + foreach (var oldProject in e.OldItems?.OfType() ?? []) + { + UnRegisterProject(oldProject); + } - foreach (var packageToCopy in Packages) - { - RegisterPackage(packageToCopy); - } - break; - } + foreach (var projectToCopy in Projects) + { + RegisterProject(projectToCopy); + } + break; } + } - private void RegisterPackage(Package package) + private void RegisterProject(PackageContainer project) + { + if (project.Session is not null) { - if (package.IsSystem) - return; - package.AssetDirtyChanged += OnAssetDirtyChanged; + throw new InvalidOperationException("Cannot attach a project to more than one session"); + } + + project.SetSessionInternal(this); - // If the package doesn't have any temporary assets, we can freeze it - if (package.TemporaryAssets.Count == 0) + if (project is SolutionProject solutionProject) + { + // Note: when loading, package might already be there + // TODO CSPROJ=XKPKG: skip it in a proper way? (context info) + if (!VSSolution.Projects.Contains(solutionProject.VSProject)) { - FreezePackage(package); + // Special case: let's put executable windows project first, so that Visual Studio use them as startup project (first project in .sln) + var insertPosition = (solutionProject.Type == ProjectType.Executable && solutionProject.Platform == PlatformType.Windows) + ? 0 + : VSSolution.Projects.Count; + VSSolution.Projects.Insert(insertPosition, solutionProject.VSProject); } - - IsDirty = true; } - /// - /// Freeze a package once it is loaded with all its assets - /// - /// The package to freeze. - private void FreezePackage(Package package) - { - if (package.IsSystem) - return; + packages.Add(project.Package); + } - // Freeze only when assets are loaded - if (package.State < PackageState.AssetsReady) - return; + private void UnRegisterProject(PackageContainer project) + { + if (project.Session != this) + { + throw new InvalidOperationException("Cannot detach a project that was not attached to this session"); + } - // Make sure it's not already been loaded by a previous Save() - if (!packagesCopy.ContainsKey(package)) - packagesCopy.Add(package, package.Clone()); + if (project.Package is not null) + packages.Remove(project.Package); + if (project is SolutionProject solutionProject) + { + VSSolution.Projects.Remove(solutionProject.VSProject); } - private void UnRegisterPackage(Package package) + project.SetSessionInternal(null); + } + + private void PackagesCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) { - if (package.IsSystem) - return; - package.AssetDirtyChanged -= OnAssetDirtyChanged; + case NotifyCollectionChangedAction.Add: + { + if (e.NewItems?[0] is Package package) + RegisterPackage(package); + } + break; + case NotifyCollectionChangedAction.Remove: + { + if (e.OldItems?[0] is Package package) + UnRegisterPackage(package); + } + break; - packagesCopy.Remove(package); + case NotifyCollectionChangedAction.Replace: + packagesCopy.Clear(); - pendingPackageUpgradesPerPackage.Remove(package); + foreach (var oldPackage in e.OldItems?.OfType() ?? []) + { + UnRegisterPackage(oldPackage); + } - IsDirty = true; + foreach (var packageToCopy in Packages) + { + RegisterPackage(packageToCopy); + } + break; } + } + + private void RegisterPackage(Package package) + { + if (package.IsSystem) + return; + package.AssetDirtyChanged += OnAssetDirtyChanged; - private void OnAssetDirtyChanged(AssetItem asset, bool oldValue, bool newValue) + // If the package doesn't have any temporary assets, we can freeze it + if (package.TemporaryAssets.Count == 0) { - AssetDirtyChanged?.Invoke(asset, oldValue, newValue); + FreezePackage(package); } - private Package PreLoadPackage(ILogger log, string filePath, PackageLoadParameters loadParameters) - { - if (log == null) throw new ArgumentNullException(nameof(log)); - if (filePath == null) throw new ArgumentNullException(nameof(filePath)); - if (loadParameters == null) throw new ArgumentNullException(nameof(loadParameters)); + IsDirty = true; + } - try - { - // Load the package without loading any assets - var package = Package.LoadRaw(log, filePath); - - // Convert UPath to absolute (Package only) - // Removed for now because it is called again in PackageSession.LoadAssembliesAndAssets (and running it twice result in dirty package) - // If we remove it from here (and call it only in the other method), templates are not loaded (Because they are loaded via the package store that do not use PreLoadPackage) - //if (loadParameters.ConvertUPathToAbsolute) - //{ - // var analysis = new PackageAnalysis(package, new PackageAnalysisParameters() - // { - // ConvertUPathTo = UPathType.Absolute, - // SetDirtyFlagOnAssetWhenFixingAbsoluteUFile = true, - // IsProcessingUPaths = true, - // }); - // analysis.Run(log); - //} - // If the package doesn't have a meta name, fix it here (This is supposed to be done in the above disabled analysis - but we still need to do it!) - if (string.IsNullOrWhiteSpace(package.Meta.Name) && package.FullPath != null) - { - package.Meta.Name = package.FullPath.GetFileNameWithoutExtension(); - package.IsDirty = true; - } + /// + /// Freeze a package once it is loaded with all its assets + /// + /// The package to freeze. + private void FreezePackage(Package package) + { + if (package.IsSystem) + return; - // Package has been loaded, register it in constraints so that we force each subsequent loads to use this one (or fails if version doesn't match) - if (package.Meta.Version != null) - { - constraintProvider.AddConstraint(package.Meta.Name, new PackageVersionRange(package.Meta.Version)); - } + // Freeze only when assets are loaded + if (package.State < PackageState.AssetsReady) + return; - return package; + // Make sure it's not already been loaded by a previous Save() + if (!packagesCopy.ContainsKey(package)) + packagesCopy.Add(package, package.Clone()); + } + + private void UnRegisterPackage(Package package) + { + if (package.IsSystem) + return; + package.AssetDirtyChanged -= OnAssetDirtyChanged; + + packagesCopy.Remove(package); + + pendingPackageUpgradesPerPackage.Remove(package); + + IsDirty = true; + } + + private void OnAssetDirtyChanged(AssetItem asset, bool oldValue, bool newValue) + { + AssetDirtyChanged?.Invoke(asset, oldValue, newValue); + } + + private Package? PreLoadPackage(ILogger log, string filePath, PackageLoadParameters loadParameters) + { + ArgumentNullException.ThrowIfNull(log); + ArgumentNullException.ThrowIfNull(filePath); + ArgumentNullException.ThrowIfNull(loadParameters); + + try + { + // Load the package without loading any assets + var package = Package.LoadRaw(log, filePath); + + // Convert UPath to absolute (Package only) + // Removed for now because it is called again in PackageSession.LoadAssembliesAndAssets (and running it twice result in dirty package) + // If we remove it from here (and call it only in the other method), templates are not loaded (Because they are loaded via the package store that do not use PreLoadPackage) + //if (loadParameters.ConvertUPathToAbsolute) + //{ + // var analysis = new PackageAnalysis(package, new PackageAnalysisParameters() + // { + // ConvertUPathTo = UPathType.Absolute, + // SetDirtyFlagOnAssetWhenFixingAbsoluteUFile = true, + // IsProcessingUPaths = true, + // }); + // analysis.Run(log); + //} + // If the package doesn't have a meta name, fix it here (This is supposed to be done in the above disabled analysis - but we still need to do it!) + if (string.IsNullOrWhiteSpace(package.Meta.Name) && package.FullPath is not null) + { + package.Meta.Name = package.FullPath.GetFileNameWithoutExtension(); + package.IsDirty = true; } - catch (Exception ex) + + // Package has been loaded, register it in constraints so that we force each subsequent loads to use this one (or fails if version doesn't match) + if (package.Meta.Version is not null) { - log.Error($"Error while pre-loading package [{filePath}]", ex); + constraintProvider.AddConstraint(package.Meta.Name, new PackageVersionRange(package.Meta.Version)); } - return null; + return package; } - - private bool TryLoadAssemblies(PackageSession session, ILogger log, Package package, PackageLoadParameters loadParameters, out AssetLoadingInfo info) + catch (Exception ex) { - info = null; - // Already loaded - if (package.State >= PackageState.AssetsReady) - return false; + log.Error($"Error while pre-loading package [{filePath}]", ex); + } - // Dependencies could not properly be loaded - if (package.State < PackageState.DependenciesReady) - return false; + return null; + } - // A package upgrade has previously been tried and denied, so let's keep the package in this state - if (package.State == PackageState.UpgradeFailed) - return false; + private bool TryLoadAssemblies(PackageSession session, ILogger log, Package package, PackageLoadParameters loadParameters, [MaybeNullWhen(false)] out AssetLoadingInfo info) + { + info = null; + // Already loaded + if (package.State >= PackageState.AssetsReady) + return false; - try - { + // Dependencies could not properly be loaded + if (package.State < PackageState.DependenciesReady) + return false; + // A package upgrade has previously been tried and denied, so let's keep the package in this state + if (package.State == PackageState.UpgradeFailed) + return false; - // Get pending package upgrades - if (!pendingPackageUpgradesPerPackage.TryGetValue(package, out var pendingPackageUpgrades)) - pendingPackageUpgrades = new List(); - pendingPackageUpgradesPerPackage.Remove(package); + try + { + // Get pending package upgrades + if (!pendingPackageUpgradesPerPackage.TryGetValue(package, out var pendingPackageUpgrades)) + pendingPackageUpgrades = []; + pendingPackageUpgradesPerPackage.Remove(package); - // Note: Default state is upgrade failed (for early exit on error/exceptions) - // We will update to success as soon as loading is finished. - package.State = PackageState.UpgradeFailed; + // Note: Default state is upgrade failed (for early exit on error/exceptions) + // We will update to success as soon as loading is finished. + package.State = PackageState.UpgradeFailed; - // Prepare asset loading - var newLoadParameters = loadParameters.Clone(); - newLoadParameters.AssemblyContainer = session.AssemblyContainer; + // Prepare asset loading + var newLoadParameters = loadParameters.Clone(); + newLoadParameters.AssemblyContainer = session.AssemblyContainer; - // Default package version override - newLoadParameters.ExtraCompileProperties = new Dictionary(); - if (loadParameters.ExtraCompileProperties != null) + // Default package version override + newLoadParameters.ExtraCompileProperties = []; + if (loadParameters.ExtraCompileProperties is not null) + { + foreach (var property in loadParameters.ExtraCompileProperties) { - foreach (var property in loadParameters.ExtraCompileProperties) - { - newLoadParameters.ExtraCompileProperties[property.Key] = property.Value; - } + newLoadParameters.ExtraCompileProperties[property.Key] = property.Value; } + } - if (pendingPackageUpgrades.Count > 0) - { - var upgradeAllowed = packageUpgradeAllowed != false ? PackageUpgradeRequestedAnswer.Upgrade : PackageUpgradeRequestedAnswer.DoNotUpgrade; - - // Need upgrades, let's ask user confirmation - if (loadParameters.PackageUpgradeRequested != null && !packageUpgradeAllowed.HasValue) - { - upgradeAllowed = loadParameters.PackageUpgradeRequested(package, pendingPackageUpgrades); - if (upgradeAllowed == PackageUpgradeRequestedAnswer.UpgradeAll) - packageUpgradeAllowed = true; - if (upgradeAllowed == PackageUpgradeRequestedAnswer.DoNotUpgradeAny) - packageUpgradeAllowed = false; - } - - if (!PackageLoadParameters.ShouldUpgrade(upgradeAllowed)) - { - log.Error($"Necessary package migration for [{package.Meta.Name}] has not been allowed"); - return false; - } + if (pendingPackageUpgrades.Count > 0) + { + var upgradeAllowed = packageUpgradeAllowed != false ? PackageUpgradeRequestedAnswer.Upgrade : PackageUpgradeRequestedAnswer.DoNotUpgrade; - // Perform pre assembly load upgrade - foreach (var pendingPackageUpgrade in pendingPackageUpgrades) - { - var packageUpgrader = pendingPackageUpgrade.PackageUpgrader; - var dependencyPackage = pendingPackageUpgrade.DependencyPackage; - if (!packageUpgrader.UpgradeBeforeAssembliesLoaded(loadParameters, session, log, package, pendingPackageUpgrade.Dependency, dependencyPackage)) - { - log.Error($"Error while upgrading package [{package.Meta.Name}] for [{dependencyPackage.Meta.Name}] from version [{pendingPackageUpgrade.Dependency.Version}] to [{dependencyPackage.Meta.Version}]"); - return false; - } - } + // Need upgrades, let's ask user confirmation + if (loadParameters.PackageUpgradeRequested is not null && !packageUpgradeAllowed.HasValue) + { + upgradeAllowed = loadParameters.PackageUpgradeRequested(package, pendingPackageUpgrades); + if (upgradeAllowed == PackageUpgradeRequestedAnswer.UpgradeAll) + packageUpgradeAllowed = true; + if (upgradeAllowed == PackageUpgradeRequestedAnswer.DoNotUpgradeAny) + packageUpgradeAllowed = false; } - // Load assemblies. Set the package filename to the path on disk, in case of renaming. - // TODO: Could referenced projects be associated to other packages than this one? - newLoadParameters.ExtraCompileProperties.Add("StrideCurrentPackagePath", package.FullPath); - package.LoadAssemblies(log, newLoadParameters); - - // Load list of assets - newLoadParameters.AssetFiles = Package.ListAssetFiles(log, package, true, false, loadParameters.CancelToken); - // Sort them by size (to improve concurrency during load) - newLoadParameters.AssetFiles.Sort(PackageLoadingAssetFile.FileSizeComparer.Default); + if (!PackageLoadParameters.ShouldUpgrade(upgradeAllowed)) + { + log.Error($"Necessary package migration for [{package.Meta.Name}] has not been allowed"); + return false; + } - if (pendingPackageUpgrades.Count > 0) + // Perform pre assembly load upgrade + foreach (var pendingPackageUpgrade in pendingPackageUpgrades) { - // Perform upgrades - foreach (var pendingPackageUpgrade in pendingPackageUpgrades) + var packageUpgrader = pendingPackageUpgrade.PackageUpgrader; + var dependencyPackage = pendingPackageUpgrade.DependencyPackage; + if (!packageUpgrader.UpgradeBeforeAssembliesLoaded(loadParameters, session, log, package, pendingPackageUpgrade.Dependency, dependencyPackage)) { - var packageUpgrader = pendingPackageUpgrade.PackageUpgrader; - var dependencyPackage = pendingPackageUpgrade.DependencyPackage; - if (!packageUpgrader.Upgrade(loadParameters, session, log, package, pendingPackageUpgrade.Dependency, dependencyPackage, newLoadParameters.AssetFiles)) - { - log.Error($"Error while upgrading package [{package.Meta.Name}] for [{dependencyPackage.Meta.Name}] from version [{pendingPackageUpgrade.Dependency.Version}] to [{dependencyPackage.Meta.Version}]"); - return false; - } - - // Update dependency to reflect new requirement - pendingPackageUpgrade.Dependency.Version = pendingPackageUpgrade.PackageUpgrader.Attribute.UpdatedVersionRange; + log.Error($"Error while upgrading package [{package.Meta.Name}] for [{dependencyPackage.Meta.Name}] from version [{pendingPackageUpgrade.Dependency.Version}] to [{dependencyPackage.Meta.Version}]"); + return false; } - - // Mark package as dirty - package.IsDirty = true; } - info = new AssetLoadingInfo(session, log, package, loadParameters, pendingPackageUpgrades, newLoadParameters); - return true; - } - catch (Exception ex) - { - log.Error($"Error while pre-loading package [{package}]", ex); - return false; } - } - private static void LoadAssets(PackageSession session, ILogger log, Package package, PackageLoadParameters loadParameters, List pendingPackageUpgrades, PackageLoadParameters newLoadParameters) - { - // Load assets - package.LoadAssets(log, newLoadParameters); + // Load assemblies. Set the package filename to the path on disk, in case of renaming. + // TODO: Could referenced projects be associated to other packages than this one? + newLoadParameters.ExtraCompileProperties.Add("StrideCurrentPackagePath", package.FullPath); + package.LoadAssemblies(log, newLoadParameters); - // Validate assets from package - package.ValidateAssets(newLoadParameters.GenerateNewAssetIds, newLoadParameters.RemoveUnloadableObjects, log); + // Load list of assets + newLoadParameters.AssetFiles = Package.ListAssetFiles(package, true, false); + // Sort them by size (to improve concurrency during load) + newLoadParameters.AssetFiles.Sort(PackageLoadingAssetFile.FileSizeComparer.Default); if (pendingPackageUpgrades.Count > 0) { - // Perform post asset load upgrade + // Perform upgrades foreach (var pendingPackageUpgrade in pendingPackageUpgrades) { var packageUpgrader = pendingPackageUpgrade.PackageUpgrader; var dependencyPackage = pendingPackageUpgrade.DependencyPackage; - if (!packageUpgrader.UpgradeAfterAssetsLoaded(loadParameters, session, log, package, pendingPackageUpgrade.Dependency, dependencyPackage, pendingPackageUpgrade.DependencyVersionBeforeUpgrade)) + if (!packageUpgrader.Upgrade(loadParameters, session, log, package, pendingPackageUpgrade.Dependency, dependencyPackage, newLoadParameters.AssetFiles)) { log.Error($"Error while upgrading package [{package.Meta.Name}] for [{dependencyPackage.Meta.Name}] from version [{pendingPackageUpgrade.Dependency.Version}] to [{dependencyPackage.Meta.Version}]"); - return; + return false; } + + // Update dependency to reflect new requirement + pendingPackageUpgrade.Dependency.Version = pendingPackageUpgrade.PackageUpgrader.Attribute.UpdatedVersionRange; } // Mark package as dirty package.IsDirty = true; } + info = new AssetLoadingInfo(session, log, package, loadParameters, pendingPackageUpgrades, newLoadParameters); + return true; + } + catch (Exception ex) + { + log.Error($"Error while pre-loading package [{package}]", ex); + return false; + } + } - // Mark package as ready - package.State = PackageState.AssetsReady; + private static void LoadAssets(PackageSession session, ILogger log, Package package, PackageLoadParameters loadParameters, List pendingPackageUpgrades, PackageLoadParameters newLoadParameters) + { + // Load assets + package.LoadAssets(log, newLoadParameters); - // Freeze the package after loading the assets - session.FreezePackage(package); - } + // Validate assets from package + package.ValidateAssets(newLoadParameters.GenerateNewAssetIds, newLoadParameters.RemoveUnloadableObjects, log); - private static PackageUpgrader CheckPackageUpgrade(ILogger log, Package dependentPackage, PackageDependency dependency, Package dependencyPackage) + if (pendingPackageUpgrades.Count > 0) { - // Don't do anything if source is a system (read-only) package for now - // We only want to process local packages - if (dependentPackage.IsSystem) - return null; - - // Check if package might need upgrading - var dependentPackagePreviousMinimumVersion = dependency.Version.MinVersion; - if (dependentPackagePreviousMinimumVersion < dependencyPackage.Meta.Version) + // Perform post asset load upgrade + foreach (var pendingPackageUpgrade in pendingPackageUpgrades) { - // Find upgrader for given package - // Note: If no upgrader is found, we assume it is still compatible with previous versions, so do nothing - var packageUpgrader = AssetRegistry.GetPackageUpgrader(dependencyPackage.Meta.Name); - if (packageUpgrader != null) + var packageUpgrader = pendingPackageUpgrade.PackageUpgrader; + var dependencyPackage = pendingPackageUpgrade.DependencyPackage; + if (!packageUpgrader.UpgradeAfterAssetsLoaded(loadParameters, session, log, package, pendingPackageUpgrade.Dependency, dependencyPackage, pendingPackageUpgrade.DependencyVersionBeforeUpgrade)) { - // Check if upgrade is necessary - if (dependency.Version.MinVersion >= packageUpgrader.Attribute.UpdatedVersionRange.MinVersion) - { - return null; - } - - // Check if upgrade is allowed - if (dependency.Version.MinVersion < packageUpgrader.Attribute.PackageMinimumVersion) - { - // Throw an exception, because the package update is not allowed and can't be done - throw new InvalidOperationException($"Upgrading package [{dependentPackage.Meta.Name}] to use [{dependencyPackage.Meta.Name}] from version [{dependentPackagePreviousMinimumVersion}] to [{dependencyPackage.Meta.Version}] is not supported"); - } - - log.Info($"Upgrading package [{dependentPackage.Meta.Name}] to use [{dependencyPackage.Meta.Name}] from version [{dependentPackagePreviousMinimumVersion}] to [{dependencyPackage.Meta.Version}] will be required"); - return packageUpgrader; + log.Error($"Error while upgrading package [{package.Meta.Name}] for [{dependencyPackage.Meta.Name}] from version [{pendingPackageUpgrade.Dependency.Version}] to [{dependencyPackage.Meta.Version}]"); + return; } } - return null; + // Mark package as dirty + package.IsDirty = true; } - public class PendingPackageUpgrade : IEquatable - { - public readonly PackageUpgrader PackageUpgrader; - public readonly PackageDependency Dependency; - public readonly Package DependencyPackage; - public readonly PackageVersionRange DependencyVersionBeforeUpgrade; + // Mark package as ready + package.State = PackageState.AssetsReady; - public PendingPackageUpgrade(PackageUpgrader packageUpgrader, PackageDependency dependency, Package dependencyPackage) - { - PackageUpgrader = packageUpgrader; - Dependency = dependency; - DependencyPackage = dependencyPackage; - DependencyVersionBeforeUpgrade = Dependency.Version; - } + // Freeze the package after loading the assets + session.FreezePackage(package); + } - public bool Equals(PendingPackageUpgrade other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(PackageUpgrader, other.PackageUpgrader) - && Equals(Dependency, other.Dependency) - && Equals(DependencyPackage, other.DependencyPackage) - && Equals(DependencyVersionBeforeUpgrade, other.DependencyVersionBeforeUpgrade); - } + private static PackageUpgrader? CheckPackageUpgrade(ILogger log, Package dependentPackage, PackageDependency dependency, Package dependencyPackage) + { + // Don't do anything if source is a system (read-only) package for now + // We only want to process local packages + if (dependentPackage.IsSystem) + return null; - public override bool Equals(object obj) + // Check if package might need upgrading + var dependentPackagePreviousMinimumVersion = dependency.Version.MinVersion; + if (dependencyPackage.Meta.Version > dependentPackagePreviousMinimumVersion) + { + // Find upgrader for given package + // Note: If no upgrader is found, we assume it is still compatible with previous versions, so do nothing + var packageUpgrader = AssetRegistry.GetPackageUpgrader(dependencyPackage.Meta.Name); + if (packageUpgrader is not null) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return Equals(obj as PendingPackageUpgrade); - } + // Check if upgrade is necessary + if (dependency.Version.MinVersion >= packageUpgrader.Attribute.UpdatedVersionRange.MinVersion) + { + return null; + } - public PendingPackageUpgrade Clone() - { - return new PendingPackageUpgrade(PackageUpgrader, Dependency.Clone(), DependencyPackage); + // Check if upgrade is allowed + if (dependency.Version.MinVersion < packageUpgrader.Attribute.PackageMinimumVersion) + { + // Throw an exception, because the package update is not allowed and can't be done + throw new InvalidOperationException($"Upgrading package [{dependentPackage.Meta.Name}] to use [{dependencyPackage.Meta.Name}] from version [{dependentPackagePreviousMinimumVersion}] to [{dependencyPackage.Meta.Version}] is not supported"); + } + + log.Info($"Upgrading package [{dependentPackage.Meta.Name}] to use [{dependencyPackage.Meta.Name}] from version [{dependentPackagePreviousMinimumVersion}] to [{dependencyPackage.Meta.Version}] will be required"); + return packageUpgrader; } } - private static PackageAnalysisParameters GetPackageAnalysisParametersForLoad() + return null; + } + + public class PendingPackageUpgrade : IEquatable + { + public readonly PackageUpgrader PackageUpgrader; + public readonly PackageDependency Dependency; + public readonly Package DependencyPackage; + public readonly PackageVersionRange DependencyVersionBeforeUpgrade; + + public PendingPackageUpgrade(PackageUpgrader packageUpgrader, PackageDependency dependency, Package dependencyPackage) { - return new PackageAnalysisParameters() - { - IsPackageCheckDependencies = true, - IsProcessingAssetReferences = true, - IsLoggingAssetNotFoundAsError = true, - }; + PackageUpgrader = packageUpgrader; + Dependency = dependency; + DependencyPackage = dependencyPackage; + DependencyVersionBeforeUpgrade = Dependency.Version; } + + public bool Equals(PendingPackageUpgrade? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(PackageUpgrader, other.PackageUpgrader) + && Equals(Dependency, other.Dependency) + && Equals(DependencyPackage, other.DependencyPackage) + && Equals(DependencyVersionBeforeUpgrade, other.DependencyVersionBeforeUpgrade); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as PendingPackageUpgrade); + } + + public PendingPackageUpgrade Clone() + { + return new PendingPackageUpgrade(PackageUpgrader, Dependency.Clone(), DependencyPackage); + } + } + + private static PackageAnalysisParameters GetPackageAnalysisParametersForLoad() + { + return new PackageAnalysisParameters() + { + IsPackageCheckDependencies = true, + IsProcessingAssetReferences = true, + IsLoggingAssetNotFoundAsError = true, + }; } } diff --git a/sources/assets/Stride.Core.Assets/PackageSessionHelper.Solution.cs b/sources/assets/Stride.Core.Assets/PackageSessionHelper.Solution.cs index a68fbed258..d1291cdde9 100644 --- a/sources/assets/Stride.Core.Assets/PackageSessionHelper.Solution.cs +++ b/sources/assets/Stride.Core.Assets/PackageSessionHelper.Solution.cs @@ -1,96 +1,95 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + +using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; -using System.Threading.Tasks; using NuGet.ProjectModel; using Stride.Core.Extensions; using Stride.Core.VisualStudio; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +internal partial class PackageSessionHelper { - internal partial class PackageSessionHelper - { - // Not used since Xenko 3.1 anymore, so doesn't apply to Stride - private static readonly string[] SolutionPackageIdentifier = new[] { "XenkoPackage", "SiliconStudioPackage" }; + // Not used since Xenko 3.1 anymore, so doesn't apply to Stride + private static readonly string[] SolutionPackageIdentifier = ["XenkoPackage", "SiliconStudioPackage"]; - public static async Task GetPackageVersion(string fullPath) + public static async Task GetPackageVersion(string fullPath) + { + try { - try - { - // Solution file: extract projects - var solutionDirectory = Path.GetDirectoryName(fullPath) ?? ""; - var solution = Solution.FromFile(fullPath); + // Solution file: extract projects + var solutionDirectory = Path.GetDirectoryName(fullPath) ?? ""; + var solution = Solution.FromFile(fullPath); - foreach (var project in solution.Projects) + foreach (var project in solution.Projects) + { + if (project.TypeGuid == KnownProjectTypeGuid.CSharp || project.TypeGuid == KnownProjectTypeGuid.CSharpNewSystem) { - if (project.TypeGuid == KnownProjectTypeGuid.CSharp || project.TypeGuid == KnownProjectTypeGuid.CSharpNewSystem) - { - var projectPath = project.FullPath; - var projectAssetsJsonPath = Path.Combine(Path.GetDirectoryName(projectPath), @"obj", LockFileFormat.AssetsFileName); + var projectPath = project.FullPath; + var projectAssetsJsonPath = Path.Combine(Path.GetDirectoryName(projectPath), "obj", LockFileFormat.AssetsFileName); #if !STRIDE_LAUNCHER && !STRIDE_VSPACKAGE - if (!File.Exists(projectAssetsJsonPath)) - { - var log = new Stride.Core.Diagnostics.LoggerResult(); - await VSProjectHelper.RestoreNugetPackages(log, projectPath); - } + if (!File.Exists(projectAssetsJsonPath)) + { + var log = new Stride.Core.Diagnostics.LoggerResult(); + await VSProjectHelper.RestoreNugetPackages(log, projectPath); + } #endif - if (File.Exists(projectAssetsJsonPath)) + if (File.Exists(projectAssetsJsonPath)) + { + var format = new LockFileFormat(); + var projectAssets = format.Read(projectAssetsJsonPath); + foreach (var library in projectAssets.Libraries) { - var format = new LockFileFormat(); - var projectAssets = format.Read(projectAssetsJsonPath); - foreach (var library in projectAssets.Libraries) + if ((library.Type == "package" || library.Type == "project") && (library.Name == "Stride.Engine" || library.Name == "Xenko.Engine")) { - if ((library.Type == "package" || library.Type == "project") && (library.Name == "Stride.Engine" || library.Name == "Xenko.Engine")) - { - return new PackageVersion(library.Version.ToString()); - } + return new PackageVersion(library.Version.ToString()); } } } } } - catch (Exception e) - { - e.Ignore(); - } - - return null; } - - internal static bool IsPackage(Project project) + catch (Exception e) { - string packagePath; - return IsPackage(project, out packagePath); + e.Ignore(); } - internal static bool IsPackage(Project project, out string packagePathRelative) + return null; + } + + internal static bool IsPackage(Project project) + { + return IsPackage(project, out _); + } + + internal static bool IsPackage(Project project, [MaybeNullWhen(false)] out string packagePathRelative) + { + packagePathRelative = null; + if (project.IsSolutionFolder) { - packagePathRelative = null; - if (project.IsSolutionFolder) + foreach (var solutionPackageIdentifier in SolutionPackageIdentifier) { - foreach (var solutionPackageIdentifier in SolutionPackageIdentifier) - if (project.Sections.Contains(solutionPackageIdentifier)) + if (project.Sections.Contains(solutionPackageIdentifier)) + { + var propertyItem = project.Sections[solutionPackageIdentifier].Properties.FirstOrDefault(); + if (propertyItem != null) { - var propertyItem = project.Sections[solutionPackageIdentifier].Properties.FirstOrDefault(); - if (propertyItem != null) - { - packagePathRelative = propertyItem.Name; - return true; - } + packagePathRelative = propertyItem.Name; + return true; } + } } - return false; } + return false; + } - internal static void RemovePackageSections(Project project) + internal static void RemovePackageSections(Project project) + { + if (project.IsSolutionFolder) { - if (project.IsSolutionFolder) - { - foreach (var solutionPackageIdentifier in SolutionPackageIdentifier) - project.Sections.Remove(solutionPackageIdentifier); - } + foreach (var solutionPackageIdentifier in SolutionPackageIdentifier) + project.Sections.Remove(solutionPackageIdentifier); } } } diff --git a/sources/assets/Stride.Core.Assets/PackageSessionProfilingKeys.cs b/sources/assets/Stride.Core.Assets/PackageSessionProfilingKeys.cs index 05c40962bc..6bf77b74b9 100644 --- a/sources/assets/Stride.Core.Assets/PackageSessionProfilingKeys.cs +++ b/sources/assets/Stride.Core.Assets/PackageSessionProfilingKeys.cs @@ -1,24 +1,24 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Diagnostics; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Keys used for profiling the game class. +/// +public static class PackageSessionProfilingKeys { + public static readonly ProfilingKey Session = new("PackageSession"); + /// - /// Keys used for profiling the game class. + /// Profiling load of a session. /// - public static class PackageSessionProfilingKeys - { - public static readonly ProfilingKey Session = new ProfilingKey("PackageSession"); + public static readonly ProfilingKey Loading = new(Session, "Load", ProfilingKeyFlags.Log); - /// - /// Profiling load of a session. - /// - public static readonly ProfilingKey Loading = new ProfilingKey(Session, "Load", ProfilingKeyFlags.Log); - - /// - /// Profiling save of a session. - /// - public static readonly ProfilingKey Saving = new ProfilingKey(Session, "Save", ProfilingKeyFlags.Log); - } + /// + /// Profiling save of a session. + /// + public static readonly ProfilingKey Saving = new(Session, "Save", ProfilingKeyFlags.Log); } diff --git a/sources/assets/Stride.Core.Assets/PackageSessionPublicHelper.cs b/sources/assets/Stride.Core.Assets/PackageSessionPublicHelper.cs index 8b8bff382f..47c9bf2599 100644 --- a/sources/assets/Stride.Core.Assets/PackageSessionPublicHelper.cs +++ b/sources/assets/Stride.Core.Assets/PackageSessionPublicHelper.cs @@ -1,142 +1,131 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; + using System.Runtime.InteropServices; using System.Runtime.Loader; -using System.Threading; using Microsoft.Build.Locator; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Helper class to load/save a VisualStudio solution. +/// +public static class PackageSessionPublicHelper { /// - /// Helper class to load/save a VisualStudio solution. + /// The current major .NET version that Stride will use. /// - public static class PackageSessionPublicHelper - { + public static int NetMajorVersion => Environment.Version.Major; - /// - /// The current major .NET version that Stride will use. - /// - public static int NetMajorVersion => Environment.Version.Major; - - private static readonly string[] s_msBuildAssemblies = - { - "Microsoft.Build", - "Microsoft.Build.Framework", - "Microsoft.Build.Tasks.Core", - "Microsoft.Build.Utilities.Core" - }; + private static readonly string[] s_msBuildAssemblies = + [ + "Microsoft.Build", + "Microsoft.Build.Framework", + "Microsoft.Build.Tasks.Core", + "Microsoft.Build.Utilities.Core" + ]; - private static int MSBuildLocatorCount = 0; - private static VisualStudioInstance MSBuildInstance; + private static int MSBuildLocatorCount = 0; + private static VisualStudioInstance? MSBuildInstance; - /// - /// This method finds a compatible version of MSBuild. - /// - public static void FindAndSetMSBuildVersion() + /// + /// This method finds a compatible version of MSBuild. + /// + public static void FindAndSetMSBuildVersion() + { + // Note: this should be called only once + if (MSBuildInstance == null && Interlocked.Increment(ref MSBuildLocatorCount) == 1) { - // Note: this should be called only once - if (MSBuildInstance == null && Interlocked.Increment(ref MSBuildLocatorCount) == 1) + // Detect either .NET Core SDK or Visual Studio depending on current runtime + var isNETCore = !RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework", StringComparison.Ordinal); + MSBuildInstance = MSBuildLocator.QueryVisualStudioInstances().FirstOrDefault(x => isNETCore + ? x.DiscoveryType == DiscoveryType.DotNetSdk && x.Version.Major == NetMajorVersion + : (x.DiscoveryType == DiscoveryType.VisualStudioSetup || x.DiscoveryType == DiscoveryType.DeveloperConsole) && x.Version.Major >= 16); + + if (MSBuildInstance == null) { - // Detect either .NET Core SDK or Visual Studio depending on current runtime - var isNETCore = !RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework", StringComparison.Ordinal); - MSBuildInstance = MSBuildLocator.QueryVisualStudioInstances().FirstOrDefault(x => isNETCore - ? x.DiscoveryType == DiscoveryType.DotNetSdk && x.Version.Major == NetMajorVersion - : (x.DiscoveryType == DiscoveryType.VisualStudioSetup || x.DiscoveryType == DiscoveryType.DeveloperConsole) && x.Version.Major >= 16); - - if (MSBuildInstance == null) - { - throw new InvalidOperationException("Could not find a MSBuild installation (expected 16.0 or later) " + - $"Please ensure you have the .NET {NetMajorVersion} SDK installed from Microsoft's website"); - } + throw new InvalidOperationException("Could not find a MSBuild installation (expected 16.0 or later) " + + $"Please ensure you have the .NET {NetMajorVersion} SDK installed from Microsoft's website"); + } - // Make sure it is not already loaded (otherwise MSBuildLocator.RegisterDefaults() throws an exception) - if (!AppDomain.CurrentDomain.GetAssemblies().Any(IsMSBuildAssembly)) - { - // We can't use directly RegisterInstance because we want to avoid NuGet verison conflicts (between MSBuild/dotnet one and ours). - // More details at https://github.com/microsoft/MSBuildLocator/issues/127 - // This code should be equivalent to MSBuildLocator.RegisterInstance(MSBuildInstance); - // except that we load everything in another context. + // Make sure it is not already loaded (otherwise MSBuildLocator.RegisterDefaults() throws an exception) + if (!AppDomain.CurrentDomain.GetAssemblies().Any(IsMSBuildAssembly)) + { + // We can't use directly RegisterInstance because we want to avoid NuGet verison conflicts (between MSBuild/dotnet one and ours). + // More details at https://github.com/microsoft/MSBuildLocator/issues/127 + // This code should be equivalent to MSBuildLocator.RegisterInstance(MSBuildInstance); + // except that we load everything in another context. - ApplyDotNetSdkEnvironmentVariables(MSBuildInstance.MSBuildPath); + ApplyDotNetSdkEnvironmentVariables(MSBuildInstance.MSBuildPath); - var msbuildAssemblyLoadContext = new AssemblyLoadContext("MSBuild"); + var msbuildAssemblyLoadContext = new AssemblyLoadContext("MSBuild"); - AssemblyLoadContext.Default.Resolving += (assemblyLoadContext, assemblyName) => + AssemblyLoadContext.Default.Resolving += (_, assemblyName) => + { + string path = Path.Combine(MSBuildInstance.MSBuildPath, assemblyName.Name + ".dll"); + if (File.Exists(path)) { - string path = Path.Combine(MSBuildInstance.MSBuildPath, assemblyName.Name + ".dll"); - if (File.Exists(path)) - { - return msbuildAssemblyLoadContext.LoadFromAssemblyPath(path); - } - - return null; - }; - } - } - - SetupMSBuildCurrentHostForOutOfProc(MSBuildInstance.MSBuildPath); - CheckMSBuildToolset(); + return msbuildAssemblyLoadContext.LoadFromAssemblyPath(path); + } - // Reset MSBUILD_EXE_PATH once MSBuild is resolved, to not spook child process (had issues with ThisProcess(MSBuild)->CompilerApp(net472): CompilerApp couldn't load MSBuild project properly) - Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", null); + return null; + }; + } } - // Function copied from MSBuildLocator.ApplyDotNetSdkEnvironmentVariables. Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. - private static void ApplyDotNetSdkEnvironmentVariables(string dotNetSdkPath) - { - const string MSBUILD_EXE_PATH = nameof(MSBUILD_EXE_PATH); - const string MSBuildExtensionsPath = nameof(MSBuildExtensionsPath); - const string MSBuildSDKsPath = nameof(MSBuildSDKsPath); + SetupMSBuildCurrentHostForOutOfProc(MSBuildInstance.MSBuildPath); + CheckMSBuildToolset(); - var variables = new Dictionary - { - [MSBUILD_EXE_PATH] = Path.Combine(dotNetSdkPath, "MSBuild.dll"), - [MSBuildExtensionsPath] = dotNetSdkPath, - [MSBuildSDKsPath] = Path.Combine(dotNetSdkPath, "Sdks") - }; + // Reset MSBUILD_EXE_PATH once MSBuild is resolved, to not spook child process (had issues with ThisProcess(MSBuild)->CompilerApp(net472): CompilerApp couldn't load MSBuild project properly) + Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", null); + } - foreach (var kvp in variables) - { - Environment.SetEnvironmentVariable(kvp.Key, kvp.Value); - } - } + // Function copied from MSBuildLocator.ApplyDotNetSdkEnvironmentVariables. Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. + private static void ApplyDotNetSdkEnvironmentVariables(string dotNetSdkPath) + { + const string MSBUILD_EXE_PATH = nameof(MSBUILD_EXE_PATH); + const string MSBuildExtensionsPath = nameof(MSBuildExtensionsPath); + const string MSBuildSDKsPath = nameof(MSBuildSDKsPath); - private static bool IsMSBuildAssembly(System.Reflection.Assembly assembly) + var variables = new Dictionary { - return IsMSBuildAssembly(assembly.GetName()); - } + [MSBUILD_EXE_PATH] = Path.Combine(dotNetSdkPath, "MSBuild.dll"), + [MSBuildExtensionsPath] = dotNetSdkPath, + [MSBuildSDKsPath] = Path.Combine(dotNetSdkPath, "Sdks") + }; - private static bool IsMSBuildAssembly(System.Reflection.AssemblyName assemblyName) + foreach (var kvp in variables) { - return s_msBuildAssemblies.Contains(assemblyName.Name, StringComparer.OrdinalIgnoreCase); + Environment.SetEnvironmentVariable(kvp.Key, kvp.Value); } + } - private static void SetupMSBuildCurrentHostForOutOfProc(string dotNetSdkPath) - { - // Workaround for https://github.com/dotnet/msbuild/pull/7013 (dotnet.exe not properly detected by MSBuild so it fallbacks to launching our own executable instead) - var currentHostField = typeof(Microsoft.Build.Evaluation.Project).Assembly - .GetType("Microsoft.Build.BackEnd.NodeProviderOutOfProcBase")? - .GetField("CurrentHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - if (currentHostField != null) - { - currentHostField.SetValue(null, Path.Combine(new DirectoryInfo(dotNetSdkPath).Parent.Parent.FullName, OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet")); - } - } + private static bool IsMSBuildAssembly(System.Reflection.Assembly assembly) + { + return IsMSBuildAssembly(assembly.GetName()); + } + + private static bool IsMSBuildAssembly(System.Reflection.AssemblyName assemblyName) + { + return s_msBuildAssemblies.Contains(assemblyName.Name, StringComparer.OrdinalIgnoreCase); + } - private static void CheckMSBuildToolset() + private static void SetupMSBuildCurrentHostForOutOfProc(string dotNetSdkPath) + { + // Workaround for https://github.com/dotnet/msbuild/pull/7013 (dotnet.exe not properly detected by MSBuild so it fallbacks to launching our own executable instead) + var currentHostField = typeof(Microsoft.Build.Evaluation.Project).Assembly + .GetType("Microsoft.Build.BackEnd.NodeProviderOutOfProcBase")? + .GetField("CurrentHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + currentHostField?.SetValue(null, Path.Combine(new DirectoryInfo(dotNetSdkPath).Parent.Parent.FullName, OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet")); + } + + private static void CheckMSBuildToolset() + { + // Check that we can create a project + using var projectCollection = new Microsoft.Build.Evaluation.ProjectCollection(); + if (projectCollection.GetToolset("Current") == null) // VS 2019+ (https://github.com/Microsoft/msbuild/issues/3778) { - // Check that we can create a project - using (var projectCollection = new Microsoft.Build.Evaluation.ProjectCollection()) - { - if (projectCollection.GetToolset("Current") == null) // VS 2019+ (https://github.com/Microsoft/msbuild/issues/3778) - { - throw new InvalidOperationException("Could not find a supported MSBuild toolset version (expected 16.0 or later)"); - } - } + throw new InvalidOperationException("Could not find a supported MSBuild toolset version (expected 16.0 or later)"); } } } diff --git a/sources/assets/Stride.Core.Assets/PackageSessionResult.cs b/sources/assets/Stride.Core.Assets/PackageSessionResult.cs index 9033733b89..0f6120bd55 100644 --- a/sources/assets/Stride.Core.Assets/PackageSessionResult.cs +++ b/sources/assets/Stride.Core.Assets/PackageSessionResult.cs @@ -2,29 +2,28 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using Stride.Core.Diagnostics; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Result returned when loading a session using +/// +public sealed class PackageSessionResult : LoggerResult { /// - /// Result returned when loading a session using + /// Gets or sets the loaded session. /// - public sealed class PackageSessionResult : LoggerResult - { - /// - /// Gets or sets the loaded session. - /// - /// The session. - public PackageSession Session { get; internal set; } + /// The session. + public PackageSession Session { get; internal set; } - /// - /// Gets or sets whether the operation has been cancelled by user. - /// - public bool OperationCancelled { get; set; } + /// + /// Gets or sets whether the operation has been cancelled by user. + /// + public bool OperationCancelled { get; set; } - /// - public override void Clear() - { - base.Clear(); - Session = null; - } + /// + public override void Clear() + { + base.Clear(); + Session = null; } } diff --git a/sources/assets/Stride.Core.Assets/PackageStore.cs b/sources/assets/Stride.Core.Assets/PackageStore.cs index 3f6914f69b..4dd834fdc4 100644 --- a/sources/assets/Stride.Core.Assets/PackageStore.cs +++ b/sources/assets/Stride.Core.Assets/PackageStore.cs @@ -1,201 +1,193 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Stride.Core; -using Stride.Core.Diagnostics; using Stride.Core.IO; -using System.Threading.Tasks; using Stride.Core.Packages; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Manage packages locally installed and accessible on the store. +/// +/// +/// This class is the frontend to the packaging/distribution system. It is currently using nuget for its packaging but may +/// change in the future. +/// +public class PackageStore { + private static readonly Lazy DefaultPackageStore = new(() => new PackageStore()); + /// - /// Manage packages locally installed and accessible on the store. + /// Associated NugetStore for our packages. Cannot be null. /// - /// - /// This class is the frontend to the packaging/distribution system. It is currently using nuget for its packaging but may - /// change in the future. - /// - public class PackageStore - { - private static readonly Lazy DefaultPackageStore = new Lazy(() => new PackageStore()); - - /// - /// Associated NugetStore for our packages. Cannot be null. - /// - private readonly NugetStore store; - - /// - /// Initializes a new instance of the class. - /// - /// Unable to find a valid Stride installation path - private PackageStore() - { - // Check if we are in a root directory with store/packages facilities - store = new NugetStore(null); - } - - /// - /// Gets the packages available online. - /// - /// IEnumerable<PackageMeta>. - public async Task> GetPackages() - { - var packages = await store.SourceSearch(null, allowPrereleaseVersions: false); + private readonly NugetStore store; - // Order by download count and Id to allow collapsing - var orderedPackages = packages.OrderByDescending(p => p.DownloadCount).ThenBy(p => p.Id); + /// + /// Initializes a new instance of the class. + /// + /// Unable to find a valid Stride installation path + private PackageStore() + { + // Check if we are in a root directory with store/packages facilities + store = new NugetStore(null); + } - // For some unknown reasons, we can't select directly from IQueryable to IQueryable, - // so we need to pass through a IEnumerable and translate it to IQueyable. Not sure it has - // an implication on the original query behinds the scene - return orderedPackages.Select(PackageMetaFromNugetPackage); - } + /// + /// Gets the packages available online. + /// + /// IEnumerable<PackageMeta>. + public async Task> GetPackages() + { + var packages = await store.SourceSearch(null, allowPrereleaseVersions: false); - public NugetLocalPackage FindLocalPackage(string packageName, PackageVersionRange versionRange = null, ConstraintProvider constraintProvider = null, bool allowPreleaseVersion = true, bool allowUnlisted = false) - { - if (packageName == null) throw new ArgumentNullException(nameof(packageName)); + // Order by download count and Id to allow collapsing + var orderedPackages = packages.OrderByDescending(p => p.DownloadCount).ThenBy(p => p.Id); - return store.FindLocalPackage(packageName, versionRange, constraintProvider, allowPreleaseVersion, allowUnlisted); - } + // For some unknown reasons, we can't select directly from IQueryable to IQueryable, + // so we need to pass through a IEnumerable and translate it to IQueyable. Not sure it has + // an implication on the original query behinds the scene + return orderedPackages.Select(PackageMetaFromNugetPackage); + } - /// - /// Gets the filename to the specific package using just a package name. - /// - /// Name of the package. - /// A location on the disk to the specified package or null if not found. - /// packageName - public UFile GetPackageWithFileName(string packageName) - { - return GetPackageFileName(packageName); - } + public NugetLocalPackage? FindLocalPackage(string packageName, PackageVersionRange? versionRange = null, ConstraintProvider? constraintProvider = null, bool allowPreleaseVersion = true, bool allowUnlisted = false) + { + ArgumentNullException.ThrowIfNull(packageName); - /// - /// Gets the filename to the specific package using the version if not null, otherwise the if specified. - /// If no constraints are specified, the first entry if any are founds is used to get the filename. - /// - /// Name of the package. - /// The version range. - /// The package constraint provider. - /// if set to true [allow prelease version]. - /// if set to true [allow unlisted]. - /// A location on the disk to the specified package or null if not found. - /// packageName - public UFile GetPackageFileName(string packageName, PackageVersionRange versionRange = null, ConstraintProvider constraintProvider = null, bool allowPreleaseVersion = true, bool allowUnlisted = false) - { - if (packageName == null) throw new ArgumentNullException(nameof(packageName)); + return store.FindLocalPackage(packageName, versionRange, constraintProvider, allowPreleaseVersion, allowUnlisted); + } - var package = store.FindLocalPackage(packageName, versionRange, constraintProvider, allowPreleaseVersion, allowUnlisted); + /// + /// Gets the filename to the specific package using just a package name. + /// + /// Name of the package. + /// A location on the disk to the specified package or null if not found. + /// packageName + public UFile? GetPackageWithFileName(string packageName) + { + return GetPackageFileName(packageName); + } - // If package was not found, - if (package != null) - { - var packageRoot = (UDirectory)store.GetRealPath(package); - var packageFilename = new UFile(packageName + Package.PackageFileExtension); - - // First look for sdpkg at package root - var packageFile = UPath.Combine(packageRoot, packageFilename); - if (File.Exists(packageFile)) - return packageFile; - - // Then look for sdpkg inside stride subfolder - packageFile = UPath.Combine(UPath.Combine(packageRoot, (UDirectory)"stride"), packageFilename); - if (File.Exists(packageFile)) - return packageFile; - } + /// + /// Gets the filename to the specific package using the version if not null, otherwise the if specified. + /// If no constraints are specified, the first entry if any are founds is used to get the filename. + /// + /// Name of the package. + /// The version range. + /// The package constraint provider. + /// if set to true [allow prelease version]. + /// if set to true [allow unlisted]. + /// A location on the disk to the specified package or null if not found. + /// packageName + public UFile? GetPackageFileName(string packageName, PackageVersionRange? versionRange = null, ConstraintProvider? constraintProvider = null, bool allowPreleaseVersion = true, bool allowUnlisted = false) + { + ArgumentNullException.ThrowIfNull(packageName); - return null; - } + var package = store.FindLocalPackage(packageName, versionRange, constraintProvider, allowPreleaseVersion, allowUnlisted); - /// - /// Gets the default package manager. - /// - /// A default instance. - public static PackageStore Instance + // If package was not found, + if (package is not null) { - get - { - return DefaultPackageStore.Value; - } + var packageRoot = (UDirectory)store.GetRealPath(package); + var packageFilename = new UFile(packageName + Package.PackageFileExtension); + + // First look for sdpkg at package root + var packageFile = UPath.Combine(packageRoot, packageFilename); + if (File.Exists(packageFile)) + return packageFile; + + // Then look for sdpkg inside stride subfolder + packageFile = UPath.Combine(UPath.Combine(packageRoot, (UDirectory)"stride"), packageFilename); + if (File.Exists(packageFile)) + return packageFile; } - private static PackageLoadParameters GetDefaultPackageLoadParameters() - { - // By default, we are not loading assets for installed packages - return new PackageLoadParameters { AutoLoadTemporaryAssets = false, LoadAssemblyReferences = false, AutoCompileProjects = false }; - } + return null; + } - /// - /// New instance of from a nuget package . - /// - /// The nuget metadata used to initialized an instance of . - public static PackageMeta PackageMetaFromNugetPackage(NugetPackage metadata) + /// + /// Gets the default package manager. + /// + /// A default instance. + public static PackageStore Instance + { + get { - var meta = new PackageMeta - { - Name = metadata.Id, - Version = new PackageVersion(metadata.Version.ToString()), - Title = metadata.Title, - IconUrl = metadata.IconUrl, - LicenseUrl = metadata.LicenseUrl, - ProjectUrl = metadata.ProjectUrl, - RequireLicenseAcceptance = metadata.RequireLicenseAcceptance, - Description = metadata.Description, - Summary = metadata.Summary, - Tags = metadata.Tags, - Listed = metadata.Listed, - Published = metadata.Published, - ReportAbuseUrl = metadata.ReportAbuseUrl, - DownloadCount = metadata.DownloadCount - }; - - meta.Authors.AddRange(metadata.Authors); - meta.Owners.AddRange(metadata.Owners); - - if (metadata.DependencySetsCount > 1) - { - throw new InvalidOperationException("Metadata loaded from nuspec cannot have more than one group of dependency"); - } - - return meta; + return DefaultPackageStore.Value; } + } - public static void ToNugetManifest(PackageMeta meta, ManifestMetadata manifestMeta) + private static PackageLoadParameters GetDefaultPackageLoadParameters() + { + // By default, we are not loading assets for installed packages + return new PackageLoadParameters { AutoLoadTemporaryAssets = false, LoadAssemblyReferences = false, AutoCompileProjects = false }; + } + + /// + /// New instance of from a nuget package . + /// + /// The nuget metadata used to initialized an instance of . + public static PackageMeta PackageMetaFromNugetPackage(NugetPackage metadata) + { + var meta = new PackageMeta { - manifestMeta.Id = meta.Name; - manifestMeta.Version = meta.Version.ToString(); - manifestMeta.Title = meta.Title.SafeTrim(); - manifestMeta.Authors = meta.Authors; - manifestMeta.Owners = meta.Owners; - manifestMeta.Tags = String.IsNullOrEmpty(meta.Tags) ? null : meta.Tags.SafeTrim(); - manifestMeta.LicenseUrl = ConvertUrlToStringSafe(meta.LicenseUrl); - manifestMeta.ProjectUrl = ConvertUrlToStringSafe(meta.ProjectUrl); - manifestMeta.IconUrl = ConvertUrlToStringSafe(meta.IconUrl); - manifestMeta.RequireLicenseAcceptance = meta.RequireLicenseAcceptance; - manifestMeta.DevelopmentDependency = false; - manifestMeta.Description = meta.Description.SafeTrim(); - manifestMeta.Copyright = meta.Copyright.SafeTrim(); - manifestMeta.Summary = meta.Summary.SafeTrim(); - manifestMeta.ReleaseNotes = meta.ReleaseNotes.SafeTrim(); - manifestMeta.Language = meta.Language.SafeTrim(); + Name = metadata.Id, + Version = new PackageVersion(metadata.Version.ToString()), + Title = metadata.Title, + IconUrl = metadata.IconUrl, + LicenseUrl = metadata.LicenseUrl, + ProjectUrl = metadata.ProjectUrl, + RequireLicenseAcceptance = metadata.RequireLicenseAcceptance, + Description = metadata.Description, + Summary = metadata.Summary, + Tags = metadata.Tags, + Listed = metadata.Listed, + Published = metadata.Published, + ReportAbuseUrl = metadata.ReportAbuseUrl, + DownloadCount = metadata.DownloadCount + }; + + meta.Authors.AddRange(metadata.Authors); + meta.Owners.AddRange(metadata.Owners); + + if (metadata.DependencySetsCount > 1) + { + throw new InvalidOperationException("Metadata loaded from nuspec cannot have more than one group of dependency"); } - private static string ConvertUrlToStringSafe(Uri url) + return meta; + } + + public static void ToNugetManifest(PackageMeta meta, ManifestMetadata manifestMeta) + { + manifestMeta.Id = meta.Name; + manifestMeta.Version = meta.Version.ToString(); + manifestMeta.Title = meta.Title.SafeTrim(); + manifestMeta.Authors = meta.Authors; + manifestMeta.Owners = meta.Owners; + manifestMeta.Tags = string.IsNullOrEmpty(meta.Tags) ? null : meta.Tags.SafeTrim(); + manifestMeta.LicenseUrl = ConvertUrlToStringSafe(meta.LicenseUrl); + manifestMeta.ProjectUrl = ConvertUrlToStringSafe(meta.ProjectUrl); + manifestMeta.IconUrl = ConvertUrlToStringSafe(meta.IconUrl); + manifestMeta.RequireLicenseAcceptance = meta.RequireLicenseAcceptance; + manifestMeta.DevelopmentDependency = false; + manifestMeta.Description = meta.Description.SafeTrim(); + manifestMeta.Copyright = meta.Copyright.SafeTrim(); + manifestMeta.Summary = meta.Summary.SafeTrim(); + manifestMeta.ReleaseNotes = meta.ReleaseNotes.SafeTrim(); + manifestMeta.Language = meta.Language.SafeTrim(); + } + + private static string? ConvertUrlToStringSafe(Uri url) + { + if (url != null) { - if (url != null) + string originalString = url.OriginalString.SafeTrim(); + if (!string.IsNullOrEmpty(originalString)) { - string originalString = url.OriginalString.SafeTrim(); - if (!string.IsNullOrEmpty(originalString)) - { - return originalString; - } + return originalString; } - - return null; } + + return null; } } diff --git a/sources/assets/Stride.Core.Assets/PackageUpgradeRequestedAnswer.cs b/sources/assets/Stride.Core.Assets/PackageUpgradeRequestedAnswer.cs index b1c51b0fee..4e806b499c 100644 --- a/sources/assets/Stride.Core.Assets/PackageUpgradeRequestedAnswer.cs +++ b/sources/assets/Stride.Core.Assets/PackageUpgradeRequestedAnswer.cs @@ -1,27 +1,27 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets + +namespace Stride.Core.Assets; + +/// +/// An enum representing the user answer to a package upgrade request. +/// +public enum PackageUpgradeRequestedAnswer { /// - /// An enum representing the user answer to a package upgrade request. + /// The related package should be upgraded. /// - public enum PackageUpgradeRequestedAnswer - { - /// - /// The related package should be upgraded. - /// - Upgrade, - /// - /// The related package and all following packages should be upgraded. - /// - UpgradeAll, - /// - /// The related package should not be upgraded. - /// - DoNotUpgrade, - /// - /// The related package and all following packages should not be upgraded. - /// - DoNotUpgradeAny, - } + Upgrade, + /// + /// The related package and all following packages should be upgraded. + /// + UpgradeAll, + /// + /// The related package should not be upgraded. + /// + DoNotUpgrade, + /// + /// The related package and all following packages should not be upgraded. + /// + DoNotUpgradeAny, } diff --git a/sources/assets/Stride.Core.Assets/PackageUpgrader.cs b/sources/assets/Stride.Core.Assets/PackageUpgrader.cs index e5f6a5ec73..78082a90aa 100644 --- a/sources/assets/Stride.Core.Assets/PackageUpgrader.cs +++ b/sources/assets/Stride.Core.Assets/PackageUpgrader.cs @@ -1,62 +1,59 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using Stride.Core; using Stride.Core.Diagnostics; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Offers a way for package to upgrade dependent packages. +/// For example, if you write package A and Game1 depends on it, you might want to offer a new version of package A that would automatically perform some upgrades on Game1. +/// +public abstract class PackageUpgrader { + public PackageUpgraderAttribute Attribute { get; internal set; } = null!; // note: set right after being intialized + /// - /// Offers a way for package to upgrade dependent packages. - /// For example, if you write package A and Game1 depends on it, you might want to offer a new version of package A that would automatically perform some upgrades on Game1. + /// Performs a preprocessing step of package migration, before assembly references are loaded. /// - public abstract class PackageUpgrader + /// The load parameters. + /// The session. + /// The log. + /// The source package. + /// The dependency. + /// The dependency package. + /// + public virtual bool UpgradeBeforeAssembliesLoaded(PackageLoadParameters loadParameters, PackageSession session, ILogger log, Package dependentPackage, PackageDependency dependency, Package dependencyPackage) { - public PackageUpgraderAttribute Attribute { get; internal set; } - - /// - /// Performs a preprocessing step of package migration, before assembly references are loaded. - /// - /// The load parameters. - /// The session. - /// The log. - /// The source package. - /// The dependency. - /// The dependency package. - /// - public virtual bool UpgradeBeforeAssembliesLoaded(PackageLoadParameters loadParameters, PackageSession session, ILogger log, Package dependentPackage, PackageDependency dependency, Package dependencyPackage) - { - return true; - } + return true; + } - /// - /// Performs the package migration, before assets are loaded - /// - /// The load parameters. - /// The session. - /// The log. - /// The source package. - /// The dependency. - /// The dependency package. - /// The asset files. - /// - public abstract bool Upgrade(PackageLoadParameters loadParameters, PackageSession session, ILogger log, Package dependentPackage, PackageDependency dependency, Package dependencyPackage, IList assetFiles); + /// + /// Performs the package migration, before assets are loaded + /// + /// The load parameters. + /// The session. + /// The log. + /// The source package. + /// The dependency. + /// The dependency package. + /// The asset files. + /// + public abstract bool Upgrade(PackageLoadParameters loadParameters, PackageSession session, ILogger log, Package dependentPackage, PackageDependency dependency, Package dependencyPackage, IList assetFiles); - /// - /// Performs the second step of package migration, after assets have been loaded. - /// - /// The load parameters. - /// The session. - /// The log. - /// The source package. - /// The dependency. - /// The dependency package. - /// The version before the update. - /// - public virtual bool UpgradeAfterAssetsLoaded(PackageLoadParameters loadParameters, PackageSession session, ILogger log, Package dependentPackage, PackageDependency dependency, Package dependencyPackage, PackageVersionRange dependencyVersionBeforeUpdate) - { - return true; - } + /// + /// Performs the second step of package migration, after assets have been loaded. + /// + /// The load parameters. + /// The session. + /// The log. + /// The source package. + /// The dependency. + /// The dependency package. + /// The version before the update. + /// + public virtual bool UpgradeAfterAssetsLoaded(PackageLoadParameters loadParameters, PackageSession session, ILogger log, Package dependentPackage, PackageDependency dependency, Package dependencyPackage, PackageVersionRange dependencyVersionBeforeUpdate) + { + return true; } } diff --git a/sources/assets/Stride.Core.Assets/PackageUpgraderAttribute.cs b/sources/assets/Stride.Core.Assets/PackageUpgraderAttribute.cs index b3b1ed4911..33eafe5c71 100644 --- a/sources/assets/Stride.Core.Assets/PackageUpgraderAttribute.cs +++ b/sources/assets/Stride.Core.Assets/PackageUpgraderAttribute.cs @@ -1,34 +1,31 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; using Stride.Core.Annotations; using Stride.Core.Reflection; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Attribute that describes what a package upgrader can do. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +[BaseTypeRequired(typeof(PackageUpgrader))] +[AssemblyScan] +public class PackageUpgraderAttribute : Attribute { - /// - /// Attribute that describes what a package upgrader can do. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - [BaseTypeRequired(typeof(PackageUpgrader))] - [AssemblyScan] - public class PackageUpgraderAttribute : Attribute - { - private readonly PackageVersionRange updatedVersionRange; - - public string[] PackageNames { get; private set; } + private readonly PackageVersionRange? updatedVersionRange; - public PackageVersion PackageMinimumVersion { get; private set; } + public string[] PackageNames { get; } - public PackageVersionRange UpdatedVersionRange { get { return updatedVersionRange; } } + public PackageVersion PackageMinimumVersion { get; } - public PackageUpgraderAttribute(string[] packageNames, string packageMinimumVersion, string packageUpdatedVersionRange) - { - PackageNames = packageNames; - PackageMinimumVersion = new PackageVersion(packageMinimumVersion); - PackageVersionRange.TryParse(packageUpdatedVersionRange, out this.updatedVersionRange); - } + public PackageVersionRange? UpdatedVersionRange => updatedVersionRange; + + public PackageUpgraderAttribute(string[] packageNames, string packageMinimumVersion, string packageUpdatedVersionRange) + { + PackageNames = packageNames; + PackageMinimumVersion = new PackageVersion(packageMinimumVersion); + _ = PackageVersionRange.TryParse(packageUpdatedVersionRange, out updatedVersionRange); } } diff --git a/sources/assets/Stride.Core.Assets/PackageUserSettings.cs b/sources/assets/Stride.Core.Assets/PackageUserSettings.cs index 00477ef5d5..64970de49f 100644 --- a/sources/assets/Stride.Core.Assets/PackageUserSettings.cs +++ b/sources/assets/Stride.Core.Assets/PackageUserSettings.cs @@ -1,68 +1,64 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.IO; using Stride.Core.Extensions; using Stride.Core.Settings; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// A class representing the user settings related to a . These settings are stored in a .user +/// file along the package file. +/// +public class PackageUserSettings { - /// - /// A class representing the user settings related to a . These settings are stored in a .user - /// file along the package file. - /// - public class PackageUserSettings - { - private const string SettingsExtension = ".user"; - private readonly Package package; - private readonly SettingsProfile profile; + private const string SettingsExtension = ".user"; + private readonly Package package; - public static SettingsContainer SettingsContainer = new SettingsContainer(); + public static SettingsContainer SettingsContainer = new(); - internal PackageUserSettings(Package package) + internal PackageUserSettings(Package package) + { + ArgumentNullException.ThrowIfNull(package); + this.package = package; + if (package.FullPath == null) + { + Profile = SettingsContainer.CreateSettingsProfile(false); + } + else { - if (package == null) throw new ArgumentNullException("package"); - this.package = package; - if (package.FullPath == null) + var path = package.FullPath + SettingsExtension; + SettingsProfile? profile = null; + try { - profile = SettingsContainer.CreateSettingsProfile(false); + profile = SettingsContainer.LoadSettingsProfile(path, false); } - else + catch (Exception e) { - var path = package.FullPath + SettingsExtension; - try - { - profile = SettingsContainer.LoadSettingsProfile(path, false); - } - catch (Exception e) - { - e.Ignore(); - } - if (profile == null) - profile = SettingsContainer.CreateSettingsProfile(false); + e.Ignore(); } + Profile = profile ?? SettingsContainer.CreateSettingsProfile(false); } + } - public bool Save() - { - if (package.FullPath == null) - return false; + public bool Save() + { + if (package.FullPath == null) + return false; - var path = package.FullPath + SettingsExtension; - return SettingsContainer.SaveSettingsProfile(profile, path); - } + var path = package.FullPath + SettingsExtension; + return SettingsContainer.SaveSettingsProfile(Profile, path); + } - public SettingsProfile Profile { get { return profile; } } + public SettingsProfile Profile { get; } - public T GetValue(SettingsKey key) - { - return key.GetValue(profile, true); - } + public T GetValue(SettingsKey key) + { + return key.GetValue(Profile, true); + } - public void SetValue(SettingsKey key, T value) - { - key.SetValue(value, profile); - } + public void SetValue(SettingsKey key, T value) + { + key.SetValue(value, Profile); } } diff --git a/sources/assets/Stride.Core.Assets/PackageVersionRangeExtensions.cs b/sources/assets/Stride.Core.Assets/PackageVersionRangeExtensions.cs index 97222bee7c..6df783b67e 100644 --- a/sources/assets/Stride.Core.Assets/PackageVersionRangeExtensions.cs +++ b/sources/assets/Stride.Core.Assets/PackageVersionRangeExtensions.cs @@ -1,20 +1,14 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public static class PackageVersionRangeExtensions { - public static class PackageVersionRangeExtensions + public static Func ToFilter(this PackageVersionRange versionInfo) { - public static Func ToFilter(this PackageVersionRange versionInfo) - { - if (versionInfo == null) - { - throw new ArgumentNullException("versionInfo"); - } - return versionInfo.ToFilter(p => p.Meta.Version); - } - + ArgumentNullException.ThrowIfNull(versionInfo); + return versionInfo.ToFilter(p => p.Meta.Version); } + } diff --git a/sources/assets/Stride.Core.Assets/ProjectFileGeneratorAsset.cs b/sources/assets/Stride.Core.Assets/ProjectFileGeneratorAsset.cs index 1b023230ef..abe27176b2 100644 --- a/sources/assets/Stride.Core.Assets/ProjectFileGeneratorAsset.cs +++ b/sources/assets/Stride.Core.Assets/ProjectFileGeneratorAsset.cs @@ -1,19 +1,17 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +[DataContract("ProjectSourceCodeWithFileGeneratorAsset")] +public abstract class ProjectSourceCodeWithFileGeneratorAsset : ProjectSourceCodeAsset, IProjectFileGeneratorAsset { - [DataContract("ProjectSourceCodeWithFileGeneratorAsset")] - public abstract class ProjectSourceCodeWithFileGeneratorAsset : ProjectSourceCodeAsset, IProjectFileGeneratorAsset - { - /// - [DataMember(Mask = DataMemberAttribute.IgnoreMask)] - [Display(Browsable = false)] - public abstract string Generator { get; } + /// + [DataMember(Mask = DataMemberAttribute.IgnoreMask)] + [Display(Browsable = false)] + public abstract string Generator { get; } - /// - /// - public abstract void SaveGeneratedAsset(AssetItem assetItem); - } + /// + /// + public abstract void SaveGeneratedAsset(AssetItem assetItem); } diff --git a/sources/assets/Stride.Core.Assets/ProjectReference.cs b/sources/assets/Stride.Core.Assets/ProjectReference.cs index aa0f936168..f5f0021e51 100644 --- a/sources/assets/Stride.Core.Assets/ProjectReference.cs +++ b/sources/assets/Stride.Core.Assets/ProjectReference.cs @@ -1,115 +1,105 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Diagnostics; -using Stride.Core; using Stride.Core.Annotations; using Stride.Core.IO; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// A reference to a Visual Studio project that is part of a +/// +[DataContract("ProjectReference")] +[DebuggerDisplay("Id: {Id}, Location: {Location}")] +public sealed class ProjectReference : IEquatable, IIdentifiable { /// - /// A reference to a Visual Studio project that is part of a + /// Initializes a new instance of the class. /// - [DataContract("ProjectReference")] - [DebuggerDisplay("Id: {Id}, Location: {Location}")] - public sealed class ProjectReference : IEquatable, IIdentifiable + /// + /// This constructor is used only for serialization. + /// + public ProjectReference() { - /// - /// Initializes a new instance of the class. - /// - /// - /// This constructor is used only for serialization. - /// - public ProjectReference() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The identifier. - /// The location. - /// The type. - public ProjectReference(Guid id, UFile location, ProjectType type) - { - Id = id; - Location = location; - Type = type; - } + /// + /// Initializes a new instance of the class. + /// + /// The identifier. + /// The location. + /// The type. + public ProjectReference(Guid id, UFile location, ProjectType type) + { + Id = id; + Location = location; + Type = type; + } - /// - /// Gets or sets the unique identifier of the VS project. - /// - /// The identifier. - /// - /// The setter should only be used during serialization. - /// - [DataMember(10)] - [NonOverridable] - public Guid Id { get; set; } + /// + /// Gets or sets the unique identifier of the VS project. + /// + /// The identifier. + /// + /// The setter should only be used during serialization. + /// + [DataMember(10)] + [NonOverridable] + public Guid Id { get; set; } - /// - /// Gets or sets the location of the file on the disk. - /// - /// The location. - /// - /// The setter should only be used during serialization. - /// - [DataMember(20)] - public UFile Location { get; set; } + /// + /// Gets or sets the location of the file on the disk. + /// + /// The location. + /// + /// The setter should only be used during serialization. + /// + [DataMember(20)] + public UFile Location { get; set; } - /// - /// Gets or sets the type of project. - /// - /// The type. - /// - /// The setter should only be used during serialization. - /// - [DataMember(30)] - public ProjectType Type { get; set; } + /// + /// Gets or sets the type of project. + /// + /// The type. + /// + /// The setter should only be used during serialization. + /// + [DataMember(30)] + public ProjectType Type { get; set; } - /// - /// Gets or set the root namespace of the project - /// - [DataMemberIgnore] - public string RootNamespace { get; internal set; } + /// + /// Gets or set the root namespace of the project + /// + [DataMemberIgnore] + public string RootNamespace { get; internal set; } - public bool Equals(ProjectReference other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Id.Equals(other.Id) && Equals(Location, other.Location) && Type == other.Type; - } + public bool Equals(ProjectReference? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id.Equals(other.Id) && Equals(Location, other.Location) && Type == other.Type; + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return Equals(obj as ProjectReference); - } + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as ProjectReference); + } - public override int GetHashCode() - { - unchecked - { - // ReSharper disable NonReadonlyMemberInGetHashCode - these properties are not supposed to be changed, except in initializers - var hashCode = Id.GetHashCode(); - hashCode = (hashCode*397) ^ (Location != null ? Location.GetHashCode() : 0); - hashCode = (hashCode*397) ^ (int)Type; - // ReSharper restore NonReadonlyMemberInGetHashCode - return hashCode; - } - } + public override int GetHashCode() + { + return HashCode.Combine(Id, Location, Type); + } - public static bool operator ==(ProjectReference left, ProjectReference right) - { - return Equals(left, right); - } + public static bool operator ==(ProjectReference? left, ProjectReference? right) + { + return Equals(left, right); + } - public static bool operator !=(ProjectReference left, ProjectReference right) - { - return !Equals(left, right); - } + public static bool operator !=(ProjectReference? left, ProjectReference? right) + { + return !Equals(left, right); } } diff --git a/sources/assets/Stride.Core.Assets/ProjectSourceCodeAsset.cs b/sources/assets/Stride.Core.Assets/ProjectSourceCodeAsset.cs index 00d1bcb349..fc1fdeabbc 100644 --- a/sources/assets/Stride.Core.Assets/ProjectSourceCodeAsset.cs +++ b/sources/assets/Stride.Core.Assets/ProjectSourceCodeAsset.cs @@ -1,11 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; -namespace Stride.Core.Assets -{ - [DataContract("ProjectSourceCodeAsset")] - public abstract class ProjectSourceCodeAsset : SourceCodeAsset, IProjectAsset - { - } -} +namespace Stride.Core.Assets; + +[DataContract("ProjectSourceCodeAsset")] +public abstract class ProjectSourceCodeAsset : SourceCodeAsset, IProjectAsset; diff --git a/sources/assets/Stride.Core.Assets/ProjectType.cs b/sources/assets/Stride.Core.Assets/ProjectType.cs index c9dc89fcb5..887dbf0647 100644 --- a/sources/assets/Stride.Core.Assets/ProjectType.cs +++ b/sources/assets/Stride.Core.Assets/ProjectType.cs @@ -1,25 +1,23 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; -namespace Stride.Core.Assets -{ - // REMARK: Beware of the order of values in this enum, it is used for sorting. +namespace Stride.Core.Assets; + +// REMARK: Beware of the order of values in this enum, it is used for sorting. +/// +/// Type of the project. +/// +[DataContract("ProjectType")] +public enum ProjectType +{ /// - /// Type of the project. + /// A library. /// - [DataContract("ProjectType")] - public enum ProjectType - { - /// - /// A library. - /// - Library, + Library, - /// - /// An executable. - /// - Executable, - } + /// + /// An executable. + /// + Executable, } diff --git a/sources/assets/Stride.Core.Assets/Properties/AssemblyInfo.cs b/sources/assets/Stride.Core.Assets/Properties/AssemblyInfo.cs index 087cef298b..c178462a28 100644 --- a/sources/assets/Stride.Core.Assets/Properties/AssemblyInfo.cs +++ b/sources/assets/Stride.Core.Assets/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Reflection; + using System.Runtime.CompilerServices; diff --git a/sources/assets/Stride.Core.Assets/PropertyCollection.cs b/sources/assets/Stride.Core.Assets/PropertyCollection.cs index beec65106c..66b2d00599 100644 --- a/sources/assets/Stride.Core.Assets/PropertyCollection.cs +++ b/sources/assets/Stride.Core.Assets/PropertyCollection.cs @@ -1,113 +1,109 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections.Concurrent; -using System.Collections.Generic; -using Stride.Core; +using System.Diagnostics.CodeAnalysis; + +namespace Stride.Core.Assets; -namespace Stride.Core.Assets +/// +/// A collection of properties. +/// +[DataContract("PropertyCollection")] +public sealed class PropertyCollection : ConcurrentDictionary { /// - /// A collection of properties. + /// Initializes a new instance of the class. /// - [DataContract("PropertyCollection")] - public sealed class PropertyCollection : ConcurrentDictionary + public PropertyCollection() { - /// - /// Initializes a new instance of the class. - /// - public PropertyCollection() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The dictionary. - public PropertyCollection(IEnumerable> dictionary) - : base(dictionary) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The dictionary. + public PropertyCollection(IEnumerable> dictionary) + : base(dictionary) + { + } - /// - /// Gets a value for the specified key, null if not found. - /// - /// The key. - /// A value for the specified key, null if not found. - public object Get(PropertyKey key) - { - object value; - TryGetValue(key, out value); - return value; - } + /// + /// Gets a value for the specified key, null if not found. + /// + /// The key. + /// A value for the specified key, null if not found. + public object? Get(PropertyKey key) + { + TryGetValue(key, out var value); + return value; + } - /// - /// Gets a value for the specified key, null if not found. - /// - /// Type of the value - /// The key. - /// a value for the specified key, null if not found. - public T Get(PropertyKey key) - { - var value = Get((PropertyKey)key); - return value == null ? default(T) : (T)value; - } + /// + /// Gets a value for the specified key, null if not found. + /// + /// Type of the value + /// The key. + /// a value for the specified key, null if not found. + public T? Get(PropertyKey key) + { + var value = Get((PropertyKey)key); + return value == null ? default : (T)value; + } - /// - /// Gets a value for the specified key, null if not found. - /// - /// Type of the value - /// The key. - /// The value. - /// - /// A value for the specified key, null if not found. - /// - public bool TryGet(PropertyKey key, out T value) - { - object valueObject; - var result = TryGetValue((PropertyKey)key, out valueObject); - value = valueObject == null ? default(T) : (T)valueObject; - return result; - } + /// + /// Gets a value for the specified key, null if not found. + /// + /// Type of the value + /// The key. + /// The value. + /// + /// A value for the specified key, null if not found. + /// + public bool TryGet(PropertyKey key, [MaybeNullWhen(false)] out T value) + { + var result = TryGetValue(key, out var valueObject); + value = valueObject == null ? default : (T)valueObject; + return result; + } - /// - /// Sets a value for the specified key. - /// - /// The key. - /// The value. - public void Set(PropertyKey key, object value) - { - this[key] = value; - } + /// + /// Sets a value for the specified key. + /// + /// The key. + /// The value. + public void Set(PropertyKey key, object value) + { + this[key] = value; + } - /// - /// Sets a value for the specified key. - /// - /// Type of the value. - /// The key. - /// The value. - public void Set(PropertyKey key, T value) - { - this[key] = value; - } + /// + /// Sets a value for the specified key. + /// + /// Type of the value. + /// The key. + /// The value. + public void Set(PropertyKey key, T value) + { + this[key] = value; + } - /// - /// Copies this properties to a output dictionary. - /// - /// The dictionary to receive a copy of the properties of this instance. - /// if set to true [override values]. - /// properties - public void CopyTo(IDictionary properties, bool overrideValues) + /// + /// Copies this properties to a output dictionary. + /// + /// The dictionary to receive a copy of the properties of this instance. + /// if set to true [override values]. + /// properties + public void CopyTo(IDictionary properties, bool overrideValues) + { + ArgumentNullException.ThrowIfNull(properties); + foreach (var propKeyValue in this) { - if (properties == null) throw new ArgumentNullException("properties"); - foreach (var propKeyValue in this) + if (!overrideValues && properties.ContainsKey(propKeyValue.Key)) { - if (!overrideValues && properties.ContainsKey(propKeyValue.Key)) - { - continue; - } - properties[propKeyValue.Key] = propKeyValue.Value; + continue; } + properties[propKeyValue.Key] = propKeyValue.Value; } } } diff --git a/sources/assets/Stride.Core.Assets/RawAsset.cs b/sources/assets/Stride.Core.Assets/RawAsset.cs index 74e85d290c..63b2e3820f 100644 --- a/sources/assets/Stride.Core.Assets/RawAsset.cs +++ b/sources/assets/Stride.Core.Assets/RawAsset.cs @@ -3,36 +3,32 @@ using System.ComponentModel; -using Stride.Core.Assets.Compiler; -using Stride.Core; +namespace Stride.Core.Assets; -namespace Stride.Core.Assets +/// +/// A raw asset, an asset that is imported as-is. +/// +/// A raw asset, an asset that is imported as-is. +[DataContract("RawAsset")] +[AssetDescription(FileExtension)] +[Display(1050, "Raw Asset")] +public sealed class RawAsset : AssetWithSource { + public const string FileExtension = ".sdraw"; + /// - /// A raw asset, an asset that is imported as-is. + /// Initializes a new instance of the class. /// - /// A raw asset, an asset that is imported as-is. - [DataContract("RawAsset")] - [AssetDescription(FileExtension)] - [Display(1050, "Raw Asset")] - public sealed class RawAsset : AssetWithSource + public RawAsset() { - public const string FileExtension = ".sdraw"; - - /// - /// Initializes a new instance of the class. - /// - public RawAsset() - { - Compress = true; - } - - /// - /// Gets or sets a value indicating whether this will be compressed when compiled. - /// - /// true if this asset will be compressed when compiled; otherwise, false. - /// A boolean indicating whether this asset will be compressed when compiled - [DefaultValue(true)] - public bool Compress { get; set; } + Compress = true; } + + /// + /// Gets or sets a value indicating whether this will be compressed when compiled. + /// + /// true if this asset will be compressed when compiled; otherwise, false. + /// A boolean indicating whether this asset will be compressed when compiled + [DefaultValue(true)] + public bool Compress { get; set; } } diff --git a/sources/assets/Stride.Core.Assets/RawAssetCompiler.cs b/sources/assets/Stride.Core.Assets/RawAssetCompiler.cs index 6fd25d2fd3..870769dee7 100644 --- a/sources/assets/Stride.Core.Assets/RawAssetCompiler.cs +++ b/sources/assets/Stride.Core.Assets/RawAssetCompiler.cs @@ -3,24 +3,23 @@ using Stride.Core.Assets.Compiler; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Raw asset compiler. +/// +[AssetCompiler(typeof(RawAsset), typeof(AssetCompilationContext))] +internal class RawAssetCompiler : AssetCompilerBase { - /// - /// Raw asset compiler. - /// - [AssetCompiler(typeof(RawAsset), typeof(AssetCompilationContext))] - internal class RawAssetCompiler : AssetCompilerBase + protected override void Prepare(AssetCompilerContext context, AssetItem assetItem, string targetUrlInStorage, AssetCompilerResult result) { - protected override void Prepare(AssetCompilerContext context, AssetItem assetItem, string targetUrlInStorage, AssetCompilerResult result) - { - var asset = (RawAsset)assetItem.Asset; + var asset = (RawAsset)assetItem.Asset; - // Get absolute path of asset source on disk - var assetSource = GetAbsolutePath(assetItem, asset.Source); - var importCommand = new ImportStreamCommand(targetUrlInStorage, assetSource) { DisableCompression = !asset.Compress }; + // Get absolute path of asset source on disk + var assetSource = GetAbsolutePath(assetItem, asset.Source); + var importCommand = new ImportStreamCommand(targetUrlInStorage, assetSource) { DisableCompression = !asset.Compress }; - result.BuildSteps = new AssetBuildStep(assetItem); - result.BuildSteps.Add(importCommand); - } + result.BuildSteps = new AssetBuildStep(assetItem); + result.BuildSteps.Add(importCommand); } } diff --git a/sources/assets/Stride.Core.Assets/RawAssetImporter.cs b/sources/assets/Stride.Core.Assets/RawAssetImporter.cs index 4b06ff415a..fb1747b187 100644 --- a/sources/assets/Stride.Core.Assets/RawAssetImporter.cs +++ b/sources/assets/Stride.Core.Assets/RawAssetImporter.cs @@ -1,37 +1,32 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; +namespace Stride.Core.Assets; -namespace Stride.Core.Assets +public sealed class RawAssetImporter : RawAssetImporterBase { - public sealed class RawAssetImporter : RawAssetImporterBase - { - private static readonly Guid Uid = new Guid("6f86ec95-c1ca-41e1-8adc-1449bb5ce3be"); - - public RawAssetImporter() - { - // Raw asset is always last - Order = int.MaxValue; - } + private static readonly Guid Uid = new("6f86ec95-c1ca-41e1-8adc-1449bb5ce3be"); - /// - public override Guid Id => Uid; + public RawAssetImporter() + { + // Raw asset is always last + Order = int.MaxValue; + } - /// - public override string Description => "Generic importer for raw assets"; + /// + public override Guid Id => Uid; - /// - public override string SupportedFileExtensions => "*.*"; + /// + public override string Description => "Generic importer for raw assets"; - /// - public override bool IsSupportingFile(string filePath) - { - // Always return true - return true; - } + /// + public override string SupportedFileExtensions => "*.*"; + /// + public override bool IsSupportingFile(string filePath) + { + // Always return true + return true; } + } diff --git a/sources/assets/Stride.Core.Assets/RawAssetImporterBase.cs b/sources/assets/Stride.Core.Assets/RawAssetImporterBase.cs index fb4022dd72..246b116080 100644 --- a/sources/assets/Stride.Core.Assets/RawAssetImporterBase.cs +++ b/sources/assets/Stride.Core.Assets/RawAssetImporterBase.cs @@ -1,29 +1,25 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; +using Stride.Core.Extensions; using Stride.Core.IO; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public abstract class RawAssetImporterBase : AssetImporterBase + where TAsset : Asset, IAssetWithSource, new() { - public abstract class RawAssetImporterBase : AssetImporterBase - where TAsset : Asset, IAssetWithSource, new() - { - /// - public sealed override IEnumerable RootAssetTypes { get { yield return typeof(TAsset); } } + /// + public sealed override IEnumerable RootAssetTypes { get { yield return typeof(TAsset); } } - /// - [ItemNotNull] - public sealed override IEnumerable Import([NotNull] UFile rawAssetPath, AssetImporterParameters importParameters) - { - if (rawAssetPath == null) throw new ArgumentNullException(nameof(rawAssetPath)); + /// + public sealed override IEnumerable Import(UFile rawAssetPath, AssetImporterParameters importParameters) + { + ArgumentNullException.ThrowIfNull(rawAssetPath); - var asset = new TAsset { Source = rawAssetPath }; - // Creates the url to the raw asset - var rawAssetUrl = new UFile(rawAssetPath.GetFileNameWithoutExtension()); - yield return new AssetItem(rawAssetUrl, asset); - } + var asset = new TAsset { Source = rawAssetPath }; + // Creates the url to the raw asset + var rawAssetUrl = new UFile(rawAssetPath.GetFileNameWithoutExtension()); + return new AssetItem(rawAssetUrl, asset).Yield()!; } } diff --git a/sources/assets/Stride.Core.Assets/Reflection/AbstractObjectInstantiator.cs b/sources/assets/Stride.Core.Assets/Reflection/AbstractObjectInstantiator.cs index fde2c962d7..dbfe428977 100644 --- a/sources/assets/Stride.Core.Assets/Reflection/AbstractObjectInstantiator.cs +++ b/sources/assets/Stride.Core.Assets/Reflection/AbstractObjectInstantiator.cs @@ -1,175 +1,170 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using System.Reflection; using System.Reflection.Emit; -using Stride.Core.Annotations; -namespace Stride.Core.Reflection +namespace Stride.Core.Reflection; + +public static class AbstractObjectInstantiator { - public static class AbstractObjectInstantiator + private static readonly Dictionary ConstructedTypes = []; + + /// + /// Creates an instance of a type implementing the specified . + /// + /// + /// + /// If is already a concrete type (not an abstract type nor an interface, the method returns an instance of itself. + /// + /// An instance of a type implementing the specified . + /// + public static object CreateConcreteInstance(Type baseType) { - private static readonly Dictionary ConstructedTypes = new Dictionary(); - - /// - /// Creates an instance of a type implementing the specified . - /// - /// - /// - /// If is already a concrete type (not an abstract type nor an interface, the method returns an instance of itself. - /// - /// An instance of a type implementing the specified . - /// - [NotNull] - public static object CreateConcreteInstance([NotNull] Type baseType) - { - if (baseType == null) throw new ArgumentNullException(nameof(baseType)); + ArgumentNullException.ThrowIfNull(baseType); - if (!baseType.IsAbstract && !baseType.IsInterface) - return Activator.CreateInstance(baseType); + if (!baseType.IsAbstract && !baseType.IsInterface) + return Activator.CreateInstance(baseType)!; - Type constructedType; - lock (ConstructedTypes) + Type? constructedType; + lock (ConstructedTypes) + { + if (!ConstructedTypes.TryGetValue(baseType, out constructedType)) { - if (!ConstructedTypes.TryGetValue(baseType, out constructedType)) - { - var asmName = new AssemblyName($"ConcreteObject_{Guid.NewGuid():N}"); + var asmName = new AssemblyName($"ConcreteObject_{Guid.NewGuid():N}"); - // Create assembly (in memory) - var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run); - var moduleBuilder = asmBuilder.DefineDynamicModule("DynamicModule"); + // Create assembly (in memory) + var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run); + var moduleBuilder = asmBuilder.DefineDynamicModule("DynamicModule"); - // Define type - var typeBuilder = moduleBuilder.DefineType($"{baseType}Impl"); - InitializeTypeBuilderFromType(typeBuilder, baseType); + // Define type + var typeBuilder = moduleBuilder.DefineType($"{baseType}Impl"); + InitializeTypeBuilderFromType(typeBuilder, baseType); - // Create type - constructedType = typeBuilder.CreateTypeInfo(); - ConstructedTypes.Add(baseType, constructedType); + // Create type + constructedType = typeBuilder.CreateTypeInfo(); + ConstructedTypes.Add(baseType, constructedType); - } } - return Activator.CreateInstance(constructedType); } + return Activator.CreateInstance(constructedType)!; + } - /// - /// Initializes the using the provided . - /// - /// The type builder to initialize. - /// The base type of the type currently under construction. - public static void InitializeTypeBuilderFromType([NotNull] TypeBuilder typeBuilder, [NotNull] Type baseType) + /// + /// Initializes the using the provided . + /// + /// The type builder to initialize. + /// The base type of the type currently under construction. + public static void InitializeTypeBuilderFromType(TypeBuilder typeBuilder, Type baseType) + { + ArgumentNullException.ThrowIfNull(typeBuilder); + ArgumentNullException.ThrowIfNull(baseType); + + // Inherit expected base type + if (baseType.IsInterface) + typeBuilder.AddInterfaceImplementation(baseType); + else + typeBuilder.SetParent(baseType); + + // Build list of class hierarchy (from deeper to closer) + var currentType = baseType; + var abstractBaseTypes = new List(); + while (currentType != null) { - if (typeBuilder == null) throw new ArgumentNullException(nameof(typeBuilder)); - if (baseType == null) throw new ArgumentNullException(nameof(baseType)); - - // Inherit expected base type - if (baseType.IsInterface) - typeBuilder.AddInterfaceImplementation(baseType); - else - typeBuilder.SetParent(baseType); - - // Build list of class hierarchy (from deeper to closer) - var currentType = baseType; - var abstractBaseTypes = new List(); - while (currentType != null) - { - abstractBaseTypes.Add(currentType); - currentType = currentType.BaseType; - } - abstractBaseTypes.Reverse(); + abstractBaseTypes.Add(currentType); + currentType = currentType.BaseType; + } + abstractBaseTypes.Reverse(); - // Check that all interfaces are implemented - var interfaceMethods = new List(); - foreach (var @interface in baseType.GetInterfaces()) - { - interfaceMethods.AddRange(@interface.GetMethods(BindingFlags.Public | BindingFlags.Instance)); - } + // Check that all interfaces are implemented + var interfaceMethods = new List(); + foreach (var @interface in baseType.GetInterfaces()) + { + interfaceMethods.AddRange(@interface.GetMethods(BindingFlags.Public | BindingFlags.Instance)); + } - // Build list of abstract methods - var abstractMethods = new List(); - foreach (var currentBaseType in abstractBaseTypes) + // Build list of abstract methods + var abstractMethods = new List(); + foreach (var currentBaseType in abstractBaseTypes) + { + foreach (var method in currentBaseType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { - foreach (var method in currentBaseType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + if ((method.Attributes & MethodAttributes.Abstract) != 0) + { + // abstract: add it + abstractMethods.Add(method); + } + else if ((method.Attributes & MethodAttributes.Virtual) != 0 && (method.Attributes & MethodAttributes.NewSlot) == 0) { - if ((method.Attributes & MethodAttributes.Abstract) != 0) - { - // abstract: add it - abstractMethods.Add(method); - } - else if ((method.Attributes & MethodAttributes.Virtual) != 0 && (method.Attributes & MethodAttributes.NewSlot) == 0) - { - // override: check if it overrides a previously described abstract method - for (var index = 0; index < abstractMethods.Count; index++) - { - var abstractMethod = abstractMethods[index]; - if (abstractMethod.Name == method.Name && CompareMethodSignature(abstractMethod, method)) - { - // Found a match, let's remove it from list of method to reimplement - abstractMethods.RemoveAt(index); - break; - } - } - } - - // Remove interface methods already implemented // override: check if it overrides a previously described abstract method - for (var index = 0; index < interfaceMethods.Count; index++) + for (var index = 0; index < abstractMethods.Count; index++) { - var interfaceMethod = interfaceMethods[index]; - if ((interfaceMethod.Name == method.Name - // explicit interface implementation - || $"{interfaceMethod.DeclaringType?.FullName}.{interfaceMethod.Name}" == method.Name) - && CompareMethodSignature(interfaceMethod, method)) + var abstractMethod = abstractMethods[index]; + if (abstractMethod.Name == method.Name && CompareMethodSignature(abstractMethod, method)) { // Found a match, let's remove it from list of method to reimplement - interfaceMethods.RemoveAt(index--); + abstractMethods.RemoveAt(index); + break; } } } - } - // Note: It seems that C# also creates a Property/Event for each override; but it doesn't seem to fail when creating the type with only non-abstract getter/setter -- so we don't recreate the property/event - // Implement all abstract methods - foreach (var method in abstractMethods.Concat(interfaceMethods)) - { - // Updates MethodAttributes for override method - var attributes = method.Attributes; - attributes &= ~MethodAttributes.Abstract; - attributes &= ~MethodAttributes.NewSlot; - attributes |= MethodAttributes.HideBySig; - - var overrideMethod = typeBuilder.DefineMethod(method.Name, attributes, method.CallingConvention, method.ReturnType, method.GetParameters().Select(x => x.ParameterType).ToArray()); - var overrideMethodIL = overrideMethod.GetILGenerator(); - - // TODO: For properties, do we want get { return default(T); } set { } instead? - // And for events, add { } remove { } too? - overrideMethodIL.ThrowException(typeof(NotImplementedException)); + // Remove interface methods already implemented + // override: check if it overrides a previously described abstract method + for (var index = 0; index < interfaceMethods.Count; index++) + { + var interfaceMethod = interfaceMethods[index]; + if ((interfaceMethod.Name == method.Name + // explicit interface implementation + || $"{interfaceMethod.DeclaringType?.FullName}.{interfaceMethod.Name}" == method.Name) + && CompareMethodSignature(interfaceMethod, method)) + { + // Found a match, let's remove it from list of method to reimplement + interfaceMethods.RemoveAt(index--); + } + } } } - /// - /// Compares the parameter types of two . - /// - /// - /// - /// true if the parameter types match one by one; otherwise, false. - private static bool CompareMethodSignature([NotNull] MethodInfo method1, [NotNull] MethodInfo method2) + // Note: It seems that C# also creates a Property/Event for each override; but it doesn't seem to fail when creating the type with only non-abstract getter/setter -- so we don't recreate the property/event + // Implement all abstract methods + foreach (var method in abstractMethods.Concat(interfaceMethods)) { - var parameters1 = method1.GetParameters(); - var parameters2 = method2.GetParameters(); + // Updates MethodAttributes for override method + var attributes = method.Attributes; + attributes &= ~MethodAttributes.Abstract; + attributes &= ~MethodAttributes.NewSlot; + attributes |= MethodAttributes.HideBySig; + + var overrideMethod = typeBuilder.DefineMethod(method.Name, attributes, method.CallingConvention, method.ReturnType, method.GetParameters().Select(x => x.ParameterType).ToArray()); + var overrideMethodIL = overrideMethod.GetILGenerator(); + + // TODO: For properties, do we want get { return default(T); } set { } instead? + // And for events, add { } remove { } too? + overrideMethodIL.ThrowException(typeof(NotImplementedException)); + } + } - if (parameters1.Length != parameters2.Length) - return false; + /// + /// Compares the parameter types of two . + /// + /// + /// + /// true if the parameter types match one by one; otherwise, false. + private static bool CompareMethodSignature(MethodInfo method1, MethodInfo method2) + { + var parameters1 = method1.GetParameters(); + var parameters2 = method2.GetParameters(); - for (var i = 0; i < parameters1.Length; ++i) - { - if (parameters1[i].ParameterType != parameters2[i].ParameterType) - return false; - } + if (parameters1.Length != parameters2.Length) + return false; - return true; + for (var i = 0; i < parameters1.Length; ++i) + { + if (parameters1[i].ParameterType != parameters2[i].ParameterType) + return false; } + + return true; } } diff --git a/sources/assets/Stride.Core.Assets/Reflection/CollectionItemIdHelper.cs b/sources/assets/Stride.Core.Assets/Reflection/CollectionItemIdHelper.cs index 77ccef0f10..8e9beabe11 100644 --- a/sources/assets/Stride.Core.Assets/Reflection/CollectionItemIdHelper.cs +++ b/sources/assets/Stride.Core.Assets/Reflection/CollectionItemIdHelper.cs @@ -1,50 +1,47 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; +using System.Diagnostics.CodeAnalysis; -namespace Stride.Core.Reflection +namespace Stride.Core.Reflection; + +/// +/// A helper static class to retrieve from a collection or dictionary through the registry. +/// +public static class CollectionItemIdHelper { - /// - /// A helper static class to retrieve from a collection or dictionary through the registry. - /// - public static class CollectionItemIdHelper + // TODO: do we really need to pass an object to this constructor? + private static readonly ShadowObjectPropertyKey CollectionItemIdKey = new(new object(), false); + + public static bool HasCollectionItemIds(object? instance) { - // TODO: do we really need to pass an object to this constructor? - private static readonly ShadowObjectPropertyKey CollectionItemIdKey = new ShadowObjectPropertyKey(new object(), false); + return ShadowObject.Get(instance)?.ContainsKey(CollectionItemIdKey) ?? false; + } - public static bool HasCollectionItemIds(object instance) + public static bool TryGetCollectionItemIds(object? instance, [MaybeNullWhen(false)] out CollectionItemIdentifiers itemIds) + { + var shadow = ShadowObject.Get(instance); + if (shadow == null) { - return ShadowObject.Get(instance)?.ContainsKey(CollectionItemIdKey) ?? false; + itemIds = null; + return false; } - public static bool TryGetCollectionItemIds(object instance, out CollectionItemIdentifiers itemIds) - { - var shadow = ShadowObject.Get(instance); - if (shadow == null) - { - itemIds = null; - return false; - } - - object result; - itemIds = shadow.TryGetValue(CollectionItemIdKey, out result) ? (CollectionItemIdentifiers)result : null; - return result != null; - } + itemIds = shadow.TryGetValue(CollectionItemIdKey, out var result) ? (CollectionItemIdentifiers)result : null; + return result != null; + } - public static CollectionItemIdentifiers GetCollectionItemIds(object instance) + public static CollectionItemIdentifiers GetCollectionItemIds(object instance) + { + if (instance.GetType().IsValueType) throw new ArgumentException(@"The given instance is a value type and cannot have a item ids attached to it.", nameof(instance)); + + var shadow = ShadowObject.GetOrCreate(instance); + if (shadow.TryGetValue(CollectionItemIdKey, out var result)) { - if (instance.GetType().IsValueType) throw new ArgumentException(@"The given instance is a value type and cannot have a item ids attached to it.", nameof(instance)); - - var shadow = ShadowObject.GetOrCreate(instance); - object result; - if (shadow.TryGetValue(CollectionItemIdKey, out result)) - { - return (CollectionItemIdentifiers)result; - } - - var itemIds = new CollectionItemIdentifiers(); - shadow.Add(CollectionItemIdKey, itemIds); - return itemIds; + return (CollectionItemIdentifiers)result; } + + var itemIds = new CollectionItemIdentifiers(); + shadow.Add(CollectionItemIdKey, itemIds); + return itemIds; } } diff --git a/sources/assets/Stride.Core.Assets/Reflection/CollectionItemIdentifiers.cs b/sources/assets/Stride.Core.Assets/Reflection/CollectionItemIdentifiers.cs index 26056b930d..04087607a8 100644 --- a/sources/assets/Stride.Core.Assets/Reflection/CollectionItemIdentifiers.cs +++ b/sources/assets/Stride.Core.Assets/Reflection/CollectionItemIdentifiers.cs @@ -1,185 +1,181 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; -using System.Linq; -namespace Stride.Core.Reflection +namespace Stride.Core.Reflection; + +/// +/// A container for item identifiers and similar metadata that is associated to a collection or a dictionary. +/// +// TODO: Arrange the API of this class once all use cases have been implemented +public class CollectionItemIdentifiers : IEnumerable> { + private readonly Dictionary keyToIdMap = []; + + private readonly HashSet deletedItems = []; + /// - /// A container for item identifiers and similar metadata that is associated to a collection or a dictionary. + /// Gets or sets the corresponding to the given key. /// - // TODO: Arrange the API of this class once all use cases have been implemented - public class CollectionItemIdentifiers : IEnumerable> - { - private readonly Dictionary keyToIdMap = new Dictionary(); + /// The key for which to retrieve the . + /// The corresponding to the given key. + public ItemId this[object key] { get { return keyToIdMap[key]; } set { Set(key, value); } } - private readonly HashSet deletedItems = new HashSet(); + /// + /// Gets the list of corresponding to deleted items that are being kept in this . + /// + public IEnumerable DeletedItems => deletedItems; - /// - /// Gets or sets the corresponding to the given key. - /// - /// The key for which to retrieve the . - /// The corresponding to the given key. - public ItemId this[object key] { get { return keyToIdMap[key]; } set { Set(key, value); } } + /// + /// Gets the number of keys/identifiers in this . + /// + public int KeyCount => keyToIdMap.Count; - /// - /// Gets the list of corresponding to deleted items that are being kept in this . - /// - public IEnumerable DeletedItems => deletedItems; + /// + /// Gets the number of deleted identifiers that are being kept in this . + /// + public int DeletedCount => deletedItems.Count; - /// - /// Gets the number of keys/identifiers in this . - /// - public int KeyCount => keyToIdMap.Count; + public void Add(object key, ItemId id) + { + keyToIdMap.Add(key, id); + if (deletedItems.Contains(id)) + UnmarkAsDeleted(id); + } - /// - /// Gets the number of deleted identifiers that are being kept in this . - /// - public int DeletedCount => deletedItems.Count; + public void Set(object key, ItemId id) + { + keyToIdMap[key] = id; + if (deletedItems.Contains(id)) + UnmarkAsDeleted(id); + } - public void Add(object key, ItemId id) + public void Insert(int index, ItemId id) + { + for (var i = keyToIdMap.Count; i > index; --i) { - keyToIdMap.Add(key, id); - if (deletedItems.Contains(id)) - UnmarkAsDeleted(id); - } + keyToIdMap[i] = keyToIdMap[i-1]; - public void Set(object key, ItemId id) - { - keyToIdMap[key] = id; - if (deletedItems.Contains(id)) - UnmarkAsDeleted(id); } + keyToIdMap[index] = id; + if (deletedItems.Contains(id)) + UnmarkAsDeleted(id); + } - public void Insert(int index, ItemId id) - { - for (var i = keyToIdMap.Count; i > index; --i) - { - keyToIdMap[i] = keyToIdMap[i-1]; - - } - keyToIdMap[index] = id; - if (deletedItems.Contains(id)) - UnmarkAsDeleted(id); - } + public void Clear() + { + keyToIdMap.Clear(); + deletedItems.Clear(); + } - public void Clear() - { - keyToIdMap.Clear(); - deletedItems.Clear(); - } + public bool ContainsKey(object key) + { + return keyToIdMap.ContainsKey(key); + } - public bool ContainsKey(object key) - { - return keyToIdMap.ContainsKey(key); - } + public bool TryGet(object key, out ItemId id) + { + return keyToIdMap.TryGetValue(key, out id); + } - public bool TryGet(object key, out ItemId id) + public ItemId Delete(object key, bool markAsDeleted = true) + { + var id = keyToIdMap[key]; + keyToIdMap.Remove(key); + if (markAsDeleted) { - return keyToIdMap.TryGetValue(key, out id); + MarkAsDeleted(id); } + return id; + } - public ItemId Delete(object key, bool markAsDeleted = true) + public ItemId DeleteAndShift(int index, bool markAsDeleted = true) + { + var id = keyToIdMap[index]; + for (var i = index + 1; i < keyToIdMap.Count; ++i) { - var id = keyToIdMap[key]; - keyToIdMap.Remove(key); - if (markAsDeleted) - { - MarkAsDeleted(id); - } - return id; + keyToIdMap[i - 1] = keyToIdMap[i]; } + keyToIdMap.Remove(keyToIdMap.Count - 1); - public ItemId DeleteAndShift(int index, bool markAsDeleted = true) + if (markAsDeleted) { - var id = keyToIdMap[index]; - for (var i = index + 1; i < keyToIdMap.Count; ++i) - { - keyToIdMap[i - 1] = keyToIdMap[i]; - } - keyToIdMap.Remove(keyToIdMap.Count - 1); - - if (markAsDeleted) - { - MarkAsDeleted(id); - } - return id; + MarkAsDeleted(id); } + return id; + } - public void MarkAsDeleted(ItemId id) - { - deletedItems.Add(id); - } + public void MarkAsDeleted(ItemId id) + { + deletedItems.Add(id); + } - public void UnmarkAsDeleted(ItemId id) - { - deletedItems.Remove(id); - } + public void UnmarkAsDeleted(ItemId id) + { + deletedItems.Remove(id); + } - public void Validate(bool isList) - { - var ids = new HashSet(keyToIdMap.Values); - if (ids.Count != keyToIdMap.Count) - throw new InvalidOperationException("Two elements of the collection have the same id"); + public void Validate(bool isList) + { + var ids = new HashSet(keyToIdMap.Values); + if (ids.Count != keyToIdMap.Count) + throw new InvalidOperationException("Two elements of the collection have the same id"); - foreach (var deleted in deletedItems) - ids.Add(deleted); + foreach (var deleted in deletedItems) + ids.Add(deleted); - if (ids.Count != keyToIdMap.Count + deletedItems.Count) - throw new InvalidOperationException("An id is both marked as deleted and associated to a key of the collection."); - } + if (ids.Count != keyToIdMap.Count + deletedItems.Count) + throw new InvalidOperationException("An id is both marked as deleted and associated to a key of the collection."); + } - public object GetKey(ItemId itemId) + public object? GetKey(ItemId itemId) + { + object? output = null; + // TODO: add indexing by guid to avoid O(n) + foreach (var kvp in keyToIdMap) { - object output = null; - // TODO: add indexing by guid to avoid O(n) - foreach (var kvp in keyToIdMap) + if (kvp.Value == itemId) { - if (kvp.Value == itemId) - { - if(output != null) - throw new InvalidOperationException("Two elements of the collection have the same id"); - output = kvp.Key; - } + if(output != null) + throw new InvalidOperationException("Two elements of the collection have the same id"); + output = kvp.Key; } - return output; } + return output; + } - public void CloneInto(CollectionItemIdentifiers target, IReadOnlyDictionary referenceTypeClonedKeys) + public void CloneInto(CollectionItemIdentifiers target, IReadOnlyDictionary? referenceTypeClonedKeys) + { + target.keyToIdMap.Clear(); + target.deletedItems.Clear(); + foreach (var key in keyToIdMap) { - target.keyToIdMap.Clear(); - target.deletedItems.Clear(); - foreach (var key in keyToIdMap) + if (key.Key.GetType().IsValueType || referenceTypeClonedKeys == null) { - object clonedKey; - if (key.Key.GetType().IsValueType || referenceTypeClonedKeys == null) - { - target.Add(key.Key, key.Value); - } - else if (referenceTypeClonedKeys.TryGetValue(key.Key, out clonedKey)) - { - target.Add(clonedKey, key.Value); - } - else - { - throw new KeyNotFoundException("Unable to find the non-value type key in the dictionary of cloned keys."); - } + target.Add(key.Key, key.Value); } - foreach (var deletedItem in DeletedItems) + else if (referenceTypeClonedKeys.TryGetValue(key.Key, out var clonedKey)) { - target.MarkAsDeleted(deletedItem); + target.Add(clonedKey, key.Value); + } + else + { + throw new KeyNotFoundException("Unable to find the non-value type key in the dictionary of cloned keys."); } } - - public bool IsDeleted(ItemId itemId) + foreach (var deletedItem in DeletedItems) { - return DeletedItems.Contains(itemId); + target.MarkAsDeleted(deletedItem); } + } - public IEnumerator> GetEnumerator() => keyToIdMap.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public bool IsDeleted(ItemId itemId) + { + return DeletedItems.Contains(itemId); } + + public IEnumerator> GetEnumerator() => keyToIdMap.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/sources/assets/Stride.Core.Assets/Reflection/ItemId.cs b/sources/assets/Stride.Core.Assets/Reflection/ItemId.cs index ebe07e0248..b156f10ffd 100644 --- a/sources/assets/Stride.Core.Assets/Reflection/ItemId.cs +++ b/sources/assets/Stride.Core.Assets/Reflection/ItemId.cs @@ -1,125 +1,122 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Storage; -namespace Stride.Core.Reflection +namespace Stride.Core.Reflection; + +/// +/// Represents the identifier of an item in a collection or an entry in a dictionary. +/// +[DataContract] +public readonly struct ItemId : IComparable, IEquatable { + private readonly ObjectId value; + /// - /// Represents the identifier of an item in a collection or an entry in a dictionary. + /// Initializes a new instance of the structure from an array of bytes. /// - [DataContract] - public struct ItemId : IComparable, IEquatable + /// The array of bytes from which to create this . + public ItemId(byte[] bytes) { - private readonly ObjectId value; - - /// - /// Initializes a new instance of the structure from an array of bytes. - /// - /// The array of bytes from which to create this . - public ItemId(byte[] bytes) - { - value = new ObjectId(bytes); - } + value = new ObjectId(bytes); + } - /// - /// Initializes a new instance of the structure from an . - /// - /// The from which to create this . - public ItemId(ObjectId id) - { - value = id; - } + /// + /// Initializes a new instance of the structure from an . + /// + /// The from which to create this . + public ItemId(ObjectId id) + { + value = id; + } - /// - /// Gets an representing an empty or non-existing item. - /// - public static ItemId Empty { get; } = new ItemId(ObjectId.Empty); + /// + /// Gets an representing an empty or non-existing item. + /// + public static ItemId Empty { get; } = new ItemId(ObjectId.Empty); - /// - /// Generates a new random . - /// - /// - public static ItemId New() - { - return new ItemId(ObjectId.New()); - } + /// + /// Generates a new random . + /// + /// + public static ItemId New() + { + return new ItemId(ObjectId.New()); + } - /// - /// Parses an from a string. - /// - /// The input string to parse. - /// An corresponding to the parsed string. - /// The given string cannot be parsed as an . - public static ItemId Parse(string input) - { - ItemId itemId; - if (!TryParse(input, out itemId)) - throw new FormatException("Unable to parse the input string."); + /// + /// Parses an from a string. + /// + /// The input string to parse. + /// An corresponding to the parsed string. + /// The given string cannot be parsed as an . + public static ItemId Parse(string input) + { + if (!TryParse(input, out var itemId)) + throw new FormatException("Unable to parse the input string."); - return itemId; - } + return itemId; + } - /// - /// Attempts to parse an from a string. - /// - /// The input string to parse. - /// The resulting . - /// True if the string could be successfully parsed, False otherwise. - public static bool TryParse(string input, out ItemId itemId) + /// + /// Attempts to parse an from a string. + /// + /// The input string to parse. + /// The resulting . + /// True if the string could be successfully parsed, False otherwise. + public static bool TryParse(string input, out ItemId itemId) + { + if (ObjectId.TryParse(input, out var objectId)) { - ObjectId objectId; - if (ObjectId.TryParse(input, out objectId)) - { - itemId = new ItemId(objectId); - return true; - } - itemId = Empty; - return false; + itemId = new ItemId(objectId); + return true; } + itemId = Empty; + return false; + } - /// - public bool Equals(ItemId other) - { - return value.Equals(other.value); - } + /// + public readonly bool Equals(ItemId other) + { + return value.Equals(other.value); + } - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - return obj is ItemId && Equals((ItemId)obj); - } + /// + public override readonly bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + return obj is ItemId id && Equals(id); + } - /// - public override int GetHashCode() - { - return value.GetHashCode(); - } + /// + public override readonly int GetHashCode() + { + return value.GetHashCode(); + } - /// - public static bool operator ==(ItemId left, ItemId right) - { - return left.Equals(right); - } + /// + public static bool operator ==(ItemId left, ItemId right) + { + return left.Equals(right); + } - /// - public static bool operator !=(ItemId left, ItemId right) - { - return !left.Equals(right); - } + /// + public static bool operator !=(ItemId left, ItemId right) + { + return !left.Equals(right); + } - /// - public int CompareTo(ItemId other) - { - return value.CompareTo(other.value); - } + /// + public readonly int CompareTo(ItemId other) + { + return value.CompareTo(other.value); + } - /// - public override string ToString() - { - return value.ToString(); - } + /// + public override readonly string ToString() + { + return value.ToString(); } } diff --git a/sources/assets/Stride.Core.Assets/RootAssetCollection.cs b/sources/assets/Stride.Core.Assets/RootAssetCollection.cs index a9b9333045..36c8b1f88c 100644 --- a/sources/assets/Stride.Core.Assets/RootAssetCollection.cs +++ b/sources/assets/Stride.Core.Assets/RootAssetCollection.cs @@ -1,20 +1,18 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using Stride.Core.Collections; using Stride.Core.Serialization; using Stride.Core.Serialization.Serializers; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +[DataSerializer(typeof(KeyedSortedListSerializer))] +public class RootAssetCollection : KeyedSortedList { - [DataSerializer(typeof(KeyedSortedListSerializer))] - public class RootAssetCollection : KeyedSortedList + /// + protected override AssetId GetKeyForItem(AssetReference item) { - /// - protected override AssetId GetKeyForItem(AssetReference item) - { - return item.Id; - } + return item.Id; } } diff --git a/sources/assets/Stride.Core.Assets/Selectors/PathSelector.cs b/sources/assets/Stride.Core.Assets/Selectors/PathSelector.cs index 180744bec6..cbdf51d802 100644 --- a/sources/assets/Stride.Core.Assets/Selectors/PathSelector.cs +++ b/sources/assets/Stride.Core.Assets/Selectors/PathSelector.cs @@ -1,120 +1,116 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using System.Text; using System.Text.RegularExpressions; -using Stride.Core; using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Selectors +namespace Stride.Core.Assets.Selectors; + +/// +/// Matches asset depending on their URL, using a gitignore-like format (based on fnmatch()). +/// +[DataContract("PathSelector")] +public class PathSelector : AssetSelector { + private KeyValuePair[]? regexes; + + public PathSelector() + { + Paths = []; + } + /// - /// Matches asset depending on their URL, using a gitignore-like format (based on fnmatch()). + /// Gets or sets the paths (gitignore format). /// - [DataContract("PathSelector")] - public class PathSelector : AssetSelector - { - private KeyValuePair[] regexes; + /// + /// The paths (gitignore format). + /// + public List Paths { get; set; } - public PathSelector() + /// + public override IEnumerable Select(PackageSession packageSession, IContentIndexMap contentIndexMap) + { + // Check if we need to create or regenerate regex. + bool needGenerateRegex = false; + if (regexes == null || regexes.Length != Paths.Count) { - Paths = new List(); + needGenerateRegex = true; } - - /// - /// Gets or sets the paths (gitignore format). - /// - /// - /// The paths (gitignore format). - /// - public List Paths { get; set; } - - /// - public override IEnumerable Select(PackageSession packageSession, IContentIndexMap contentIndexMap) + else { - // Check if we need to create or regenerate regex. - bool needGenerateRegex = false; - if (regexes == null || regexes.Length != Paths.Count) - { - needGenerateRegex = true; - } - else + // Check used pattern + for (int i = 0; i < Paths.Count; ++i) { - // Check used pattern - for (int i = 0; i < Paths.Count; ++i) + if (Paths[i] != regexes[i].Key) { - if (Paths[i] != regexes[i].Key) - { - needGenerateRegex = true; - break; - } + needGenerateRegex = true; + break; } } + } - // Transform gitignore patterns to regex. - if (needGenerateRegex) - regexes = Paths.Select(x => new KeyValuePair(x, new Regex(TransformToRegex(x)))).ToArray(); + // Transform gitignore patterns to regex. + if (needGenerateRegex) + regexes = Paths.Select(x => new KeyValuePair(x, new Regex(TransformToRegex(x)))).ToArray(); - return contentIndexMap.GetMergedIdMap() - .Select(asset => asset.Key) // Select url - .Where(assetUrl => regexes.Any(regex => regex.Value.IsMatch(assetUrl))); // Check if any Regex matches - } + return contentIndexMap.GetMergedIdMap() + .Select(asset => asset.Key) // Select url + .Where(assetUrl => regexes!.Any(regex => regex.Value.IsMatch(assetUrl))); // Check if any Regex matches + } - internal static string TransformToRegex(string pattern) - { - // Try to allocate slightly more than original size - var result = new StringBuilder(pattern.Length + pattern.Length / 2); + internal static string TransformToRegex(string pattern) + { + // Try to allocate slightly more than original size + var result = new StringBuilder(pattern.Length + pattern.Length / 2); - int startPosition = 0; + int startPosition = 0; - if (pattern.Length > 0 && pattern[0] == '/') - { - // If pattern start with a /, it must match from beginning - result.Append('^'); - startPosition = 1; - } - else - { - // If pattern doesn't start with a /, it can match either beginning or right after a / - result.Append(@"(^|/)"); - } + if (pattern.Length > 0 && pattern[0] == '/') + { + // If pattern start with a /, it must match from beginning + result.Append('^'); + startPosition = 1; + } + else + { + // If pattern doesn't start with a /, it can match either beginning or right after a / + result.Append(@"(^|/)"); + } - for (int i = startPosition; i < pattern.Length; ++i) + for (int i = startPosition; i < pattern.Length; ++i) + { + var c = pattern[i]; + switch (c) { - var c = pattern[i]; - switch (c) - { - case '*': - // Match everything (except '/') - result.Append("[^/]*"); - break; - case '?': - // Match a single character (except '/') - result.Append("[^/]"); - break; - case '\\': - // If not last character, escape next one - if (++i < pattern.Length) - c = pattern[i]; + case '*': + // Match everything (except '/') + result.Append("[^/]*"); + break; + case '?': + // Match a single character (except '/') + result.Append("[^/]"); + break; + case '\\': + // If not last character, escape next one + if (++i < pattern.Length) + c = pattern[i]; - // Default case (add character as is) - goto default; - case '[': - throw new NotImplementedException("Can't match pattern that uses '['"); - default: - result.Append(Regex.Escape(c.ToString())); - break; - } + // Default case (add character as is) + goto default; + case '[': + throw new NotImplementedException("Can't match pattern that uses '['"); + default: + result.Append(Regex.Escape(c.ToString())); + break; } + } - // If there is no '/' at the end, it must either finish or have another path after - if (pattern.Length > 0 && pattern[pattern.Length - 1] != '/') - { - result.Append(@"($|/)"); - } - return result.ToString(); + // If there is no '/' at the end, it must either finish or have another path after + if (pattern.Length > 0 && pattern[^1] != '/') + { + result.Append(@"($|/)"); } + return result.ToString(); } } diff --git a/sources/assets/Stride.Core.Assets/Selectors/TagSelector.cs b/sources/assets/Stride.Core.Assets/Selectors/TagSelector.cs index 385d963389..9113cd3466 100644 --- a/sources/assets/Stride.Core.Assets/Selectors/TagSelector.cs +++ b/sources/assets/Stride.Core.Assets/Selectors/TagSelector.cs @@ -1,30 +1,27 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Linq; -using Stride.Core; + using Stride.Core.Serialization.Contents; -namespace Stride.Core.Assets.Selectors +namespace Stride.Core.Assets.Selectors; + +/// +/// An using tags stored in +/// +[DataContract("TagSelector")] +public class TagSelector : AssetSelector { /// - /// An using tags stored in + /// Gets the tags that will be used to select an asset. /// - [DataContract("TagSelector")] - public class TagSelector : AssetSelector - { - /// - /// Gets the tags that will be used to select an asset. - /// - /// The tags. - public TagCollection Tags { get; } = new TagCollection(); + /// The tags. + public TagCollection Tags { get; } = []; - public override IEnumerable Select(PackageSession packageSession, IContentIndexMap contentIndexMap) - { - return packageSession.Packages - .SelectMany(package => package.Assets) // Select all assets - .Where(assetItem => assetItem.Asset.Tags.Any(tag => Tags.Contains(tag))) // Matches tags - .Select(x => x.Location.FullPath); // Convert to string; - } + public override IEnumerable Select(PackageSession packageSession, IContentIndexMap contentIndexMap) + { + return packageSession.Packages + .SelectMany(package => package.Assets) // Select all assets + .Where(assetItem => assetItem.Asset.Tags.Any(tag => Tags.Contains(tag))) // Matches tags + .Select(x => x.Location.FullPath); // Convert to string; } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/AssetItemSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/AssetItemSerializer.cs index 003a99406f..d24ce46317 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/AssetItemSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/AssetItemSerializer.cs @@ -1,79 +1,76 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.ComponentModel; -using Stride.Core; using Stride.Core.IO; using Stride.Core.Reflection; -using Stride.Core.Yaml; using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// A Yaml Serializer for . Because this type is immutable +/// we need to implement a special serializer. +/// +[YamlSerializerFactory(YamlAssetProfile.Name)] +internal class AssetItemSerializer : ObjectSerializer, IDataCustomVisitor { - /// - /// A Yaml Serializer for . Because this type is immutable - /// we need to implement a special serializer. - /// - [YamlSerializerFactory(YamlAssetProfile.Name)] - internal class AssetItemSerializer : ObjectSerializer, IDataCustomVisitor + public override IYamlSerializable? TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) { - public override IYamlSerializable TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) - { - return CanVisit(typeDescriptor.Type) ? this : null; - } + return CanVisit(typeDescriptor.Type) ? this : null; + } - protected override void CreateOrTransformObject(ref ObjectContext objectContext) - { - objectContext.Instance = objectContext.SerializerContext.IsSerializing ? new AssetItemMutable((AssetItem)objectContext.Instance) : new AssetItemMutable(); - } + protected override void CreateOrTransformObject(ref ObjectContext objectContext) + { + objectContext.Instance = objectContext.SerializerContext.IsSerializing ? new AssetItemMutable((AssetItem)objectContext.Instance) : new AssetItemMutable(); + } + + protected override void TransformObjectAfterRead(ref ObjectContext objectContext) + { + objectContext.Instance = ((AssetItemMutable)objectContext.Instance).ToAssetItem(); + } - protected override void TransformObjectAfterRead(ref ObjectContext objectContext) + private class AssetItemMutable + { + public AssetItemMutable() { - objectContext.Instance = ((AssetItemMutable)objectContext.Instance).ToAssetItem(); } - private class AssetItemMutable + public AssetItemMutable(AssetItem item) { - public AssetItemMutable() - { - } - - public AssetItemMutable(AssetItem item) - { - Location = item.Location; - SourceFolder = item.SourceFolder; - Asset = item.Asset; - AlternativePath = item.AlternativePath; - } - - [DataMember(0)] - public UFile Location; + Location = item.Location; + SourceFolder = item.SourceFolder; + Asset = item.Asset; + AlternativePath = item.AlternativePath; + } - [DataMember(1)] - [DefaultValue(null)] - public UDirectory SourceFolder; + [DataMember(0)] + public UFile Location; - [DataMember(2)] - public Asset Asset; + [DataMember(1)] + [DefaultValue(null)] + public UDirectory? SourceFolder; - [DataMember(3)] - public UFile AlternativePath; + [DataMember(2)] + public Asset Asset; - public AssetItem ToAssetItem() - { - return new AssetItem(Location, Asset) { SourceFolder = SourceFolder, AlternativePath = AlternativePath }; - } - } + [DataMember(3)] + public UFile AlternativePath; - public bool CanVisit(Type type) + public AssetItem ToAssetItem() { - return type == typeof(AssetItem); + return new AssetItem(Location, Asset) { SourceFolder = SourceFolder, AlternativePath = AlternativePath }; } + } - public void Visit(ref VisitorContext context) - { - context.Visitor.VisitObject(context.Instance, context.Descriptor, true); - } + public bool CanVisit(Type type) + { + return type == typeof(AssetItem); + } + + public void Visit(ref VisitorContext context) + { + context.Visitor.VisitObject(context.Instance, context.Descriptor, true); } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/AssetPartContainedAttribute.cs b/sources/assets/Stride.Core.Assets/Serializers/AssetPartContainedAttribute.cs index 6e795335d5..6268cb8603 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/AssetPartContainedAttribute.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/AssetPartContainedAttribute.cs @@ -1,27 +1,25 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// Changes rules on what types can be naturally contained inside a given member. All other types will be serialized as references. +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public class AssetPartContainedAttribute : Attribute { /// - /// Changes rules on what types can be naturally contained inside a given member. All other types will be serialized as references. + /// Initializes a new instance of the . /// - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - public class AssetPartContainedAttribute : Attribute + /// The collection of asset part types that are naturally contained in the member having this attribute. + public AssetPartContainedAttribute(params Type[] containedTypes) { - /// - /// Initializes a new instance of the . - /// - /// The collection of asset part types that are naturally contained in the member having this attribute. - public AssetPartContainedAttribute(params Type[] containedTypes) - { - ContainedTypes = containedTypes ?? new Type[0]; - } - - /// - /// Gets the types of asset part that will still be fully serialized if contained in a part of the member having this attribute. - /// - public Type[] ContainedTypes { get; } + ContainedTypes = containedTypes ?? []; } + + /// + /// Gets the types of asset part that will still be fully serialized if contained in a part of the member having this attribute. + /// + public Type[] ContainedTypes { get; } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/AssetReferenceSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/AssetReferenceSerializer.cs index 328659bab8..ca30f2e77c 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/AssetReferenceSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/AssetReferenceSerializer.cs @@ -1,38 +1,34 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; -using Stride.Core.Reflection; + using Stride.Core.Yaml; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// A Yaml serializer for +/// +[YamlSerializerFactory(YamlAssetProfile.Name)] +internal class AssetReferenceSerializer : AssetScalarSerializerBase { - /// - /// A Yaml serializer for - /// - [YamlSerializerFactory(YamlAssetProfile.Name)] - internal class AssetReferenceSerializer : AssetScalarSerializerBase + public override bool CanVisit(Type type) { - public override bool CanVisit(Type type) - { - return typeof(AssetReference).IsAssignableFrom(type); - } + return typeof(AssetReference).IsAssignableFrom(type); + } - public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + { + if (!AssetReference.TryParse(fromScalar.Value, out var assetReference)) { - AssetReference assetReference; - if (!AssetReference.TryParse(fromScalar.Value, out assetReference)) - { - throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to decode asset reference [{0}]. Expecting format GUID:LOCATION".ToFormat(fromScalar.Value)); - } - return assetReference; + throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to decode asset reference [{0}]. Expecting format GUID:LOCATION".ToFormat(fromScalar.Value)); } + return assetReference; + } - public override string ConvertTo(ref ObjectContext objectContext) - { - return objectContext.Instance.ToString(); - } + public override string ConvertTo(ref ObjectContext objectContext) + { + return objectContext.Instance.ToString()!; } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/ContentReferenceSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/ContentReferenceSerializer.cs index 41a6a27ad9..ce151f822f 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/ContentReferenceSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/ContentReferenceSerializer.cs @@ -1,47 +1,38 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; -using Stride.Core.IO; -using Stride.Core.Reflection; + using Stride.Core.Serialization; -using Stride.Core.Serialization.Contents; using Stride.Core.Yaml; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +[YamlSerializerFactory(YamlAssetProfile.Name)] +public class ContentReferenceSerializer : AssetScalarSerializerBase { - [YamlSerializerFactory(YamlAssetProfile.Name)] - public class ContentReferenceSerializer : AssetScalarSerializerBase - { - public static readonly ContentReferenceSerializer Default = new ContentReferenceSerializer(); + public static readonly ContentReferenceSerializer Default = new(); - public override bool CanVisit(Type type) - { - return AssetRegistry.IsExactContentType(type); - } + public override bool CanVisit(Type type) + { + return AssetRegistry.IsExactContentType(type); + } - public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + { + if (!AssetReference.TryParse(fromScalar.Value, out var guid, out var location)) { - AssetId guid; - UFile location; - if (!AssetReference.TryParse(fromScalar.Value, out guid, out location)) - { - throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to decode asset reference [{0}]. Expecting format GUID:LOCATION".ToFormat(fromScalar.Value)); - } - - var instance = AttachedReferenceManager.CreateProxyObject(context.Descriptor.Type, guid, location); - return instance; + throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to decode asset reference [{0}]. Expecting format GUID:LOCATION".ToFormat(fromScalar.Value)); } - public override string ConvertTo(ref ObjectContext objectContext) - { - var attachedReference = AttachedReferenceManager.GetAttachedReference(objectContext.Instance); - if (attachedReference == null) - throw new YamlException($"Unable to extract asset reference from object [{objectContext.Instance}]"); + var instance = AttachedReferenceManager.CreateProxyObject(context.Descriptor.Type, guid, location); + return instance; + } - return $"{attachedReference.Id}:{attachedReference.Url}"; - } + public override string ConvertTo(ref ObjectContext objectContext) + { + var attachedReference = AttachedReferenceManager.GetAttachedReference(objectContext.Instance) + ?? throw new YamlException($"Unable to extract asset reference from object [{objectContext.Instance}]"); + return $"{attachedReference.Id}:{attachedReference.Url}"; } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/FixupObjectReferences.cs b/sources/assets/Stride.Core.Assets/Serializers/FixupObjectReferences.cs index d73e897a18..40c1dcfe6a 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/FixupObjectReferences.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/FixupObjectReferences.cs @@ -1,109 +1,103 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using Stride.Core.Assets.Yaml; -using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.Diagnostics; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// A static class that can be used to fix up object references. +/// +public static class FixupObjectReferences { /// - /// A static class that can be used to fix up object references. + /// Fix up references represented by the dictionary into the object, by visiting the object + /// to find all instances it references, and modify the references described by to point + /// to the proper identifiable object matching the same . /// - public static class FixupObjectReferences + /// The root object to fix up. + /// The path to each object reference and the of the tar + /// If true, any object refernce that cannot be resolved will be reset to null. + /// If true, an exception will be thrown if two + /// An optional logger. + public static void RunFixupPass(object root, YamlAssetMetadata objectReferences, bool clearBrokenObjectReferences, bool throwOnDuplicateIds, ILogger? logger = null) { - /// - /// Fix up references represented by the dictionary into the object, by visiting the object - /// to find all instances it references, and modify the references described by to point - /// to the proper identifiable object matching the same . - /// - /// The root object to fix up. - /// The path to each object reference and the of the tar - /// If true, any object refernce that cannot be resolved will be reset to null. - /// If true, an exception will be thrown if two - /// An optional logger. - public static void RunFixupPass(object root, YamlAssetMetadata objectReferences, bool clearBrokenObjectReferences, bool throwOnDuplicateIds, [CanBeNull] ILogger logger = null) - { - // First collect IIdentifiable objects - var referenceTargets = CollectReferenceableObjects(root, objectReferences, throwOnDuplicateIds, logger); + // First collect IIdentifiable objects + var referenceTargets = CollectReferenceableObjects(root, objectReferences, throwOnDuplicateIds, logger); - // Then resolve and update object references - FixupReferences(root, objectReferences, referenceTargets, clearBrokenObjectReferences, logger); - } + // Then resolve and update object references + FixupReferences(root, objectReferences, referenceTargets, clearBrokenObjectReferences, logger); + } - public static Dictionary CollectReferenceableObjects(object root, YamlAssetMetadata objectReferences, bool throwOnDuplicateIds, [CanBeNull] ILogger logger = null) - { - var hashSet = new HashSet(objectReferences.Select(x => x.Key.ToMemberPath(root))); - var visitor = new FixupObjectReferenceVisitor(hashSet, throwOnDuplicateIds, logger); - visitor.Visit(root); - return visitor.ReferenceableObjects; - } + public static Dictionary CollectReferenceableObjects(object root, YamlAssetMetadata objectReferences, bool throwOnDuplicateIds, ILogger? logger = null) + { + var hashSet = new HashSet(objectReferences.Select(x => x.Key.ToMemberPath(root))); + var visitor = new FixupObjectReferenceVisitor(hashSet, throwOnDuplicateIds, logger); + visitor.Visit(root); + return visitor.ReferenceableObjects; + } - public static void FixupReferences([NotNull] object root, [NotNull] YamlAssetMetadata objectReferences, [NotNull] Dictionary referenceTargets, bool clearMissingReferences, ILogger logger = null) - { - FixupReferences(root, objectReferences, referenceTargets, clearMissingReferences, (p, r, v) => p.Apply(r, MemberPathAction.ValueSet, v)); - } + public static void FixupReferences(object root, YamlAssetMetadata objectReferences, Dictionary referenceTargets, bool clearMissingReferences, ILogger? logger = null) + { + FixupReferences(root, objectReferences, referenceTargets, clearMissingReferences, (p, r, v) => p.Apply(r, MemberPathAction.ValueSet, v)); + } - public static void FixupReferences([NotNull] object root, [NotNull] YamlAssetMetadata objectReferences, [NotNull] Dictionary referenceTargets, bool clearMissingReferences, [NotNull] Action applyAction, ILogger logger = null) + public static void FixupReferences(object root, YamlAssetMetadata objectReferences, Dictionary referenceTargets, bool clearMissingReferences, Action applyAction, ILogger? logger = null) + { + foreach (var objectReference in objectReferences) { - foreach (var objectReference in objectReferences) + var path = objectReference.Key.ToMemberPath(root); + if (!referenceTargets.TryGetValue(objectReference.Value, out var target)) { - var path = objectReference.Key.ToMemberPath(root); - if (!referenceTargets.TryGetValue(objectReference.Value, out IIdentifiable target)) + logger?.Warning($"Unable to resolve target object [{objectReference.Value}] of reference [{objectReference.Key}]"); + if (clearMissingReferences) + applyAction(path, root, null); + } + else + { + var current = path.GetValue(root); + if (!Equals(current, target)) { - logger?.Warning($"Unable to resolve target object [{objectReference.Value}] of reference [{objectReference.Key}]"); - if (clearMissingReferences) - applyAction(path, root, null); - } - else - { - var current = path.GetValue(root); - if (!Equals(current, target)) - { - applyAction(path, root, target); - } + applyAction(path, root, target); } } } + } - private class FixupObjectReferenceVisitor : DataVisitorBase - { - public readonly Dictionary ReferenceableObjects = new Dictionary(); - private readonly HashSet objectReferences; - private readonly bool throwOnDuplicateIds; - private readonly ILogger logger; + private class FixupObjectReferenceVisitor : DataVisitorBase + { + public readonly Dictionary ReferenceableObjects = []; + private readonly HashSet objectReferences; + private readonly bool throwOnDuplicateIds; + private readonly ILogger? logger; - public FixupObjectReferenceVisitor(HashSet objectReferences, bool throwOnDuplicateIds, [CanBeNull] ILogger logger = null) - { - this.objectReferences = objectReferences; - this.throwOnDuplicateIds = throwOnDuplicateIds; - this.logger = logger; - } + public FixupObjectReferenceVisitor(HashSet objectReferences, bool throwOnDuplicateIds, ILogger? logger = null) + { + this.objectReferences = objectReferences; + this.throwOnDuplicateIds = throwOnDuplicateIds; + this.logger = logger; + } - public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) + public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) + { + if (obj is IIdentifiable identifiable) { - var identifiable = obj as IIdentifiable; - if (obj is IIdentifiable) + // Skip reference, we're looking for real objects + if (!objectReferences.Any(x => x.Match(CurrentPath))) { - // Skip reference, we're looking for real objects - if (!objectReferences.Any(x => x.Match(CurrentPath))) + if (ReferenceableObjects.ContainsKey(identifiable.Id)) { - if (ReferenceableObjects.ContainsKey(identifiable.Id)) - { - var message = $"Multiple identifiable objects with the same id {identifiable.Id}"; - logger?.Error(message); - if (throwOnDuplicateIds) - throw new InvalidOperationException(message); - } - ReferenceableObjects[identifiable.Id] = identifiable; + var message = $"Multiple identifiable objects with the same id {identifiable.Id}"; + logger?.Error(message); + if (throwOnDuplicateIds) + throw new InvalidOperationException(message); } + ReferenceableObjects[identifiable.Id] = identifiable; } - base.VisitObject(obj, descriptor, visitMembers); } + base.VisitObject(obj, descriptor, visitMembers); } } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/IAssetPartReference.cs b/sources/assets/Stride.Core.Assets/Serializers/IAssetPartReference.cs index 0047713248..e1c9a80f7c 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/IAssetPartReference.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/IAssetPartReference.cs @@ -1,33 +1,31 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// An interface representing a reference to an asset part that is used for serialization. +/// +public interface IAssetPartReference { /// - /// An interface representing a reference to an asset part that is used for serialization. + /// Gets or sets the actual type of object that is being deserialized. /// - public interface IAssetPartReference - { - /// - /// Gets or sets the actual type of object that is being deserialized. - /// - /// - /// This property is transient and used only during serialization. Therefore, implementations should have the set on this property. - /// - Type InstanceType { get; set; } + /// + /// This property is transient and used only during serialization. Therefore, implementations should have the set on this property. + /// + Type InstanceType { get; set; } - /// - /// Fills properties of this object from the actual asset part being referenced. - /// - /// The actual asset part being referenced. - void FillFromPart(object assetPart); + /// + /// Fills properties of this object from the actual asset part being referenced. + /// + /// The actual asset part being referenced. + void FillFromPart(object assetPart); - /// - /// Generates a proxy asset part from the information contained in this instance. - /// - /// The type of asset part to generate. - /// A proxy asset part built from this instance. - object GenerateProxyPart(Type partType); - } + /// + /// Generates a proxy asset part from the information contained in this instance. + /// + /// The type of asset part to generate. + /// A proxy asset part built from this instance. + object? GenerateProxyPart(Type partType); } diff --git a/sources/assets/Stride.Core.Assets/Serializers/IAssetSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/IAssetSerializer.cs index fc0c590668..6322dd61fd 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/IAssetSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/IAssetSerializer.cs @@ -1,26 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; using Stride.Core.Assets.Yaml; using Stride.Core.Diagnostics; using Stride.Core.IO; -using Stride.Core.Reflection; -using Stride.Core.Yaml; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +public interface IAssetSerializerFactory { - public interface IAssetSerializerFactory - { - IAssetSerializer TryCreate(string assetFileExtension); - } + IAssetSerializer? TryCreate(string assetFileExtension); +} - public interface IAssetSerializer - { - object Load(Stream stream, UFile filePath, ILogger log, bool clearBrokenObjectReferences, out bool aliasOccurred, out AttachedYamlAssetMetadata yamlMetadata); +public interface IAssetSerializer +{ + object Load(Stream stream, UFile filePath, ILogger? log, bool clearBrokenObjectReferences, out bool aliasOccurred, out AttachedYamlAssetMetadata yamlMetadata); - void Save(Stream stream, object asset, AttachedYamlAssetMetadata yamlMetadata, ILogger log = null); - } + void Save(Stream stream, object asset, AttachedYamlAssetMetadata? yamlMetadata, ILogger? log = null); } diff --git a/sources/assets/Stride.Core.Assets/Serializers/IdentifiableAssetPartReference.cs b/sources/assets/Stride.Core.Assets/Serializers/IdentifiableAssetPartReference.cs index f646a327a9..838607c0d0 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/IdentifiableAssetPartReference.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/IdentifiableAssetPartReference.cs @@ -1,57 +1,53 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// An implementation of that represents an asset part implementing . +/// +/// +/// This type is the default type used when `AssetPartReferenceAttribute.ReferenceType` is undefined. +/// +[DataContract] +[DataStyle(DataStyle.Compact)] +public class IdentifiableAssetPartReference : IAssetPartReference { /// - /// An implementation of that represents an asset part implementing . + /// Gets or sets the identifier of the asset part represented by this reference. /// - /// - /// This type is the default type used when `AssetPartReferenceAttribute.ReferenceType` is undefined. - /// - [DataContract] - [DataStyle(DataStyle.Compact)] - public class IdentifiableAssetPartReference : IAssetPartReference + public Guid Id { get; set; } + + /// + [DataMemberIgnore] + public Type InstanceType { get; set; } + + /// + public override string ToString() + { + return $"{{AssetPartReference: {Id}}}"; + } + + /// + public void FillFromPart(object assetPart) { - /// - /// Gets or sets the identifier of the asset part represented by this reference. - /// - public Guid Id { get; set; } - - /// - [DataMemberIgnore] - public Type InstanceType { get; set; } - - /// - public override string ToString() - { - return $"{{AssetPartReference: {Id}}}"; - } - - /// - public void FillFromPart(object assetPart) - { - if (assetPart != null && !(assetPart is IIdentifiable)) - throw new InvalidOperationException($"Cannot serialize an object of type {assetPart.GetType().Name} as an asset part reference: the type does not implement {typeof(IIdentifiable).Name}"); - - var identifiable = (IIdentifiable)assetPart; - Id = identifiable?.Id ?? Guid.Empty; - } - - /// - public object GenerateProxyPart(Type partType) - { - if (!typeof(IIdentifiable).IsAssignableFrom(partType)) - throw new InvalidOperationException($"Cannot serialize an object of type {partType.Name} as an asset part reference: the type does not implement {typeof(IIdentifiable).Name}"); - - if (Id == Guid.Empty) - return null; - - var assetPart = (IIdentifiable)Activator.CreateInstance(partType); - assetPart.Id = Id; - return assetPart; - } + if (assetPart is not IIdentifiable identifiable) + throw new InvalidOperationException($"Cannot serialize an object of type {assetPart.GetType().Name} as an asset part reference: the type does not implement {typeof(IIdentifiable).Name}"); + + Id = identifiable?.Id ?? Guid.Empty; + } + + /// + public object? GenerateProxyPart(Type partType) + { + if (!typeof(IIdentifiable).IsAssignableFrom(partType)) + throw new InvalidOperationException($"Cannot serialize an object of type {partType.Name} as an asset part reference: the type does not implement {typeof(IIdentifiable).Name}"); + + if (Id == Guid.Empty) + return null; + + var assetPart = (IIdentifiable)Activator.CreateInstance(partType)!; + assetPart.Id = Id; + return assetPart; } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/IdentifiableAssetPartReferenceSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/IdentifiableAssetPartReferenceSerializer.cs index a15f8edd9b..f655a73664 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/IdentifiableAssetPartReferenceSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/IdentifiableAssetPartReferenceSerializer.cs @@ -1,38 +1,35 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; + using Stride.Core.Yaml; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +[YamlSerializerFactory(YamlAssetProfile.Name)] +public sealed class IdentifiableAssetPartReferenceSerializer : ScalarOrObjectSerializer { - [YamlSerializerFactory(YamlAssetProfile.Name)] - public sealed class IdentifiableAssetPartReferenceSerializer : ScalarOrObjectSerializer + public override bool CanVisit(Type type) { - public override bool CanVisit(Type type) - { - return type == typeof(IdentifiableAssetPartReference); - } + return type == typeof(IdentifiableAssetPartReference); + } - public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + { + if (!Guid.TryParse(fromScalar.Value, out var guid)) { - Guid guid; - if (!Guid.TryParse(fromScalar.Value, out guid)) - { - throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to decode asset part reference [{0}]. Expecting an ENTITY_GUID".ToFormat(fromScalar.Value)); - } + throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to decode asset part reference [{0}]. Expecting an ENTITY_GUID".ToFormat(fromScalar.Value)); + } - var result = context.Instance as IdentifiableAssetPartReference ?? (IdentifiableAssetPartReference)(context.Instance = new IdentifiableAssetPartReference()); - result.Id = guid; + var result = context.Instance as IdentifiableAssetPartReference ?? (IdentifiableAssetPartReference)(context.Instance = new IdentifiableAssetPartReference()); + result.Id = guid; - return result; - } + return result; + } - public override string ConvertTo(ref ObjectContext objectContext) - { - return ((IdentifiableAssetPartReference)objectContext.Instance).Id.ToString(); - } + public override string ConvertTo(ref ObjectContext objectContext) + { + return ((IdentifiableAssetPartReference)objectContext.Instance).Id.ToString(); } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/IdentifiableObjectSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/IdentifiableObjectSerializer.cs index 90237799e1..4b3497fe87 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/IdentifiableObjectSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/IdentifiableObjectSerializer.cs @@ -1,125 +1,119 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using Stride.Core.Assets.Yaml; -using Stride.Core; using Stride.Core.Reflection; using Stride.Core.Yaml; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// A serializer for instances, that can either serialize them directly or as an object reference. +/// +public sealed class IdentifiableObjectSerializer : ChainedSerializer { - /// - /// A serializer for instances, that can either serialize them directly or as an object reference. - /// - public sealed class IdentifiableObjectSerializer : ChainedSerializer - { - public const string Prefix = "ref!! "; - private readonly IdentifiableObjectReferenceSerializer scalarRedirectSerializer = new IdentifiableObjectReferenceSerializer(); + public const string Prefix = "ref!! "; + private readonly IdentifiableObjectReferenceSerializer scalarRedirectSerializer = new(); - public void Visit(ref VisitorContext context) - { - // For a scalar object, we don't visit its members - // But we do still visit the instance (either struct or class) - context.Visitor.VisitObject(context.Instance, context.Descriptor, false); - } + public void Visit(ref VisitorContext context) + { + // For a scalar object, we don't visit its members + // But we do still visit the instance (either struct or class) + context.Visitor.VisitObject(context.Instance, context.Descriptor, false); + } - public override object ReadYaml(ref ObjectContext objectContext) + public override object ReadYaml(ref ObjectContext objectContext) + { + if (objectContext.Reader.Accept()) { - if (objectContext.Reader.Accept()) + var next = objectContext.Reader.Peek(); + if (next.Value.StartsWith(Prefix, StringComparison.Ordinal)) { - var next = objectContext.Reader.Peek(); - if (next.Value.StartsWith(Prefix, StringComparison.Ordinal)) - { - return scalarRedirectSerializer.ReadYaml(ref objectContext); - } + return scalarRedirectSerializer.ReadYaml(ref objectContext); } - return base.ReadYaml(ref objectContext); } + return base.ReadYaml(ref objectContext); + } - public override void WriteYaml(ref ObjectContext objectContext) + public override void WriteYaml(ref ObjectContext objectContext) + { + if (ShouldSerializeAsScalar(ref objectContext)) { - if (ShouldSerializeAsScalar(ref objectContext)) - { - scalarRedirectSerializer.WriteYaml(ref objectContext); - } - else - { - base.WriteYaml(ref objectContext); - } + scalarRedirectSerializer.WriteYaml(ref objectContext); + } + else + { + base.WriteYaml(ref objectContext); } + } - public IYamlSerializable TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) => typeof(IIdentifiable).IsAssignableFrom(typeDescriptor.Type) ? this : null; + public IYamlSerializable? TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) => typeof(IIdentifiable).IsAssignableFrom(typeDescriptor.Type) ? this : null; - private static bool ShouldSerializeAsScalar(ref ObjectContext objectContext) - { - YamlAssetMetadata objectReferences; - if (!objectContext.SerializerContext.Properties.TryGetValue(AssetObjectSerializerBackend.ObjectReferencesKey, out objectReferences)) - return false; + private static bool ShouldSerializeAsScalar(ref ObjectContext objectContext) + { + if (!objectContext.SerializerContext.Properties.TryGetValue(AssetObjectSerializerBackend.ObjectReferencesKey, out var objectReferences)) + return false; - var path = AssetObjectSerializerBackend.GetCurrentPath(ref objectContext, true); - return objectReferences.TryGet(path) != Guid.Empty; - } + var path = AssetObjectSerializerBackend.GetCurrentPath(ref objectContext, true); + return objectReferences.TryGet(path) != Guid.Empty; + } - private static bool TryParse(string text, out Guid identifier) + private static bool TryParse(string text, out Guid identifier) + { + if (!text.StartsWith(Prefix, StringComparison.Ordinal)) { - if (!text.StartsWith(Prefix, StringComparison.Ordinal)) - { - identifier = Guid.Empty; - return false; - } - return Guid.TryParse(text.Substring(Prefix.Length), out identifier); + identifier = Guid.Empty; + return false; } + return Guid.TryParse(text.AsSpan(Prefix.Length), out identifier); + } - private class IdentifiableObjectReferenceSerializer : ScalarSerializerBase + private class IdentifiableObjectReferenceSerializer : ScalarSerializerBase + { + public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) { - public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + if (!TryParse(fromScalar.Value, out var identifier)) { - Guid identifier; - if (!TryParse(fromScalar.Value, out identifier)) - { - throw new YamlException($"Unable to deserialize reference: [{fromScalar.Value}]"); - } - - // Add the path to the currently deserialized object to the list of object references - YamlAssetMetadata objectReferences; - if (!context.SerializerContext.Properties.TryGetValue(AssetObjectSerializerBackend.ObjectReferencesKey, out objectReferences)) - { - objectReferences = new YamlAssetMetadata(); - context.SerializerContext.Properties.Add(AssetObjectSerializerBackend.ObjectReferencesKey, objectReferences); - } - var path = AssetObjectSerializerBackend.GetCurrentPath(ref context, true); - objectReferences.Set(path, identifier); - - // Return default(T) - //return !context.Descriptor.Type.IsValueType ? null : Activator.CreateInstance(context.Descriptor.Type); - // Return temporary proxy instance - var proxy = AbstractObjectInstantiator.CreateConcreteInstance(context.Descriptor.Type); - // Filtering out interface and abstracts here as they're using a proxy type which doesn't have Id implemented - if (context.Descriptor.Type.IsInterface == false && context.Descriptor.Type.IsAbstract == false && proxy is IIdentifiable identifiable) - identifiable.Id = identifier; - return proxy; + throw new YamlException($"Unable to deserialize reference: [{fromScalar.Value}]"); } - public override string ConvertTo(ref ObjectContext objectContext) + // Add the path to the currently deserialized object to the list of object references + if (!context.SerializerContext.Properties.TryGetValue(AssetObjectSerializerBackend.ObjectReferencesKey, out var objectReferences)) { - var identifiable = (IIdentifiable)objectContext.Instance; - return $"{Prefix}{identifiable.Id}"; + objectReferences = new YamlAssetMetadata(); + context.SerializerContext.Properties.Add(AssetObjectSerializerBackend.ObjectReferencesKey, objectReferences); } + var path = AssetObjectSerializerBackend.GetCurrentPath(ref context, true); + objectReferences.Set(path, identifier); - protected override void WriteScalar(ref ObjectContext objectContext, ScalarEventInfo scalar) - { - // Remove the tag if one was added, which might happen if the concrete type is different from the container type. - // NOTE: disabled for now, although it doesn't seem necessary anymore. To re-enable removing, just uncomment these two lines - //scalar.Tag = null; - //scalar.IsPlainImplicit = true; + // Return default(T) + //return !context.Descriptor.Type.IsValueType ? null : Activator.CreateInstance(context.Descriptor.Type); + // Return temporary proxy instance + var proxy = AbstractObjectInstantiator.CreateConcreteInstance(context.Descriptor.Type); + // Filtering out interface and abstracts here as they're using a proxy type which doesn't have Id implemented + if (context.Descriptor.Type.IsInterface == false && context.Descriptor.Type.IsAbstract == false && proxy is IIdentifiable identifiable) + identifiable.Id = identifier; + return proxy; + } - // Emit the scalar - objectContext.SerializerContext.Writer.Emit(scalar); - } + public override string ConvertTo(ref ObjectContext objectContext) + { + var identifiable = (IIdentifiable)objectContext.Instance; + return $"{Prefix}{identifiable.Id}"; + } + + protected override void WriteScalar(ref ObjectContext objectContext, ScalarEventInfo scalar) + { + // Remove the tag if one was added, which might happen if the concrete type is different from the container type. + // NOTE: disabled for now, although it doesn't seem necessary anymore. To re-enable removing, just uncomment these two lines + //scalar.Tag = null; + //scalar.IsPlainImplicit = true; + + // Emit the scalar + objectContext.SerializerContext.Writer.Emit(scalar); } } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/InvariantObjectCloneSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/InvariantObjectCloneSerializer.cs index 80306e06e6..94f6c0f3c3 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/InvariantObjectCloneSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/InvariantObjectCloneSerializer.cs @@ -1,33 +1,30 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; - using Stride.Core.Serialization; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// A fake serializer used for cloning invariant objects. +/// Instead of actually cloning the invariant object, this serializer store it in a list of the context and restore when deserializing. +/// +/// +[DataSerializerGlobal(typeof(InvariantObjectCloneSerializer), Profile = "AssetClone")] +public class InvariantObjectCloneSerializer : DataSerializer { - /// - /// A fake serializer used for cloning invariant objects. - /// Instead of actually cloning the invariant object, this serializer store it in a list of the context and restore when deserializing. - /// - /// - [DataSerializerGlobal(typeof(InvariantObjectCloneSerializer), Profile = "AssetClone")] - public class InvariantObjectCloneSerializer : DataSerializer + public override void Serialize(ref T obj, ArchiveMode mode, SerializationStream stream) { - public override void Serialize(ref T obj, ArchiveMode mode, SerializationStream stream) + var invariantObjectList = stream.Context.Get(AssetCloner.InvariantObjectListProperty); + if (mode == ArchiveMode.Serialize) + { + stream.Write(invariantObjectList.Count); + invariantObjectList.Add(obj); + } + else { - var invariantObjectList = stream.Context.Get(AssetCloner.InvariantObjectListProperty); - if (mode == ArchiveMode.Serialize) - { - stream.Write(invariantObjectList.Count); - invariantObjectList.Add(obj); - } - else - { - var index = stream.Read(); - obj = (T)invariantObjectList[index]; - } + var index = stream.Read(); + obj = (T)invariantObjectList[index]; } } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/PackageVersionRangeSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/PackageVersionRangeSerializer.cs index f0e85a190f..669d255830 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/PackageVersionRangeSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/PackageVersionRangeSerializer.cs @@ -1,37 +1,34 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; + using Stride.Core.Yaml; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// A Yaml serializer for +/// +[YamlSerializerFactory(YamlAssetProfile.Name)] +internal class PackageVersionRangeSerializer : AssetScalarSerializerBase { - /// - /// A Yaml serializer for - /// - [YamlSerializerFactory(YamlAssetProfile.Name)] - internal class PackageVersionRangeSerializer : AssetScalarSerializerBase + public override bool CanVisit(Type type) { - public override bool CanVisit(Type type) - { - return typeof(PackageVersionRange).IsAssignableFrom(type); - } + return typeof(PackageVersionRange).IsAssignableFrom(type); + } - public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + { + if (!PackageVersionRange.TryParse(fromScalar.Value, out var versionRange)) { - PackageVersionRange versionRange; - if (!PackageVersionRange.TryParse(fromScalar.Value, out versionRange)) - { - throw new YamlException(fromScalar.Start, fromScalar.End, "Invalid version dependency format. Unable to decode [{0}]".ToFormat(fromScalar.Value)); - } - return versionRange; + throw new YamlException(fromScalar.Start, fromScalar.End, "Invalid version dependency format. Unable to decode [{0}]".ToFormat(fromScalar.Value)); } + return versionRange; + } - public override string ConvertTo(ref ObjectContext objectContext) - { - return objectContext.Instance.ToString(); - } + public override string ConvertTo(ref ObjectContext objectContext) + { + return objectContext.Instance.ToString()!; } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/PackageVersionSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/PackageVersionSerializer.cs index f27592cf8b..69a03aff86 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/PackageVersionSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/PackageVersionSerializer.cs @@ -1,37 +1,34 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; + using Stride.Core.Yaml; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// A Yaml serializer for +/// +[YamlSerializerFactory(YamlAssetProfile.Name)] +internal class PackageVersionSerializer : AssetScalarSerializerBase { - /// - /// A Yaml serializer for - /// - [YamlSerializerFactory(YamlAssetProfile.Name)] - internal class PackageVersionSerializer : AssetScalarSerializerBase + public override bool CanVisit(Type type) { - public override bool CanVisit(Type type) - { - return typeof(PackageVersion).IsAssignableFrom(type); - } + return typeof(PackageVersion).IsAssignableFrom(type); + } - public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + { + if (!PackageVersion.TryParse(fromScalar.Value, out var packageVersion)) { - PackageVersion packageVersion; - if (!PackageVersion.TryParse(fromScalar.Value, out packageVersion)) - { - throw new YamlException(fromScalar.Start, fromScalar.End, "Invalid version format. Unable to decode [{0}]".ToFormat(fromScalar.Value)); - } - return packageVersion; + throw new YamlException(fromScalar.Start, fromScalar.End, "Invalid version format. Unable to decode [{0}]".ToFormat(fromScalar.Value)); } + return packageVersion; + } - public override string ConvertTo(ref ObjectContext objectContext) - { - return objectContext.Instance.ToString(); - } + public override string ConvertTo(ref ObjectContext objectContext) + { + return objectContext.Instance.ToString()!; } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/PropertyKeyYamlSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/PropertyKeyYamlSerializer.cs index 221613686f..6a66b245a2 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/PropertyKeyYamlSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/PropertyKeyYamlSerializer.cs @@ -1,76 +1,65 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Reflection; -using System.Text; -using Stride.Core; using Stride.Core.Yaml; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +[YamlSerializerFactory(YamlSerializerFactoryAttribute.Default)] +internal class PropertyKeyYamlSerializer : AssetScalarSerializerBase { - [YamlSerializerFactory(YamlSerializerFactoryAttribute.Default)] - internal class PropertyKeyYamlSerializer : AssetScalarSerializerBase + public override bool CanVisit(Type type) { - public override bool CanVisit(Type type) + // Because a PropertyKey<> inherits directly from PropertyKey, we can directly check the base only + // ParameterKey<> inherits from ParameterKey, so it won't conflict with the custom ParameterKeyYamlSerializer + // defined in the Stride.Assets assembly + + if (type == typeof(PropertyKey)) { - // Because a PropertyKey<> inherits directly from PropertyKey, we can directly check the base only - // ParameterKey<> inherits from ParameterKey, so it won't conflict with the custom ParameterKeyYamlSerializer - // defined in the Stride.Assets assembly + return true; + } - if (type == typeof(PropertyKey)) - { + for (Type? t = type; t != null; t = t.BaseType) + if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(PropertyKey<>)) return true; - } - - for (Type t = type; t != null; t = t.BaseType) - if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(PropertyKey<>)) - return true; - return false; - } - - public override object ConvertFrom(ref ObjectContext objectContext, Scalar fromScalar) - { - var lastDot = fromScalar.Value.LastIndexOf('.'); - if (lastDot == -1) - return null; + return false; + } - var className = fromScalar.Value.Substring(0, lastDot); + public override object? ConvertFrom(ref ObjectContext objectContext, Scalar fromScalar) + { + var lastDot = fromScalar.Value.LastIndexOf('.'); + if (lastDot == -1) + return null; - bool typeAliased; - var containingClass = objectContext.SerializerContext.TypeFromTag("!" + className, out typeAliased); // Readd initial '!' - if (containingClass == null) - { - throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to find class from tag [{0}]".ToFormat(className)); - } + var className = fromScalar.Value[..lastDot]; - var propertyName = fromScalar.Value.Substring(lastDot + 1); - var propertyField = containingClass.GetField(propertyName, BindingFlags.Public | BindingFlags.Static); - if (propertyField == null) - { - throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to find property [{0}] in class [{1}]".ToFormat(propertyName, containingClass.Name)); - } + var containingClass = objectContext.SerializerContext.TypeFromTag("!" + className, out var typeAliased) + ?? throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to find class from tag [{0}]".ToFormat(className)); // Readd initial '!' - return propertyField.GetValue(null); - } - - protected override void WriteScalar(ref ObjectContext objectContext, ScalarEventInfo scalar) - { - // TODO: if ParameterKey is written to an object, It will not serialized a tag - scalar.Tag = null; - scalar.IsPlainImplicit = true; - base.WriteScalar(ref objectContext, scalar); - } + var propertyName = fromScalar.Value[(lastDot + 1)..]; + var propertyField = containingClass.GetField(propertyName, BindingFlags.Public | BindingFlags.Static) + ?? throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to find property [{0}] in class [{1}]".ToFormat(propertyName, containingClass.Name)); + return propertyField.GetValue(null); + } - public override string ConvertTo(ref ObjectContext objectContext) - { - var propertyKey = (PropertyKey)objectContext.Instance; + protected override void WriteScalar(ref ObjectContext objectContext, ScalarEventInfo scalar) + { + // TODO: if ParameterKey is written to an object, It will not serialized a tag + scalar.Tag = null; + scalar.IsPlainImplicit = true; + base.WriteScalar(ref objectContext, scalar); + } - return PropertyKeyNameResolver.ComputePropertyKeyName(objectContext.SerializerContext, propertyKey); - } + public override string ConvertTo(ref ObjectContext objectContext) + { + var propertyKey = (PropertyKey)objectContext.Instance; - + return PropertyKeyNameResolver.ComputePropertyKeyName(objectContext.SerializerContext, propertyKey); } + + } diff --git a/sources/assets/Stride.Core.Assets/Serializers/ScalarOrObjectSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/ScalarOrObjectSerializer.cs index 30a6c8900a..75dfc16445 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/ScalarOrObjectSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/ScalarOrObjectSerializer.cs @@ -1,109 +1,108 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Reflection; using Stride.Core.Yaml; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// Serializer that works with scalar, but could still read older ObjectSerializer format. +/// +public abstract class ScalarOrObjectSerializer : IYamlSerializableFactory, IYamlSerializable, IDataCustomVisitor { - /// - /// Serializer that works with scalar, but could still read older ObjectSerializer format. - /// - public abstract class ScalarOrObjectSerializer : IYamlSerializableFactory, IYamlSerializable, IDataCustomVisitor + private static readonly ObjectSerializer ObjectSerializer = new(); + private readonly YamlRedirectSerializer scalarRedirectSerializer; + + protected ScalarOrObjectSerializer() { - private static readonly ObjectSerializer ObjectSerializer = new ObjectSerializer(); - private readonly YamlRedirectSerializer scalarRedirectSerializer; + scalarRedirectSerializer = new YamlRedirectSerializer(this); + } - protected ScalarOrObjectSerializer() - { - scalarRedirectSerializer = new YamlRedirectSerializer(this); - } + public IYamlSerializable? TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) + { + return CanVisit(typeDescriptor.Type) ? this : null; + } - public IYamlSerializable TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) + public abstract bool CanVisit(Type type); + + public void Visit(ref VisitorContext context) + { + // For a scalar object, we don't visit its members + // But we do still visit the instance (either struct or class) + context.Visitor.VisitObject(context.Instance, context.Descriptor, false); + } + + public object ReadYaml(ref ObjectContext objectContext) + { + if (!objectContext.Reader.Accept()) { - return CanVisit(typeDescriptor.Type) ? this : null; + // Old format, fallback to ObjectSerializer + return ObjectSerializer.ReadYaml(ref objectContext); } - public abstract bool CanVisit(Type type); + // If it's a scalar (new format), redirect to our scalar serializer + return scalarRedirectSerializer.ReadYaml(ref objectContext); + } - public void Visit(ref VisitorContext context) + public void WriteYaml(ref ObjectContext objectContext) + { + if (ShouldSerializeAsScalar(ref objectContext)) { - // For a scalar object, we don't visit its members - // But we do still visit the instance (either struct or class) - context.Visitor.VisitObject(context.Instance, context.Descriptor, false); + scalarRedirectSerializer.WriteYaml(ref objectContext); } - - public object ReadYaml(ref ObjectContext objectContext) + else { - if (!objectContext.Reader.Accept()) - { - // Old format, fallback to ObjectSerializer - return ObjectSerializer.ReadYaml(ref objectContext); - } - - // If it's a scalar (new format), redirect to our scalar serializer - return scalarRedirectSerializer.ReadYaml(ref objectContext); + ObjectSerializer.WriteYaml(ref objectContext); } + } + + public abstract object ConvertFrom(ref ObjectContext context, Scalar fromScalar); + + public abstract string ConvertTo(ref ObjectContext objectContext); - public void WriteYaml(ref ObjectContext objectContext) + protected virtual void WriteScalar(ref ObjectContext objectContext, ScalarEventInfo scalar) + { + // Emit the scalar + objectContext.SerializerContext.Writer.Emit(scalar); + } + + protected virtual bool ShouldSerializeAsScalar(ref ObjectContext objectContext) + { + // Always write in the new format by default + return true; + } + + internal class YamlRedirectSerializer : AssetScalarSerializerBase + { + private readonly ScalarOrObjectSerializer realScalarSerializer; + + public YamlRedirectSerializer(ScalarOrObjectSerializer realScalarSerializer) { - if (ShouldSerializeAsScalar(ref objectContext)) - { - scalarRedirectSerializer.WriteYaml(ref objectContext); - } - else - { - ObjectSerializer.WriteYaml(ref objectContext); - } + this.realScalarSerializer = realScalarSerializer; } - public abstract object ConvertFrom(ref ObjectContext context, Scalar fromScalar); - - public abstract string ConvertTo(ref ObjectContext objectContext); + public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + { + return realScalarSerializer.ConvertFrom(ref context, fromScalar); + } - protected virtual void WriteScalar(ref ObjectContext objectContext, ScalarEventInfo scalar) + public override string ConvertTo(ref ObjectContext objectContext) { - // Emit the scalar - objectContext.SerializerContext.Writer.Emit(scalar); + return realScalarSerializer.ConvertTo(ref objectContext); } - protected virtual bool ShouldSerializeAsScalar(ref ObjectContext objectContext) + protected override void WriteScalar(ref ObjectContext objectContext, ScalarEventInfo scalar) { - // Always write in the new format by default - return true; + realScalarSerializer.WriteScalar(ref objectContext, scalar); } - internal class YamlRedirectSerializer : AssetScalarSerializerBase + public override bool CanVisit(Type type) { - private readonly ScalarOrObjectSerializer realScalarSerializer; - - public YamlRedirectSerializer(ScalarOrObjectSerializer realScalarSerializer) - { - this.realScalarSerializer = realScalarSerializer; - } - - public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) - { - return realScalarSerializer.ConvertFrom(ref context, fromScalar); - } - - public override string ConvertTo(ref ObjectContext objectContext) - { - return realScalarSerializer.ConvertTo(ref objectContext); - } - - protected override void WriteScalar(ref ObjectContext objectContext, ScalarEventInfo scalar) - { - realScalarSerializer.WriteScalar(ref objectContext, scalar); - } - - public override bool CanVisit(Type type) - { - return realScalarSerializer.CanVisit(type); - } + return realScalarSerializer.CanVisit(type); } } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/SourceCodeAssetSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/SourceCodeAssetSerializer.cs index 44a33d311a..e9e49fc75d 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/SourceCodeAssetSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/SourceCodeAssetSerializer.cs @@ -1,61 +1,57 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.IO; using System.Text; using Stride.Core.Assets.TextAccessors; using Stride.Core.Assets.Yaml; using Stride.Core.Diagnostics; using Stride.Core.IO; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +internal class SourceCodeAssetSerializer : IAssetSerializer, IAssetSerializerFactory { - internal class SourceCodeAssetSerializer : IAssetSerializer, IAssetSerializerFactory + public static readonly SourceCodeAssetSerializer Default = new(); + + public object Load(Stream stream, UFile filePath, ILogger log, bool clearBrokenObjectReferences, out bool aliasOccurred, out AttachedYamlAssetMetadata yamlMetadata) { - public static readonly SourceCodeAssetSerializer Default = new SourceCodeAssetSerializer(); + aliasOccurred = false; - public object Load(Stream stream, UFile filePath, ILogger log, bool clearBrokenObjectReferences, out bool aliasOccurred, out AttachedYamlAssetMetadata yamlMetadata) - { - aliasOccurred = false; + var assetFileExtension = filePath.GetFileExtension()!.ToLowerInvariant(); + var type = AssetRegistry.GetAssetTypeFromFileExtension(assetFileExtension)!; + var asset = (SourceCodeAsset)Activator.CreateInstance(type)!; - var assetFileExtension = filePath.GetFileExtension().ToLowerInvariant(); - var type = AssetRegistry.GetAssetTypeFromFileExtension(assetFileExtension); - var asset = (SourceCodeAsset)Activator.CreateInstance(type); + if (asset.TextAccessor is DefaultTextAccessor textAccessor) + { + // Don't load the file if we have the file path + textAccessor.FilePath = filePath; - var textAccessor = asset.TextAccessor as DefaultTextAccessor; - if (textAccessor != null) + // Set the assets text if it loaded from an in-memory version + // TODO: Propagate dirtiness? + if (stream is MemoryStream) { - // Don't load the file if we have the file path - textAccessor.FilePath = filePath; - - // Set the assets text if it loaded from an in-memory version - // TODO: Propagate dirtiness? - if (stream is MemoryStream) - { - var reader = new StreamReader(stream, Encoding.UTF8); - textAccessor.Set(reader.ReadToEnd()); - } + var reader = new StreamReader(stream, Encoding.UTF8); + textAccessor.Set(reader.ReadToEnd()); } - - // No override in source code assets - yamlMetadata = new AttachedYamlAssetMetadata(); - return asset; } - public void Save(Stream stream, object asset, AttachedYamlAssetMetadata yamlMetadata, ILogger log = null) - { - ((SourceCodeAsset)asset).Save(stream); - } + // No override in source code assets + yamlMetadata = new AttachedYamlAssetMetadata(); + return asset; + } + + public void Save(Stream stream, object asset, AttachedYamlAssetMetadata? yamlMetadata, ILogger? log = null) + { + ((SourceCodeAsset)asset).Save(stream); + } - public IAssetSerializer TryCreate(string assetFileExtension) + public IAssetSerializer? TryCreate(string assetFileExtension) + { + var assetType = AssetRegistry.GetAssetTypeFromFileExtension(assetFileExtension); + if (assetType != null && typeof(SourceCodeAsset).IsAssignableFrom(assetType)) { - var assetType = AssetRegistry.GetAssetTypeFromFileExtension(assetFileExtension); - if (assetType != null && typeof(SourceCodeAsset).IsAssignableFrom(assetType)) - { - return this; - } - return null; + return this; } + return null; } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/UriYamlSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/UriYamlSerializer.cs index c0cdc81dec..9c3af23eb9 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/UriYamlSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/UriYamlSerializer.cs @@ -1,31 +1,30 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Yaml; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// A Yaml serializer for +/// +[YamlSerializerFactory(YamlAssetProfile.Name)] +internal class UriYamlSerializer : AssetScalarSerializerBase { - /// - /// A Yaml serializer for - /// - [YamlSerializerFactory(YamlAssetProfile.Name)] - internal class UriYamlSerializer : AssetScalarSerializerBase + public override bool CanVisit(Type type) { - public override bool CanVisit(Type type) - { - return typeof(System.Uri) == type; - } + return typeof(System.Uri) == type; + } - public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) - { - return new System.Uri(fromScalar.Value); - } + public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + { + return new System.Uri(fromScalar.Value); + } - public override string ConvertTo(ref ObjectContext objectContext) - { - return objectContext.Instance.ToString(); - } + public override string ConvertTo(ref ObjectContext objectContext) + { + return objectContext.Instance.ToString()!; } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/UrlReferenceSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/UrlReferenceSerializer.cs index ffe8a8e46f..c08bf1b3ca 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/UrlReferenceSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/UrlReferenceSerializer.cs @@ -1,44 +1,41 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Serialization; using Stride.Core.Yaml; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// A Yaml serializer for +/// +[YamlSerializerFactory(YamlAssetProfile.Name)] +internal class UrlReferenceSerializer : AssetScalarSerializerBase { - /// - /// A Yaml serializer for - /// - [YamlSerializerFactory(YamlAssetProfile.Name)] - internal class UrlReferenceSerializer : AssetScalarSerializerBase + public override bool CanVisit(Type type) { - public override bool CanVisit(Type type) - { - return UrlReferenceBase.IsUrlReferenceType(type); - } + return UrlReferenceBase.IsUrlReferenceType(type); + } - public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + { + if (!AssetReference.TryParse(fromScalar.Value, out var guid, out var location)) { - if (!AssetReference.TryParse(fromScalar.Value, out var guid, out var location)) - { - throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to decode url reference [{0}]. Expecting format GUID:LOCATION".ToFormat(fromScalar.Value)); - } - - return UrlReferenceBase.New(context.Descriptor.Type, guid, location.FullPath); + throw new YamlException(fromScalar.Start, fromScalar.End, "Unable to decode url reference [{0}]. Expecting format GUID:LOCATION".ToFormat(fromScalar.Value)); } - public override string ConvertTo(ref ObjectContext objectContext) - { - if (objectContext.Instance is UrlReferenceBase urlReference) - { - return $"{urlReference.Id}:{urlReference.Url}"; - } + return UrlReferenceBase.New(context.Descriptor.Type, guid, location.FullPath); + } - throw new YamlException($"Unable to extract url reference from object [{objectContext.Instance}]"); + public override string ConvertTo(ref ObjectContext objectContext) + { + if (objectContext.Instance is UrlReferenceBase urlReference) + { + return $"{urlReference.Id}:{urlReference.Url}"; } - + throw new YamlException($"Unable to extract url reference from object [{objectContext.Instance}]"); } } diff --git a/sources/assets/Stride.Core.Assets/Serializers/YamlAssetProfile.cs b/sources/assets/Stride.Core.Assets/Serializers/YamlAssetProfile.cs index 1a62519a53..e4e503001c 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/YamlAssetProfile.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/YamlAssetProfile.cs @@ -1,9 +1,9 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets.Serializers + +namespace Stride.Core.Assets.Serializers; + +public static class YamlAssetProfile { - public static class YamlAssetProfile - { - public const string Name = "Assets"; - } + public const string Name = "Assets"; } diff --git a/sources/assets/Stride.Core.Assets/Serializers/YamlAssetSerializer.cs b/sources/assets/Stride.Core.Assets/Serializers/YamlAssetSerializer.cs index dc6bc2ff0d..4ebf81b3b5 100644 --- a/sources/assets/Stride.Core.Assets/Serializers/YamlAssetSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Serializers/YamlAssetSerializer.cs @@ -1,61 +1,56 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.IO; using Stride.Core.Assets.Yaml; -using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.Diagnostics; using Stride.Core.IO; using Stride.Core.Yaml; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets.Serializers +namespace Stride.Core.Assets.Serializers; + +/// +/// Default serializer used for all Yaml content +/// +public class YamlAssetSerializer : IAssetSerializer, IAssetSerializerFactory { - /// - /// Default serializer used for all Yaml content - /// - public class YamlAssetSerializer : IAssetSerializer, IAssetSerializerFactory + public static AttachedYamlAssetMetadata CreateAndProcessMetadata(PropertyContainer yamlPropertyContainer, object deserializedObject, bool clearBrokenObjectReferences, ILogger? log = null) { - public static AttachedYamlAssetMetadata CreateAndProcessMetadata(PropertyContainer yamlPropertyContainer, object deserializedObject, bool clearBrokenObjectReferences, [CanBeNull] ILogger log = null) + var yamlMetadata = AttachedYamlAssetMetadata.FromPropertyContainer(yamlPropertyContainer); + var objectReferences = yamlMetadata.RetrieveMetadata(AssetObjectSerializerBackend.ObjectReferencesKey); + if (objectReferences != null) { - var yamlMetadata = AttachedYamlAssetMetadata.FromPropertyContainer(yamlPropertyContainer); - var objectReferences = yamlMetadata.RetrieveMetadata(AssetObjectSerializerBackend.ObjectReferencesKey); - if (objectReferences != null) - { - // This metadata is consumed here, no need to return it as attached metadata - FixupObjectReferences.RunFixupPass(deserializedObject, objectReferences, clearBrokenObjectReferences, false, log); - } - return yamlMetadata; + // This metadata is consumed here, no need to return it as attached metadata + FixupObjectReferences.RunFixupPass(deserializedObject, objectReferences, clearBrokenObjectReferences, false, log); } + return yamlMetadata; + } - public object Load(Stream stream, UFile filePath, ILogger log, bool clearBrokenObjectReferences, out bool aliasOccurred, out AttachedYamlAssetMetadata yamlMetadata) - { - PropertyContainer properties; - var result = AssetYamlSerializer.Default.Deserialize(stream, null, log != null ? new SerializerContextSettings { Logger = log } : null, out aliasOccurred, out properties); - yamlMetadata = CreateAndProcessMetadata(properties, result, clearBrokenObjectReferences, log); - return result; - } + public object Load(Stream stream, UFile filePath, ILogger? log, bool clearBrokenObjectReferences, out bool aliasOccurred, out AttachedYamlAssetMetadata yamlMetadata) + { + var result = AssetYamlSerializer.Default.Deserialize(stream, null, log != null ? new SerializerContextSettings { Logger = log } : null, out aliasOccurred, out var properties); + yamlMetadata = CreateAndProcessMetadata(properties, result, clearBrokenObjectReferences, log); + return result; + } - public void Save(Stream stream, object asset, AttachedYamlAssetMetadata yamlMetadata, ILogger log = null) + public void Save(Stream stream, object asset, AttachedYamlAssetMetadata? yamlMetadata, ILogger? log = null) + { + var settings = new SerializerContextSettings(log); + var overrides = yamlMetadata?.RetrieveMetadata(AssetObjectSerializerBackend.OverrideDictionaryKey); + if (overrides != null) { - var settings = new SerializerContextSettings(log); - var overrides = yamlMetadata?.RetrieveMetadata(AssetObjectSerializerBackend.OverrideDictionaryKey); - if (overrides != null) - { - settings.Properties.Add(AssetObjectSerializerBackend.OverrideDictionaryKey, overrides); - } - var objectReferences = yamlMetadata?.RetrieveMetadata(AssetObjectSerializerBackend.ObjectReferencesKey); - if (objectReferences != null) - { - settings.Properties.Add(AssetObjectSerializerBackend.ObjectReferencesKey, objectReferences); - } - AssetYamlSerializer.Default.Serialize(stream, asset, null, settings); + settings.Properties.Add(AssetObjectSerializerBackend.OverrideDictionaryKey, overrides); } - - public IAssetSerializer TryCreate(string assetFileExtension) + var objectReferences = yamlMetadata?.RetrieveMetadata(AssetObjectSerializerBackend.ObjectReferencesKey); + if (objectReferences != null) { - return this; + settings.Properties.Add(AssetObjectSerializerBackend.ObjectReferencesKey, objectReferences); } + AssetYamlSerializer.Default.Serialize(stream, asset, null, settings); + } + + public IAssetSerializer TryCreate(string assetFileExtension) + { + return this; } } diff --git a/sources/assets/Stride.Core.Assets/SolutionPlatform.cs b/sources/assets/Stride.Core.Assets/SolutionPlatform.cs index 87d4504444..8386f96b24 100644 --- a/sources/assets/Stride.Core.Assets/SolutionPlatform.cs +++ b/sources/assets/Stride.Core.Assets/SolutionPlatform.cs @@ -1,85 +1,86 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Diagnostics; -using System.Linq; -using Stride.Core; -using Stride.Core.Settings; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Defines a solution platform. +/// +[DataContract("SolutionPlatform")] +public class SolutionPlatform : SolutionPlatformPart { /// - /// Defines a solution platform. + /// Gets the alternative names that will appear in the .sln file equivalent to this platform. + /// + /// The alternative names. + [DataMember(20)] + public SolutionPlatformPartCollection PlatformsPart { get; } = []; + + /// + /// Gets or sets the type of the platform. + /// + /// The type. + [DataMember(30)] + public PlatformType Type { get; set; } + + /// + /// Gets or sets the type of the platform. + /// + /// The type. + [DataMember(35)] + public string TargetFramework { get; set; } + + /// + /// Gets or sets the runtime identifier. + /// + /// The runtime identifier. + [DataMember(35)] + public string RuntimeIdentifier { get; set; } + + /// + /// Gets the define constants that will be used by the csproj of the platform. /// - [DataContract("SolutionPlatform")] - public class SolutionPlatform : SolutionPlatformPart + /// The define constants. + [DataMember(40)] + public List DefineConstants { get; } = []; + + /// + /// Gets or sets a value indicating whether this is available on this machine. + /// + /// true if available; otherwise, false. + [DataMember(50)] + public bool IsAvailable { get; set; } + + /// + /// The list of templates. If empty, no choice will be given to user and default one will be created by concatening ProjectExecutable and . + /// + [DataMember(60)] + public List Templates { get; } = []; + + /// + /// Gets the all . + /// + /// IEnumerable<SolutionPlatformPart>. + public IEnumerable GetParts() { - /// - /// Gets the alternative names that will appear in the .sln file equivalent to this platform. - /// - /// The alternative names. - [DataMember(20)] - public SolutionPlatformPartCollection PlatformsPart { get; } = new SolutionPlatformPartCollection(); - - /// - /// Gets or sets the type of the platform. - /// - /// The type. - [DataMember(30)] - public PlatformType Type { get; set; } - - /// - /// Gets or sets the type of the platform. - /// - /// The type. - [DataMember(35)] - public string TargetFramework { get; set; } - - /// - /// Gets or sets the runtime identifier. - /// - /// The runtime identifier. - [DataMember(35)] - public string RuntimeIdentifier { get; set; } - - /// - /// Gets the define constants that will be used by the csproj of the platform. - /// - /// The define constants. - [DataMember(40)] - public List DefineConstants { get; } = new List(); - - /// - /// Gets or sets a value indicating whether this is available on this machine. - /// - /// true if available; otherwise, false. - [DataMember(50)] - public bool IsAvailable { get; set; } - - /// - /// The list of templates. If empty, no choice will be given to user and default one will be created by concatening ProjectExecutable and . - /// - [DataMember(60)] - public List Templates { get; } = new List(); - - /// - /// Gets the all . - /// - /// IEnumerable<SolutionPlatformPart>. - public IEnumerable GetParts() - { - // Returns solution platform in alphabetical order - var parts = new List(1 + PlatformsPart.Count) { this }; - parts.AddRange(PlatformsPart); + // Returns solution platform in alphabetical order + var parts = new List(1 + PlatformsPart.Count) { this }; + parts.AddRange(PlatformsPart); - return parts.OrderBy(part => part.SolutionName ?? part.Name, StringComparer.InvariantCultureIgnoreCase); - } + return parts.OrderBy(part => part.SolutionName ?? part.Name, StringComparer.InvariantCultureIgnoreCase); + } + + public IEnumerable GetConfigurationProperties(SolutionPlatformPart part, string configuration) + { + ArgumentNullException.ThrowIfNull(part); + + return Impl(); - public IEnumerable GetConfigurationProperties(SolutionPlatformPart part, string configuration) + IEnumerable Impl() { - if (part == null) throw new ArgumentNullException("part"); if (part.Configurations.Contains(configuration)) { foreach (var property in part.Configurations[configuration].Properties) @@ -96,195 +97,192 @@ public IEnumerable GetConfigurationProperties(SolutionPlatformPart part, } } } + } - public override string ToString() - { - return $"SolutionPlatform {Type}"; - } + public override string ToString() + { + return $"SolutionPlatform {Type}"; } +} +/// +/// Class SolutionAlternativePlatform. +/// +[DebuggerDisplay("Solution {Name} Configs [{Configurations.Count}]")] +public class SolutionPlatformPart +{ /// - /// Class SolutionAlternativePlatform. + /// Initializes a new instance of the class. /// - [DebuggerDisplay("Solution {Name} Configs [{Configurations.Count}]")] - public class SolutionPlatformPart + public SolutionPlatformPart() { - /// - /// Initializes a new instance of the class. - /// - public SolutionPlatformPart() - { - UseWithExecutables = true; - UseWithLibraries = true; - IncludeInSolution = true; - Configurations = new SolutionConfigurationCollection { new SolutionConfiguration("Debug") { IsDebug = true }, new SolutionConfiguration("Release") }; - } + UseWithExecutables = true; + UseWithLibraries = true; + IncludeInSolution = true; + Configurations = [new SolutionConfiguration("Debug") { IsDebug = true }, new SolutionConfiguration("Release")]; + } - /// - /// Initializes a new instance of the class. - /// - /// The name. - /// name - public SolutionPlatformPart(string name) : this() - { - if (name == null) throw new ArgumentNullException("name"); - Name = name; - } + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// name + public SolutionPlatformPart(string name) : this() + { + ArgumentNullException.ThrowIfNull(name); + Name = name; + } - /// - /// Gets or sets the name of the alternative platform. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the solution name. If null, use the - /// - /// The name. - public string SolutionName { get; set; } - - /// - /// Gets or sets the display name. If null, use the . - /// - /// - /// The display name. - /// - public string DisplayName { get; set; } - - /// - /// Gets the name of the solution from , using as a fallback. - /// - /// The name of the safe solution. - public string SafeSolutionName - { - get - { - return SolutionName ?? Name; - } - } + /// + /// Gets or sets the name of the alternative platform. + /// + /// The name. + public string Name { get; set; } - /// - /// Gets or sets the CPU name, if this platform is CPU specific. - /// - /// - /// The CPU name. - /// - public string Cpu { get; set; } - - /// - /// Gets or sets the name of the alias. If != null, then this platform is only available in the solution and remapped - /// to the platform with this - /// - /// The name of the alias. - public string Alias { get; set; } - - /// - /// Gets or sets a value indicating whether inherit configurations from parent - /// - /// true if [inherit configurations]; otherwise, false. - public bool InheritConfigurations { get; set; } - - /// - /// Gets the configurations supported by this platform (by default contains 'Debug' and 'Release') - /// - public SolutionConfigurationCollection Configurations { get; private set; } - - public bool UseWithExecutables { get; set; } - public bool UseWithLibraries { get; set; } - public bool IncludeInSolution { get; set; } - - public string LibraryProjectName { get; set; } - public string ExecutableProjectName { get; set; } - - /// - /// Determines whether the project is handled by this platform. - /// - /// Type of the project. - /// true if the project is handled by this platform; otherwise, false. - public bool IsProjectHandled(ProjectType projectType) - { - return (projectType != ProjectType.Executable || UseWithExecutables) && (projectType != ProjectType.Library || UseWithLibraries); - } + /// + /// Gets or sets the solution name. If null, use the + /// + /// The name. + public string SolutionName { get; set; } - /// - /// Gets the name of the project configuration from , with as a fallback. - /// - /// The name of the safe solution. - public string GetProjectName(ProjectType projectType) - { - switch (projectType) - { - case ProjectType.Executable: - return ExecutableProjectName ?? Alias ?? SafeSolutionName; - case ProjectType.Library: - return LibraryProjectName ?? Alias ?? SafeSolutionName; - default: - throw new ArgumentOutOfRangeException("projectType"); - } - } + /// + /// Gets or sets the display name. If null, use the . + /// + /// + /// The display name. + /// + public string DisplayName { get; set; } - public override string ToString() + /// + /// Gets the name of the solution from , using as a fallback. + /// + /// The name of the safe solution. + public string SafeSolutionName + { + get { - return $"SolutionPlatformPart {Name} ({Configurations.Count} configs)"; + return SolutionName ?? Name; } } /// - /// A collection of + /// Gets or sets the CPU name, if this platform is CPU specific. /// - [DataContract("SolutionPlatformPartCollection")] - public class SolutionPlatformPartCollection : KeyedCollection + /// + /// The CPU name. + /// + public string Cpu { get; set; } + + /// + /// Gets or sets the name of the alias. If != null, then this platform is only available in the solution and remapped + /// to the platform with this + /// + /// The name of the alias. + public string Alias { get; set; } + + /// + /// Gets or sets a value indicating whether inherit configurations from parent + /// + /// true if [inherit configurations]; otherwise, false. + public bool InheritConfigurations { get; set; } + + /// + /// Gets the configurations supported by this platform (by default contains 'Debug' and 'Release') + /// + public SolutionConfigurationCollection Configurations { get; } + + public bool UseWithExecutables { get; set; } + public bool UseWithLibraries { get; set; } + public bool IncludeInSolution { get; set; } + + public string LibraryProjectName { get; set; } + public string ExecutableProjectName { get; set; } + + /// + /// Determines whether the project is handled by this platform. + /// + /// Type of the project. + /// true if the project is handled by this platform; otherwise, false. + public bool IsProjectHandled(ProjectType projectType) { - protected override string GetKeyForItem(SolutionPlatformPart item) - { - return item.Name; - } + return (projectType != ProjectType.Executable || UseWithExecutables) && (projectType != ProjectType.Library || UseWithLibraries); } /// - /// A collection of + /// Gets the name of the project configuration from , with as a fallback. /// - [DataContract("SolutionConfigurationCollection")] - public class SolutionConfigurationCollection : KeyedCollection + /// The name of the safe solution. + public string GetProjectName(ProjectType projectType) { - protected override string GetKeyForItem(SolutionConfiguration item) + return projectType switch { - return item.Name; - } + ProjectType.Executable => ExecutableProjectName ?? Alias ?? SafeSolutionName, + ProjectType.Library => LibraryProjectName ?? Alias ?? SafeSolutionName, + _ => throw new ArgumentOutOfRangeException(nameof(projectType)), + }; } + public override string ToString() + { + return $"SolutionPlatformPart {Name} ({Configurations.Count} configs)"; + } +} + +/// +/// A collection of +/// +[DataContract("SolutionPlatformPartCollection")] +public class SolutionPlatformPartCollection : KeyedCollection +{ + protected override string GetKeyForItem(SolutionPlatformPart item) + { + return item.Name; + } +} + +/// +/// A collection of +/// +[DataContract("SolutionConfigurationCollection")] +public class SolutionConfigurationCollection : KeyedCollection +{ + protected override string GetKeyForItem(SolutionConfiguration item) + { + return item.Name; + } +} + +/// +/// A solution configuration used by +/// +[DataContract("SolutionConfiguration")] +[DebuggerDisplay("Config [{Name}]")] +public class SolutionConfiguration +{ /// - /// A solution configuration used by + /// Initializes a new instance of the class. /// - [DataContract("SolutionConfiguration")] - [DebuggerDisplay("Config [{Name}]")] - public class SolutionConfiguration + public SolutionConfiguration(string name) { - /// - /// Initializes a new instance of the class. - /// - public SolutionConfiguration(string name) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - Name = name; - } - - /// - /// Gets or sets the configuration name (e.g. Debug, Release) - /// - /// The name. - public string Name { get; init; } - - /// - /// Gets or sets a value indicating whether this instance is a debug configuration. - /// - /// true if this instance is debug; otherwise, false. - public bool IsDebug { get; set; } - - /// - /// Gets the additional msbuild properties for a specific configuration (Debug or Release) - /// - /// The msbuild configuration properties. - public List Properties { get; } = new List(); + ArgumentNullException.ThrowIfNull(name); + Name = name; } + + /// + /// Gets or sets the configuration name (e.g. Debug, Release) + /// + /// The name. + public string Name { get; init; } + + /// + /// Gets or sets a value indicating whether this instance is a debug configuration. + /// + /// true if this instance is debug; otherwise, false. + public bool IsDebug { get; set; } + + /// + /// Gets the additional msbuild properties for a specific configuration (Debug or Release) + /// + /// The msbuild configuration properties. + public List Properties { get; } = []; } diff --git a/sources/assets/Stride.Core.Assets/SolutionPlatformCollection.cs b/sources/assets/Stride.Core.Assets/SolutionPlatformCollection.cs index d7ad7c4e1d..a721203883 100644 --- a/sources/assets/Stride.Core.Assets/SolutionPlatformCollection.cs +++ b/sources/assets/Stride.Core.Assets/SolutionPlatformCollection.cs @@ -1,18 +1,17 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System.Collections.ObjectModel; -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// A collection of . +/// +public sealed class SolutionPlatformCollection : KeyedCollection { - /// - /// A collection of . - /// - public sealed class SolutionPlatformCollection : KeyedCollection + protected override PlatformType GetKeyForItem(SolutionPlatform item) { - protected override PlatformType GetKeyForItem(SolutionPlatform item) - { - return item.Type; - } + return item.Type; } } diff --git a/sources/assets/Stride.Core.Assets/SolutionPlatformTemplate.cs b/sources/assets/Stride.Core.Assets/SolutionPlatformTemplate.cs index fa832d1cc8..657aa8e7f2 100644 --- a/sources/assets/Stride.Core.Assets/SolutionPlatformTemplate.cs +++ b/sources/assets/Stride.Core.Assets/SolutionPlatformTemplate.cs @@ -1,30 +1,27 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Defines a custom project template for a . +/// +[DataContract("SolutionPlatformTemplate")] +public class SolutionPlatformTemplate { - /// - /// Defines a custom project template for a . - /// - [DataContract("SolutionPlatformTemplate")] - public class SolutionPlatformTemplate + public SolutionPlatformTemplate(string templatePath, string displayName) { - public SolutionPlatformTemplate(string templatePath, string displayName) - { - TemplatePath = templatePath ?? throw new ArgumentNullException(nameof(templatePath)); - DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName)); - } + TemplatePath = templatePath ?? throw new ArgumentNullException(nameof(templatePath)); + DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName)); + } - /// - /// The template path. - /// - public string TemplatePath { get; set; } + /// + /// The template path. + /// + public string TemplatePath { get; set; } - /// - /// The display name, which will be shown to user when choosing template. - /// - public string DisplayName { get; set; } - } -} \ No newline at end of file + /// + /// The display name, which will be shown to user when choosing template. + /// + public string DisplayName { get; set; } +} diff --git a/sources/assets/Stride.Core.Assets/SourceCodeAsset.cs b/sources/assets/Stride.Core.Assets/SourceCodeAsset.cs index e1a9454310..ba5d6b86e6 100644 --- a/sources/assets/Stride.Core.Assets/SourceCodeAsset.cs +++ b/sources/assets/Stride.Core.Assets/SourceCodeAsset.cs @@ -1,75 +1,68 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.IO; using System.Text; -using System.Threading.Tasks; using Stride.Core.Assets.TextAccessors; -using Stride.Core; -using Stride.Core.Extensions; -using Stride.Core.Serialization; using Stride.Core.Storage; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Class SourceCodeAsset. +/// +[DataContract("SourceCodeAsset")] +public abstract class SourceCodeAsset : Asset { + [DataMemberIgnore] + [Display(Browsable = false)] + public ITextAccessor TextAccessor { get; set; } = new DefaultTextAccessor(); + /// - /// Class SourceCodeAsset. + /// Used internally by serialization. /// - [DataContract("SourceCodeAsset")] - public abstract class SourceCodeAsset : Asset + [DataMember(DataMemberMode.Assign)] + [Display(Browsable = false)] + public ISerializableTextAccessor InternalSerializableTextAccessor { - [DataMemberIgnore] - [Display(Browsable = false)] - public ITextAccessor TextAccessor { get; set; } = new DefaultTextAccessor(); - - /// - /// Used internally by serialization. - /// - [DataMember(DataMemberMode.Assign)] - [Display(Browsable = false)] - public ISerializableTextAccessor InternalSerializableTextAccessor - { - get { return TextAccessor.GetSerializableVersion(); } - set { TextAccessor = value.Create(); } - } + get { return TextAccessor.GetSerializableVersion(); } + set { TextAccessor = value.Create(); } + } - /// - /// Gets the sourcecode text. - /// - /// The sourcecode text. - [DataMemberIgnore] - [Display(Browsable = false)] - public string Text + /// + /// Gets the sourcecode text. + /// + /// The sourcecode text. + [DataMemberIgnore] + [Display(Browsable = false)] + public string Text + { + get { - get - { - return TextAccessor.Get(); - } - set - { - TextAccessor.Set(value); - } + return TextAccessor.Get(); } - - /// - /// Saves the content to a stream. - /// - /// - public virtual void Save(Stream stream) + set { - TextAccessor.Save(stream).Wait(); + TextAccessor.Set(value); } + } - /// - /// Generates a unique identifier from location. - /// - /// The location. - /// Guid. - public static AssetId GenerateIdFromLocation(string packageName, string location) - { - if (location == null) throw new ArgumentNullException(nameof(location)); - return (AssetId)ObjectId.Combine(ObjectId.FromBytes(Encoding.UTF8.GetBytes(packageName)), ObjectId.FromBytes(Encoding.UTF8.GetBytes(location))).ToGuid(); - } + /// + /// Saves the content to a stream. + /// + /// + public virtual void Save(Stream stream) + { + TextAccessor.Save(stream).Wait(); + } + + /// + /// Generates a unique identifier from location. + /// + /// The location. + /// Guid. + public static AssetId GenerateIdFromLocation(string packageName, string location) + { + ArgumentNullException.ThrowIfNull(location); + return (AssetId)ObjectId.Combine(ObjectId.FromBytes(Encoding.UTF8.GetBytes(packageName)), ObjectId.FromBytes(Encoding.UTF8.GetBytes(location))).ToGuid(); } } diff --git a/sources/assets/Stride.Core.Assets/SourceFileMemberAttribute.cs b/sources/assets/Stride.Core.Assets/SourceFileMemberAttribute.cs index 190005d931..ea1c8e36e6 100644 --- a/sources/assets/Stride.Core.Assets/SourceFileMemberAttribute.cs +++ b/sources/assets/Stride.Core.Assets/SourceFileMemberAttribute.cs @@ -1,32 +1,30 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// An attribute indicating whether a member of an asset represents the path to a source file for this asset. +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public sealed class SourceFileMemberAttribute : Attribute { /// - /// An attribute indicating whether a member of an asset represents the path to a source file for this asset. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - public sealed class SourceFileMemberAttribute : Attribute + /// If true, the asset should be updated when the related source file changes. + public SourceFileMemberAttribute(bool updateAssetIfChanged) { - /// - /// Initializes a new instance of the class. - /// - /// If true, the asset should be updated when the related source file changes. - public SourceFileMemberAttribute(bool updateAssetIfChanged) - { - UpdateAssetIfChanged = updateAssetIfChanged; - } + UpdateAssetIfChanged = updateAssetIfChanged; + } - /// - /// Gets whether the asset should be updated when the related source file changes. - /// - public bool UpdateAssetIfChanged { get; } + /// + /// Gets whether the asset should be updated when the related source file changes. + /// + public bool UpdateAssetIfChanged { get; } - /// - /// Gets or sets whether this source file is optional for the compilation of the asset. - /// - public bool Optional { get; set; } - } + /// + /// Gets or sets whether this source file is optional for the compilation of the asset. + /// + public bool Optional { get; set; } } diff --git a/sources/assets/Stride.Core.Assets/Stride.Core.Assets.csproj b/sources/assets/Stride.Core.Assets/Stride.Core.Assets.csproj index fe15594aee..37f2772674 100644 --- a/sources/assets/Stride.Core.Assets/Stride.Core.Assets.csproj +++ b/sources/assets/Stride.Core.Assets/Stride.Core.Assets.csproj @@ -4,8 +4,10 @@ 8.0.30703 2.0 true - enable $(StrideXplatEditorTargetFramework) + enable + latest + enable true --auto-module-initializer --serialization true diff --git a/sources/assets/Stride.Core.Assets/StridePackagesToSkipUpgrade.cs b/sources/assets/Stride.Core.Assets/StridePackagesToSkipUpgrade.cs index 0e0e916969..da88878d49 100644 --- a/sources/assets/Stride.Core.Assets/StridePackagesToSkipUpgrade.cs +++ b/sources/assets/Stride.Core.Assets/StridePackagesToSkipUpgrade.cs @@ -1,6 +1,5 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; namespace Stride.Core.Assets; diff --git a/sources/assets/Stride.Core.Assets/SupportedProgrammingLanguage.cs b/sources/assets/Stride.Core.Assets/SupportedProgrammingLanguage.cs index 4a76acb1e5..b64438fa1f 100644 --- a/sources/assets/Stride.Core.Assets/SupportedProgrammingLanguage.cs +++ b/sources/assets/Stride.Core.Assets/SupportedProgrammingLanguage.cs @@ -1,46 +1,42 @@ -using System; -using System.Collections.Generic; -using System.Linq; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Represents a programming language supported by the system. +/// +/// The name of the programming language. +/// The associated project file extension. +public record SupportedProgrammingLanguage(string Name, string Extension); + +/// +/// Provides a list of programming languages supported by the system. +/// +public static class SupportedProgrammingLanguages { /// - /// Represents a programming language supported by the system. + /// Gets the list of supported programming languages. /// - /// The name of the programming language. - /// The associated project file extension. - public record SupportedProgrammingLanguage(string Name, string Extension); + /// + /// A list of objects. + /// + /// + /// Use this list to check for supported programming languages when needed. + /// The list is initialized with common programming languages and their respective project file extensions. + /// + public static IReadOnlyList Languages { get; } = + [ + new("C#", ".csproj"), + new("F#", ".fsproj"), + new("VB", ".vbproj"), + ]; /// - /// Provides a list of programming languages supported by the system. + /// Determines if a given project file extension is supported. /// - public static class SupportedProgrammingLanguages + /// The project file extension to check. + /// True if the extension is supported, otherwise false. + public static bool IsProjectExtensionSupported(string extension) { - /// - /// Gets the list of supported programming languages. - /// - /// - /// A list of objects. - /// - /// - /// Use this list to check for supported programming languages when needed. - /// The list is initialized with common programming languages and their respective project file extensions. - /// - public static IReadOnlyList Languages { get; } = new List - { - new SupportedProgrammingLanguage("C#", ".csproj"), - new SupportedProgrammingLanguage("F#", ".fsproj"), - new SupportedProgrammingLanguage("VB", ".vbproj"), - }; - - /// - /// Determines if a given project file extension is supported. - /// - /// The project file extension to check. - /// True if the extension is supported, otherwise false. - public static bool IsProjectExtensionSupported(string extension) - { - return Languages.Any(lang => string.Equals(lang.Extension, extension, StringComparison.InvariantCultureIgnoreCase)); - } + return Languages.Any(lang => string.Equals(lang.Extension, extension, StringComparison.InvariantCultureIgnoreCase)); } } diff --git a/sources/assets/Stride.Core.Assets/TagCollection.cs b/sources/assets/Stride.Core.Assets/TagCollection.cs index 2c78015315..d48185432a 100644 --- a/sources/assets/Stride.Core.Assets/TagCollection.cs +++ b/sources/assets/Stride.Core.Assets/TagCollection.cs @@ -1,15 +1,10 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using Stride.Core; -namespace Stride.Core.Assets -{ - /// - /// A collection of tags. - /// - [DataContract("TagCollection")] - public class TagCollection : List - { - } -} +namespace Stride.Core.Assets; + +/// +/// A collection of tags. +/// +[DataContract("TagCollection")] +public class TagCollection : List; diff --git a/sources/assets/Stride.Core.Assets/TemplateFolder.cs b/sources/assets/Stride.Core.Assets/TemplateFolder.cs index 9cfd945b3b..714ea6cc1c 100644 --- a/sources/assets/Stride.Core.Assets/TemplateFolder.cs +++ b/sources/assets/Stride.Core.Assets/TemplateFolder.cs @@ -1,64 +1,62 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; using System.ComponentModel; -using Stride.Core; + using Stride.Core.IO; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Describes a template folder. +/// +[DataContract("TemplateFolder")] +public sealed class TemplateFolder { /// - /// Describes a template folder. + /// Initializes a new instance of the class. /// - [DataContract("TemplateFolder")] - public sealed class TemplateFolder + public TemplateFolder() : this(null) { - /// - /// Initializes a new instance of the class. - /// - public TemplateFolder() : this(null) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The path. - public TemplateFolder(UDirectory path) - { - Path = path; - Files = new List(); - } + /// + /// Initializes a new instance of the class. + /// + /// The path. + public TemplateFolder(UDirectory path) + { + Path = path; + Files = []; + } - /// - /// Gets or sets the folder relative to the package where templates are available. - /// - /// The path. - [DataMember(10)] - public UDirectory Path { get; set; } + /// + /// Gets or sets the folder relative to the package where templates are available. + /// + /// The path. + [DataMember(10)] + public UDirectory Path { get; set; } - /// - /// Gets or sets the group (used when building a package archive) - /// - /// The group. - [DataMember(20)] - [DefaultValue(null)] - [UPath(UPathRelativeTo.None)] - public UDirectory Group { get; set; } + /// + /// Gets or sets the group (used when building a package archive) + /// + /// The group. + [DataMember(20)] + [DefaultValue(null)] + [UPath(UPathRelativeTo.None)] + public UDirectory? Group { get; set; } - /// - /// Gets or sets the exclude pattern to exclude files from package archive. - /// - /// The exclude. - [DataMember(30)] - [DefaultValue(null)] - public string Exclude { get; set; } + /// + /// Gets or sets the exclude pattern to exclude files from package archive. + /// + /// The exclude. + [DataMember(30)] + [DefaultValue(null)] + public string? Exclude { get; set; } - /// - /// Gets or sets the files. - /// - /// The files. - [DataMember(40)] - public List Files { get; private set; } - } + /// + /// Gets or sets the files. + /// + /// The files. + [DataMember(40)] + public List Files { get; } } diff --git a/sources/assets/Stride.Core.Assets/Templates/ITemplateGenerator.cs b/sources/assets/Stride.Core.Assets/Templates/ITemplateGenerator.cs index 207bc3ffdd..1e9868a253 100644 --- a/sources/assets/Stride.Core.Assets/Templates/ITemplateGenerator.cs +++ b/sources/assets/Stride.Core.Assets/Templates/ITemplateGenerator.cs @@ -1,49 +1,47 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Threading.Tasks; +namespace Stride.Core.Assets.Templates; -namespace Stride.Core.Assets.Templates -{ - public delegate bool RunGeneratorDelegate(); +public delegate bool RunGeneratorDelegate(); +/// +/// The interface to represent a template generator. +/// +public interface ITemplateGenerator +{ /// - /// The interface to represent a template generator. + /// Determines whether this generator is supporting the specified template /// - public interface ITemplateGenerator - { - /// - /// Determines whether this generator is supporting the specified template - /// - /// The template description. - /// true if this generator is supporting the specified template; otherwise, false. - bool IsSupportingTemplate(TemplateDescription templateDescription); - } + /// The template description. + /// true if this generator is supporting the specified template; otherwise, false. + bool IsSupportingTemplate(TemplateDescription templateDescription); +} +/// +/// The interface to represent a template generator. +/// +/// The type of parameters this generator uses. +public interface ITemplateGenerator : ITemplateGenerator + where TParameters : TemplateGeneratorParameters +{ /// - /// The interface to represent a template generator. + /// Prepares this generator with the specified parameters. /// - /// The type of parameters this generator uses. - public interface ITemplateGenerator : ITemplateGenerator where TParameters : TemplateGeneratorParameters - { - /// - /// Prepares this generator with the specified parameters. - /// - /// The parameters for the template generator. - /// This method should be used to verify that the parameters are correct, and to ask user for additional - /// information before running the template. - /// - /// A task completing when the preparation is finished, with the result True if the preparation was successful, false otherwise. - Task PrepareForRun(TParameters parameters); + /// The parameters for the template generator. + /// This method should be used to verify that the parameters are correct, and to ask user for additional + /// information before running the template. + /// + /// A task completing when the preparation is finished, with the result True if the preparation was successful, false otherwise. + Task PrepareForRun(TParameters parameters); - /// - /// Runs the generator with the given parameter. - /// - /// The parameters for the template generator. - /// - /// This method should work in unattended mode and should not ask user for information anymore. - /// - /// True if the generation was successful, false otherwise. - bool Run(TParameters parameters); - } + /// + /// Runs the generator with the given parameter. + /// + /// The parameters for the template generator. + /// + /// This method should work in unattended mode and should not ask user for information anymore. + /// + /// True if the generation was successful, false otherwise. + bool Run(TParameters parameters); } diff --git a/sources/assets/Stride.Core.Assets/Templates/TemplateAssetDescription.cs b/sources/assets/Stride.Core.Assets/Templates/TemplateAssetDescription.cs index 4b8dbe423f..8d2b44e131 100644 --- a/sources/assets/Stride.Core.Assets/Templates/TemplateAssetDescription.cs +++ b/sources/assets/Stride.Core.Assets/Templates/TemplateAssetDescription.cs @@ -1,70 +1,67 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using Stride.Core.Assets.IO; using Stride.Core; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Templates +namespace Stride.Core.Assets.Templates; + +/// +/// A template for creating assets. +/// +[DataContract("TemplateAsset")] +public class TemplateAssetDescription : TemplateDescription { - /// - /// A template for creating assets. - /// - [DataContract("TemplateAsset")] - public class TemplateAssetDescription : TemplateDescription - { - public string AssetTypeName { get; set; } + public string AssetTypeName { get; set; } - public bool RequireName { get; set; } = true; + public bool RequireName { get; set; } = true; - public bool ImportSource { get; set; } + public bool ImportSource { get; set; } - public Type GetAssetType() - { - return AssetRegistry.GetPublicTypes().FirstOrDefault(x => x.Name == AssetTypeName); - } + public Type GetAssetType() + { + return AssetRegistry.GetPublicTypes().First(x => x.Name == AssetTypeName); + } - public FileExtensionCollection GetSupportedExtensions() + public FileExtensionCollection GetSupportedExtensions() + { + var allExtensions = new List(); + var assetType = GetAssetType(); + foreach (var importer in AssetRegistry.RegisteredImporters) { - var allExtensions = new List(); - var assetType = GetAssetType(); - foreach (var importer in AssetRegistry.RegisteredImporters) + if (importer.RootAssetTypes.Contains(assetType)) { - if (importer.RootAssetTypes.Contains(assetType)) - { - allExtensions.Add(importer.SupportedFileExtensions); - } + allExtensions.Add(importer.SupportedFileExtensions); } - var assetTypeName = TypeDescriptorFactory.Default.AttributeRegistry.GetAttribute(assetType)?.Name ?? assetType.Name; - return new FileExtensionCollection($"Source files for {assetTypeName}", string.Join(";", allExtensions)); } + var assetTypeName = TypeDescriptorFactory.Default.AttributeRegistry.GetAttribute(assetType)?.Name ?? assetType.Name; + return new FileExtensionCollection($"Source files for {assetTypeName}", string.Join(";", allExtensions)); } +} - [DataContract("TemplateAssetFactory")] - public class TemplateAssetFactoryDescription : TemplateAssetDescription - { - private IAssetFactory factory; - - public string FactoryTypeName { get; set; } +[DataContract("TemplateAssetFactory")] +public class TemplateAssetFactoryDescription : TemplateAssetDescription +{ + private IAssetFactory? factory; - public IAssetFactory GetFactory() - { - if (factory != null) - return factory; + public string? FactoryTypeName { get; set; } - if (FactoryTypeName != null) - { - factory = AssetRegistry.GetAssetFactory(FactoryTypeName); - } - else - { - var assetType = GetAssetType(); - var factoryType = typeof(DefaultAssetFactory<>).MakeGenericType(assetType); - factory = (IAssetFactory)Activator.CreateInstance(factoryType); - } + public IAssetFactory? GetFactory() + { + if (factory != null) return factory; + + if (FactoryTypeName != null) + { + factory = AssetRegistry.GetAssetFactory(FactoryTypeName); + } + else + { + var assetType = GetAssetType(); + var factoryType = typeof(DefaultAssetFactory<>).MakeGenericType(assetType); + factory = (IAssetFactory)Activator.CreateInstance(factoryType)!; } + return factory; } } diff --git a/sources/assets/Stride.Core.Assets/Templates/TemplateDescription.cs b/sources/assets/Stride.Core.Assets/Templates/TemplateDescription.cs index 07d14322b4..2a2485c0bf 100644 --- a/sources/assets/Stride.Core.Assets/Templates/TemplateDescription.cs +++ b/sources/assets/Stride.Core.Assets/Templates/TemplateDescription.cs @@ -1,127 +1,122 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using Stride.Core; using Stride.Core.Annotations; using Stride.Core.IO; -using Stride.Core.Yaml; -namespace Stride.Core.Assets.Templates +namespace Stride.Core.Assets.Templates; + +/// +/// Description of a template generator that can be displayed in the GameStudio. +/// +[DataContract("Template")] +[NonIdentifiableCollectionItems] +[DebuggerDisplay("Id: {Id}, Name: {Name}")] +public class TemplateDescription : IFileSynchronizable { /// - /// Description of a template generator that can be displayed in the GameStudio. + /// The file extension used when loading/saving this template description. + /// + public const string FileExtension = ".sdtpl"; + + /// + /// Gets or sets the unique identifier. + /// + /// The identifier. + [DataMember(0)] + public Guid Id { get; set; } + + /// + /// Gets or sets the short name of this template + /// + /// The name. + [DataMember(10)] + public string Name { get; set; } + + /// + /// Gets or sets the scope of this template. + /// + /// The context. + [DataMember(15)] + [DefaultValue(TemplateScope.Session)] + public TemplateScope Scope { get; set; } + + /// + /// Gets or sets the order (lower value means higher order) + /// + /// The order. + [DataMember(17)] + [DefaultValue(0)] + public int Order { get; set; } + + /// + /// Gets or sets the group. + /// + /// The group. + [DataMember(20)] + [DefaultValue(null)] + public string? Group { get; set; } + + /// + /// Gets or sets the icon/bitmap. + /// + /// The icon. + [DataMember(30)] + [DefaultValue(null)] + public UFile? Icon { get; set; } + + /// + /// Gets the screenshots. + /// + /// The screenshots. + [DataMember(30)] + public List Screenshots { get; private set; } = []; + + /// + /// Gets or sets the description. + /// + /// The description. + [DataMember(40)] + [DefaultValue(null)] + public string? Description { get; set; } + + /// + /// Gets or sets a longer description. + /// + /// The longer description. + [DataMember(43)] + [DefaultValue(null)] + public string? FullDescription { get; set; } + + /// + /// Gets or set the default name for the output package/library. + /// + /// The default output name. + [DataMember(45)] + public string? DefaultOutputName { get; set; } + + /// + /// Gets or sets the status. + /// + /// The status. + [DataMember(60)] + [DefaultValue(TemplateStatus.None)] + public TemplateStatus Status { get; set; } + + /// + [DataMemberIgnore] + public bool IsDirty { get; set; } + + /// + [DataMemberIgnore] + public UFile FullPath { get; set; } + + /// + /// Gets the directory from where this template was loaded /// - [DataContract("Template")] - [NonIdentifiableCollectionItems] - [DebuggerDisplay("Id: {Id}, Name: {Name}")] - public class TemplateDescription : IFileSynchronizable - { - /// - /// The file extension used when loading/saving this template description. - /// - public const string FileExtension = ".sdtpl"; - - /// - /// Gets or sets the unique identifier. - /// - /// The identifier. - [DataMember(0)] - public Guid Id { get; set; } - - /// - /// Gets or sets the short name of this template - /// - /// The name. - [DataMember(10)] - public string Name { get; set; } - - /// - /// Gets or sets the scope of this template. - /// - /// The context. - [DataMember(15)] - [DefaultValue(TemplateScope.Session)] - public TemplateScope Scope { get; set; } - - /// - /// Gets or sets the order (lower value means higher order) - /// - /// The order. - [DataMember(17)] - [DefaultValue(0)] - public int Order { get; set; } - - /// - /// Gets or sets the group. - /// - /// The group. - [DataMember(20)] - [DefaultValue(null)] - public string Group { get; set; } - - /// - /// Gets or sets the icon/bitmap. - /// - /// The icon. - [DataMember(30)] - [DefaultValue(null)] - public UFile Icon { get; set; } - - /// - /// Gets the screenshots. - /// - /// The screenshots. - [DataMember(30)] - public List Screenshots { get; private set; } = new List(); - - /// - /// Gets or sets the description. - /// - /// The description. - [DataMember(40)] - [DefaultValue(null)] - public string Description { get; set; } - - /// - /// Gets or sets a longer description. - /// - /// The longer description. - [DataMember(43)] - [DefaultValue(null)] - public string FullDescription { get; set; } - - /// - /// Gets or set the default name for the output package/library. - /// - /// The default output name. - [DataMember(45)] - public string DefaultOutputName { get; set; } - - /// - /// Gets or sets the status. - /// - /// The status. - [DataMember(60)] - [DefaultValue(TemplateStatus.None)] - public TemplateStatus Status { get; set; } - - /// - [DataMemberIgnore] - public bool IsDirty { get; set; } - - /// - [DataMemberIgnore] - public UFile FullPath { get; set; } - - /// - /// Gets the directory from where this template was loaded - /// - /// The resource directory. - [DataMemberIgnore] - public UDirectory TemplateDirectory => FullPath?.GetParent(); - } + /// The resource directory. + [DataMemberIgnore] + public UDirectory? TemplateDirectory => FullPath?.GetParent(); } diff --git a/sources/assets/Stride.Core.Assets/Templates/TemplateGeneratorBase.cs b/sources/assets/Stride.Core.Assets/Templates/TemplateGeneratorBase.cs index 15090a5e1c..db193332a9 100644 --- a/sources/assets/Stride.Core.Assets/Templates/TemplateGeneratorBase.cs +++ b/sources/assets/Stride.Core.Assets/Templates/TemplateGeneratorBase.cs @@ -1,22 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Threading.Tasks; -namespace Stride.Core.Assets.Templates +namespace Stride.Core.Assets.Templates; + +/// +/// Base implementation for and . +/// +/// The type of parameters this generator uses. +public abstract class TemplateGeneratorBase : ITemplateGenerator where TParameters : TemplateGeneratorParameters { - /// - /// Base implementation for and . - /// - /// The type of parameters this generator uses. - public abstract class TemplateGeneratorBase : ITemplateGenerator where TParameters : TemplateGeneratorParameters - { - /// - public abstract bool IsSupportingTemplate(TemplateDescription templateDescription); + /// + public abstract bool IsSupportingTemplate(TemplateDescription templateDescription); - /// - public abstract Task PrepareForRun(TParameters parameters); + /// + public abstract Task PrepareForRun(TParameters parameters); - /// - public abstract bool Run(TParameters parameters); - } + /// + public abstract bool Run(TParameters parameters); } diff --git a/sources/assets/Stride.Core.Assets/Templates/TemplateGeneratorContext.cs b/sources/assets/Stride.Core.Assets/Templates/TemplateGeneratorContext.cs index 70788912f1..e1eab7cee2 100644 --- a/sources/assets/Stride.Core.Assets/Templates/TemplateGeneratorContext.cs +++ b/sources/assets/Stride.Core.Assets/Templates/TemplateGeneratorContext.cs @@ -1,46 +1,44 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets.Templates +namespace Stride.Core.Assets.Templates; + +/// +/// Context that will be used to run the tempplate generator. +/// +public sealed class TemplateGeneratorContext { /// - /// Context that will be used to run the tempplate generator. + /// Initializes a new instance of the class. /// - public sealed class TemplateGeneratorContext + /// The session. + public TemplateGeneratorContext(PackageSession session) { - /// - /// Initializes a new instance of the class. - /// - /// The session. - public TemplateGeneratorContext(PackageSession session) - { - if (session == null) throw new ArgumentNullException("session"); - Session = session; - } + ArgumentNullException.ThrowIfNull(session); + Session = session; + } - /// - /// Initializes a new instance of the class. - /// - /// The package. - public TemplateGeneratorContext(Package package) - { - if (package == null) throw new ArgumentNullException("package"); - if (package.Session == null) throw new ArgumentException("Package must be added to an existing PackageSession", "package"); - Package = package; - Session = package.Session; - } + /// + /// Initializes a new instance of the class. + /// + /// The package. + public TemplateGeneratorContext(Package package) + { + ArgumentNullException.ThrowIfNull(package); + if (package.Session == null) throw new ArgumentException("Package must be added to an existing PackageSession", nameof(package)); + Package = package; + Session = package.Session; + } - /// - /// Gets or sets the current session. - /// - /// The session. - public PackageSession Session { get; private set; } + /// + /// Gets or sets the current session. + /// + /// The session. + public PackageSession Session { get; private set; } - /// - /// Gets or sets the current package (may be null) - /// - /// The package. - public Package Package { get; private set; } - } + /// + /// Gets or sets the current package (may be null) + /// + /// The package. + public Package? Package { get; private set; } } diff --git a/sources/assets/Stride.Core.Assets/Templates/TemplateGeneratorParameters.cs b/sources/assets/Stride.Core.Assets/Templates/TemplateGeneratorParameters.cs index 8b9be3ee8b..a2d1811337 100644 --- a/sources/assets/Stride.Core.Assets/Templates/TemplateGeneratorParameters.cs +++ b/sources/assets/Stride.Core.Assets/Templates/TemplateGeneratorParameters.cs @@ -1,238 +1,234 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; + using Stride.Core.Diagnostics; using Stride.Core.IO; -namespace Stride.Core.Assets.Templates +namespace Stride.Core.Assets.Templates; + +public sealed class SessionTemplateGeneratorParameters : TemplateGeneratorParameters { - public sealed class SessionTemplateGeneratorParameters : TemplateGeneratorParameters + /// + /// Gets or sets the current session. + /// + /// The session. + public PackageSession Session { get; set; } + + protected override void ValidateParameters() { - /// - /// Gets or sets the current session. - /// - /// The session. - public PackageSession Session { get; set; } + base.ValidateParameters(); - protected override void ValidateParameters() + if (Description.Scope != TemplateScope.Session) + { + throw new InvalidOperationException($"[{nameof(Description)}.{nameof(Description.Scope)}] must be {TemplateScope.Session} in {GetType().Name}"); + } + if (Session == null) { - base.ValidateParameters(); - - if (Description.Scope != TemplateScope.Session) - { - throw new InvalidOperationException($"[{nameof(Description)}.{nameof(Description.Scope)}] must be {TemplateScope.Session} in {GetType().Name}"); - } - if (Session == null) - { - throw new InvalidOperationException($"[{nameof(Session)}] cannot be null in {GetType().Name}"); - } + throw new InvalidOperationException($"[{nameof(Session)}] cannot be null in {GetType().Name}"); } } +} - public class PackageTemplateGeneratorParameters : TemplateGeneratorParameters +public class PackageTemplateGeneratorParameters : TemplateGeneratorParameters +{ + public PackageTemplateGeneratorParameters() { - public PackageTemplateGeneratorParameters() - { - } + } - public PackageTemplateGeneratorParameters(TemplateGeneratorParameters parameters, Package package) - : base(parameters) + public PackageTemplateGeneratorParameters(TemplateGeneratorParameters parameters, Package package) + : base(parameters) + { + Package = package; + } + /// + /// Gets or sets the package in which to execute this template + /// + /// The package. + public Package Package { get; set; } + + protected override void ValidateParameters() + { + base.ValidateParameters(); + + if (Description.Scope != TemplateScope.Package && GetType() == typeof(PackageTemplateGeneratorParameters)) { - Package = package; + throw new InvalidOperationException($"[{nameof(Description)}.{nameof(Description.Scope)}] must be {TemplateScope.Package} in {GetType().Name}"); } - /// - /// Gets or sets the package in which to execute this template - /// - /// The package. - public Package Package { get; set; } - - protected override void ValidateParameters() + if (Package == null) { - base.ValidateParameters(); - - if (Description.Scope != TemplateScope.Package && GetType() == typeof(PackageTemplateGeneratorParameters)) - { - throw new InvalidOperationException($"[{nameof(Description)}.{nameof(Description.Scope)}] must be {TemplateScope.Package} in {GetType().Name}"); - } - if (Package == null) - { - throw new InvalidOperationException($"[{nameof(Package)}] cannot be null in {GetType().Name}"); - } + throw new InvalidOperationException($"[{nameof(Package)}] cannot be null in {GetType().Name}"); } } +} - public class AssetTemplateGeneratorParameters : PackageTemplateGeneratorParameters +public class AssetTemplateGeneratorParameters : PackageTemplateGeneratorParameters +{ + public AssetTemplateGeneratorParameters(UDirectory targetLocation, IEnumerable? sourceFiles = null) { - public AssetTemplateGeneratorParameters(UDirectory targetLocation, IEnumerable sourceFiles = null) - { - TargetLocation = targetLocation; - SourceFiles = new List(); - if (sourceFiles != null) - SourceFiles.AddRange(sourceFiles); - } + TargetLocation = targetLocation; + SourceFiles = []; + if (sourceFiles != null) + SourceFiles.AddRange(sourceFiles); + } + + public UDirectory TargetLocation { get; } - public UDirectory TargetLocation { get; } + public List SourceFiles { get; } - public List SourceFiles { get; } + /// + /// Indicates whether the session has to be saved after the asset template generator has completed. The default is false. + /// + /// This is an out parameter that asset template generator should set if needed. + public bool RequestSessionSave { get; set; } = false; - /// - /// Indicates whether the session has to be saved after the asset template generator has completed. The default is false. - /// - /// This is an out parameter that asset template generator should set if needed. - public bool RequestSessionSave { get; set; } = false; + protected override void ValidateParameters() + { + base.ValidateParameters(); - protected override void ValidateParameters() + if (Description.Scope != TemplateScope.Asset) { - base.ValidateParameters(); - - if (Description.Scope != TemplateScope.Asset) - { - throw new InvalidOperationException($"[{nameof(Description)}.{nameof(Description.Scope)}] must be {TemplateScope.Asset} in {GetType().Name}"); - } - if (Package == null) - { - throw new InvalidOperationException($"[{nameof(Package)}] cannot be null in {GetType().Name}"); - } + throw new InvalidOperationException($"[{nameof(Description)}.{nameof(Description.Scope)}] must be {TemplateScope.Asset} in {GetType().Name}"); } + if (Package == null) + { + throw new InvalidOperationException($"[{nameof(Package)}] cannot be null in {GetType().Name}"); + } + } +} + +/// +/// Parameters used by +/// +public abstract class TemplateGeneratorParameters +{ + protected TemplateGeneratorParameters() + { + } + + protected TemplateGeneratorParameters(TemplateGeneratorParameters parameters) + { + Name = parameters.Name; + Namespace = parameters.Namespace; + OutputDirectory = parameters.OutputDirectory; + Description = parameters.Description; + Logger = parameters.Logger; + parameters.Tags.CopyTo(ref Tags); + Id = parameters.Id; } /// - /// Parameters used by + /// Gets or sets the project name used to generate the template. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the project ID used to generate the template. + /// + /// The ID + public Guid Id { get; set; } + + /// + /// Gets or sets the default namespace of this project. /// - public abstract class TemplateGeneratorParameters + /// The namespace. + public string Namespace { get; set; } + + /// + /// Gets or sets the output directory. + /// + /// The output directory. + public UDirectory OutputDirectory { get; set; } + + /// + /// The actual template description. + /// + public TemplateDescription Description { get; set; } + + /// + /// Specifies if the template generator should run in unattended mode (no UI). + /// + public bool Unattended { get; set; } + + /// + /// Gets or sets the logger. + /// + /// The logger. + public LoggerResult Logger { get; set; } + + /// + /// Contains extra properties that can be consumed by template generators. + /// + public PropertyContainer Tags; + + /// + /// Validates this instance (all fields must be setup) + /// + public void Validate() { - protected TemplateGeneratorParameters() - { - } + ValidateParameters(); + } - protected TemplateGeneratorParameters(TemplateGeneratorParameters parameters) + /// + /// Gets the tag corresponding to the given property key. This will fail if key doesn't exist. + /// + /// The generic type of the property key. + /// The property key for which to retrieve the value. + /// The value of the tag corresponding to the given property key. + /// Tag not found in template generator parameters. + public T GetTag(PropertyKey key) + { + if (!Tags.TryGetValue(key, out var result)) { - Name = parameters.Name; - Namespace = parameters.Namespace; - OutputDirectory = parameters.OutputDirectory; - Description = parameters.Description; - Logger = parameters.Logger; - parameters.Tags.CopyTo(ref Tags); - Id = parameters.Id; + throw new KeyNotFoundException("Tag not found in template generator parameters"); } + return result; + } - /// - /// Gets or sets the project name used to generate the template. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the project ID used to generate the template. - /// - /// The ID - public Guid Id { get; set; } - - /// - /// Gets or sets the default namespace of this project. - /// - /// The namespace. - public string Namespace { get; set; } - - /// - /// Gets or sets the output directory. - /// - /// The output directory. - public UDirectory OutputDirectory { get; set; } - - /// - /// The actual template description. - /// - public TemplateDescription Description { get; set; } - - /// - /// Specifies if the template generator should run in unattended mode (no UI). - /// - public bool Unattended { get; set; } - - /// - /// Gets or sets the logger. - /// - /// The logger. - public LoggerResult Logger { get; set; } - - /// - /// Contains extra properties that can be consumed by template generators. - /// - public PropertyContainer Tags; - - /// - /// Validates this instance (all fields must be setup) - /// - public void Validate() - { - ValidateParameters(); - } + /// + /// Gets the tag corresponding to the given property key if available, otherwise returns default value. + /// + /// The generic type of the property key. + /// The property key for which to retrieve the value. + /// The value of the tag corresponding to the given property key if available, the default value of the property key otherwise. + public T? TryGetTag(PropertyKey key) + { + return Tags.Get(key); + } - /// - /// Gets the tag corresponding to the given property key. This will fail if key doesn't exist. - /// - /// The generic type of the property key. - /// The property key for which to retrieve the value. - /// The value of the tag corresponding to the given property key. - /// Tag not found in template generator parameters. - public T GetTag(PropertyKey key) - { - T result; - if (!Tags.TryGetValue(key, out result)) - { - throw new KeyNotFoundException("Tag not found in template generator parameters"); - } - return result; - } + /// + /// + /// + /// + /// + /// + public bool HasTag(PropertyKey key) + { + return Tags.ContainsKey(key); + } - /// - /// Gets the tag corresponding to the given property key if available, otherwise returns default value. - /// - /// The generic type of the property key. - /// The property key for which to retrieve the value. - /// The value of the tag corresponding to the given property key if available, the default value of the property key otherwise. - public T TryGetTag(PropertyKey key) + public void SetTag(PropertyKey key, T value) + { + Tags[key] = value; + } + + protected virtual void ValidateParameters() + { + if (Name == null) { - return Tags.Get(key); + throw new InvalidOperationException($"[{nameof(Name)}] cannot be null in {GetType().Name}"); } - - /// - /// - /// - /// - /// - /// - public bool HasTag(PropertyKey key) + if (OutputDirectory == null && Description.Scope == TemplateScope.Session) { - return Tags.ContainsKey(key); + throw new InvalidOperationException($"[{nameof(OutputDirectory)}] cannot be null in {GetType().Name}"); } - - public void SetTag(PropertyKey key, T value) + if (Description == null) { - Tags[key] = value; + throw new InvalidOperationException($"[{nameof(Description)}] cannot be null in {GetType().Name}"); } - - protected virtual void ValidateParameters() + if (Logger == null) { - if (Name == null) - { - throw new InvalidOperationException($"[{nameof(Name)}] cannot be null in {GetType().Name}"); - } - if (OutputDirectory == null && Description.Scope == TemplateScope.Session) - { - throw new InvalidOperationException($"[{nameof(OutputDirectory)}] cannot be null in {GetType().Name}"); - } - if (Description == null) - { - throw new InvalidOperationException($"[{nameof(Description)}] cannot be null in {GetType().Name}"); - } - if (Logger == null) - { - throw new InvalidOperationException($"[{nameof(Logger)}] cannot be null in {GetType().Name}"); - } + throw new InvalidOperationException($"[{nameof(Logger)}] cannot be null in {GetType().Name}"); } } } diff --git a/sources/assets/Stride.Core.Assets/Templates/TemplateManager.cs b/sources/assets/Stride.Core.Assets/Templates/TemplateManager.cs index e86a56b864..18dc0798cb 100644 --- a/sources/assets/Stride.Core.Assets/Templates/TemplateManager.cs +++ b/sources/assets/Stride.Core.Assets/Templates/TemplateManager.cs @@ -1,146 +1,143 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -namespace Stride.Core.Assets.Templates +namespace Stride.Core.Assets.Templates; + +/// +/// Handle templates for creating , +/// +public class TemplateManager { + private static readonly object ThisLock = new(); + private static readonly List Generators = []; + private static readonly PackageCollection ExtraPackages = []; + + public static void RegisterPackage(Package package) + { + ExtraPackages.Add(package); + } + /// - /// Handle templates for creating , + /// Registers the specified factory. /// - public class TemplateManager + /// The factory. + /// factory + public static void Register(ITemplateGenerator generator) { - private static readonly object ThisLock = new object(); - private static readonly List Generators = new List(); - private static readonly PackageCollection ExtraPackages = new PackageCollection(); + ArgumentNullException.ThrowIfNull(generator); - public static void RegisterPackage(Package package) + lock (ThisLock) { - ExtraPackages.Add(package); - } - - /// - /// Registers the specified factory. - /// - /// The factory. - /// factory - public static void Register(ITemplateGenerator generator) - { - if (generator == null) throw new ArgumentNullException(nameof(generator)); - - lock (ThisLock) + if (!Generators.Contains(generator)) { - if (!Generators.Contains(generator)) - { - Generators.Add(generator); - } + Generators.Add(generator); } } + } - /// - /// Unregisters the specified factory. - /// - /// The factory. - /// factory - public static void Unregister(ITemplateGenerator generator) - { - if (generator == null) throw new ArgumentNullException(nameof(generator)); - - lock (ThisLock) - { - Generators.Remove(generator); - } - } + /// + /// Unregisters the specified factory. + /// + /// The factory. + /// factory + public static void Unregister(ITemplateGenerator generator) + { + ArgumentNullException.ThrowIfNull(generator); - /// - /// Finds all template descriptions. - /// - /// A sequence containing all registered template descriptions. - public static IEnumerable FindTemplates(PackageSession session = null) + lock (ThisLock) { - var packages = session?.Packages.Concat(ExtraPackages).Distinct(DistinctPackagePathComparer.Default) ?? ExtraPackages; - // TODO this will not work if the same package has different versions - return packages.SelectMany(package => package.Templates).OrderBy(tpl => tpl.Order).ThenBy(tpl => tpl.Name).ToList(); + Generators.Remove(generator); } + } - /// - /// Finds template descriptions that match the given scope. - /// - /// A sequence containing all registered template descriptions that match the given scope. - public static IEnumerable FindTemplates(TemplateScope scope, PackageSession session = null) - { + /// + /// Finds all template descriptions. + /// + /// A sequence containing all registered template descriptions. + public static IEnumerable FindTemplates(PackageSession? session = null) + { + var packages = session?.Packages.Concat(ExtraPackages).Distinct(DistinctPackagePathComparer.Default) ?? ExtraPackages; + // TODO this will not work if the same package has different versions + return [.. packages.SelectMany(package => package.Templates).OrderBy(tpl => tpl.Order).ThenBy(tpl => tpl.Name)]; + } - return FindTemplates(session).Where(x => x.Scope == scope); - } + /// + /// Finds template descriptions that match the given scope. + /// + /// A sequence containing all registered template descriptions that match the given scope. + public static IEnumerable FindTemplates(TemplateScope scope, PackageSession? session = null) + { - /// - /// Finds a template generator supporting the specified template description - /// - /// The description. - /// A template generator supporting the specified description or null if not found. - public static ITemplateGenerator FindTemplateGenerator(TemplateDescription description) where TParameters : TemplateGeneratorParameters + return FindTemplates(session).Where(x => x.Scope == scope); + } + + /// + /// Finds a template generator supporting the specified template description + /// + /// The description. + /// A template generator supporting the specified description or null if not found. + public static ITemplateGenerator? FindTemplateGenerator(TemplateDescription description) + where TParameters : TemplateGeneratorParameters + { + ArgumentNullException.ThrowIfNull(description); + lock (ThisLock) { - if (description == null) throw new ArgumentNullException(nameof(description)); - lock (ThisLock) + // From most recently registered to older + for (int i = Generators.Count - 1; i >= 0; i--) { - // From most recently registered to older - for (int i = Generators.Count - 1; i >= 0; i--) + if (Generators[i] is ITemplateGenerator generator && + generator.IsSupportingTemplate(description)) { - var generator = Generators[i] as ITemplateGenerator; - if (generator != null && generator.IsSupportingTemplate(description)) - { - return generator; - } + return generator; } } - return null; } - /// - /// Finds a template generator supporting the specified template description - /// - /// The parameters. - /// A template generator supporting the specified description or null if not found. - public static ITemplateGenerator FindTemplateGenerator(TParameters parameters) where TParameters : TemplateGeneratorParameters + return null; + } + /// + /// Finds a template generator supporting the specified template description + /// + /// The parameters. + /// A template generator supporting the specified description or null if not found. + public static ITemplateGenerator? FindTemplateGenerator(TParameters parameters) + where TParameters : TemplateGeneratorParameters + { + ArgumentNullException.ThrowIfNull(parameters); + lock (ThisLock) { - if (parameters == null) throw new ArgumentNullException(nameof(parameters)); - lock (ThisLock) + // From most recently registered to older + for (int i = Generators.Count - 1; i >= 0; i--) { - // From most recently registered to older - for (int i = Generators.Count - 1; i >= 0; i--) + if (Generators[i] is ITemplateGenerator generator && + generator.IsSupportingTemplate(parameters.Description)) { - var generator = Generators[i] as ITemplateGenerator; - if (generator != null && generator.IsSupportingTemplate(parameters.Description)) - { - return generator; - } + return generator; } } - return null; } + return null; + } - private class DistinctPackagePathComparer : IEqualityComparer + private class DistinctPackagePathComparer : IEqualityComparer + { + private static DistinctPackagePathComparer? defaultInstance; + public static DistinctPackagePathComparer Default { - private static DistinctPackagePathComparer defaultInstance; - public static DistinctPackagePathComparer Default + get { - get - { - if (defaultInstance == null) - defaultInstance = new DistinctPackagePathComparer(); - return defaultInstance; - } + defaultInstance ??= new DistinctPackagePathComparer(); + return defaultInstance; } + } - public bool Equals(Package x, Package y) - { - return x.FullPath == y.FullPath; - } + public bool Equals(Package? x, Package? y) + { + return x?.FullPath == y?.FullPath; + } - public int GetHashCode(Package obj) - { - return obj.FullPath.GetHashCode(); - } + public int GetHashCode(Package obj) + { + return obj.FullPath.GetHashCode(); } } } diff --git a/sources/assets/Stride.Core.Assets/Templates/TemplateSampleDescription.cs b/sources/assets/Stride.Core.Assets/Templates/TemplateSampleDescription.cs index 88422cdbc5..b9e97ee8af 100644 --- a/sources/assets/Stride.Core.Assets/Templates/TemplateSampleDescription.cs +++ b/sources/assets/Stride.Core.Assets/Templates/TemplateSampleDescription.cs @@ -3,24 +3,21 @@ using System.ComponentModel; -using Stride.Core; +namespace Stride.Core.Assets.Templates; -namespace Stride.Core.Assets.Templates +/// +/// A template for using an existing package as a template, expecting a to be accessible +/// from with the same name as this template. +/// +[DataContract("TemplateSample")] +public class TemplateSampleDescription : TemplateDescription { /// - /// A template for using an existing package as a template, expecting a to be accessible - /// from with the same name as this template. + /// Gets or sets the name of the pattern used to substitute files and content. If null, use the + /// . /// - [DataContract("TemplateSample")] - public class TemplateSampleDescription : TemplateDescription - { - /// - /// Gets or sets the name of the pattern used to substitute files and content. If null, use the - /// . - /// - /// The name of the pattern. - [DataMember(70)] - [DefaultValue(null)] - public string PatternName { get; set; } - } + /// The name of the pattern. + [DataMember(70)] + [DefaultValue(null)] + public string? PatternName { get; set; } } diff --git a/sources/assets/Stride.Core.Assets/Templates/TemplateScope.cs b/sources/assets/Stride.Core.Assets/Templates/TemplateScope.cs index 92adc68800..968eb732e7 100644 --- a/sources/assets/Stride.Core.Assets/Templates/TemplateScope.cs +++ b/sources/assets/Stride.Core.Assets/Templates/TemplateScope.cs @@ -1,30 +1,28 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; -namespace Stride.Core.Assets.Templates +namespace Stride.Core.Assets.Templates; + +/// +/// Describes if a template is supporting a particular context +/// +[DataContract("TemplateScope")] +public enum TemplateScope { + // TODO We could use flags instead + /// - /// Describes if a template is supporting a particular context + /// The template can be applied to an existing . /// - [DataContract("TemplateScope")] - public enum TemplateScope - { - // TODO We could use flags instead + Session, - /// - /// The template can be applied to an existing . - /// - Session, - - /// - /// The template can be applied to an existing . - /// - Package, + /// + /// The template can be applied to an existing . + /// + Package, - /// - /// The template can be applied to certain types of Assets . - /// - Asset - } + /// + /// The template can be applied to certain types of Assets . + /// + Asset } diff --git a/sources/assets/Stride.Core.Assets/Templates/TemplateStatus.cs b/sources/assets/Stride.Core.Assets/Templates/TemplateStatus.cs index a6b2206ba7..912d8f6d5c 100644 --- a/sources/assets/Stride.Core.Assets/Templates/TemplateStatus.cs +++ b/sources/assets/Stride.Core.Assets/Templates/TemplateStatus.cs @@ -1,25 +1,25 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets.Templates + +namespace Stride.Core.Assets.Templates; + +/// +/// Status of the template +/// +public enum TemplateStatus { /// - /// Status of the template + /// Nothing particular. /// - public enum TemplateStatus - { - /// - /// Nothing particular. - /// - None, + None, - /// - /// The template is new - /// - New, + /// + /// The template is new + /// + New, - /// - /// The template has been updated - /// - Updated - } + /// + /// The template has been updated + /// + Updated } diff --git a/sources/assets/Stride.Core.Assets/TextAccessors/DefaultTextAccessor.cs b/sources/assets/Stride.Core.Assets/TextAccessors/DefaultTextAccessor.cs index 6d747b8cec..d4d18d1c3f 100644 --- a/sources/assets/Stride.Core.Assets/TextAccessors/DefaultTextAccessor.cs +++ b/sources/assets/Stride.Core.Assets/TextAccessors/DefaultTextAccessor.cs @@ -1,70 +1,63 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.IO; using System.Text; -using System.Threading.Tasks; -namespace Stride.Core.Assets.TextAccessors +namespace Stride.Core.Assets.TextAccessors; + +public class DefaultTextAccessor : ITextAccessor { - public class DefaultTextAccessor : ITextAccessor + private string? text; + + public string? FilePath { get; internal set; } + + /// + public string Get() { - private string text; + return text ??= (FilePath != null ? LoadFromFile() : FilePath) ?? ""; + } - public string FilePath { get; internal set; } + /// + public void Set(string? value) + { + text = value; + } - /// - public string Get() + public async Task Save(Stream stream) + { + if (text != null) { - return text ?? (text = (FilePath != null ? LoadFromFile() : FilePath) ?? ""); + using var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, true); + await streamWriter.WriteAsync(text); } - - /// - public void Set(string value) + else if (FilePath != null) { - text = value; + using var inputStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, bufferSize: 4096, useAsync: true); + await inputStream.CopyToAsync(stream); } + } - public async Task Save(Stream stream) - { - if (text != null) - { - using (var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - await streamWriter.WriteAsync(text); - } - } - else if (FilePath != null) - { - using (var inputStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, bufferSize: 4096, useAsync: true)) - { - await inputStream.CopyToAsync(stream); - } - } - } + public ISerializableTextAccessor GetSerializableVersion() + { + // Still not loaded? + if (text == null && FilePath != null) + return new FileTextAccessor { FilePath = FilePath }; - public ISerializableTextAccessor GetSerializableVersion() - { - // Still not loaded? - if (text == null && FilePath != null) - return new FileTextAccessor { FilePath = FilePath }; + return new StringTextAccessor { Text = text }; + } - return new StringTextAccessor { Text = text }; - } + private string? LoadFromFile() + { + if (FilePath == null) + return null; - private string LoadFromFile() + try { - if (FilePath == null) - return null; - - try - { - return File.ReadAllText(FilePath); - } - catch - { - return null; - } + return File.ReadAllText(FilePath); + } + catch + { + return null; } } } diff --git a/sources/assets/Stride.Core.Assets/TextAccessors/FileTextAccessor.cs b/sources/assets/Stride.Core.Assets/TextAccessors/FileTextAccessor.cs index 572d1c7fee..b273062a5a 100644 --- a/sources/assets/Stride.Core.Assets/TextAccessors/FileTextAccessor.cs +++ b/sources/assets/Stride.Core.Assets/TextAccessors/FileTextAccessor.cs @@ -1,18 +1,16 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; -namespace Stride.Core.Assets.TextAccessors +namespace Stride.Core.Assets.TextAccessors; + +[DataContract] +public class FileTextAccessor : ISerializableTextAccessor { - [DataContract] - public class FileTextAccessor : ISerializableTextAccessor - { - [DataMember] - public string FilePath { get; set; } + [DataMember] + public string? FilePath { get; set; } - public ITextAccessor Create() - { - return new DefaultTextAccessor { FilePath = FilePath }; - } + public ITextAccessor Create() + { + return new DefaultTextAccessor { FilePath = FilePath }; } } diff --git a/sources/assets/Stride.Core.Assets/TextAccessors/ISerializableTextAccessor.cs b/sources/assets/Stride.Core.Assets/TextAccessors/ISerializableTextAccessor.cs index 6b78d551ce..1bbde510e0 100644 --- a/sources/assets/Stride.Core.Assets/TextAccessors/ISerializableTextAccessor.cs +++ b/sources/assets/Stride.Core.Assets/TextAccessors/ISerializableTextAccessor.cs @@ -1,10 +1,9 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets.TextAccessors +namespace Stride.Core.Assets.TextAccessors; + +public interface ISerializableTextAccessor { - public interface ISerializableTextAccessor - { - ITextAccessor Create(); - } + ITextAccessor Create(); } diff --git a/sources/assets/Stride.Core.Assets/TextAccessors/ITextAccessor.cs b/sources/assets/Stride.Core.Assets/TextAccessors/ITextAccessor.cs index 76cf758a00..74a7e0c142 100644 --- a/sources/assets/Stride.Core.Assets/TextAccessors/ITextAccessor.cs +++ b/sources/assets/Stride.Core.Assets/TextAccessors/ITextAccessor.cs @@ -1,31 +1,27 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.IO; -using System.Threading.Tasks; +namespace Stride.Core.Assets.TextAccessors; -namespace Stride.Core.Assets.TextAccessors +public interface ITextAccessor { - public interface ITextAccessor - { - /// - /// Gets the underlying text. - /// - /// - string Get(); + /// + /// Gets the underlying text. + /// + /// + string Get(); - /// - /// Sets the underlying text. - /// - /// - void Set(string value); + /// + /// Sets the underlying text. + /// + /// + void Set(string value); - /// - /// Writes the text to the given . - /// - /// - Task Save(Stream streamWriter); + /// + /// Writes the text to the given . + /// + /// + Task Save(Stream streamWriter); - ISerializableTextAccessor GetSerializableVersion(); - } + ISerializableTextAccessor GetSerializableVersion(); } diff --git a/sources/assets/Stride.Core.Assets/TextAccessors/StringTextAccessor.cs b/sources/assets/Stride.Core.Assets/TextAccessors/StringTextAccessor.cs index e7dd29cada..193245ccda 100644 --- a/sources/assets/Stride.Core.Assets/TextAccessors/StringTextAccessor.cs +++ b/sources/assets/Stride.Core.Assets/TextAccessors/StringTextAccessor.cs @@ -1,21 +1,18 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; +namespace Stride.Core.Assets.TextAccessors; -namespace Stride.Core.Assets.TextAccessors +[DataContract] +public class StringTextAccessor : ISerializableTextAccessor { - [DataContract] - public class StringTextAccessor : ISerializableTextAccessor - { - [DataMember] - public string Text { get; set; } + [DataMember] + public string? Text { get; set; } - public ITextAccessor Create() - { - var result = new DefaultTextAccessor(); - result.Set(Text); - return result; - } + public ITextAccessor Create() + { + var result = new DefaultTextAccessor(); + result.Set(Text); + return result; } } diff --git a/sources/assets/Stride.Core.Assets/Tracking/AssetSourceTracker.cs b/sources/assets/Stride.Core.Assets/Tracking/AssetSourceTracker.cs index 31c0407ba0..b947a6367e 100644 --- a/sources/assets/Stride.Core.Assets/Tracking/AssetSourceTracker.cs +++ b/sources/assets/Stride.Core.Assets/Tracking/AssetSourceTracker.cs @@ -1,527 +1,522 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.Collections.Specialized; -using System.Linq; -using System.Threading; using System.Threading.Tasks.Dataflow; using Stride.Core.Assets.Analysis; using Stride.Core.Diagnostics; using Stride.Core.IO; using Stride.Core.Storage; -namespace Stride.Core.Assets.Tracking +namespace Stride.Core.Assets.Tracking; + +// TODO: Inherit from AssetTracker +public sealed class AssetSourceTracker : IDisposable { - // TODO: Inherit from AssetTracker - public sealed class AssetSourceTracker : IDisposable + private readonly PackageSession session; + internal readonly object ThisLock = new(); + internal readonly HashSet Packages; + private readonly Dictionary trackedAssets = []; + // Objects used to track directories + internal DirectoryWatcher? DirectoryWatcher; + private readonly Dictionary> mapSourceFilesToAssets = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary currentHashes = new(StringComparer.OrdinalIgnoreCase); + private readonly List fileEvents = []; + private readonly ManualResetEvent threadWatcherEvent; + private readonly CancellationTokenSource tokenSourceForImportHash; + private Thread? fileEventThreadHandler; + private int trackingSleepTime; + private bool isDisposed; + private bool isDisposing; + private bool isTrackingPaused; + private bool isSaving; + + /// + /// Initializes a new instance of the class. + /// + /// The session. + /// session + internal AssetSourceTracker(PackageSession session) { - private readonly PackageSession session; - internal readonly object ThisLock = new object(); - internal readonly HashSet Packages; - private readonly Dictionary trackedAssets = new Dictionary(); - // Objects used to track directories - internal DirectoryWatcher DirectoryWatcher; - private readonly Dictionary> mapSourceFilesToAssets = new Dictionary>(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary currentHashes = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly List fileEvents = new List(); - private readonly ManualResetEvent threadWatcherEvent; - private readonly CancellationTokenSource tokenSourceForImportHash; - private Thread fileEventThreadHandler; - private int trackingSleepTime; - private bool isDisposed; - private bool isDisposing; - private bool isTrackingPaused; - private bool isSaving; - - /// - /// Initializes a new instance of the class. - /// - /// The session. - /// session - internal AssetSourceTracker(PackageSession session) - { - if (session == null) throw new ArgumentNullException(nameof(session)); - this.session = session; - this.session.Packages.CollectionChanged += Packages_CollectionChanged; - session.AssetDirtyChanged += Session_AssetDirtyChanged; - Packages = new HashSet(); - TrackingSleepTime = 100; - tokenSourceForImportHash = new CancellationTokenSource(); - threadWatcherEvent = new ManualResetEvent(false); - } + ArgumentNullException.ThrowIfNull(session); + this.session = session; + this.session.Packages.CollectionChanged += Packages_CollectionChanged; + session.AssetDirtyChanged += Session_AssetDirtyChanged; + Packages = []; + TrackingSleepTime = 100; + tokenSourceForImportHash = new CancellationTokenSource(); + threadWatcherEvent = new ManualResetEvent(false); + } - /// - /// Gets a source dataflow block in which notifications that a source file has changed are pushed. - /// - public BroadcastBlock> SourceFileChanged { get; } = new BroadcastBlock>(null); + /// + /// Gets a source dataflow block in which notifications that a source file has changed are pushed. + /// + public BroadcastBlock> SourceFileChanged { get; } = new BroadcastBlock>(null); - /// - /// Gets or sets a value indicating whether this instance should track file disk changed events. Default is false - /// - /// true if this instance should track file disk changed events; otherwise, false. - public bool EnableTracking + /// + /// Gets or sets a value indicating whether this instance should track file disk changed events. Default is false + /// + /// true if this instance should track file disk changed events; otherwise, false. + public bool EnableTracking + { + get + { + return fileEventThreadHandler != null; + } + set { - get + if (isDisposed) { - return fileEventThreadHandler != null; + throw new InvalidOperationException("Cannot enable tracking when this instance is disposed"); } - set - { - if (isDisposed) - { - throw new InvalidOperationException("Cannot enable tracking when this instance is disposed"); - } - lock (ThisLock) + lock (ThisLock) + { + if (value) { - if (value) + bool activateTracking = false; + if (DirectoryWatcher == null) { - bool activateTracking = false; - if (DirectoryWatcher == null) - { - DirectoryWatcher = new DirectoryWatcher(); - DirectoryWatcher.Modified += directoryWatcher_Modified; - activateTracking = true; - } + DirectoryWatcher = new DirectoryWatcher(); + DirectoryWatcher.Modified += DirectoryWatcher_Modified; + activateTracking = true; + } - if (fileEventThreadHandler == null) - { - fileEventThreadHandler = new Thread(SafeAction.Wrap(RunChangeWatcher)) { IsBackground = true, Name = "RunChangeWatcher thread" }; - fileEventThreadHandler.Start(); - } + if (fileEventThreadHandler == null) + { + fileEventThreadHandler = new Thread(SafeAction.Wrap(RunChangeWatcher)) { IsBackground = true, Name = "RunChangeWatcher thread" }; + fileEventThreadHandler.Start(); + } - if (activateTracking) - { - ActivateTracking(); - } + if (activateTracking) + { + ActivateTracking(); + } - foreach (var package in session.Packages) - { - TrackPackage(package); - } + foreach (var package in session.Packages) + { + TrackPackage(package); } - else + } + else + { + if (DirectoryWatcher != null) { - if (DirectoryWatcher != null) - { - DirectoryWatcher.Dispose(); - DirectoryWatcher = null; - } + DirectoryWatcher.Dispose(); + DirectoryWatcher = null; + } - if (fileEventThreadHandler != null) - { - threadWatcherEvent.Set(); - fileEventThreadHandler.Join(); - fileEventThreadHandler = null; - } + if (fileEventThreadHandler != null) + { + threadWatcherEvent.Set(); + fileEventThreadHandler.Join(); + fileEventThreadHandler = null; + } - foreach (var package in session.Packages) - { - UnTrackPackage(package); - } + foreach (var package in session.Packages) + { + UnTrackPackage(package); } } } } + } - /// - /// Gets or sets a value indicating whether this instance is processing tracking events or it is paused. Default is false. - /// - /// true if this instance is tracking paused; otherwise, false. - public bool IsTrackingPaused + /// + /// Gets or sets a value indicating whether this instance is processing tracking events or it is paused. Default is false. + /// + /// true if this instance is tracking paused; otherwise, false. + public bool IsTrackingPaused + { + get { - get - { - return isTrackingPaused; - } - set - { - if (!EnableTracking) - return; + return isTrackingPaused; + } + set + { + if (!EnableTracking) + return; - isTrackingPaused = value; - } + isTrackingPaused = value; } + } - /// - /// Gets or sets the number of ms the file tracker should sleep before checking changes. Default is 1000ms. - /// - /// The tracking sleep time. - public int TrackingSleepTime + /// + /// Gets or sets the number of ms the file tracker should sleep before checking changes. Default is 1000ms. + /// + /// The tracking sleep time. + public int TrackingSleepTime + { + get { - get - { - return trackingSleepTime; - } - set + return trackingSleepTime; + } + set + { + if (value <= 0) { - if (value <= 0) - { - throw new ArgumentOutOfRangeException(nameof(value), @"TrackingSleepTime must be > 0"); - } - trackingSleepTime = value; + throw new ArgumentOutOfRangeException(nameof(value), "TrackingSleepTime must be > 0"); } + trackingSleepTime = value; } + } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - if (isDisposed) - return; + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (isDisposed) + return; - isDisposing = true; - tokenSourceForImportHash.Cancel(); - EnableTracking = false; // Will terminate the thread if running + isDisposing = true; + tokenSourceForImportHash.Cancel(); + EnableTracking = false; // Will terminate the thread if running - DirectoryWatcher?.Dispose(); - isDisposed = true; - } + DirectoryWatcher?.Dispose(); + isDisposed = true; + } - public void BeginSavingSession() - { - isSaving = true; - } + public void BeginSavingSession() + { + isSaving = true; + } - public void EndSavingSession() - { - isSaving = false; - } + public void EndSavingSession() + { + isSaving = false; + } - public ObjectId GetCurrentHash(UFile file) - { - ObjectId result; - currentHashes.TryGetValue(file, out result); - return result; - } + public ObjectId GetCurrentHash(UFile file) + { + currentHashes.TryGetValue(file, out var result); + return result; + } - /// - /// This method is called when a package needs to be tracked - /// - /// The package to track. - private void TrackPackage(Package package) + /// + /// This method is called when a package needs to be tracked + /// + /// The package to track. + private void TrackPackage(Package package) + { + lock (ThisLock) { - lock (ThisLock) - { - if (Packages.Contains(package)) - return; + if (Packages.Contains(package)) + return; - Packages.Add(package); + Packages.Add(package); - foreach (var asset in package.Assets) + foreach (var asset in package.Assets) + { + // If the package is cancelled, don't try to do anything + // A cancellation means that the package session will be destroyed + if (isDisposing) { - // If the package is cancelled, don't try to do anything - // A cancellation means that the package session will be destroyed - if (isDisposing) - { - return; - } - - TrackAsset(asset.Id); + return; } - package.Assets.CollectionChanged += Assets_CollectionChanged; + TrackAsset(asset.Id); } + + package.Assets.CollectionChanged += Assets_CollectionChanged; } + } - /// - /// This method is called when a package needs to be un-tracked - /// - /// The package to un-track. - private void UnTrackPackage(Package package) + /// + /// This method is called when a package needs to be un-tracked + /// + /// The package to un-track. + private void UnTrackPackage(Package package) + { + lock (ThisLock) { - lock (ThisLock) - { - if (!Packages.Contains(package)) - return; - - package.Assets.CollectionChanged -= Assets_CollectionChanged; + if (!Packages.Contains(package)) + return; - foreach (var asset in package.Assets) - { - UnTrackAsset(asset.Id); - } + package.Assets.CollectionChanged -= Assets_CollectionChanged; - Packages.Remove(package); + foreach (var asset in package.Assets) + { + UnTrackAsset(asset.Id); } + + Packages.Remove(package); } + } - /// - /// This method is called when an asset needs to be tracked - /// - /// AssetDependencies. - private void TrackAsset(AssetId assetId) + /// + /// This method is called when an asset needs to be tracked + /// + /// AssetDependencies. + private void TrackAsset(AssetId assetId) + { + lock (ThisLock) { - lock (ThisLock) - { - if (trackedAssets.ContainsKey(assetId)) - return; + if (trackedAssets.ContainsKey(assetId)) + return; - // TODO provide an optimized version of TrackAsset method - // taking directly a well known asset (loaded from a Package...etc.) - // to avoid session.FindAsset - var assetItem = session.FindAsset(assetId); - if (assetItem == null) - return; + // TODO provide an optimized version of TrackAsset method + // taking directly a well known asset (loaded from a Package...etc.) + // to avoid session.FindAsset + var assetItem = session.FindAsset(assetId); + if (assetItem == null) + return; - // Clone the asset before using it in this instance to make sure that - // we have some kind of immutable state - // TODO: This is not handling shadow registry + // Clone the asset before using it in this instance to make sure that + // we have some kind of immutable state + // TODO: This is not handling shadow registry - // No need to clone assets from readonly package - var clonedAsset = assetItem.Package.IsSystem ? assetItem.Asset : AssetCloner.Clone(assetItem.Asset); - var trackedAsset = new TrackedAsset(this, assetItem.Asset, clonedAsset); + // No need to clone assets from readonly package + var clonedAsset = assetItem.Package?.IsSystem == true ? assetItem.Asset : AssetCloner.Clone(assetItem.Asset); + var trackedAsset = new TrackedAsset(this, assetItem.Asset, clonedAsset); - // Adds to global list - trackedAssets.Add(assetId, trackedAsset); - } + // Adds to global list + trackedAssets.Add(assetId, trackedAsset); } + } - private void UnTrackAsset(AssetId assetId) + private void UnTrackAsset(AssetId assetId) + { + lock (ThisLock) { - lock (ThisLock) - { - TrackedAsset trackedAsset; - if (!trackedAssets.TryGetValue(assetId, out trackedAsset)) - return; + if (!trackedAssets.TryGetValue(assetId, out var trackedAsset)) + return; - trackedAsset.Dispose(); + trackedAsset.Dispose(); - // Remove from global list - trackedAssets.Remove(assetId); - } + // Remove from global list + trackedAssets.Remove(assetId); } + } - internal void TrackAssetImportInput(AssetId assetId, string inputPath) + internal void TrackAssetImportInput(AssetId assetId, string inputPath) + { + lock (ThisLock) { - lock (ThisLock) + if (!mapSourceFilesToAssets.TryGetValue(inputPath, out var assetsTrackedByPath)) { - HashSet assetsTrackedByPath; - if (!mapSourceFilesToAssets.TryGetValue(inputPath, out assetsTrackedByPath)) - { - assetsTrackedByPath = new HashSet(); - mapSourceFilesToAssets.Add(inputPath, assetsTrackedByPath); - DirectoryWatcher?.Track(inputPath); - } - assetsTrackedByPath.Add(assetId); + assetsTrackedByPath = []; + mapSourceFilesToAssets.Add(inputPath, assetsTrackedByPath); + DirectoryWatcher?.Track(inputPath); } - - // We will always issue a compute of the hash in order to verify SourceHash haven't changed - FileVersionManager.Instance.ComputeFileHashAsync(inputPath, SourceImportFileHashCallback, tokenSourceForImportHash.Token); + assetsTrackedByPath.Add(assetId); } - internal void UnTrackAssetImportInput(AssetId assetId, string inputPath) + // We will always issue a compute of the hash in order to verify SourceHash haven't changed + FileVersionManager.Instance.ComputeFileHashAsync(inputPath, SourceImportFileHashCallback, tokenSourceForImportHash.Token); + } + + internal void UnTrackAssetImportInput(AssetId assetId, string inputPath) + { + lock (ThisLock) { - lock (ThisLock) + if (mapSourceFilesToAssets.TryGetValue(inputPath, out var assetsTrackedByPath)) { - HashSet assetsTrackedByPath; - if (mapSourceFilesToAssets.TryGetValue(inputPath, out assetsTrackedByPath)) + assetsTrackedByPath.Remove(assetId); + if (assetsTrackedByPath.Count == 0) { - assetsTrackedByPath.Remove(assetId); - if (assetsTrackedByPath.Count == 0) - { - mapSourceFilesToAssets.Remove(inputPath); - DirectoryWatcher?.UnTrack(inputPath); - } + mapSourceFilesToAssets.Remove(inputPath); + DirectoryWatcher?.UnTrack(inputPath); } } } + } - private void ActivateTracking() + private void ActivateTracking() + { + List files; + lock (ThisLock) { - List files; - lock (ThisLock) - { - files = mapSourceFilesToAssets.Keys.ToList(); - } - foreach (var inputPath in files) - { - DirectoryWatcher.Track(inputPath); - FileVersionManager.Instance.ComputeFileHashAsync(inputPath, SourceImportFileHashCallback, tokenSourceForImportHash.Token); - } + files = [.. mapSourceFilesToAssets.Keys]; + } + foreach (var inputPath in files) + { + DirectoryWatcher?.Track(inputPath); + FileVersionManager.Instance.ComputeFileHashAsync(inputPath, SourceImportFileHashCallback, tokenSourceForImportHash.Token); } + } - private void Session_AssetDirtyChanged(AssetItem asset, bool oldValue, bool newValue) + private void Session_AssetDirtyChanged(AssetItem asset, bool oldValue, bool newValue) + { + // Don't update the source tracker while saving + if (!isSaving) { - // Don't update the source tracker while saving - if (!isSaving) + lock (ThisLock) { - lock (ThisLock) + if (trackedAssets.TryGetValue(asset.Id, out var trackedAsset)) { - TrackedAsset trackedAsset; - if (trackedAssets.TryGetValue(asset.Id, out trackedAsset)) - { - trackedAsset.NotifyAssetChanged(); - } + trackedAsset.NotifyAssetChanged(); } } } + } - private void Packages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void Packages_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + lock (ThisLock) { - lock (ThisLock) + if (EnableTracking) { - if (EnableTracking) + switch (e.Action) { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - TrackPackage((Package)e.NewItems[0]); - break; - case NotifyCollectionChangedAction.Remove: - UnTrackPackage((Package)e.OldItems[0]); - break; - - case NotifyCollectionChangedAction.Replace: - foreach (var oldPackage in e.OldItems.OfType()) - { - UnTrackPackage(oldPackage); - } - - foreach (var package in session.Packages) - { + case NotifyCollectionChangedAction.Add: + { + if (e.NewItems?[0] is Package package) TrackPackage(package); - } - break; - } + } + break; + case NotifyCollectionChangedAction.Remove: + { + if (e.OldItems?[0] is Package package) + UnTrackPackage(package); + } + break; + + case NotifyCollectionChangedAction.Replace: + foreach (var oldPackage in e.OldItems?.OfType() ?? []) + { + UnTrackPackage(oldPackage); + } + + foreach (var package in session.Packages) + { + TrackPackage(package); + } + break; } } } + } - private void Assets_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void Assets_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + lock (ThisLock) { - lock (ThisLock) + if (EnableTracking) { - if (EnableTracking) + switch (e.Action) { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - foreach (AssetItem assetItem in e.NewItems) + case NotifyCollectionChangedAction.Add: + foreach (var assetItem in e.NewItems?.OfType() ?? []) + { + TrackAsset(assetItem.Id); + } + break; + case NotifyCollectionChangedAction.Remove: + foreach (var assetItem in e.OldItems?.OfType() ?? []) + { + UnTrackAsset(assetItem.Id); + } + break; + case NotifyCollectionChangedAction.Reset: + { + //var assets = (PackageAssetCollection)sender; + var allAssetIds = new HashSet(session.Packages.SelectMany(x => x.Assets).Select(x => x.Id)); + var assetsToUntrack = new List(); + foreach (var asset in trackedAssets) { - TrackAsset(assetItem.Id); + // Untrack assets that are currently tracked, but absent from the package session. + if (!allAssetIds.Contains(asset.Key)) + assetsToUntrack.Add(asset.Key); } - break; - case NotifyCollectionChangedAction.Remove: - foreach (AssetItem assetItem in e.OldItems) + foreach (var asset in assetsToUntrack) { - UnTrackAsset(assetItem.Id); + UnTrackAsset(asset); } - break; - case NotifyCollectionChangedAction.Reset: + // Track assets that are present in the package session, but not currently in the list of tracked assets. + allAssetIds.ExceptWith(trackedAssets.Keys); + foreach (var asset in allAssetIds) { - //var assets = (PackageAssetCollection)sender; - var allAssetIds = new HashSet(session.Packages.SelectMany(x => x.Assets).Select(x => x.Id)); - var assetsToUntrack = new List(); - foreach (var asset in trackedAssets) - { - // Untrack assets that are currently tracked, but absent from the package session. - if (!allAssetIds.Contains(asset.Key)) - assetsToUntrack.Add(asset.Key); - } - foreach (var asset in assetsToUntrack) - { - UnTrackAsset(asset); - } - // Track assets that are present in the package session, but not currently in the list of tracked assets. - allAssetIds.ExceptWith(trackedAssets.Keys); - foreach (var asset in allAssetIds) - { - TrackAsset(asset); - } + TrackAsset(asset); } - break; - default: - throw new NotSupportedException("This operation is not supported by the source tracker."); - } + } + break; + default: + throw new NotSupportedException("This operation is not supported by the source tracker."); } } } + } - private void directoryWatcher_Modified(object sender, FileEvent e) - { - // If tracking is not enabled, don't bother to track files on disk - if (!EnableTracking) - return; + private void DirectoryWatcher_Modified(object? sender, FileEvent e) + { + // If tracking is not enabled, don't bother to track files on disk + if (!EnableTracking) + return; - // Store only the most recent events - lock (fileEvents) - { - fileEvents.Add(e); - } + // Store only the most recent events + lock (fileEvents) + { + fileEvents.Add(e); } + } - /// - /// This method is running in a separate thread and process file events received from - /// in order to generate the appropriate list of . - /// - private void RunChangeWatcher() + /// + /// This method is running in a separate thread and process file events received from + /// in order to generate the appropriate list of . + /// + private void RunChangeWatcher() + { + while (!threadWatcherEvent.WaitOne(TrackingSleepTime)) { - while (!threadWatcherEvent.WaitOne(TrackingSleepTime)) - { - // Use a working copy in order to limit the locking - var fileEventsWorkingCopy = new List(); + // Use a working copy in order to limit the locking + var fileEventsWorkingCopy = new List(); - lock (fileEvents) - { - fileEventsWorkingCopy.AddRange(fileEvents); - fileEvents.Clear(); - } + lock (fileEvents) + { + fileEventsWorkingCopy.AddRange(fileEvents); + fileEvents.Clear(); + } - if (fileEventsWorkingCopy.Count == 0 || isTrackingPaused || isSaving) - continue; + if (fileEventsWorkingCopy.Count == 0 || isTrackingPaused || isSaving) + continue; - // If this an asset belonging to a package - lock (ThisLock) + // If this an asset belonging to a package + lock (ThisLock) + { + // File event + foreach (var fileEvent in fileEventsWorkingCopy) { - // File event - foreach (var fileEvent in fileEventsWorkingCopy) + var file = new UFile(fileEvent.FullPath); + if (mapSourceFilesToAssets.ContainsKey(file.FullPath)) { - var file = new UFile(fileEvent.FullPath); - if (mapSourceFilesToAssets.ContainsKey(file.FullPath)) - { - // Prepare the hash of the import file in advance for later re-import - FileVersionManager.Instance.ComputeFileHashAsync(file.FullPath, SourceImportFileHashCallback, tokenSourceForImportHash.Token); - } + // Prepare the hash of the import file in advance for later re-import + FileVersionManager.Instance.ComputeFileHashAsync(file.FullPath, SourceImportFileHashCallback, tokenSourceForImportHash.Token); } } } } + } - /// - /// This callback is receiving hash calculated from asset source file. If the source hash is changing from what - /// we had previously stored, we can emit a event. - /// - /// The source file. - /// The object identifier hash calculated from this source file. - private void SourceImportFileHashCallback(UFile sourceFile, ObjectId hash) + /// + /// This callback is receiving hash calculated from asset source file. If the source hash is changing from what + /// we had previously stored, we can emit a event. + /// + /// The source file. + /// The object identifier hash calculated from this source file. + private void SourceImportFileHashCallback(UFile sourceFile, ObjectId hash) + { + lock (ThisLock) { - lock (ThisLock) - { - HashSet items; - if (!mapSourceFilesToAssets.TryGetValue(sourceFile, out items)) - return; + if (!mapSourceFilesToAssets.TryGetValue(sourceFile, out var items)) + return; - currentHashes[sourceFile] = hash; + currentHashes[sourceFile] = hash; - var message = new List(); - foreach (var itemId in items) - { - TrackedAsset trackedAsset; - if (trackedAssets.TryGetValue(itemId, out trackedAsset)) - { - bool needUpdate = trackedAsset.DependsOnSource(sourceFile); - var data = new SourceFileChangedData(SourceFileChangeType.SourceFile, trackedAsset.AssetId, new[] { sourceFile }, needUpdate); - message.Add(data); - } - } - if (message.Count > 0) + var message = new List(); + foreach (var itemId in items) + { + if (trackedAssets.TryGetValue(itemId, out var trackedAsset)) { - SourceFileChanged.Post(message); + bool needUpdate = trackedAsset.DependsOnSource(sourceFile); + var data = new SourceFileChangedData(SourceFileChangeType.SourceFile, trackedAsset.AssetId, [sourceFile], needUpdate); + message.Add(data); } } + if (message.Count > 0) + { + SourceFileChanged.Post(message); + } } } } diff --git a/sources/assets/Stride.Core.Assets/Tracking/SourceFileChangeType.cs b/sources/assets/Stride.Core.Assets/Tracking/SourceFileChangeType.cs index a518f956d3..381ad5f3bc 100644 --- a/sources/assets/Stride.Core.Assets/Tracking/SourceFileChangeType.cs +++ b/sources/assets/Stride.Core.Assets/Tracking/SourceFileChangeType.cs @@ -1,19 +1,19 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Assets.Tracking + +namespace Stride.Core.Assets.Tracking; + +/// +/// Describes a change related to the source files used by an asset. +/// +public enum SourceFileChangeType { /// - /// Describes a change related to the source files used by an asset. + /// The change occurred in an asset that now has a different list of source files. /// - public enum SourceFileChangeType - { - /// - /// The change occurred in an asset that now has a different list of source files. - /// - Asset, - /// - /// The change occurred in an source file that has been modified externally. - /// - SourceFile - } + Asset, + /// + /// The change occurred in an source file that has been modified externally. + /// + SourceFile } diff --git a/sources/assets/Stride.Core.Assets/Tracking/SourceFileChangedData.cs b/sources/assets/Stride.Core.Assets/Tracking/SourceFileChangedData.cs index 6a9bec50c0..a510a917e3 100644 --- a/sources/assets/Stride.Core.Assets/Tracking/SourceFileChangedData.cs +++ b/sources/assets/Stride.Core.Assets/Tracking/SourceFileChangedData.cs @@ -1,49 +1,47 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using Stride.Core.IO; -namespace Stride.Core.Assets.Tracking +namespace Stride.Core.Assets.Tracking; + +/// +/// Data structure for the block. +/// +public struct SourceFileChangedData { /// - /// Data structure for the block. + /// Initializes a new instance of the structure. /// - public struct SourceFileChangedData + /// The type of change that occurred. + /// The id of the asset affected by this change. + /// The list of files that changed. + /// Indicate whether the asset needs to be updated from its sources due to this change. + public SourceFileChangedData(SourceFileChangeType type, AssetId assetId, IReadOnlyList files, bool needUpdate) { - /// - /// Initializes a new instance of the structure. - /// - /// The type of change that occurred. - /// The id of the asset affected by this change. - /// The list of files that changed. - /// Indicate whether the asset needs to be updated from its sources due to this change. - public SourceFileChangedData(SourceFileChangeType type, AssetId assetId, IReadOnlyList files, bool needUpdate) - { - Type = type; - AssetId = assetId; - Files = files; - NeedUpdate = needUpdate; - } + Type = type; + AssetId = assetId; + Files = files; + NeedUpdate = needUpdate; + } - /// - /// Gets the type of change that occurred. - /// - public SourceFileChangeType Type { get; } + /// + /// Gets the type of change that occurred. + /// + public SourceFileChangeType Type { get; } - /// - /// Gets the id of the asset affected by this change. - /// - public AssetId AssetId { get; } + /// + /// Gets the id of the asset affected by this change. + /// + public AssetId AssetId { get; } - /// - /// Gets the list of files that changed - /// - public IReadOnlyList Files { get; } + /// + /// Gets the list of files that changed + /// + public IReadOnlyList Files { get; } - /// - /// Gets whether the asset needs to be updated from its sources due to this change. - /// - public bool NeedUpdate { get; } - } + /// + /// Gets whether the asset needs to be updated from its sources due to this change. + /// + public bool NeedUpdate { get; } } diff --git a/sources/assets/Stride.Core.Assets/Tracking/SourceFilesCollector.cs b/sources/assets/Stride.Core.Assets/Tracking/SourceFilesCollector.cs index 3d2307e4c5..f974a3570e 100644 --- a/sources/assets/Stride.Core.Assets/Tracking/SourceFilesCollector.cs +++ b/sources/assets/Stride.Core.Assets/Tracking/SourceFilesCollector.cs @@ -1,98 +1,96 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Linq; + using Stride.Core.Assets.Visitors; using Stride.Core.IO; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Tracking +namespace Stride.Core.Assets.Tracking; + +public class SourceFilesCollector : AssetVisitorBase { - public class SourceFilesCollector : AssetVisitorBase - { - private Dictionary sourceFiles; - private HashSet compilationInputFiles; - private Dictionary sourceMembers; + private Dictionary? sourceFiles; + private HashSet? compilationInputFiles; + private Dictionary? sourceMembers; - public Dictionary GetSourceFiles(Asset asset) - { - sourceFiles = new Dictionary(); - Visit(asset); - var result = sourceFiles; - sourceFiles = null; - return result; - } + public Dictionary GetSourceFiles(Asset asset) + { + sourceFiles = []; + Visit(asset); + var result = sourceFiles; + sourceFiles = null; + return result; + } - public HashSet GetCompilationInputFiles(Asset asset) - { - compilationInputFiles = new HashSet(); - Visit(asset); - var result = compilationInputFiles; - compilationInputFiles = null; - return result; - } + public HashSet GetCompilationInputFiles(Asset asset) + { + compilationInputFiles = []; + Visit(asset); + var result = compilationInputFiles; + compilationInputFiles = null; + return result; + } - public Dictionary GetSourceMembers(Asset asset) - { - sourceMembers = new Dictionary(); - Visit(asset); - var result = sourceMembers; - sourceMembers = null; - return result; - } + public Dictionary GetSourceMembers(Asset asset) + { + sourceMembers = []; + Visit(asset); + var result = sourceMembers; + sourceMembers = null; + return result; + } - public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object value) + public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object? value) + { + if (sourceFiles is not null) { - if (sourceFiles != null) + if (member.Type == typeof(UFile) && value is not null) { - if (member.Type == typeof(UFile) && value != null) + var file = (UFile)value; + if (!string.IsNullOrWhiteSpace(file.ToString())) { - var file = (UFile)value; - if (!string.IsNullOrWhiteSpace(file.ToString())) + var attribute = member.GetCustomAttributes(true).SingleOrDefault(); + if (attribute is not null) { - var attribute = member.GetCustomAttributes(true).SingleOrDefault(); - if (attribute != null) + if (!sourceFiles.ContainsKey(file)) + { + sourceFiles.Add(file, attribute.UpdateAssetIfChanged); + } + else if (attribute.UpdateAssetIfChanged) { - if (!sourceFiles.ContainsKey(file)) - { - sourceFiles.Add(file, attribute.UpdateAssetIfChanged); - } - else if (attribute.UpdateAssetIfChanged) - { - // If the file has already been collected, just update whether it should update the asset when changed - sourceFiles[file] = true; - } + // If the file has already been collected, just update whether it should update the asset when changed + sourceFiles[file] = true; } } } } - if (compilationInputFiles != null) + } + if (compilationInputFiles is not null) + { + if (member.Type == typeof(UFile) && value is not null) { - if (member.Type == typeof(UFile) && value != null) + var file = (UFile)value; + if (!string.IsNullOrWhiteSpace(file.ToString())) { - var file = (UFile)value; - if (!string.IsNullOrWhiteSpace(file.ToString())) + var attribute = member.GetCustomAttributes(true).SingleOrDefault(); + if (attribute is not null && !attribute.Optional) { - var attribute = member.GetCustomAttributes(true).SingleOrDefault(); - if (attribute != null && !attribute.Optional) - { - compilationInputFiles.Add(file); - } + compilationInputFiles.Add(file); } } } - if (sourceMembers != null) + } + if (sourceMembers is not null) + { + if (member.Type == typeof(UFile)) { - if (member.Type == typeof(UFile)) + var attribute = member.GetCustomAttributes(true).SingleOrDefault(); + if (attribute is not null) { - var attribute = member.GetCustomAttributes(true).SingleOrDefault(); - if (attribute != null) - { - sourceMembers[CurrentPath.Clone()] = value as UFile; - } + sourceMembers[CurrentPath.Clone()] = value as UFile; } } - base.VisitObjectMember(container, containerDescriptor, member, value); } + base.VisitObjectMember(container, containerDescriptor, member, value); } } diff --git a/sources/assets/Stride.Core.Assets/Tracking/SourceHashesHelper.cs b/sources/assets/Stride.Core.Assets/Tracking/SourceHashesHelper.cs index f17de90cf3..f8f337f660 100644 --- a/sources/assets/Stride.Core.Assets/Tracking/SourceHashesHelper.cs +++ b/sources/assets/Stride.Core.Assets/Tracking/SourceHashesHelper.cs @@ -1,173 +1,167 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.Reflection; -using Stride.Core; using Stride.Core.Extensions; using Stride.Core.IO; using Stride.Core.Reflection; using Stride.Core.Storage; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Assets.Tracking +namespace Stride.Core.Assets.Tracking; + +public static class SourceHashesHelper { - public static class SourceHashesHelper - { - public const string MemberName = "~SourceHashes"; + public const string MemberName = "~SourceHashes"; - private static readonly ShadowObjectPropertyKey AbsoluteSourceHashesKey = new ShadowObjectPropertyKey(new object(), true); - private static readonly ShadowObjectPropertyKey RelativeSourceHashesKey = new ShadowObjectPropertyKey(new object(), true); - private static readonly object LockObj = new object(); + private static readonly ShadowObjectPropertyKey AbsoluteSourceHashesKey = new(new object(), true); + private static readonly ShadowObjectPropertyKey RelativeSourceHashesKey = new(new object(), true); + private static readonly object LockObj = new(); - public static void UpdateHashes(Asset asset, IReadOnlyDictionary newHashes) + public static void UpdateHashes(Asset asset, IReadOnlyDictionary newHashes) + { + lock (LockObj) { - lock (LockObj) - { - var hashes = GetOrCreate(asset, AbsoluteSourceHashesKey); - hashes.Clear(); - newHashes.ForEach(x => hashes.Add(x.Key, x.Value)); - } + var hashes = GetOrCreate(asset, AbsoluteSourceHashesKey); + hashes.Clear(); + newHashes.ForEach(x => hashes.Add(x.Key, x.Value)); } + } - public static Dictionary GetAllHashes(Asset asset) - { - var hashes = TryGet(asset, AbsoluteSourceHashesKey); - var result = new Dictionary(); - hashes?.ForEach(x => result.Add(x.Key, x.Value)); - return result; - } + public static Dictionary GetAllHashes(Asset asset) + { + var hashes = TryGet(asset, AbsoluteSourceHashesKey); + var result = new Dictionary(); + hashes?.ForEach(x => result.Add(x.Key, x.Value)); + return result; + } - private static Dictionary TryGet(Asset asset, ShadowObjectPropertyKey key) + private static Dictionary? TryGet(Asset asset, ShadowObjectPropertyKey key) + { + var shadow = ShadowObject.GetOrCreate(asset); + if (shadow.TryGetValue(key, out var obj)) { - var shadow = ShadowObject.GetOrCreate(asset); - object obj; - if (shadow.TryGetValue(key, out obj)) - { - return (Dictionary)obj; - } - return null; + return (Dictionary)obj; } + return null; + } - private static Dictionary GetOrCreate(Asset asset, ShadowObjectPropertyKey key) + private static Dictionary GetOrCreate(Asset asset, ShadowObjectPropertyKey key) + { + var shadow = ShadowObject.GetOrCreate(asset); + if (shadow.TryGetValue(key, out var obj)) { - var shadow = ShadowObject.GetOrCreate(asset); - object obj; - if (shadow.TryGetValue(key, out obj)) - { - return (Dictionary)obj; - } - var hashes = new Dictionary(); - shadow[key] = hashes; - return hashes; + return (Dictionary)obj; } + var hashes = new Dictionary(); + shadow[key] = hashes; + return hashes; + } - private static void SetDictionary(Asset asset, ShadowObjectPropertyKey key, Dictionary dictionary) - { - var shadow = ShadowObject.GetOrCreate(asset); - shadow[key] = dictionary; - } + private static void SetDictionary(Asset asset, ShadowObjectPropertyKey key, Dictionary dictionary) + { + var shadow = ShadowObject.GetOrCreate(asset); + shadow[key] = dictionary; + } - internal static void AddSourceHashesMember(ObjectDescriptor objectDescriptor, List memberDescriptors) - { - var type = objectDescriptor.Type; - if (!typeof(Asset).IsAssignableFrom(type)) - return; + internal static void AddSourceHashesMember(ObjectDescriptor objectDescriptor, List memberDescriptors) + { + var type = objectDescriptor.Type; + if (!typeof(Asset).IsAssignableFrom(type)) + return; + + memberDescriptors.Add(SourceHashesDynamicMember.Default); + } - memberDescriptors.Add(SourceHashesDynamicMember.Default); + internal static void UpdateUPaths(Asset asset, UDirectory assetFolder, UPathType convertUPathTo) + { + switch (convertUPathTo) + { + case UPathType.Absolute: + ConvertUPaths(asset, RelativeSourceHashesKey, AbsoluteSourceHashesKey, x => UPath.Combine(assetFolder, x)); + break; + case UPathType.Relative: + ConvertUPaths(asset, AbsoluteSourceHashesKey, RelativeSourceHashesKey, x => x.MakeRelative(assetFolder)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(convertUPathTo), convertUPathTo, null); } + } - internal static void UpdateUPaths(Asset asset, UDirectory assetFolder, UPathType convertUPathTo) + private static void ConvertUPaths(Asset asset, ShadowObjectPropertyKey from, ShadowObjectPropertyKey to, Func converter) + { + var fromHashes = TryGet(asset, from); + if (fromHashes != null) { - switch (convertUPathTo) + var toHashes = GetOrCreate(asset, to); + toHashes.Clear(); + + foreach (var fromHAsh in fromHashes) { - case UPathType.Absolute: - ConvertUPaths(asset, RelativeSourceHashesKey, AbsoluteSourceHashesKey, x => UPath.Combine(assetFolder, x)); - break; - case UPathType.Relative: - ConvertUPaths(asset, AbsoluteSourceHashesKey, RelativeSourceHashesKey, x => x.MakeRelative(assetFolder)); - break; - default: - throw new ArgumentOutOfRangeException(nameof(convertUPathTo), convertUPathTo, null); + var path = converter(fromHAsh.Key); + toHashes[path] = fromHAsh.Value; } } + } - private static void ConvertUPaths(Asset asset, ShadowObjectPropertyKey from, ShadowObjectPropertyKey to, Func converter) + internal class SourceHashesDynamicMember : DynamicMemberDescriptorBase + { + public const int DefaultOrder = int.MaxValue; + + public static readonly SourceHashesDynamicMember Default = new() { - var fromHashes = TryGet(asset, from); - if (fromHashes != null) + ShouldSerialize = static (x, parentTypeMemberDesc) => { - var toHashes = GetOrCreate(asset, to); - toHashes.Clear(); - - foreach (var fromHAsh in fromHashes) - { - var path = converter(fromHAsh.Key); - toHashes[path] = fromHAsh.Value; - } + return x is Asset asset && TryGet(asset, AbsoluteSourceHashesKey)?.Count > 0; } - } + }; - internal class SourceHashesDynamicMember : DynamicMemberDescriptorBase + static SourceHashesDynamicMember() { - public const int DefaultOrder = int.MaxValue; + // Safety check, we need to have the asset id deserialized before the source hashes + var idOrder = typeof(Asset).GetProperty(nameof(Asset.Id))!.GetCustomAttribute()!.Order; + if (idOrder >= DefaultOrder) + throw new InvalidOperationException("The order of the Asset.Id property must be lower than the order of the SourceHashes property."); + } - public static readonly SourceHashesDynamicMember Default = new SourceHashesDynamicMember - { - ShouldSerialize = (x, parentTypeMemberDesc) => - { - var asset = x as Asset; - return asset != null && TryGet(asset, AbsoluteSourceHashesKey)?.Count > 0; - } - }; - - static SourceHashesDynamicMember() - { - // Safety check, we need to have the asset id deserialized before the source hashes - var idOrder = typeof(Asset).GetProperty(nameof(Asset.Id)).GetCustomAttribute().Order; - if (idOrder >= DefaultOrder) - throw new InvalidOperationException("The order of the Asset.Id property must be lower than the order of the SourceHashes property."); - } + public SourceHashesDynamicMember() : base(MemberName, typeof(Dictionary), typeof(Asset)) + { + Order = DefaultOrder; + } - public SourceHashesDynamicMember() : base(MemberName, typeof(Dictionary), typeof(Asset)) - { - Order = DefaultOrder; - } + public override bool HasSet => true; - public override bool HasSet => true; + public override object? Get(object thisObject) + { + var asset = (Asset)thisObject; + // Id can be empty when the asset is contained in a base. + if (asset.Id == AssetId.Empty) + return null; - public override object Get(object thisObject) + lock (LockObj) { - var asset = (Asset)thisObject; - // Id can be empty when the asset is contained in a base. - if (asset.Id == AssetId.Empty) + var value = TryGet(asset, RelativeSourceHashesKey); + if (value == null || value.Count == 0) return null; - lock (LockObj) - { - var value = TryGet(asset, RelativeSourceHashesKey); - if (value == null || value.Count == 0) - return null; - - return value; - } + return value; } + } + + public override void Set(object thisObject, object value) + { + if (value == null) + return; - public override void Set(object thisObject, object value) + var sourceHashes = (Dictionary)value; + var asset = (Asset)thisObject; + // Id can be empty when the asset is contained in a base. + if (asset.Id == AssetId.Empty) + return; + + lock (LockObj) { - if (value == null) - return; - - var sourceHashes = (Dictionary)value; - var asset = (Asset)thisObject; - // Id can be empty when the asset is contained in a base. - if (asset.Id == AssetId.Empty) - return; - - lock (LockObj) - { - SetDictionary(asset, RelativeSourceHashesKey, sourceHashes); - } + SetDictionary(asset, RelativeSourceHashesKey, sourceHashes); } } } diff --git a/sources/assets/Stride.Core.Assets/Tracking/TrackedAsset.cs b/sources/assets/Stride.Core.Assets/Tracking/TrackedAsset.cs index 51fee846fa..d5b900f00b 100644 --- a/sources/assets/Stride.Core.Assets/Tracking/TrackedAsset.cs +++ b/sources/assets/Stride.Core.Assets/Tracking/TrackedAsset.cs @@ -1,112 +1,108 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using System.Threading.Tasks.Dataflow; using Stride.Core.IO; -namespace Stride.Core.Assets.Tracking +namespace Stride.Core.Assets.Tracking; + +/// +/// Represents a single asset which has source files currently being tracked for changes. +/// +internal class TrackedAsset : IDisposable { + private readonly AssetSourceTracker tracker; + private readonly Asset sessionAsset; + private Dictionary sourceFiles = []; + private Asset clonedAsset; + /// - /// Represents a single asset which has source files currently being tracked for changes. + /// Initializes a new instance of the class. /// - internal class TrackedAsset : IDisposable + /// The source tracker managing this object. + /// The actual asset in the current session. + /// A clone of the actual asset. If the actual asset is read-only, it is acceptable to use it instead of a clone. + public TrackedAsset(AssetSourceTracker tracker, Asset sessionAsset, Asset clonedAsset) { - private readonly AssetSourceTracker tracker; - private readonly Asset sessionAsset; - private Dictionary sourceFiles = new Dictionary(); - private Asset clonedAsset; - - /// - /// Initializes a new instance of the class. - /// - /// The source tracker managing this object. - /// The actual asset in the current session. - /// A clone of the actual asset. If the actual asset is read-only, it is acceptable to use it instead of a clone. - public TrackedAsset(AssetSourceTracker tracker, Asset sessionAsset, Asset clonedAsset) - { - if (tracker == null) throw new ArgumentNullException(nameof(tracker)); - if (sessionAsset == null) throw new ArgumentNullException(nameof(sessionAsset)); - this.tracker = tracker; - this.sessionAsset = sessionAsset; - this.clonedAsset = clonedAsset; - UpdateAssetImportPathsTracked(true); - } + ArgumentNullException.ThrowIfNull(tracker); + ArgumentNullException.ThrowIfNull(sessionAsset); + this.tracker = tracker; + this.sessionAsset = sessionAsset; + this.clonedAsset = clonedAsset; + UpdateAssetImportPathsTracked(true); + } - /// - /// Gets the id of this asset. - /// - internal AssetId AssetId => sessionAsset.Id; + /// + /// Gets the id of this asset. + /// + internal AssetId AssetId => sessionAsset.Id; - /// - public void Dispose() - { - // Track asset import paths - UpdateAssetImportPathsTracked(false); - } + /// + public void Dispose() + { + // Track asset import paths + UpdateAssetImportPathsTracked(false); + } - /// - /// Notifies this object that the asset has been modified. - /// - /// This method will trigger the re-evaluation of properties containing the path to a source file. - public void NotifyAssetChanged() - { - clonedAsset = AssetCloner.Clone(sessionAsset); - UpdateAssetImportPathsTracked(true); - } + /// + /// Notifies this object that the asset has been modified. + /// + /// This method will trigger the re-evaluation of properties containing the path to a source file. + public void NotifyAssetChanged() + { + clonedAsset = AssetCloner.Clone(sessionAsset); + UpdateAssetImportPathsTracked(true); + } - public bool DependsOnSource(UFile sourceFile) - { - bool result; - sourceFiles.TryGetValue(sourceFile, out result); - return result; - } + public bool DependsOnSource(UFile sourceFile) + { + sourceFiles.TryGetValue(sourceFile, out var result); + return result; + } - private void UpdateAssetImportPathsTracked(bool isTracking) + private void UpdateAssetImportPathsTracked(bool isTracking) + { + if (isTracking) { - if (isTracking) + var collector = new SourceFilesCollector(); + var newSourceFiles = collector.GetSourceFiles(clonedAsset); + bool changed = false; + bool needUpdate = false; + // Untrack previous paths + foreach (var sourceFile in sourceFiles) { - var collector = new SourceFilesCollector(); - var newSourceFiles = collector.GetSourceFiles(clonedAsset); - bool changed = false; - bool needUpdate = false; - // Untrack previous paths - foreach (var sourceFile in sourceFiles) + if (!newSourceFiles.ContainsKey(sourceFile.Key)) { - if (!newSourceFiles.ContainsKey(sourceFile.Key)) - { - tracker.UnTrackAssetImportInput(AssetId, sourceFile.Key); - needUpdate = needUpdate || sourceFile.Value; - changed = true; - } + tracker.UnTrackAssetImportInput(AssetId, sourceFile.Key); + needUpdate = needUpdate || sourceFile.Value; + changed = true; } + } - // Track new paths - foreach (var sourceFile in newSourceFiles) + // Track new paths + foreach (var sourceFile in newSourceFiles) + { + if (!sourceFiles.ContainsKey(sourceFile.Key)) { - if (!sourceFiles.ContainsKey(sourceFile.Key)) - { - tracker.TrackAssetImportInput(AssetId, sourceFile.Key); - needUpdate = needUpdate || sourceFile.Value; - changed = true; - } + tracker.TrackAssetImportInput(AssetId, sourceFile.Key); + needUpdate = needUpdate || sourceFile.Value; + changed = true; } + } - sourceFiles = newSourceFiles; + sourceFiles = newSourceFiles; - if (changed) - { - var files = sourceFiles.Where(x => x.Value).Select(x => x.Key).ToList(); - tracker.SourceFileChanged.Post(new[] { new SourceFileChangedData(SourceFileChangeType.Asset, AssetId, files, needUpdate) }); - } + if (changed) + { + var files = sourceFiles.Where(x => x.Value).Select(x => x.Key).ToList(); + tracker.SourceFileChanged.Post([new SourceFileChangedData(SourceFileChangeType.Asset, AssetId, files, needUpdate)]); } - else + } + else + { + foreach (var sourceFile in sourceFiles.Keys) { - foreach (var sourceFile in sourceFiles.Keys) - { - tracker.UnTrackAssetImportInput(AssetId, sourceFile); - } + tracker.UnTrackAssetImportInput(AssetId, sourceFile); } } } diff --git a/sources/assets/Stride.Core.Assets/UPathAttribute.cs b/sources/assets/Stride.Core.Assets/UPathAttribute.cs index c805a895cd..bbcf068fa5 100644 --- a/sources/assets/Stride.Core.Assets/UPathAttribute.cs +++ b/sources/assets/Stride.Core.Assets/UPathAttribute.cs @@ -1,52 +1,43 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Assets -{ +namespace Stride.Core.Assets; + +/// +/// Enum UPathRelativeTo +/// +public enum UPathRelativeTo +{ /// - /// Enum UPathRelativeTo + /// The UPath is stored as-is without post-processing /// - public enum UPathRelativeTo - { - /// - /// The UPath is stored as-is without post-processing - /// - None, + None, - /// - /// The UPath is stored in relative mode when storing on the disk and relative to the current package. - /// - Package, - } + /// + /// The UPath is stored in relative mode when storing on the disk and relative to the current package. + /// + Package, +} +/// +/// Specifies how to normalize a UPath stored in a class after loading/saving an asset. +/// +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class UPathAttribute : Attribute +{ /// - /// Specifies how to normalize a UPath stored in a class after loading/saving an asset. + /// Initializes a new instance of the class. /// - public class UPathAttribute : Attribute + /// The relative to. + public UPathAttribute(UPathRelativeTo relativeTo) { - private readonly UPathRelativeTo relativeTo; - - /// - /// Initializes a new instance of the class. - /// - /// The relative to. - public UPathAttribute(UPathRelativeTo relativeTo) - { - this.relativeTo = relativeTo; - } - - /// - /// Gets how to normalize the path relative to. - /// - /// The relative to. - public UPathRelativeTo RelativeTo - { - get - { - return relativeTo; - } - } + RelativeTo = relativeTo; } + + /// + /// Gets how to normalize the path relative to. + /// + /// The relative to. + public UPathRelativeTo RelativeTo { get; } } diff --git a/sources/assets/Stride.Core.Assets/UnloadableObjectRemover.cs b/sources/assets/Stride.Core.Assets/UnloadableObjectRemover.cs index 823d429d87..c0ff63a090 100644 --- a/sources/assets/Stride.Core.Assets/UnloadableObjectRemover.cs +++ b/sources/assets/Stride.Core.Assets/UnloadableObjectRemover.cs @@ -1,124 +1,120 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; -using System.Linq; using Stride.Core.Assets.Visitors; using Stride.Core.Reflection; using Stride.Core.Yaml; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +/// +/// Removes objects implementing 'IYamlProxy' from the object. +/// +public class UnloadableObjectRemover : AssetVisitorBase { - /// - /// Removes objects implementing 'IYamlProxy' from the object. - /// - public class UnloadableObjectRemover : AssetVisitorBase + private List unloadableitems; + [ThreadStatic] private static UnloadableObjectRemover instanceTLS; + + public static IReadOnlyList Run(object obj) { - private List unloadableitems; - [ThreadStatic] private static UnloadableObjectRemover instanceTLS; + var instance = GetYamlProxyRemover(); - public static IReadOnlyList Run(object obj) - { - var instance = GetYamlProxyRemover(); + instance.DiscoverInternal(obj); - instance.DiscoverInternal(obj); + // We apply changes in opposite visit order so that indices remains valid when we remove objects while iterating + for (int index = instance.unloadableitems.Count - 1; index >= 0; index--) + { + var unloadableItem = instance.unloadableitems[index]; + unloadableItem.MemberPath.Apply(obj, MemberPathAction.ValueClear, null); + } - // We apply changes in opposite visit order so that indices remains valid when we remove objects while iterating - for (int index = instance.unloadableitems.Count - 1; index >= 0; index--) - { - var unloadableItem = instance.unloadableitems[index]; - unloadableItem.MemberPath.Apply(obj, MemberPathAction.ValueClear, null); - } + return instance.unloadableitems; + } - return instance.unloadableitems; - } + public static IReadOnlyList Discover(object obj) + { + var instance = GetYamlProxyRemover(); + instance.DiscoverInternal(obj); + return instance.unloadableitems; + } - public static IReadOnlyList Discover(object obj) + private static UnloadableObjectRemover GetYamlProxyRemover() + { + var yamlProxyRemover = instanceTLS; + if (yamlProxyRemover == null) { - var instance = GetYamlProxyRemover(); - instance.DiscoverInternal(obj); - return instance.unloadableitems; + instanceTLS = yamlProxyRemover = new UnloadableObjectRemover(); } + return yamlProxyRemover; + } - private static UnloadableObjectRemover GetYamlProxyRemover() - { - var yamlProxyRemover = instanceTLS; - if (yamlProxyRemover == null) - { - instanceTLS = yamlProxyRemover = new UnloadableObjectRemover(); - } - return yamlProxyRemover; - } + private void DiscoverInternal(object obj) + { + Reset(); + unloadableitems = []; + Visit(obj); + } - private void DiscoverInternal(object obj) - { - Reset(); - unloadableitems = new List(); - Visit(obj); - } + public override void VisitCollectionItem(IEnumerable collection, CollectionDescriptor descriptor, int index, object? item, ITypeDescriptor? itemDescriptor) + { + if (ProcessObject(item)) return; - public override void VisitCollectionItem(IEnumerable collection, CollectionDescriptor descriptor, int index, object item, ITypeDescriptor itemDescriptor) - { - if (ProcessObject(item, descriptor.ElementType)) return; + base.VisitCollectionItem(collection, descriptor, index, item, itemDescriptor); + } - base.VisitCollectionItem(collection, descriptor, index, item, itemDescriptor); - } + public override void VisitArrayItem(Array array, ArrayDescriptor descriptor, int index, object? item, ITypeDescriptor? itemDescriptor) + { + if (ProcessObject(item)) return; - public override void VisitArrayItem(Array array, ArrayDescriptor descriptor, int index, object item, ITypeDescriptor itemDescriptor) - { - if (ProcessObject(item, descriptor.ElementType)) return; + base.VisitArrayItem(array, descriptor, index, item, itemDescriptor); + } - base.VisitArrayItem(array, descriptor, index, item, itemDescriptor); - } + public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object? value) + { + if (ProcessObject(value)) return; - public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object value) - { - if (ProcessObject(value, member.TypeDescriptor.Type)) return; + base.VisitObjectMember(container, containerDescriptor, member, value); + } - base.VisitObjectMember(container, containerDescriptor, member, value); - } + public override void VisitDictionaryKeyValue(object dictionary, DictionaryDescriptor descriptor, object key, ITypeDescriptor? keyDescriptor, object? value, ITypeDescriptor? valueDescriptor) + { + // TODO: CurrentPath is valid only for value, not key + //if (ProcessObject(key, keyDescriptor.Type)) key = null; + if (ProcessObject(value)) return; - public override void VisitDictionaryKeyValue(object dictionary, DictionaryDescriptor descriptor, object key, ITypeDescriptor keyDescriptor, object value, ITypeDescriptor valueDescriptor) - { - // TODO: CurrentPath is valid only for value, not key - //if (ProcessObject(key, keyDescriptor.Type)) key = null; - if (ProcessObject(value, valueDescriptor.Type)) return; + Visit(value, valueDescriptor); + //base.VisitDictionaryKeyValue(dictionary, descriptor, key, keyDescriptor, value, valueDescriptor); + } - Visit(value, valueDescriptor); - //base.VisitDictionaryKeyValue(dictionary, descriptor, key, keyDescriptor, value, valueDescriptor); - } + public override void VisitSetItem(IEnumerable set, SetDescriptor descriptor, object? item, ITypeDescriptor? itemDescriptor) + { + if (ProcessObject(item)) return; + + Visit(item, itemDescriptor); + } - public override void VisitSetItem(IEnumerable set, SetDescriptor descriptor, object item, ITypeDescriptor itemDescriptor) + private bool ProcessObject(object? obj) + { + if (obj is IUnloadable unloadable) { - if (ProcessObject(item, itemDescriptor.Type)) return; + unloadableitems.Add(new UnloadableItem(unloadable, CurrentPath.Clone())); - Visit(item, itemDescriptor); + // Don't recurse inside + return true; } + return false; + } - private bool ProcessObject(object obj, Type expectedType) - { - var unloadable = obj as IUnloadable; - if (unloadable != null) - { - unloadableitems.Add(new UnloadableItem(unloadable, CurrentPath.Clone())); - - // Don't recurse inside - return true; - } - return false; - } + public readonly struct UnloadableItem + { + public readonly IUnloadable UnloadableObject; + public readonly MemberPath MemberPath; - public struct UnloadableItem + public UnloadableItem(IUnloadable o, MemberPath memberPath) { - public readonly IUnloadable UnloadableObject; - public readonly MemberPath MemberPath; - - public UnloadableItem(IUnloadable o, MemberPath memberPath) - { - UnloadableObject = o; - MemberPath = memberPath; - } + UnloadableObject = o; + MemberPath = memberPath; } } } diff --git a/sources/assets/Stride.Core.Assets/VSProjectHelper.cs b/sources/assets/Stride.Core.Assets/VSProjectHelper.cs index 412478b0db..00a5bcad2d 100644 --- a/sources/assets/Stride.Core.Assets/VSProjectHelper.cs +++ b/sources/assets/Stride.Core.Assets/VSProjectHelper.cs @@ -1,166 +1,120 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; + using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; using NuGet.ProjectModel; -using Stride.Core; using Stride.Core.Diagnostics; using Stride.Core.IO; using ILogger = Stride.Core.Diagnostics.ILogger; +using MicrosoftProject = Microsoft.Build.Evaluation.Project; + +namespace Stride.Core.Assets; -namespace Stride.Core.Assets +public interface ICancellableAsyncBuild { - public interface ICancellableAsyncBuild - { - string AssemblyPath { get; } + string AssemblyPath { get; } - Task BuildTask { get; } + Task BuildTask { get; } - bool IsCanceled { get; } + bool IsCanceled { get; } - void Cancel(); - } + void Cancel(); +} - public static class VSProjectHelper - { - private const string StrideProjectType = "StrideProjectType"; - private const string StridePlatform = "StridePlatform"; +public static class VSProjectHelper +{ + private const string StrideProjectType = "StrideProjectType"; + private const string StridePlatform = "StridePlatform"; - private static BuildManager mainBuildManager = new BuildManager(); + private static readonly BuildManager mainBuildManager = new(); - public static Guid GetProjectGuid(Microsoft.Build.Evaluation.Project project) - { - if (project == null) throw new ArgumentNullException("project"); - return Guid.Parse(project.GetPropertyValue("ProjectGuid")); - } + public static Guid GetProjectGuid(MicrosoftProject project) + { + ArgumentNullException.ThrowIfNull(project); + return Guid.Parse(project.GetPropertyValue("ProjectGuid")); + } - public static PlatformType? GetPlatformTypeFromProject(Microsoft.Build.Evaluation.Project project) - { - return GetEnumFromProperty(project, StridePlatform); - } + public static PlatformType? GetPlatformTypeFromProject(MicrosoftProject project) + { + return GetEnumFromProperty(project, StridePlatform); + } - public static ProjectType? GetProjectTypeFromProject(Microsoft.Build.Evaluation.Project project) - { - return GetEnumFromProperty(project, StrideProjectType); - } + public static ProjectType? GetProjectTypeFromProject(MicrosoftProject project) + { + return GetEnumFromProperty(project, StrideProjectType); + } - private static T? GetEnumFromProperty(Microsoft.Build.Evaluation.Project project, string propertyName) where T : struct + private static T? GetEnumFromProperty(MicrosoftProject project, string propertyName) where T : struct + { + ArgumentNullException.ThrowIfNull(project); + ArgumentNullException.ThrowIfNull(propertyName); + var value = project.GetPropertyValue(propertyName); + if (string.IsNullOrEmpty(value)) { - if (project == null) throw new ArgumentNullException("project"); - if (propertyName == null) throw new ArgumentNullException("propertyName"); - var value = project.GetPropertyValue(propertyName); - if (string.IsNullOrEmpty(value)) - { - return null; - } - return (T)Enum.Parse(typeof(T), value); + return null; } + return (T)Enum.Parse(typeof(T), value); + } - public static string GetOrCompileProjectAssembly(string solutionFullPath, string fullProjectLocation, ILogger logger, string targets, bool autoCompileProject, string configuration, string platform = "AnyCPU", Dictionary extraProperties = null, bool onlyErrors = false, BuildRequestDataFlags flags = BuildRequestDataFlags.None) - { - if (fullProjectLocation == null) throw new ArgumentNullException("fullProjectLocation"); - if (logger == null) throw new ArgumentNullException("logger"); + public static string GetOrCompileProjectAssembly(string fullProjectLocation, ILogger logger, string targets, bool autoCompileProject, string configuration, string platform = "AnyCPU", Dictionary? extraProperties = null, bool onlyErrors = false, BuildRequestDataFlags flags = BuildRequestDataFlags.None) + { + ArgumentNullException.ThrowIfNull(fullProjectLocation); + ArgumentNullException.ThrowIfNull(logger); - var project = LoadProject(fullProjectLocation, configuration, platform, extraProperties); - var assemblyPath = project.GetPropertyValue("TargetPath"); - try + var project = LoadProject(fullProjectLocation, configuration, platform, extraProperties); + var assemblyPath = project.GetPropertyValue("TargetPath"); + try + { + if (!string.IsNullOrWhiteSpace(assemblyPath)) { - if (!string.IsNullOrWhiteSpace(assemblyPath)) + if (autoCompileProject) { - if (autoCompileProject) - { - var asyncBuild = new CancellableAsyncBuild(project, assemblyPath); - asyncBuild.Build(project, targets, flags, new LoggerRedirect(logger, onlyErrors)); - var buildResult = asyncBuild.BuildTask.Result; - } + var asyncBuild = new CancellableAsyncBuild(project, assemblyPath); + asyncBuild.Build(project, targets, flags, new LoggerRedirect(logger, onlyErrors)); + var buildResult = asyncBuild.BuildTask.Result; } } - finally - { - project.ProjectCollection.UnloadAllProjects(); - project.ProjectCollection.Dispose(); - } - - return assemblyPath; } - - public static ICancellableAsyncBuild CompileProjectAssemblyAsync(string solutionFullPath, string fullProjectLocation, ILogger logger, string targets = "Build", string configuration = "Debug", string platform = "AnyCPU", Dictionary extraProperties = null, BuildRequestDataFlags flags = BuildRequestDataFlags.None) + finally { - if (fullProjectLocation == null) throw new ArgumentNullException("fullProjectLocation"); - if (logger == null) throw new ArgumentNullException("logger"); + project.ProjectCollection.UnloadAllProjects(); + project.ProjectCollection.Dispose(); + } - var project = LoadProject(fullProjectLocation, configuration, platform, extraProperties); - var assemblyPath = project.GetPropertyValue("TargetPath"); - try - { - if (!string.IsNullOrWhiteSpace(assemblyPath)) - { - var asyncBuild = new CancellableAsyncBuild(project, assemblyPath); - asyncBuild.Build(project, targets, flags, new LoggerRedirect(logger)); - return asyncBuild; - } - } - finally - { - project.ProjectCollection.UnloadAllProjects(); - project.ProjectCollection.Dispose(); - } + return assemblyPath; + } - return null; - } + public static ICancellableAsyncBuild? CompileProjectAssemblyAsync(string fullProjectLocation, ILogger logger, string targets = "Build", string configuration = "Debug", string platform = "AnyCPU", Dictionary? extraProperties = null, BuildRequestDataFlags flags = BuildRequestDataFlags.None) + { + ArgumentNullException.ThrowIfNull(fullProjectLocation); + ArgumentNullException.ThrowIfNull(logger); - public static async Task GenerateRestoreGraphFile(ILogger logger, string projectPath) + var project = LoadProject(fullProjectLocation, configuration, platform, extraProperties); + var assemblyPath = project.GetPropertyValue("TargetPath"); + try { - DependencyGraphSpec spec = null; - using (var restoreGraphResult = new TemporaryFile()) + if (!string.IsNullOrWhiteSpace(assemblyPath)) { - await Task.Run(() => - { - var pc = new Microsoft.Build.Evaluation.ProjectCollection(); - - try - { - var parameters = new BuildParameters(pc) - { - Loggers = new[] { new LoggerRedirect(logger, true) }, //Instance of ILogger instantiated earlier - DisableInProcNode = true, - }; - - // Run a MSBuild /t:Restore - var request = new BuildRequestData(projectPath, new Dictionary { { "RestoreGraphOutputPath", restoreGraphResult.Path }, { "RestoreRecursive", "false" } }, null, new[] { "GenerateRestoreGraphFile" }, null, BuildRequestDataFlags.None); - - mainBuildManager.Build(parameters, request); - } - finally - { - pc.UnloadAllProjects(); - pc.Dispose(); - } - }); - - if (File.Exists(restoreGraphResult.Path) && new FileInfo(restoreGraphResult.Path).Length != 0) - { - spec = DependencyGraphSpec.Load(restoreGraphResult.Path); - File.Delete(restoreGraphResult.Path); - } - else - { - spec = new DependencyGraphSpec(); - } + var asyncBuild = new CancellableAsyncBuild(project, assemblyPath); + asyncBuild.Build(project, targets, flags, new LoggerRedirect(logger)); + return asyncBuild; } - - return spec; + } + finally + { + project.ProjectCollection.UnloadAllProjects(); + project.ProjectCollection.Dispose(); } - public static async Task RestoreNugetPackages(ILogger logger, string projectPath) + return null; + } + + public static async Task GenerateRestoreGraphFile(ILogger logger, string projectPath) + { + DependencyGraphSpec? spec = null; + using (var restoreGraphResult = new TemporaryFile()) { await Task.Run(() => { @@ -170,12 +124,12 @@ await Task.Run(() => { var parameters = new BuildParameters(pc) { - Loggers = new[] { new LoggerRedirect(logger, true) }, //Instance of ILogger instantiated earlier + Loggers = [new LoggerRedirect(logger, true)], //Instance of ILogger instantiated earlier DisableInProcNode = true, }; // Run a MSBuild /t:Restore - var request = new BuildRequestData(projectPath, new Dictionary(), null, new[] { "Restore" }, null, BuildRequestDataFlags.None); + var request = new BuildRequestData(projectPath, new Dictionary { { "RestoreGraphOutputPath", restoreGraphResult.Path }, { "RestoreRecursive", "false" } }, null, ["GenerateRestoreGraphFile"], null, BuildRequestDataFlags.None); mainBuildManager.Build(parameters, request); } @@ -185,189 +139,225 @@ await Task.Run(() => pc.Dispose(); } }); + + if (File.Exists(restoreGraphResult.Path) && new FileInfo(restoreGraphResult.Path).Length != 0) + { + spec = DependencyGraphSpec.Load(restoreGraphResult.Path); + File.Delete(restoreGraphResult.Path); + } + else + { + spec = new DependencyGraphSpec(); + } } - public static Microsoft.Build.Evaluation.Project LoadProject(string fullProjectLocation, string configuration = "Debug", string platform = "AnyCPU", Dictionary extraProperties = null) - { - configuration = configuration ?? "Debug"; - platform = platform ?? "AnyCPU"; + return spec; + } - var globalProperties = new Dictionary(); - globalProperties["Configuration"] = configuration; - globalProperties["Platform"] = platform; + public static async Task RestoreNugetPackages(ILogger logger, string projectPath) + { + await Task.Run(() => + { + var pc = new Microsoft.Build.Evaluation.ProjectCollection(); - if (extraProperties != null) + try { - foreach (var extraProperty in extraProperties) + var parameters = new BuildParameters(pc) { - globalProperties[extraProperty.Key] = extraProperty.Value; - } - } + Loggers = [new LoggerRedirect(logger, true)], //Instance of ILogger instantiated earlier + DisableInProcNode = true, + }; - var projectCollection = new Microsoft.Build.Evaluation.ProjectCollection(globalProperties); - projectCollection.LoadProject(fullProjectLocation); - var project = projectCollection.LoadedProjects.First(); + // Run a MSBuild /t:Restore + var request = new BuildRequestData(projectPath, new Dictionary(), null, ["Restore"], null, BuildRequestDataFlags.None); - // Support for cross-targeting (TargetFrameworks and RuntimeIdentifiers) - // Reload project with first TargetFramework and/or RuntimeIdentifier - void TryReloadWithFirstValue(string valuePropertyName, string valuesPropertyName) + mainBuildManager.Build(parameters, request); + } + finally { - if (globalProperties.ContainsKey(valuePropertyName)) - return; + pc.UnloadAllProjects(); + pc.Dispose(); + } + }); + } - var propertyValue = project.GetPropertyValue(valuePropertyName); - var propertyValues = project.GetPropertyValue(valuesPropertyName); - if (string.IsNullOrWhiteSpace(propertyValue) && !string.IsNullOrWhiteSpace(propertyValues)) - { - project.ProjectCollection.UnloadAllProjects(); - project.ProjectCollection.Dispose(); + public static MicrosoftProject LoadProject(string fullProjectLocation, string configuration = "Debug", string platform = "AnyCPU", Dictionary? extraProperties = null) + { + configuration ??= "Debug"; + platform ??= "AnyCPU"; - globalProperties.Add(valuePropertyName, propertyValues.Split(';').First()); - projectCollection = new Microsoft.Build.Evaluation.ProjectCollection(globalProperties); - projectCollection.LoadProject(fullProjectLocation); - project = projectCollection.LoadedProjects.First(); - } + var globalProperties = new Dictionary + { + ["Configuration"] = configuration, + ["Platform"] = platform + }; + + if (extraProperties != null) + { + foreach (var extraProperty in extraProperties) + { + globalProperties[extraProperty.Key] = extraProperty.Value; } + } + + var projectCollection = new Microsoft.Build.Evaluation.ProjectCollection(globalProperties); + projectCollection.LoadProject(fullProjectLocation); + var project = projectCollection.LoadedProjects.First(); + + // Support for cross-targeting (TargetFrameworks and RuntimeIdentifiers) + // Reload project with first TargetFramework and/or RuntimeIdentifier + void TryReloadWithFirstValue(string valuePropertyName, string valuesPropertyName) + { + if (globalProperties.ContainsKey(valuePropertyName)) + return; - // We need to go through them one by one (because a MSBuild Condition might depend on previous step) - // TODO: We should deduct TFM from referencing project(s) (if any) rather than default one. - TryReloadWithFirstValue("TargetFramework", "TargetFrameworks"); - TryReloadWithFirstValue("RuntimeIdentifier", "RuntimeIdentifiers"); - TryReloadWithFirstValue("StrideGraphicsApi", "StrideGraphicsApis"); + var propertyValue = project.GetPropertyValue(valuePropertyName); + var propertyValues = project.GetPropertyValue(valuesPropertyName); + if (string.IsNullOrWhiteSpace(propertyValue) && !string.IsNullOrWhiteSpace(propertyValues)) + { + project.ProjectCollection.UnloadAllProjects(); + project.ProjectCollection.Dispose(); - return project; + globalProperties.Add(valuePropertyName, propertyValues.Split(';').First()); + projectCollection = new Microsoft.Build.Evaluation.ProjectCollection(globalProperties); + projectCollection.LoadProject(fullProjectLocation); + project = projectCollection.LoadedProjects.First(); + } } - private class LoggerRedirect : Microsoft.Build.Utilities.Logger + // We need to go through them one by one (because a MSBuild Condition might depend on previous step) + // TODO: We should deduct TFM from referencing project(s) (if any) rather than default one. + TryReloadWithFirstValue("TargetFramework", "TargetFrameworks"); + TryReloadWithFirstValue("RuntimeIdentifier", "RuntimeIdentifiers"); + TryReloadWithFirstValue("StrideGraphicsApi", "StrideGraphicsApis"); + + return project; + } + + private class LoggerRedirect : Microsoft.Build.Utilities.Logger + { + private readonly ILogger logger; + private readonly bool onlyErrors; + + public LoggerRedirect(ILogger logger, bool onlyErrors = false) { - private readonly ILogger logger; - private readonly bool onlyErrors; + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.onlyErrors = onlyErrors; + } - public LoggerRedirect(ILogger logger, bool onlyErrors = false) + public override void Initialize(IEventSource eventSource) + { + ArgumentNullException.ThrowIfNull(eventSource); + if (!onlyErrors) { - if (logger == null) throw new ArgumentNullException("logger"); - this.logger = logger; - this.onlyErrors = onlyErrors; + eventSource.MessageRaised += MessageRaised; + eventSource.WarningRaised += WarningRaised; } + eventSource.ErrorRaised += ErrorRaised; + } - public override void Initialize(Microsoft.Build.Framework.IEventSource eventSource) + void MessageRaised(object sender, BuildMessageEventArgs e) + { + if (logger is LoggerResult loggerResult) { - if (eventSource == null) throw new ArgumentNullException("eventSource"); - if (!onlyErrors) - { - eventSource.MessageRaised += MessageRaised; - eventSource.WarningRaised += WarningRaised; - } - eventSource.ErrorRaised += ErrorRaised; + loggerResult.Module = $"{e.File}({e.LineNumber},{e.ColumnNumber})"; } - void MessageRaised(object sender, BuildMessageEventArgs e) + // Redirect task execution messages to verbose output + switch (e is TaskCommandLineEventArgs ? MessageImportance.Normal : e.Importance) { - var loggerResult = logger as LoggerResult; - if (loggerResult != null) - { - loggerResult.Module = $"{e.File}({e.LineNumber},{e.ColumnNumber})"; - } - - // Redirect task execution messages to verbose output - var importance = e is TaskCommandLineEventArgs ? MessageImportance.Normal : e.Importance; - - switch (importance) - { - case MessageImportance.High: - logger.Info(e.Message); - break; - case MessageImportance.Normal: - logger.Verbose(e.Message); - break; - case MessageImportance.Low: - logger.Debug(e.Message); - break; - } + case MessageImportance.High: + logger.Info(e.Message); + break; + case MessageImportance.Normal: + logger.Verbose(e.Message); + break; + case MessageImportance.Low: + logger.Debug(e.Message); + break; } + } - void WarningRaised(object sender, BuildWarningEventArgs e) + void WarningRaised(object sender, BuildWarningEventArgs e) + { + if (logger is LoggerResult loggerResult) { - var loggerResult = logger as LoggerResult; - if (loggerResult != null) - { - loggerResult.Module = string.Format("{0}({1},{2})", e.File, e.LineNumber, e.ColumnNumber); - } - logger.Warning(e.Message); + loggerResult.Module = string.Format("{0}({1},{2})", e.File, e.LineNumber, e.ColumnNumber); } + logger.Warning(e.Message); + } - void ErrorRaised(object sender, Microsoft.Build.Framework.BuildErrorEventArgs e) + void ErrorRaised(object sender, BuildErrorEventArgs e) + { + if (logger is LoggerResult loggerResult) { - if (logger is LoggerResult loggerResult) - { - loggerResult.Module = $"{e.File}({e.LineNumber},{e.ColumnNumber})"; - } + loggerResult.Module = $"{e.File}({e.LineNumber},{e.ColumnNumber})"; + } - if (e.Code == "NETSDK1045") - { - var netVersion = Regex.Match(e.Message, @"\.(NET|net) ?(\d+\.\d+)"); - if (netVersion.Success) - logger.Error($"{e.Code}: this project requires {netVersion} SDK, please go to https://dotnet.microsoft.com/download and download {netVersion} SDK."); - else - logger.Error($"{e.Code}: {e.Message}"); - } + if (e.Code == "NETSDK1045") + { + var netVersion = Regex.Match(e.Message, @"\.(NET|net) ?(\d+\.\d+)"); + if (netVersion.Success) + logger.Error($"{e.Code}: this project requires {netVersion} SDK, please go to https://dotnet.microsoft.com/download and download {netVersion} SDK."); else - logger.Error(e.Message); + logger.Error($"{e.Code}: {e.Message}"); + } + else + { + logger.Error(e.Message); } } + } - public static void Reset() - { - mainBuildManager.ResetCaches(); - } + public static void Reset() + { + mainBuildManager.ResetCaches(); + } - private class CancellableAsyncBuild : ICancellableAsyncBuild + private class CancellableAsyncBuild : ICancellableAsyncBuild + { + public CancellableAsyncBuild(MicrosoftProject project, string assemblyPath) { - public CancellableAsyncBuild(Project project, string assemblyPath) - { - Project = project; - AssemblyPath = assemblyPath; - } + Project = project; + AssemblyPath = assemblyPath; + } - public string AssemblyPath { get; private set; } + public string AssemblyPath { get; } - public Project Project { get; private set; } + public MicrosoftProject Project { get; } - public Task BuildTask { get; private set; } + public Task BuildTask { get; private set; } - public bool IsCanceled { get; private set; } + public bool IsCanceled { get; private set; } - internal void Build(Microsoft.Build.Evaluation.Project project, string targets, BuildRequestDataFlags flags, Microsoft.Build.Utilities.Logger logger) - { - if (project == null) throw new ArgumentNullException("project"); - if (logger == null) throw new ArgumentNullException("logger"); + internal void Build(MicrosoftProject project, string targets, BuildRequestDataFlags flags, Microsoft.Build.Utilities.Logger logger) + { + ArgumentNullException.ThrowIfNull(project); + ArgumentNullException.ThrowIfNull(logger); - // Make sure that we are using the project collection from the loaded project, otherwise we are getting - // weird cache behavior with the msbuild system - var projectInstance = new ProjectInstance(project.Xml, project.ProjectCollection.GlobalProperties, project.ToolsVersion, project.ProjectCollection); + // Make sure that we are using the project collection from the loaded project, otherwise we are getting + // weird cache behavior with the msbuild system + var projectInstance = new ProjectInstance(project.Xml, project.ProjectCollection.GlobalProperties, project.ToolsVersion, project.ProjectCollection); - BuildTask = Task.Run(() => - { - var buildResult = mainBuildManager.Build( - new BuildParameters(project.ProjectCollection) - { - Loggers = new[] { logger }, - DisableInProcNode = true, - }, - new BuildRequestData(projectInstance, targets.Split(';'), null, flags)); - - return buildResult; - }); - } + BuildTask = Task.Run(() => + { + return mainBuildManager.Build( + new BuildParameters(project.ProjectCollection) + { + Loggers = [logger], + DisableInProcNode = true, + }, + new BuildRequestData(projectInstance, targets.Split(';'), null, flags)); + }); + } - public void Cancel() + public void Cancel() + { + var localManager = mainBuildManager; + if (localManager != null) { - var localManager = mainBuildManager; - if (localManager != null) - { - localManager.CancelAllSubmissions(); - IsCanceled = true; - } + localManager.CancelAllSubmissions(); + IsCanceled = true; } } } diff --git a/sources/assets/Stride.Core.Assets/Visitors/AssetMemberVisitorBase.cs b/sources/assets/Stride.Core.Assets/Visitors/AssetMemberVisitorBase.cs index 82fe2b0a7e..ef183def20 100644 --- a/sources/assets/Stride.Core.Assets/Visitors/AssetMemberVisitorBase.cs +++ b/sources/assets/Stride.Core.Assets/Visitors/AssetMemberVisitorBase.cs @@ -1,95 +1,92 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using System.Collections; -using System.Collections.Generic; using Stride.Core.Reflection; -namespace Stride.Core.Assets.Visitors +namespace Stride.Core.Assets.Visitors; + +public abstract class AssetMemberVisitorBase : AssetVisitorBase { - public abstract class AssetMemberVisitorBase : AssetVisitorBase - { - /// - /// Gets the that will be checked against when visiting. - /// - /// - protected MemberPath MemberPath { get; set; } + /// + /// Gets the that will be checked against when visiting. + /// + /// + protected MemberPath? MemberPath { get; set; } - /// - public override void VisitArrayItem(Array array, ArrayDescriptor descriptor, int index, object item, ITypeDescriptor itemDescriptor) - { - if (CurrentPath.Match(MemberPath)) - VisitAssetMember(item, itemDescriptor); - else - base.VisitArrayItem(array, descriptor, index, item, itemDescriptor); - } + /// + public override void VisitArrayItem(Array array, ArrayDescriptor descriptor, int index, object? item, ITypeDescriptor? itemDescriptor) + { + if (CurrentPath.Match(MemberPath)) + VisitAssetMember(item, itemDescriptor); + else + base.VisitArrayItem(array, descriptor, index, item, itemDescriptor); + } - /// - public override void VisitCollectionItem(IEnumerable collection, CollectionDescriptor descriptor, int index, object item, ITypeDescriptor itemDescriptor) - { - if (CurrentPath.Match(MemberPath)) - VisitAssetMember(item, itemDescriptor); - else - base.VisitCollectionItem(collection, descriptor, index, item, itemDescriptor); - } + /// + public override void VisitCollectionItem(IEnumerable collection, CollectionDescriptor descriptor, int index, object? item, ITypeDescriptor? itemDescriptor) + { + if (CurrentPath.Match(MemberPath)) + VisitAssetMember(item, itemDescriptor); + else + base.VisitCollectionItem(collection, descriptor, index, item, itemDescriptor); + } - /// - public override void VisitDictionaryKeyValue(object dictionary, DictionaryDescriptor descriptor, object key, ITypeDescriptor keyDescriptor, object value, ITypeDescriptor valueDescriptor) + /// + public override void VisitDictionaryKeyValue(object dictionary, DictionaryDescriptor descriptor, object key, ITypeDescriptor? keyDescriptor, object? value, ITypeDescriptor? valueDescriptor) + { + if (CurrentPath.Match(MemberPath)) { - if (CurrentPath.Match(MemberPath)) - { - var keyValueType = typeof(KeyValuePair<,>).MakeGenericType(keyDescriptor.Type, valueDescriptor.Type); - var keyValueDescriptor = TypeDescriptorFactory.Find(keyValueType); - var keyValuePair = Activator.CreateInstance(keyValueType, key, value); - VisitAssetMember(keyValuePair, keyValueDescriptor); - } - else - { - base.VisitDictionaryKeyValue(dictionary, descriptor, key, keyDescriptor, value, valueDescriptor); - } + var keyValueType = typeof(KeyValuePair<,>).MakeGenericType(keyDescriptor!.Type, valueDescriptor!.Type); + var keyValueDescriptor = TypeDescriptorFactory.Find(keyValueType); + var keyValuePair = Activator.CreateInstance(keyValueType, key, value); + VisitAssetMember(keyValuePair, keyValueDescriptor); } - - public override void VisitSetItem(IEnumerable set, SetDescriptor descriptor, object item, ITypeDescriptor itemDescriptor) + else { - if (CurrentPath.Match(MemberPath)) - VisitAssetMember(item, itemDescriptor); - else - base.VisitSetItem(set, descriptor, item, itemDescriptor); + base.VisitDictionaryKeyValue(dictionary, descriptor, key, keyDescriptor, value, valueDescriptor); } + } - /// - public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) - { - if (CurrentPath.Match(MemberPath)) - VisitAssetMember(obj, descriptor); - else - base.VisitObject(obj, descriptor, visitMembers); - } + public override void VisitSetItem(IEnumerable set, SetDescriptor descriptor, object? item, ITypeDescriptor? itemDescriptor) + { + if (CurrentPath.Match(MemberPath)) + VisitAssetMember(item, itemDescriptor); + else + base.VisitSetItem(set, descriptor, item, itemDescriptor); + } - /// - public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object value) - { - if (CurrentPath.Match(MemberPath)) - VisitAssetMember(value, member.TypeDescriptor); - else - base.VisitObjectMember(container, containerDescriptor, member, value); - } + /// + public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) + { + if (CurrentPath.Match(MemberPath)) + VisitAssetMember(obj, descriptor); + else + base.VisitObject(obj, descriptor, visitMembers); + } - /// - public override void VisitPrimitive(object primitive, PrimitiveDescriptor descriptor) - { - if (CurrentPath.Match(MemberPath)) - VisitAssetMember(primitive, descriptor); - else - base.VisitPrimitive(primitive, descriptor); - } + /// + public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object? value) + { + if (CurrentPath.Match(MemberPath)) + VisitAssetMember(value, member.TypeDescriptor); + else + base.VisitObjectMember(container, containerDescriptor, member, value); + } - /// - /// Called when matches the given when creating this instance. - /// - /// - /// - protected abstract void VisitAssetMember(object value, ITypeDescriptor descriptor); + /// + public override void VisitPrimitive(object primitive, PrimitiveDescriptor descriptor) + { + if (CurrentPath.Match(MemberPath)) + VisitAssetMember(primitive, descriptor); + else + base.VisitPrimitive(primitive, descriptor); } + + /// + /// Called when matches the given when creating this instance. + /// + /// + /// + protected abstract void VisitAssetMember(object? value, ITypeDescriptor? descriptor); } diff --git a/sources/assets/Stride.Core.Assets/Visitors/AssetVisitorBase.cs b/sources/assets/Stride.Core.Assets/Visitors/AssetVisitorBase.cs index 7c087db73b..f5eecd837a 100644 --- a/sources/assets/Stride.Core.Assets/Visitors/AssetVisitorBase.cs +++ b/sources/assets/Stride.Core.Assets/Visitors/AssetVisitorBase.cs @@ -1,22 +1,22 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Reflection; -namespace Stride.Core.Assets.Visitors +namespace Stride.Core.Assets.Visitors; + +/// +/// Visitor for assets. +/// +public abstract class AssetVisitorBase : DataVisitorBase { - /// - /// Visitor for assets. - /// - public abstract class AssetVisitorBase : DataVisitorBase + protected AssetVisitorBase() : this(Core.Reflection.TypeDescriptorFactory.Default) { - protected AssetVisitorBase() : this(Core.Reflection.TypeDescriptorFactory.Default) - { - } + } - protected AssetVisitorBase(ITypeDescriptorFactory typeDescriptorFactory) : base(typeDescriptorFactory) - { - // Add automatically registered custom data visitors - CustomVisitors.AddRange(AssetRegistry.GetDataVisitNodes()); - } + protected AssetVisitorBase(ITypeDescriptorFactory typeDescriptorFactory) : base(typeDescriptorFactory) + { + // Add automatically registered custom data visitors + CustomVisitors.AddRange(AssetRegistry.GetDataVisitNodes()); } } diff --git a/sources/assets/Stride.Core.Assets/XenkoToStrideRenameHelper.cs b/sources/assets/Stride.Core.Assets/XenkoToStrideRenameHelper.cs index 2fe18f729d..cdf043e4ce 100644 --- a/sources/assets/Stride.Core.Assets/XenkoToStrideRenameHelper.cs +++ b/sources/assets/Stride.Core.Assets/XenkoToStrideRenameHelper.cs @@ -1,75 +1,68 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; -namespace Stride.Core.Assets +namespace Stride.Core.Assets; + +public static class XenkoToStrideRenameHelper { - public static class XenkoToStrideRenameHelper + public enum StrideContentType { - public enum StrideContentType - { - Asset, - Project, - Code, - Package, - } + Asset, + Project, + Code, + Package, + } - public static string ReplaceStrideContent(string content, StrideContentType type) + public static string ReplaceStrideContent(string content, StrideContentType type) + { + // Rename various instances of Xenko to Stride + switch (type) { - // Rename various instances of Xenko to Stride - switch (type) - { - case StrideContentType.Package: - break; - case StrideContentType.Project: - content = Regex.Replace(content, "Include=\"Xenko", "Include=\"Stride"); - break; - case StrideContentType.Code: - content = Regex.Replace(content, @"using Xenko", "using Stride"); - content = Regex.Replace(content, @"\bXenko\.", "Stride."); - break; - case StrideContentType.Asset: - content = Regex.Replace(content, @"SerializedVersion: \{Xenko", "SerializedVersion: {Stride"); - content = Regex.Replace(content, @"\!Xenko\.([^\s]+),Xenko\.([^\s]+)", "!Stride.$1,Stride.$2"); - // xkeffectlog - content = Regex.Replace(content, @"EffectName: Xenko", "EffectName: Stride"); - content = Regex.Replace(content, @"Name: XENKO_", "Name: STRIDE_"); - content = Regex.Replace(content, @"XenkoEffectBase\.", "StrideEffectBase."); - break; - } - - return content; + case StrideContentType.Package: + break; + case StrideContentType.Project: + content = Regex.Replace(content, "Include=\"Xenko", "Include=\"Stride"); + break; + case StrideContentType.Code: + content = Regex.Replace(content, @"using Xenko", "using Stride"); + content = Regex.Replace(content, @"\bXenko\.", "Stride."); + break; + case StrideContentType.Asset: + content = Regex.Replace(content, @"SerializedVersion: \{Xenko", "SerializedVersion: {Stride"); + content = Regex.Replace(content, @"\!Xenko\.([^\s]+),Xenko\.([^\s]+)", "!Stride.$1,Stride.$2"); + // xkeffectlog + content = Regex.Replace(content, @"EffectName: Xenko", "EffectName: Stride"); + content = Regex.Replace(content, @"Name: XENKO_", "Name: STRIDE_"); + content = Regex.Replace(content, @"XenkoEffectBase\.", "StrideEffectBase."); + break; } - public static string RenameStrideFile(string filePath, StrideContentType type) - { - var fileContents = File.ReadAllText(filePath); - var newFileContents = fileContents; + return content; + } - // Rename namespaces - newFileContents = ReplaceStrideContent(newFileContents, type); + public static string RenameStrideFile(string filePath, StrideContentType type) + { + var fileContents = File.ReadAllText(filePath); + var newFileContents = fileContents; - // Save file if there were any changes - if (newFileContents != fileContents) - { - File.WriteAllText(filePath, newFileContents); - } + // Rename namespaces + newFileContents = ReplaceStrideContent(newFileContents, type); - // Rename extension - var extension = Path.GetExtension(filePath); - if (extension.StartsWith(".xk", StringComparison.Ordinal)) - { - File.Move(filePath, filePath = filePath.Replace(".xk", ".sd")); - } + // Save file if there were any changes + if (newFileContents != fileContents) + { + File.WriteAllText(filePath, newFileContents); + } - return filePath; + // Rename extension + var extension = Path.GetExtension(filePath); + if (extension.StartsWith(".xk", StringComparison.Ordinal)) + { + File.Move(filePath, filePath = filePath.Replace(".xk", ".sd")); } + + return filePath; } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/AssetObjectSerializerBackend.cs b/sources/assets/Stride.Core.Assets/Yaml/AssetObjectSerializerBackend.cs index 0c0ddfbc34..21a7e745de 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/AssetObjectSerializerBackend.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/AssetObjectSerializerBackend.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using Stride.Core.Assets.Yaml; using Stride.Core.Annotations; using Stride.Core.Reflection; @@ -9,353 +8,332 @@ using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// Internal class used when serializing/deserializing an object. +/// +public class AssetObjectSerializerBackend : DefaultObjectSerializerBackend { - /// - /// Internal class used when serializing/deserializing an object. - /// - public class AssetObjectSerializerBackend : DefaultObjectSerializerBackend + private readonly ITypeDescriptorFactory typeDescriptorFactory; + private static readonly PropertyKey MemberPathKey = new("MemberPath", typeof(AssetObjectSerializerBackend)); + public static readonly PropertyKey> OverrideDictionaryKey = new("OverrideDictionary", typeof(AssetObjectSerializerBackend)); + public static readonly PropertyKey> ObjectReferencesKey = new("ObjectReferences", typeof(AssetObjectSerializerBackend)); + + public AssetObjectSerializerBackend(ITypeDescriptorFactory typeDescriptorFactory) + { + ArgumentNullException.ThrowIfNull(typeDescriptorFactory); + this.typeDescriptorFactory = typeDescriptorFactory; + } + + public override object ReadMemberValue(ref ObjectContext objectContext, IMemberDescriptor memberDescriptor, object memberValue, Type memberType) { - private readonly ITypeDescriptorFactory typeDescriptorFactory; - private static readonly PropertyKey MemberPathKey = new PropertyKey("MemberPath", typeof(AssetObjectSerializerBackend)); - public static readonly PropertyKey> OverrideDictionaryKey = new PropertyKey>("OverrideDictionary", typeof(AssetObjectSerializerBackend)); - public static readonly PropertyKey> ObjectReferencesKey = new PropertyKey>("ObjectReferences", typeof(AssetObjectSerializerBackend)); + var memberObjectContext = new ObjectContext(objectContext.SerializerContext, memberValue, objectContext.SerializerContext.FindTypeDescriptor(memberType), objectContext.Descriptor, memberDescriptor); - public AssetObjectSerializerBackend(ITypeDescriptorFactory typeDescriptorFactory) + if (memberDescriptor is MemberDescriptorBase member && objectContext.Settings.Attributes.GetAttribute(member.MemberInfo) != null) { - if (typeDescriptorFactory == null) - throw new ArgumentNullException(nameof(typeDescriptorFactory)); - this.typeDescriptorFactory = typeDescriptorFactory; + memberObjectContext.Properties.Add(CollectionWithIdsSerializerBase.NonIdentifiableCollectionItemsKey, true); } - public override object ReadMemberValue(ref ObjectContext objectContext, IMemberDescriptor memberDescriptor, object memberValue, Type memberType) - { - var memberObjectContext = new ObjectContext(objectContext.SerializerContext, memberValue, objectContext.SerializerContext.FindTypeDescriptor(memberType), objectContext.Descriptor, memberDescriptor); + var path = GetCurrentPath(ref objectContext, true); + path.PushMember(memberDescriptor.Name); + SetCurrentPath(ref memberObjectContext, path); - var member = memberDescriptor as MemberDescriptorBase; - if (member != null && objectContext.Settings.Attributes.GetAttribute(member.MemberInfo) != null) - { - memberObjectContext.Properties.Add(CollectionWithIdsSerializerBase.NonIdentifiableCollectionItemsKey, true); - } + var result = ReadYaml(ref memberObjectContext); + return result; + } - var path = GetCurrentPath(ref objectContext, true); - path.PushMember(memberDescriptor.Name); - SetCurrentPath(ref memberObjectContext, path); + public override void WriteMemberValue(ref ObjectContext objectContext, IMemberDescriptor memberDescriptor, object memberValue, Type memberType) + { + var memberObjectContext = new ObjectContext(objectContext.SerializerContext, memberValue, objectContext.SerializerContext.FindTypeDescriptor(memberType), objectContext.Descriptor, memberDescriptor) + { + ScalarStyle = memberDescriptor.ScalarStyle, + }; + + // We allow compact style only for collection with non-identifiable items + var allowCompactStyle = objectContext.SerializerContext.Properties.TryGetValue(CollectionWithIdsSerializerBase.NonIdentifiableCollectionItemsKey, out var nonIdentifiableItems) && nonIdentifiableItems; - var result = ReadYaml(ref memberObjectContext); - return result; + if (memberDescriptor is MemberDescriptorBase member && objectContext.Settings.Attributes.GetAttribute(member.MemberInfo) != null) + { + memberObjectContext.Properties.Add(CollectionWithIdsSerializerBase.NonIdentifiableCollectionItemsKey, true); + allowCompactStyle = true; } - public override void WriteMemberValue(ref ObjectContext objectContext, IMemberDescriptor memberDescriptor, object memberValue, Type memberType) + if (allowCompactStyle) { - var memberObjectContext = new ObjectContext(objectContext.SerializerContext, memberValue, objectContext.SerializerContext.FindTypeDescriptor(memberType), objectContext.Descriptor, memberDescriptor) - { - ScalarStyle = memberDescriptor.ScalarStyle, - }; + memberObjectContext.Style = memberDescriptor.Style; + } - bool nonIdentifiableItems; - // We allow compact style only for collection with non-identifiable items - var allowCompactStyle = objectContext.SerializerContext.Properties.TryGetValue(CollectionWithIdsSerializerBase.NonIdentifiableCollectionItemsKey, out nonIdentifiableItems) && nonIdentifiableItems; + var path = GetCurrentPath(ref objectContext, true); + path.PushMember(memberDescriptor.Name); + SetCurrentPath(ref memberObjectContext, path); - var member = memberDescriptor as MemberDescriptorBase; - if (member != null && objectContext.Settings.Attributes.GetAttribute(member.MemberInfo) != null) - { - memberObjectContext.Properties.Add(CollectionWithIdsSerializerBase.NonIdentifiableCollectionItemsKey, true); - allowCompactStyle = true; - } + WriteYaml(ref memberObjectContext); + } + + public override string ReadMemberName(ref ObjectContext objectContext, string memberName, out bool skipMember) + { + var objectType = objectContext.Instance.GetType(); + + var realMemberName = TrimAndParseOverride(memberName, out var overrideTypes); - if (allowCompactStyle) + // For member names, we have a single override, so we always take the last one of the array (In case of legacy property serialized with ~Name) + var overrideType = overrideTypes[^1]; + if (overrideType != OverrideType.Base) + { + if (!objectContext.SerializerContext.Properties.TryGetValue(OverrideDictionaryKey, out var overrides)) { - memberObjectContext.Style = memberDescriptor.Style; + overrides = new YamlAssetMetadata(); + objectContext.SerializerContext.Properties.Add(OverrideDictionaryKey, overrides); } var path = GetCurrentPath(ref objectContext, true); - path.PushMember(memberDescriptor.Name); - SetCurrentPath(ref memberObjectContext, path); - - WriteYaml(ref memberObjectContext); + path.PushMember(realMemberName); + overrides.Set(path, overrideType); } - public override string ReadMemberName(ref ObjectContext objectContext, string memberName, out bool skipMember) - { - var objectType = objectContext.Instance.GetType(); - - OverrideType[] overrideTypes; - var realMemberName = TrimAndParseOverride(memberName, out overrideTypes); + var resultMemberName = base.ReadMemberName(ref objectContext, realMemberName, out skipMember); + return resultMemberName; + } - // For member names, we have a single override, so we always take the last one of the array (In case of legacy property serialized with ~Name) - var overrideType = overrideTypes[overrideTypes.Length - 1]; - if (overrideType != OverrideType.Base) + public override void WriteMemberName(ref ObjectContext objectContext, IMemberDescriptor member, string memberName) + { + // Replace the key with Stride.Core.Reflection IMemberDescriptor + // Cache previous + if (member != null) + { + var customDescriptor = (IMemberDescriptor)member.Tag; + if (customDescriptor == null) { - YamlAssetMetadata overrides; - if (!objectContext.SerializerContext.Properties.TryGetValue(OverrideDictionaryKey, out overrides)) - { - overrides = new YamlAssetMetadata(); - objectContext.SerializerContext.Properties.Add(OverrideDictionaryKey, overrides); - } - - var path = GetCurrentPath(ref objectContext, true); - path.PushMember(realMemberName); - overrides.Set(path, overrideType); + customDescriptor = typeDescriptorFactory.Find(objectContext.Instance.GetType()).TryGetMember(memberName); + member.Tag = customDescriptor; } - var resultMemberName = base.ReadMemberName(ref objectContext, realMemberName, out skipMember); - return resultMemberName; - } - - public override void WriteMemberName(ref ObjectContext objectContext, IMemberDescriptor member, string memberName) - { - // Replace the key with Stride.Core.Reflection IMemberDescriptor - // Cache previous - if (member != null) + if (customDescriptor != null) { - var customDescriptor = (IMemberDescriptor)member.Tag; - if (customDescriptor == null) + if (objectContext.SerializerContext.Properties.TryGetValue(OverrideDictionaryKey, out var overrides)) { - customDescriptor = typeDescriptorFactory.Find(objectContext.Instance.GetType()).TryGetMember(memberName); - member.Tag = customDescriptor; - } + var path = GetCurrentPath(ref objectContext, true); + path.PushMember(memberName); - if (customDescriptor != null) - { - YamlAssetMetadata overrides; - if (objectContext.SerializerContext.Properties.TryGetValue(OverrideDictionaryKey, out overrides)) + var overrideType = overrides.TryGet(path); + if ((overrideType & OverrideType.New) != 0) + { + memberName += OverridePostfixes.PostFixNew; + } + if ((overrideType & OverrideType.Sealed) != 0) { - var path = GetCurrentPath(ref objectContext, true); - path.PushMember(memberName); - - var overrideType = overrides.TryGet(path); - if ((overrideType & OverrideType.New) != 0) - { - memberName += OverridePostfixes.PostFixNew; - } - if ((overrideType & OverrideType.Sealed) != 0) - { - memberName += OverridePostfixes.PostFixSealed; - } + memberName += OverridePostfixes.PostFixSealed; } } } - - base.WriteMemberName(ref objectContext, member, memberName); } - public override object ReadCollectionItem(ref ObjectContext objectContext, object value, Type itemType, int index) - { - var path = GetCurrentPath(ref objectContext, true); - path.PushIndex(index); - var itemObjectContext = new ObjectContext(objectContext.SerializerContext, value, objectContext.SerializerContext.FindTypeDescriptor(itemType)); - SetCurrentPath(ref itemObjectContext, path); - return ReadYaml(ref itemObjectContext); - } + base.WriteMemberName(ref objectContext, member, memberName); + } - public override void WriteCollectionItem(ref ObjectContext objectContext, object item, Type itemType, int index) + public override object ReadCollectionItem(ref ObjectContext objectContext, object value, Type itemType, int index) + { + var path = GetCurrentPath(ref objectContext, true); + path.PushIndex(index); + var itemObjectContext = new ObjectContext(objectContext.SerializerContext, value, objectContext.SerializerContext.FindTypeDescriptor(itemType)); + SetCurrentPath(ref itemObjectContext, path); + return ReadYaml(ref itemObjectContext); + } + + public override void WriteCollectionItem(ref ObjectContext objectContext, object item, Type itemType, int index) + { + var path = GetCurrentPath(ref objectContext, true); + path.PushIndex(index); + var itemObjectContext = new ObjectContext(objectContext.SerializerContext, item, objectContext.SerializerContext.FindTypeDescriptor(itemType)); + SetCurrentPath(ref itemObjectContext, path); + WriteYaml(ref itemObjectContext); + } + + public override object ReadDictionaryKey(ref ObjectContext objectContext, Type keyType) + { + var key = objectContext.Reader.Peek(); + var keyName = TrimAndParseOverride(key.Value, out var overrideTypes); + key.Value = keyName; + + var keyValue = base.ReadDictionaryKey(ref objectContext, keyType); + + if (overrideTypes[0] != OverrideType.Base) { + if (!objectContext.SerializerContext.Properties.TryGetValue(OverrideDictionaryKey, out var overrides)) + { + overrides = new YamlAssetMetadata(); + objectContext.SerializerContext.Properties.Add(OverrideDictionaryKey, overrides); + } + var path = GetCurrentPath(ref objectContext, true); - path.PushIndex(index); - var itemObjectContext = new ObjectContext(objectContext.SerializerContext, item, objectContext.SerializerContext.FindTypeDescriptor(itemType)); - SetCurrentPath(ref itemObjectContext, path); - WriteYaml(ref itemObjectContext); + if (YamlAssetPath.IsCollectionWithIdType(objectContext.Descriptor.Type, keyValue, out var id, out _)) + { + path.PushItemId(id); + } + else + { + path.PushIndex(key); + } + overrides.Set(path, overrideTypes[0]); } - public override object ReadDictionaryKey(ref ObjectContext objectContext, Type keyType) + if (overrideTypes.Length > 1 && overrideTypes[1] != OverrideType.Base) { - var key = objectContext.Reader.Peek(); - OverrideType[] overrideTypes; - var keyName = TrimAndParseOverride(key.Value, out overrideTypes); - key.Value = keyName; - - var keyValue = base.ReadDictionaryKey(ref objectContext, keyType); - - if (overrideTypes[0] != OverrideType.Base) + if (YamlAssetPath.IsCollectionWithIdType(objectContext.Descriptor.Type, keyValue, out _, out var actualKey)) { - YamlAssetMetadata overrides; - if (!objectContext.SerializerContext.Properties.TryGetValue(OverrideDictionaryKey, out overrides)) + if (!objectContext.SerializerContext.Properties.TryGetValue(OverrideDictionaryKey, out var overrides)) { overrides = new YamlAssetMetadata(); objectContext.SerializerContext.Properties.Add(OverrideDictionaryKey, overrides); } var path = GetCurrentPath(ref objectContext, true); - ItemId id; - object actualKey; - if (YamlAssetPath.IsCollectionWithIdType(objectContext.Descriptor.Type, keyValue, out id, out actualKey)) - { - path.PushItemId(id); - } - else - { - path.PushIndex(key); - } - overrides.Set(path, overrideTypes[0]); - } - - if (overrideTypes.Length > 1 && overrideTypes[1] != OverrideType.Base) - { - ItemId id; - object actualKey; - if (YamlAssetPath.IsCollectionWithIdType(objectContext.Descriptor.Type, keyValue, out id, out actualKey)) - { - YamlAssetMetadata overrides; - if (!objectContext.SerializerContext.Properties.TryGetValue(OverrideDictionaryKey, out overrides)) - { - overrides = new YamlAssetMetadata(); - objectContext.SerializerContext.Properties.Add(OverrideDictionaryKey, overrides); - } - - var path = GetCurrentPath(ref objectContext, true); - path.PushIndex(actualKey); - overrides.Set(path, overrideTypes[1]); - } + path.PushIndex(actualKey); + overrides.Set(path, overrideTypes[1]); } - - return keyValue; } - public override void WriteDictionaryKey(ref ObjectContext objectContext, object key, Type keyType) + return keyValue; + } + + public override void WriteDictionaryKey(ref ObjectContext objectContext, object key, Type keyType) + { + if (objectContext.SerializerContext.Properties.TryGetValue(OverrideDictionaryKey, out var overrides)) { - YamlAssetMetadata overrides; - if (objectContext.SerializerContext.Properties.TryGetValue(OverrideDictionaryKey, out overrides)) + var itemPath = GetCurrentPath(ref objectContext, true); + YamlAssetPath? keyPath = null; + if (YamlAssetPath.IsCollectionWithIdType(objectContext.Descriptor.Type, key, out var id, out var actualKey)) { - var itemPath = GetCurrentPath(ref objectContext, true); - YamlAssetPath keyPath = null; - ItemId id; - object actualKey; - if (YamlAssetPath.IsCollectionWithIdType(objectContext.Descriptor.Type, key, out id, out actualKey)) - { - keyPath = itemPath.Clone(); - keyPath.PushIndex(actualKey); - itemPath.PushItemId(id); - } - else - { - itemPath.PushIndex(key); - } - var overrideType = overrides.TryGet(itemPath); + keyPath = itemPath.Clone(); + keyPath.PushIndex(actualKey); + itemPath.PushItemId(id); + } + else + { + itemPath.PushIndex(key); + } + var overrideType = overrides.TryGet(itemPath); + if ((overrideType & OverrideType.New) != 0) + { + objectContext.SerializerContext.Properties.Set(ItemIdSerializerBase.OverrideInfoKey, OverridePostfixes.PostFixNew.ToString()); + } + if ((overrideType & OverrideType.Sealed) != 0) + { + objectContext.SerializerContext.Properties.Set(ItemIdSerializerBase.OverrideInfoKey, OverridePostfixes.PostFixSealed.ToString()); + } + if (keyPath != null) + { + overrideType = overrides.TryGet(keyPath); if ((overrideType & OverrideType.New) != 0) { - objectContext.SerializerContext.Properties.Set(ItemIdSerializerBase.OverrideInfoKey, OverridePostfixes.PostFixNew.ToString()); + objectContext.SerializerContext.Properties.Set(KeyWithIdSerializer.OverrideKeyInfoKey, OverridePostfixes.PostFixNew.ToString()); } if ((overrideType & OverrideType.Sealed) != 0) { - objectContext.SerializerContext.Properties.Set(ItemIdSerializerBase.OverrideInfoKey, OverridePostfixes.PostFixSealed.ToString()); - } - if (keyPath != null) - { - overrideType = overrides.TryGet(keyPath); - if ((overrideType & OverrideType.New) != 0) - { - objectContext.SerializerContext.Properties.Set(KeyWithIdSerializer.OverrideKeyInfoKey, OverridePostfixes.PostFixNew.ToString()); - } - if ((overrideType & OverrideType.Sealed) != 0) - { - objectContext.SerializerContext.Properties.Set(KeyWithIdSerializer.OverrideKeyInfoKey, OverridePostfixes.PostFixSealed.ToString()); - } + objectContext.SerializerContext.Properties.Set(KeyWithIdSerializer.OverrideKeyInfoKey, OverridePostfixes.PostFixSealed.ToString()); } } - base.WriteDictionaryKey(ref objectContext, key, keyType); } + base.WriteDictionaryKey(ref objectContext, key, keyType); + } - public override object ReadDictionaryValue(ref ObjectContext objectContext, Type valueType, object key) + public override object ReadDictionaryValue(ref ObjectContext objectContext, Type valueType, object key) + { + var path = GetCurrentPath(ref objectContext, true); + if (YamlAssetPath.IsCollectionWithIdType(objectContext.Descriptor.Type, key, out var id)) { - var path = GetCurrentPath(ref objectContext, true); - ItemId id; - if (YamlAssetPath.IsCollectionWithIdType(objectContext.Descriptor.Type, key, out id)) - { - path.PushItemId(id); - } - else - { - path.PushIndex(key); - } - var valueObjectContext = new ObjectContext(objectContext.SerializerContext, null, objectContext.SerializerContext.FindTypeDescriptor(valueType)); - SetCurrentPath(ref valueObjectContext, path); - return ReadYaml(ref valueObjectContext); + path.PushItemId(id); } - - public override void WriteDictionaryValue(ref ObjectContext objectContext, object key, object value, Type valueType) + else { - var path = GetCurrentPath(ref objectContext, true); - ItemId id; - if (YamlAssetPath.IsCollectionWithIdType(objectContext.Descriptor.Type, key, out id)) - { - path.PushItemId(id); - } - else - { - path.PushIndex(key); - } - var itemObjectContext = new ObjectContext(objectContext.SerializerContext, value, objectContext.SerializerContext.FindTypeDescriptor(valueType)); - SetCurrentPath(ref itemObjectContext, path); - WriteYaml(ref itemObjectContext); + path.PushIndex(key); } + var valueObjectContext = new ObjectContext(objectContext.SerializerContext, null, objectContext.SerializerContext.FindTypeDescriptor(valueType)); + SetCurrentPath(ref valueObjectContext, path); + return ReadYaml(ref valueObjectContext); + } - public override bool ShouldSerialize(IMemberDescriptor member, ref ObjectContext objectContext) + public override void WriteDictionaryValue(ref ObjectContext objectContext, object key, object value, Type valueType) + { + var path = GetCurrentPath(ref objectContext, true); + if (YamlAssetPath.IsCollectionWithIdType(objectContext.Descriptor.Type, key, out var id)) { - YamlAssetMetadata overrides; - if (objectContext.SerializerContext.Properties.TryGetValue(OverrideDictionaryKey, out overrides)) - { - var path = GetCurrentPath(ref objectContext, true); - path.PushMember(member.Name); - - var overrideType = overrides.TryGet(path); - if (overrideType != OverrideType.Base) - return true; - } - - return base.ShouldSerialize(member, ref objectContext); + path.PushItemId(id); + } + else + { + path.PushIndex(key); } + var itemObjectContext = new ObjectContext(objectContext.SerializerContext, value, objectContext.SerializerContext.FindTypeDescriptor(valueType)); + SetCurrentPath(ref itemObjectContext, path); + WriteYaml(ref itemObjectContext); + } - public static YamlAssetPath GetCurrentPath(ref ObjectContext objectContext, bool clone) + public override bool ShouldSerialize(IMemberDescriptor member, ref ObjectContext objectContext) + { + if (objectContext.SerializerContext.Properties.TryGetValue(OverrideDictionaryKey, out var overrides)) { - YamlAssetPath path; - path = objectContext.Properties.TryGetValue(MemberPathKey, out path) ? path : new YamlAssetPath(); - if (clone) - { - path = path.Clone(); - } - return path; + var path = GetCurrentPath(ref objectContext, true); + path.PushMember(member.Name); + + var overrideType = overrides.TryGet(path); + if (overrideType != OverrideType.Base) + return true; } - private static void SetCurrentPath(ref ObjectContext objectContext, YamlAssetPath path) + return base.ShouldSerialize(member, ref objectContext); + } + + public static YamlAssetPath GetCurrentPath(ref ObjectContext objectContext, bool clone) + { + YamlAssetPath? path; + path = objectContext.Properties.TryGetValue(MemberPathKey, out path) ? path : new YamlAssetPath(); + if (clone) { - objectContext.Properties.Set(MemberPathKey, path); + path = path.Clone(); } + return path; + } + + private static void SetCurrentPath(ref ObjectContext objectContext, YamlAssetPath path) + { + objectContext.Properties.Set(MemberPathKey, path); + } - internal static string TrimAndParseOverride(string name, out OverrideType[] overrideTypes) + internal static string TrimAndParseOverride(string name, out OverrideType[] overrideTypes) + { + var split = name.Split('~'); + + overrideTypes = new OverrideType[split.Length]; + int i = 0; + var trimmedName = string.Empty; + foreach (var namePart in split) { - var split = name.Split('~'); + var realName = namePart.Trim(OverridePostfixes.PostFixSealed, OverridePostfixes.PostFixNew); - overrideTypes = new OverrideType[split.Length]; - int i = 0; - var trimmedName = string.Empty; - foreach (var namePart in split) + var overrideType = OverrideType.Base; + if (realName.Length != namePart.Length) { - var realName = namePart.Trim(OverridePostfixes.PostFixSealed, OverridePostfixes.PostFixNew); - - var overrideType = OverrideType.Base; - if (realName.Length != namePart.Length) + if (namePart.Contains(OverridePostfixes.PostFixNewSealed) || namePart.EndsWith(OverridePostfixes.PostFixNewSealedAlt, StringComparison.Ordinal)) { - if (namePart.Contains(OverridePostfixes.PostFixNewSealed) || namePart.EndsWith(OverridePostfixes.PostFixNewSealedAlt, StringComparison.Ordinal)) - { - overrideType = OverrideType.New | OverrideType.Sealed; - } - else if (namePart.EndsWith(OverridePostfixes.PostFixNew)) - { - overrideType = OverrideType.New; - } - else if (namePart.EndsWith(OverridePostfixes.PostFixSealed)) - { - overrideType = OverrideType.Sealed; - } + overrideType = OverrideType.New | OverrideType.Sealed; + } + else if (namePart.EndsWith(OverridePostfixes.PostFixNew)) + { + overrideType = OverrideType.New; + } + else if (namePart.EndsWith(OverridePostfixes.PostFixSealed)) + { + overrideType = OverrideType.Sealed; } - overrideTypes[i] = overrideType; - if (i > 0) - trimmedName += '~'; - trimmedName += realName; - ++i; } - return trimmedName; + overrideTypes[i] = overrideType; + if (i > 0) + trimmedName += '~'; + trimmedName += realName; + ++i; } + return trimmedName; } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/AssetPartCollectionSerializer.cs b/sources/assets/Stride.Core.Assets/Yaml/AssetPartCollectionSerializer.cs index f6c86eba5c..8745b0ad1c 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/AssetPartCollectionSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/AssetPartCollectionSerializer.cs @@ -1,7 +1,4 @@ -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; using Stride.Core.Assets; using Stride.Core.Assets.Serializers; using Stride.Core.Assets.Yaml; @@ -10,134 +7,134 @@ using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// A custom serializer for asset part collections, that serializes this dictionary in the form of a collection. +/// +[YamlSerializerFactory(YamlAssetProfile.Name)] +internal class AssetPartCollectionSerializer : CollectionSerializer, IDataCustomVisitor { - /// - /// A custom serializer for asset part collections, that serializes this dictionary in the form of a collection. - /// - [YamlSerializerFactory(YamlAssetProfile.Name)] - internal class AssetPartCollectionSerializer : CollectionSerializer, IDataCustomVisitor + private static readonly PropertyKey OriginalValue = new("OriginalValue", typeof(AssetPartCollectionSerializer)); + + /// + public override IYamlSerializable? TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) { - private static readonly PropertyKey OriginalValue = new PropertyKey("OriginalValue", typeof(AssetPartCollectionSerializer)); + var type = typeDescriptor.Type; + return CanVisit(type) ? this : null; + } - /// - public override IYamlSerializable TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) - { - var type = typeDescriptor.Type; - return CanVisit(type) ? this : null; - } + /// + protected override bool CheckIsSequence(ref ObjectContext objectContext) + { + // We always want to serialize this collection as a sequence. Also, the base implementation relies on the fact that the descriptor + // is a CollectionDescriptor which is not the case for this specific type. + return true; + } - /// - protected override bool CheckIsSequence(ref ObjectContext objectContext) - { - // We always want to serialize this collection as a sequence. Also, the base implementation relies on the fact that the descriptor - // is a CollectionDescriptor which is not the case for this specific type. - return true; - } + /// + protected override void CreateOrTransformObject(ref ObjectContext objectContext) + { + // Build a list that matches the type of part design objects. Not that this relies on the fact that this type is the first generic argument of AssetPartCollection. + var elementType = objectContext.Descriptor.Type.GetGenericArguments()[0]; + var listType = typeof(List<>).MakeGenericType(elementType); + var list = (IList)objectContext.SerializerContext.ObjectFactory.Create(listType); - /// - protected override void CreateOrTransformObject(ref ObjectContext objectContext) + if (objectContext.SerializerContext.IsSerializing) { - // Build a list that matches the type of part design objects. Not that this relies on the fact that this type is the first generic argument of AssetPartCollection. - var elementType = objectContext.Descriptor.Type.GetGenericArguments()[0]; - var listType = typeof(List<>).MakeGenericType(elementType); - var list = (IList)objectContext.SerializerContext.ObjectFactory.Create(listType); - - if (objectContext.SerializerContext.IsSerializing) + var guidToIndex = new Dictionary(); + // If we're writing, lets copy values of the dictionary into the list. + var i = 0; + foreach (DictionaryEntry entry in (IDictionary)objectContext.Instance) { - var guidToIndex = new Dictionary(); - // If we're writing, lets copy values of the dictionary into the list. - var i = 0; - foreach (DictionaryEntry entry in (IDictionary)objectContext.Instance) - { - list.Add(entry.Value); - guidToIndex.Add((Guid)entry.Key, i++); - } - // We need to convert path in attached YAML metadata, from the guid keys of the dictionary to the indices of the list. - FixupPaths(ref objectContext, guidToIndex); + list.Add(entry.Value); + guidToIndex.Add((Guid)entry.Key, i++); } - else - { - // If we're reading, let's store the initial object to transfer items in TransformObjectAfterRead, in case there is no setter for the owning member. - objectContext.Properties.Add(OriginalValue, objectContext.Instance); - } - // Apply the transformation to a list. - objectContext.Instance = list; + // We need to convert path in attached YAML metadata, from the guid keys of the dictionary to the indices of the list. + FixupPaths(ref objectContext, guidToIndex); + } + else + { + // If we're reading, let's store the initial object to transfer items in TransformObjectAfterRead, in case there is no setter for the owning member. + objectContext.Properties.Add(OriginalValue, objectContext.Instance); } + // Apply the transformation to a list. + objectContext.Instance = list; + } - /// - protected override void TransformObjectAfterRead(ref ObjectContext objectContext) + /// + protected override void TransformObjectAfterRead(ref ObjectContext objectContext) + { + // Retrieve the list, and the original AssetPartCollection, and transfer items from one to the other. + var list = (IList)objectContext.Instance; + var partCollection = (IDictionary)objectContext.Properties.Get(OriginalValue)!; + var indexToGuid = new Dictionary(); + for (var i = 0; i < list.Count; ++i) { - // Retrieve the list, and the original AssetPartCollection, and transfer items from one to the other. - var list = (IList)objectContext.Instance; - var partCollection = (IDictionary)objectContext.Properties.Get(OriginalValue); - var indexToGuid = new Dictionary(); - for (var i = 0; i < list.Count; ++i) - { - var partDesign = (IAssetPartDesign)list[i]; - partCollection.Add(partDesign.Part.Id, partDesign); - indexToGuid.Add(i, partDesign.Part.Id); - } + var partDesign = (IAssetPartDesign)list[i]!; + partCollection.Add(partDesign.Part.Id, partDesign); + indexToGuid.Add(i, partDesign.Part.Id); + } - // Restore the correct instance for the serializer. - objectContext.Instance = partCollection; + // Restore the correct instance for the serializer. + objectContext.Instance = partCollection; - // We need to convert path in attached YAML metadata, from the integer indices of the list to the Guid that are keys of the dictionary. - FixupPaths(ref objectContext, indexToGuid); - base.TransformObjectAfterRead(ref objectContext); - } + // We need to convert path in attached YAML metadata, from the integer indices of the list to the Guid that are keys of the dictionary. + FixupPaths(ref objectContext, indexToGuid); + base.TransformObjectAfterRead(ref objectContext); + } - /// - /// Converts all from the metadata to switch between keys of the - /// and the integer indices of the serialized list, and vice-versa. - /// - /// The current type of indices in the metadata. - /// The type of indices to convert to. - /// The current object context of the serialization. - /// The mapping between the source indices and the target indices. - private static void FixupPaths(ref ObjectContext objectContext, Dictionary mapping) + /// + /// Converts all from the metadata to switch between keys of the + /// and the integer indices of the serialized list, and vice-versa. + /// + /// The current type of indices in the metadata. + /// The type of indices to convert to. + /// The current object context of the serialization. + /// The mapping between the source indices and the target indices. + private static void FixupPaths(ref ObjectContext objectContext, Dictionary mapping) + where TIndexSource : notnull + { + var currentPath = AssetObjectSerializerBackend.GetCurrentPath(ref objectContext, false); + foreach (var property in objectContext.SerializerContext.Properties) { - var currentPath = AssetObjectSerializerBackend.GetCurrentPath(ref objectContext, false); - foreach (var property in objectContext.SerializerContext.Properties) + if (typeof(IYamlAssetMetadata).IsAssignableFrom(property.Key.PropertyType)) { - if (typeof(IYamlAssetMetadata).IsAssignableFrom(property.Key.PropertyType)) + var metadata = (IYamlAssetMetadata)property.Value; + foreach (var entry in metadata.Cast().ToList()) { - var metadata = (IYamlAssetMetadata)property.Value; - foreach (var entry in metadata.Cast().ToList()) + var path = (YamlAssetPath)entry.Key; + // Only modify path that are "inside" the collection. + if (path.StartsWith(currentPath)) { - var path = (YamlAssetPath)entry.Key; - // Only modify path that are "inside" the collection. - if (path.StartsWith(currentPath)) - { - // Use the same beginning for the path. - var replacementPath = currentPath.Clone(); - // Retrieve the index that was used (int or Guid). - var indexSource = (TIndexSource)path.Elements[currentPath.Elements.Count].Value; - // Fetch the corresponding target index (int or Guid). - var indexTarget = mapping[indexSource]; - // Replace the initial index by the target index in our new path. - replacementPath.PushIndex(indexTarget); - // Finally push the rest of the original path, that shouldn't be different. - path.Elements.Skip(replacementPath.Elements.Count).ForEach(x => replacementPath.Push(x)); - // And replace the entry in the dictionary of metadata - metadata.Remove(path); - metadata.Set(replacementPath, entry.Value); - } + // Use the same beginning for the path. + var replacementPath = currentPath.Clone(); + // Retrieve the index that was used (int or Guid). + var indexSource = (TIndexSource)path.Elements[currentPath.Elements.Count].Value; + // Fetch the corresponding target index (int or Guid). + var indexTarget = mapping[indexSource]; + // Replace the initial index by the target index in our new path. + replacementPath.PushIndex(indexTarget); + // Finally push the rest of the original path, that shouldn't be different. + path.Elements.Skip(replacementPath.Elements.Count).ForEach(x => replacementPath.Push(x)); + // And replace the entry in the dictionary of metadata + metadata.Remove(path); + metadata.Set(replacementPath, entry.Value); } } } } + } - /// - public bool CanVisit(Type type) - { - return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(AssetPartCollection<,>); - } + /// + public bool CanVisit(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(AssetPartCollection<,>); + } - /// - public void Visit(ref VisitorContext context) - { - context.Visitor.VisitObject(context.Instance, context.Descriptor, false); - } + /// + public void Visit(ref VisitorContext context) + { + context.Visitor.VisitObject(context.Instance, context.Descriptor, false); } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/AssetYamlSerializer.cs b/sources/assets/Stride.Core.Assets/Yaml/AssetYamlSerializer.cs index 33dd3f924d..bbf0e74e41 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/AssetYamlSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/AssetYamlSerializer.cs @@ -1,213 +1,204 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; + using Stride.Core.Assets.Serializers; using Stride.Core.Reflection; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -using SerializerContext = Stride.Core.Yaml.Serialization.SerializerContext; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// Default Yaml serializer used to serialize assets by default. +/// +public class AssetYamlSerializer : YamlSerializerBase { - /// - /// Default Yaml serializer used to serialize assets by default. - /// - public class AssetYamlSerializer : YamlSerializerBase - { - private event Action> PrepareMembersEvent; + private event Action>? PrepareMembersEvent; - private Serializer serializer; + private Serializer? serializer; - public static AssetYamlSerializer Default { get; set; } = new AssetYamlSerializer(); + public static AssetYamlSerializer Default { get; set; } = new AssetYamlSerializer(); - public event Action> PrepareMembers + public event Action> PrepareMembers + { + add { - add - { - if (serializer != null) - throw new InvalidOperationException("Event handlers can't be added or removed after the serializer has been initialized."); + if (serializer != null) + throw new InvalidOperationException("Event handlers can't be added or removed after the serializer has been initialized."); - PrepareMembersEvent += value; - } - remove - { - if (serializer != null) - throw new InvalidOperationException("Event handlers can't be added or removed after the serializer has been initialized."); - PrepareMembersEvent -= value; - } - } - - /// - /// Deserializes an object from the specified stream (expecting a YAML string). - /// - /// A YAML string from a stream. - /// The expected type. - /// The context settings. - /// An instance of the YAML data. - public object Deserialize(Stream stream, Type expectedType = null, SerializerContextSettings contextSettings = null) - { - bool aliasOccurred; - PropertyContainer contextProperties; - return Deserialize(stream, expectedType, contextSettings, out aliasOccurred, out contextProperties); + PrepareMembersEvent += value; } - - /// - /// Deserializes an object from the specified stream (expecting a YAML string). - /// - /// A YAML string from a stream . - /// The expected type. - /// The context settings. - /// if set to true a class/field/property/enum name has been renamed during deserialization. - /// A dictionary or properties that were generated during deserialization. - /// An instance of the YAML data. - public object Deserialize(Stream stream, Type expectedType, SerializerContextSettings contextSettings, out bool aliasOccurred, out PropertyContainer contextProperties) + remove { - EnsureYamlSerializer(); - SerializerContext context; - var result = serializer.Deserialize(stream, expectedType, contextSettings, out context); - aliasOccurred = context.HasRemapOccurred; - contextProperties = context.Properties; - return result; + if (serializer != null) + throw new InvalidOperationException("Event handlers can't be added or removed after the serializer has been initialized."); + PrepareMembersEvent -= value; } + } - /// - /// Deserializes an object from the specified stream (expecting a YAML string). - /// - /// A YAML event reader. - /// The value. - /// The expected type. - /// A dictionary or properties that were generated during deserialization. - /// The context settings. - /// An instance of the YAML data. - public object Deserialize(EventReader eventReader, object value, Type expectedType, out PropertyContainer contextProperties, SerializerContextSettings contextSettings = null) - { - EnsureYamlSerializer(); - SerializerContext context; - var result = serializer.Deserialize(eventReader, expectedType, value, contextSettings, out context); - contextProperties = context.Properties; - return result; - } + /// + /// Deserializes an object from the specified stream (expecting a YAML string). + /// + /// A YAML string from a stream. + /// The expected type. + /// The context settings. + /// An instance of the YAML data. + public object Deserialize(Stream stream, Type? expectedType = null, SerializerContextSettings? contextSettings = null) + { + return Deserialize(stream, expectedType, contextSettings, out _, out _); + } - /// - /// Deserializes an object from the specified stream (expecting a YAML string). - /// - /// A YAML string from a stream . - /// An instance of the YAML data. - public IEnumerable DeserializeMultiple(Stream stream) - { - EnsureYamlSerializer(); + /// + /// Deserializes an object from the specified stream (expecting a YAML string). + /// + /// A YAML string from a stream . + /// The expected type. + /// The context settings. + /// if set to true a class/field/property/enum name has been renamed during deserialization. + /// A dictionary or properties that were generated during deserialization. + /// An instance of the YAML data. + public object Deserialize(Stream stream, Type? expectedType, SerializerContextSettings? contextSettings, out bool aliasOccurred, out PropertyContainer contextProperties) + { + EnsureYamlSerializer(); + var result = serializer!.Deserialize(stream, expectedType, contextSettings, out var context); + aliasOccurred = context.HasRemapOccurred; + contextProperties = context.Properties; + return result; + } - var input = new StreamReader(stream); - var reader = new EventReader(new Parser(input)); - reader.Expect(); + /// + /// Deserializes an object from the specified stream (expecting a YAML string). + /// + /// A YAML event reader. + /// The value. + /// The expected type. + /// A dictionary or properties that were generated during deserialization. + /// The context settings. + /// An instance of the YAML data. + public object Deserialize(EventReader eventReader, object value, Type expectedType, out PropertyContainer contextProperties, SerializerContextSettings? contextSettings = null) + { + EnsureYamlSerializer(); + var result = serializer!.Deserialize(eventReader, expectedType, value, contextSettings, out var context); + contextProperties = context.Properties; + return result; + } - while (reader.Accept()) - { - // Deserialize the document - var doc = serializer.Deserialize(reader); + /// + /// Deserializes an object from the specified stream (expecting a YAML string). + /// + /// A YAML string from a stream . + /// An instance of the YAML data. + public IEnumerable DeserializeMultiple(Stream stream) + { + EnsureYamlSerializer(); - yield return doc; - } - } + var input = new StreamReader(stream); + var reader = new EventReader(new Parser(input)); + reader.Expect(); - /// - /// Serializes an object to specified stream in YAML format. - /// - /// The emitter. - /// The object to serialize. - /// The type. - /// The context settings. - public void Serialize(IEmitter emitter, object instance, Type type, SerializerContextSettings contextSettings = null) + while (reader.Accept()) { - EnsureYamlSerializer(); - serializer.Serialize(emitter, instance, type, contextSettings); - } + // Deserialize the document + var doc = serializer!.Deserialize(reader); - /// - /// Serializes an object to specified stream in YAML format. - /// - /// The stream to receive the YAML representation of the object. - /// The instance. - /// The expected type. - /// The context settings. - public void Serialize(Stream stream, object instance, Type type = null, SerializerContextSettings contextSettings = null) - { - EnsureYamlSerializer(); - serializer.Serialize(stream, instance, type, contextSettings); + yield return doc; } + } - /// - /// Gets the serializer settings. - /// - /// SerializerSettings. - public SerializerSettings GetSerializerSettings() - { - EnsureYamlSerializer(); - return serializer.Settings; - } + /// + /// Serializes an object to specified stream in YAML format. + /// + /// The emitter. + /// The object to serialize. + /// The type. + /// The context settings. + public void Serialize(IEmitter emitter, object instance, Type type, SerializerContextSettings? contextSettings = null) + { + EnsureYamlSerializer(); + serializer!.Serialize(emitter, instance, type, contextSettings); + } + + /// + /// Serializes an object to specified stream in YAML format. + /// + /// The stream to receive the YAML representation of the object. + /// The instance. + /// The expected type. + /// The context settings. + public void Serialize(Stream stream, object instance, Type? type = null, SerializerContextSettings? contextSettings = null) + { + EnsureYamlSerializer(); + serializer!.Serialize(stream, instance, type, contextSettings); + } + + /// + /// Gets the serializer settings. + /// + /// SerializerSettings. + public SerializerSettings GetSerializerSettings() + { + EnsureYamlSerializer(); + return serializer!.Settings; + } - /// - /// Reset the assembly cache used by this class. - /// - public override void ResetCache() + /// + /// Reset the assembly cache used by this class. + /// + public override void ResetCache() + { + lock (Lock) { - lock (Lock) - { - // Reset the current serializer as the set of assemblies has changed - serializer = null; - } + // Reset the current serializer as the set of assemblies has changed + serializer = null; } + } - private void EnsureYamlSerializer() + private void EnsureYamlSerializer() + { + lock (Lock) { - lock (Lock) + if (serializer == null) { - if (serializer == null) - { - // var clock = Stopwatch.StartNew(); + // var clock = Stopwatch.StartNew(); - var config = new SerializerSettings - { - EmitAlias = false, - LimitPrimitiveFlowSequence = 0, - Attributes = new AttributeRegistry(), - PreferredIndent = 4, - EmitShortTypeName = true, - ComparerForKeySorting = new DefaultMemberComparer(), - PreSerializer = new ContextAttributeSerializer(), - PostSerializer = new ErrorRecoverySerializer(), - SerializerFactorySelector = new ProfileSerializerFactorySelector(YamlSerializerFactoryAttribute.Default, "Assets"), - ChainedSerializerFactory = x => - { - var routingSerializer = x.FindNext(); - if (routingSerializer == null) - throw new InvalidOperationException("RoutingSerializer expected in the chain of serializers"); - // Prepend the IdentifiableObjectSerializer just before the routing serializer - routingSerializer.Prepend(new IdentifiableObjectSerializer()); - // Prepend the ContextAttributeSerializer just before the routing serializer - routingSerializer.Prepend(new ContextAttributeSerializer()); - // Prepend the ErrorRecoverySerializer at the beginning - routingSerializer.First.Prepend(new ErrorRecoverySerializer()); - } - }; - - config.Attributes.PrepareMembersCallback += (objDesc, members) => PrepareMembersEvent?.Invoke(objDesc, members); - - for (var index = RegisteredAssemblies.Count - 1; index >= 0; index--) + var config = new SerializerSettings + { + EmitAlias = false, + LimitPrimitiveFlowSequence = 0, + Attributes = new AttributeRegistry(), + PreferredIndent = 4, + EmitShortTypeName = true, + ComparerForKeySorting = new DefaultMemberComparer(), + PreSerializer = new ContextAttributeSerializer(), + PostSerializer = new ErrorRecoverySerializer(), + SerializerFactorySelector = new ProfileSerializerFactorySelector(YamlSerializerFactoryAttribute.Default, "Assets"), + ChainedSerializerFactory = x => { - var registeredAssembly = RegisteredAssemblies[index]; - config.RegisterAssembly(registeredAssembly); + var routingSerializer = x.FindNext() + ?? throw new InvalidOperationException("RoutingSerializer expected in the chain of serializers"); + // Prepend the IdentifiableObjectSerializer just before the routing serializer + routingSerializer.Prepend(new IdentifiableObjectSerializer()); + // Prepend the ContextAttributeSerializer just before the routing serializer + routingSerializer.Prepend(new ContextAttributeSerializer()); + // Prepend the ErrorRecoverySerializer at the beginning + routingSerializer.First.Prepend(new ErrorRecoverySerializer()); } + }; - var newSerializer = new Serializer(config); - newSerializer.Settings.ObjectSerializerBackend = new AssetObjectSerializerBackend(TypeDescriptorFactory.Default); + config.Attributes.PrepareMembersCallback += (objDesc, members) => PrepareMembersEvent?.Invoke(objDesc, members); - // Log.Info("New YAML serializer created in {0}ms", clock.ElapsedMilliseconds); - serializer = newSerializer; + for (var index = RegisteredAssemblies.Count - 1; index >= 0; index--) + { + var registeredAssembly = RegisteredAssemblies[index]; + config.RegisterAssembly(registeredAssembly); } + + var newSerializer = new Serializer(config); + newSerializer.Settings.ObjectSerializerBackend = new AssetObjectSerializerBackend(TypeDescriptorFactory.Default); + + // Log.Info("New YAML serializer created in {0}ms", clock.ElapsedMilliseconds); + serializer = newSerializer; } } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/AttachedYamlAssetMetadata.cs b/sources/assets/Stride.Core.Assets/Yaml/AttachedYamlAssetMetadata.cs index e3758ca98b..eb4ad4cefc 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/AttachedYamlAssetMetadata.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/AttachedYamlAssetMetadata.cs @@ -1,74 +1,66 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; -using Stride.Core.Annotations; -namespace Stride.Core.Assets.Yaml +namespace Stride.Core.Assets.Yaml; + +/// +/// An interface representing an object that has attached to it. +/// +public class AttachedYamlAssetMetadata { + private readonly Dictionary yamlMetadata = []; + /// - /// An interface representing an object that has attached to it. + /// Attaches metadata to this object. /// - public class AttachedYamlAssetMetadata + /// The type of metadata being attached. + /// The property key that identifies this type of metadata. + /// The metadata to attach. + public void AttachMetadata(PropertyKey> key, YamlAssetMetadata metadata) { - private readonly Dictionary yamlMetadata = new Dictionary(); - - /// - /// Attaches metadata to this object. - /// - /// The type of metadata being attached. - /// The property key that identifies this type of metadata. - /// The metadata to attach. - public void AttachMetadata([NotNull] PropertyKey> key, [NotNull] YamlAssetMetadata metadata) - { - if (key == null) throw new ArgumentNullException(nameof(key)); - if (metadata == null) throw new ArgumentNullException(nameof(metadata)); - yamlMetadata[key] = metadata; - } + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(metadata); + yamlMetadata[key] = metadata; + } - /// - /// Retrieves metadata attached to this object. - /// - /// The type of metadata being attached. - /// The property key that identifies this type of metadata. - /// The corresponding metadata attached to this object, or null if it couldn't be found. - [CanBeNull] - public YamlAssetMetadata RetrieveMetadata([NotNull] PropertyKey> key) - { - if (key == null) throw new ArgumentNullException(nameof(key)); - IYamlAssetMetadata metadata; - yamlMetadata.TryGetValue(key, out metadata); - return (YamlAssetMetadata)metadata; - } + /// + /// Retrieves metadata attached to this object. + /// + /// The type of metadata being attached. + /// The property key that identifies this type of metadata. + /// The corresponding metadata attached to this object, or null if it couldn't be found. + public YamlAssetMetadata? RetrieveMetadata(PropertyKey> key) + { + ArgumentNullException.ThrowIfNull(key); + yamlMetadata.TryGetValue(key, out var metadata); + return (YamlAssetMetadata?)metadata; + } - public void CopyInto(AttachedYamlAssetMetadata target) + public void CopyInto(AttachedYamlAssetMetadata target) + { + foreach (var metadata in yamlMetadata) { - foreach (var metadata in yamlMetadata) - { - target.yamlMetadata.Add(metadata.Key, metadata.Value); - } + target.yamlMetadata.Add(metadata.Key, metadata.Value); } + } - internal PropertyContainer ToPropertyContainer() + internal PropertyContainer ToPropertyContainer() + { + var container = new PropertyContainer(); + foreach (var metadata in yamlMetadata) { - var container = new PropertyContainer(); - foreach (var metadata in yamlMetadata) - { - container.SetObject(metadata.Key, metadata.Value); - } - return container; + container.SetObject(metadata.Key, metadata.Value); } + return container; + } - [NotNull] - internal static AttachedYamlAssetMetadata FromPropertyContainer(PropertyContainer container) + internal static AttachedYamlAssetMetadata FromPropertyContainer(PropertyContainer container) + { + var result = new AttachedYamlAssetMetadata(); + foreach (var property in container) { - var result = new AttachedYamlAssetMetadata(); - foreach (var property in container) - { - result.yamlMetadata.Add(property.Key, (IYamlAssetMetadata)property.Value); - } - return result; + result.yamlMetadata.Add(property.Key, (IYamlAssetMetadata)property.Value); } + return result; } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/CollectionWithIdsSerializer.cs b/sources/assets/Stride.Core.Assets/Yaml/CollectionWithIdsSerializer.cs index 5fc9378273..0dae92c917 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/CollectionWithIdsSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/CollectionWithIdsSerializer.cs @@ -1,168 +1,161 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; using System.Reflection; using Stride.Core.Reflection; using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// An implementation of for actual collections. +/// +[YamlSerializerFactory("Assets")] +public class CollectionWithIdsSerializer : CollectionWithIdsSerializerBase { /// - /// An implementation of for actual collections. + /// A collection serializer used in case we determine that the given collection should not be serialized with ids. /// - [YamlSerializerFactory("Assets")] - public class CollectionWithIdsSerializer : CollectionWithIdsSerializerBase - { - /// - /// A collection serializer used in case we determine that the given collection should not be serialized with ids. - /// - private readonly CollectionSerializer collectionSerializer = new CollectionSerializer(); + private readonly CollectionSerializer collectionSerializer = new(); - /// - public override IYamlSerializable TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) + /// + public override IYamlSerializable? TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) + { + if (typeDescriptor is CollectionDescriptor) { - if (typeDescriptor is CollectionDescriptor) - { - var dataStyle = typeDescriptor.Type.GetCustomAttribute(); - if (dataStyle == null || dataStyle.Style != DataStyle.Compact) - return this; - } - return null; + var dataStyle = typeDescriptor.Type.GetCustomAttribute(); + if (dataStyle == null || dataStyle.Style != DataStyle.Compact) + return this; } + return null; + } - /// - protected override void ReadYamlAfterTransform(ref ObjectContext objectContext, bool transformed) - { - if (transformed) - base.ReadYamlAfterTransform(ref objectContext, true); - else - GetCollectionSerializerForNonTransformedObject().ReadYaml(ref objectContext); - } + /// + protected override void ReadYamlAfterTransform(ref ObjectContext objectContext, bool transformed) + { + if (transformed) + base.ReadYamlAfterTransform(ref objectContext, true); + else + GetCollectionSerializerForNonTransformedObject().ReadYaml(ref objectContext); + } - /// - protected override void WriteYamlAfterTransform(ref ObjectContext objectContext, bool transformed) + /// + protected override void WriteYamlAfterTransform(ref ObjectContext objectContext, bool transformed) + { + if (transformed) + base.WriteYamlAfterTransform(ref objectContext, true); + else + GetCollectionSerializerForNonTransformedObject().WriteYaml(ref objectContext); + } + + protected virtual CollectionSerializer GetCollectionSerializerForNonTransformedObject() + { + return collectionSerializer; + } + + /// + protected override void TransformObjectAfterRead(ref ObjectContext objectContext) + { + if (!objectContext.Properties.TryGetValue(InstanceInfoKey, out var info)) { - if (transformed) - base.WriteYamlAfterTransform(ref objectContext, true); - else - GetCollectionSerializerForNonTransformedObject().WriteYaml(ref objectContext); + base.TransformObjectAfterRead(ref objectContext); + return; } - protected virtual CollectionSerializer GetCollectionSerializerForNonTransformedObject() + var instance = info.Instance ?? objectContext.SerializerContext.ObjectFactory.Create(info.Descriptor.Type); + objectContext.Properties.TryGetValue(DeletedItemsKey, out var deletedItems); + TransformAfterDeserialization((IDictionary)objectContext.Instance, info.Descriptor, instance, deletedItems); + objectContext.Instance = instance; + + base.TransformObjectAfterRead(ref objectContext); + } + + /// + protected override object TransformForSerialization(ITypeDescriptor descriptor, object collection) + { + var instance = CreatEmptyContainer(descriptor); + if (!CollectionItemIdHelper.TryGetCollectionItemIds(collection, out var identifier)) { - return collectionSerializer; + identifier = []; } - - /// - protected override void TransformObjectAfterRead(ref ObjectContext objectContext) + var i = 0; + foreach (var item in (IEnumerable)collection) { - InstanceInfo info; - if (!objectContext.Properties.TryGetValue(InstanceInfoKey, out info)) + if (!identifier.TryGet(i, out var id)) { - base.TransformObjectAfterRead(ref objectContext); - return; + id = ItemId.New(); + identifier.Add(i, id); } + instance.Add(id, item); + ++i; + } - var instance = info.Instance ?? objectContext.SerializerContext.ObjectFactory.Create(info.Descriptor.Type); - ICollection deletedItems; - objectContext.Properties.TryGetValue(DeletedItemsKey, out deletedItems); - TransformAfterDeserialization((IDictionary)objectContext.Instance, info.Descriptor, instance, deletedItems); - objectContext.Instance = instance; + return instance; + } - base.TransformObjectAfterRead(ref objectContext); - } + /// + protected override IDictionary CreatEmptyContainer(ITypeDescriptor descriptor) + { + var collectionDescriptor = (CollectionDescriptor)descriptor; + var type = typeof(CollectionWithItemIds<>).MakeGenericType(collectionDescriptor.ElementType); + if (type.GetConstructor(Type.EmptyTypes) == null) + throw new InvalidOperationException("The type of collection does not have a parameterless constructor."); + return (IDictionary)Activator.CreateInstance(type)!; + } - /// - protected override object TransformForSerialization(ITypeDescriptor descriptor, object collection) + /// + protected override void TransformAfterDeserialization(IDictionary container, ITypeDescriptor targetDescriptor, object targetCollection, ICollection? deletedItems = null) + { + var collectionDescriptor = (CollectionDescriptor)targetDescriptor; + var type = typeof(CollectionWithItemIds<>).MakeGenericType(collectionDescriptor.ElementType); + if (!type.IsInstanceOfType(container)) + throw new InvalidOperationException("The given container does not match the expected type."); + var identifier = CollectionItemIdHelper.GetCollectionItemIds(targetCollection); + identifier.Clear(); + var i = 0; + var enumerator = container.GetEnumerator(); + while (enumerator.MoveNext()) { - var instance = CreatEmptyContainer(descriptor); - CollectionItemIdentifiers identifier; - if (!CollectionItemIdHelper.TryGetCollectionItemIds(collection, out identifier)) + collectionDescriptor.Add(targetCollection, enumerator.Value); + if (targetDescriptor.Category == DescriptorCategory.Set) { - identifier = new CollectionItemIdentifiers(); + identifier.Add(enumerator.Value!, (ItemId)enumerator.Key); } - var i = 0; - foreach (var item in (IEnumerable)collection) + else { - ItemId id; - if (!identifier.TryGet(i, out id)) - { - id = ItemId.New(); - identifier.Add(i, id); - } - instance.Add(id, item); - ++i; + identifier.Add(i, (ItemId)enumerator.Key); } - - return instance; + ++i; } - - /// - protected override IDictionary CreatEmptyContainer(ITypeDescriptor descriptor) + if (deletedItems != null) { - var collectionDescriptor = (CollectionDescriptor)descriptor; - var type = typeof(CollectionWithItemIds<>).MakeGenericType(collectionDescriptor.ElementType); - if (type.GetConstructor(Type.EmptyTypes) == null) - throw new InvalidOperationException("The type of collection does not have a parameterless constructor."); - return (IDictionary)Activator.CreateInstance(type); - } - - /// - protected override void TransformAfterDeserialization(IDictionary container, ITypeDescriptor targetDescriptor, object targetCollection, ICollection deletedItems = null) - { - var collectionDescriptor = (CollectionDescriptor)targetDescriptor; - var type = typeof(CollectionWithItemIds<>).MakeGenericType(collectionDescriptor.ElementType); - if (!type.IsInstanceOfType(container)) - throw new InvalidOperationException("The given container does not match the expected type."); - var identifier = CollectionItemIdHelper.GetCollectionItemIds(targetCollection); - identifier.Clear(); - var i = 0; - var enumerator = container.GetEnumerator(); - while (enumerator.MoveNext()) + foreach (var deletedItem in deletedItems) { - collectionDescriptor.Add(targetCollection, enumerator.Value); - if (targetDescriptor.Category == DescriptorCategory.Set) - { - identifier.Add(enumerator.Value, (ItemId)enumerator.Key); - } - else - { - identifier.Add(i, (ItemId)enumerator.Key); - } - ++i; - } - if (deletedItems != null) - { - foreach (var deletedItem in deletedItems) - { - identifier.MarkAsDeleted(deletedItem); - } + identifier.MarkAsDeleted(deletedItem); } } + } - protected override void WriteDeletedItems(ref ObjectContext objectContext) + protected override void WriteDeletedItems(ref ObjectContext objectContext) + { + objectContext.Properties.TryGetValue(DeletedItemsKey, out var deletedItems); + if (deletedItems != null) { - ICollection deletedItems; - objectContext.Properties.TryGetValue(DeletedItemsKey, out deletedItems); - if (deletedItems != null) + var keyValueType = new KeyValuePair(typeof(ItemId), typeof(string)); + foreach (var deletedItem in deletedItems) { - var keyValueType = new KeyValuePair(typeof(ItemId), typeof(string)); - foreach (var deletedItem in deletedItems) - { - var entry = new KeyValuePair(deletedItem, YamlDeletedKey); - WriteDictionaryItem(ref objectContext, entry, keyValueType); - } + var entry = new KeyValuePair(deletedItem, YamlDeletedKey); + WriteDictionaryItem(ref objectContext, entry, keyValueType); } } + } - protected override KeyValuePair ReadDeletedDictionaryItem(ref ObjectContext objectContext, object keyResult) - { - var valueResult = objectContext.ObjectSerializerBackend.ReadDictionaryValue(ref objectContext, typeof(string), keyResult); - var id = (ItemId)keyResult; - return new KeyValuePair(id, valueResult); - } + protected override KeyValuePair ReadDeletedDictionaryItem(ref ObjectContext objectContext, object keyResult) + { + var valueResult = objectContext.ObjectSerializerBackend.ReadDictionaryValue(ref objectContext, typeof(string), keyResult); + var id = (ItemId)keyResult; + return new KeyValuePair(id, valueResult); } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/CollectionWithIdsSerializerBase.cs b/sources/assets/Stride.Core.Assets/Yaml/CollectionWithIdsSerializerBase.cs index 83a31be9d6..8067736392 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/CollectionWithIdsSerializerBase.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/CollectionWithIdsSerializerBase.cs @@ -1,276 +1,269 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; -using System.Linq; using Stride.Core.Diagnostics; using Stride.Core.Reflection; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// A base class to serialize collections with unique identifiers for each item. +/// +[YamlSerializerFactory("Assets")] +public abstract class CollectionWithIdsSerializerBase : DictionarySerializer { /// - /// A base class to serialize collections with unique identifiers for each item. + /// A string token to identify deleted items in a collection. + /// + public const string YamlDeletedKey = "~(Deleted)"; + /// + /// A property key to indicate whether a collection has non-identifiable items + /// + public static readonly PropertyKey NonIdentifiableCollectionItemsKey = new("NonIdentifiableCollectionItems", typeof(CollectionWithIdsSerializer)); + /// + /// A key that identifies the information about the instance that we need the store in the dictionary. /// - [YamlSerializerFactory("Assets")] - public abstract class CollectionWithIdsSerializerBase : DictionarySerializer + protected static readonly PropertyKey InstanceInfoKey = new("InstanceInfo", typeof(CollectionWithIdsSerializer)); + /// + /// A key that identifies deleted items during deserialization. + /// + protected static readonly PropertyKey> DeletedItemsKey = new("DeletedItems", typeof(CollectionWithIdsSerializer)); + + /// + /// A structure containing the information about the instance that we need the store in the dictionary. + /// + protected internal class InstanceInfo { - /// - /// A string token to identify deleted items in a collection. - /// - public const string YamlDeletedKey = "~(Deleted)"; - /// - /// A property key to indicate whether a collection has non-identifiable items - /// - public static readonly PropertyKey NonIdentifiableCollectionItemsKey = new PropertyKey("NonIdentifiableCollectionItems", typeof(CollectionWithIdsSerializer)); - /// - /// A key that identifies the information about the instance that we need the store in the dictionary. - /// - protected static readonly PropertyKey InstanceInfoKey = new PropertyKey("InstanceInfo", typeof(CollectionWithIdsSerializer)); - /// - /// A key that identifies deleted items during deserialization. - /// - protected static readonly PropertyKey> DeletedItemsKey = new PropertyKey>("DeletedItems", typeof(CollectionWithIdsSerializer)); - - /// - /// A structure containing the information about the instance that we need the store in the dictionary. - /// - protected internal class InstanceInfo - { - public InstanceInfo(object instance, ITypeDescriptor typeDescriptor) - { - Instance = instance; - Descriptor = typeDescriptor; - } - public readonly object Instance; - public readonly ITypeDescriptor Descriptor; + public InstanceInfo(object instance, ITypeDescriptor typeDescriptor) + { + Instance = instance; + Descriptor = typeDescriptor; } + public readonly object Instance; + public readonly ITypeDescriptor Descriptor; + } - public override object ReadYaml(ref ObjectContext objectContext) - { - // Create or transform the value to deserialize - // If the new value to serialize is not the same as the one we were expecting to serialize - CreateOrTransformObject(ref objectContext); - var newValue = objectContext.Instance; - - var transformed = false; - if (newValue != null && newValue.GetType() != objectContext.Descriptor.Type) - { - transformed = true; - objectContext.Descriptor = objectContext.SerializerContext.FindTypeDescriptor(newValue.GetType()); - } + public override object ReadYaml(ref ObjectContext objectContext) + { + // Create or transform the value to deserialize + // If the new value to serialize is not the same as the one we were expecting to serialize + CreateOrTransformObject(ref objectContext); + var newValue = objectContext.Instance; - ReadYamlAfterTransform(ref objectContext, transformed); + var transformed = false; + if (newValue != null && newValue.GetType() != objectContext.Descriptor.Type) + { + transformed = true; + objectContext.Descriptor = objectContext.SerializerContext.FindTypeDescriptor(newValue.GetType()); + } - TransformObjectAfterRead(ref objectContext); + ReadYamlAfterTransform(ref objectContext, transformed); - // Process members - return objectContext.Instance; - } + TransformObjectAfterRead(ref objectContext); - public override void WriteYaml(ref ObjectContext objectContext) - { - // TODO: the API could be changed at the ObjectSerializer level so we can, through override: - // TODO: - customize what to do -if- the object got transformed (currently: ObjectSerializer = routing, here = keep same serializer) - // TODO: - override WriteYamlAfterTransform without overriding WriteYaml - // TODO: - and similar with reading + // Process members + return objectContext.Instance; + } - CreateOrTransformObject(ref objectContext); - var newValue = objectContext.Instance; + public override void WriteYaml(ref ObjectContext objectContext) + { + // TODO: the API could be changed at the ObjectSerializer level so we can, through override: + // TODO: - customize what to do -if- the object got transformed (currently: ObjectSerializer = routing, here = keep same serializer) + // TODO: - override WriteYamlAfterTransform without overriding WriteYaml + // TODO: - and similar with reading - var transformed = false; - if (newValue != null && newValue.GetType() != objectContext.Descriptor.Type) - { - transformed = true; - objectContext.Descriptor = objectContext.SerializerContext.FindTypeDescriptor(newValue.GetType()); - } + CreateOrTransformObject(ref objectContext); + var newValue = objectContext.Instance; - WriteYamlAfterTransform(ref objectContext, transformed); + var transformed = false; + if (newValue != null && newValue.GetType() != objectContext.Descriptor.Type) + { + transformed = true; + objectContext.Descriptor = objectContext.SerializerContext.FindTypeDescriptor(newValue.GetType()); } - /// - protected override void CreateOrTransformObject(ref ObjectContext objectContext) - { - base.CreateOrTransformObject(ref objectContext); + WriteYamlAfterTransform(ref objectContext, transformed); + } - // Allow to deserialize the old way - if (!objectContext.SerializerContext.IsSerializing && objectContext.Reader.Accept()) - return; + /// + protected override void CreateOrTransformObject(ref ObjectContext objectContext) + { + base.CreateOrTransformObject(ref objectContext); - // Ignore collections flagged as having non-identifiable items - if (!AreCollectionItemsIdentifiable(ref objectContext)) - return; + // Allow to deserialize the old way + if (!objectContext.SerializerContext.IsSerializing && objectContext.Reader.Accept()) + return; - // Store the information on the actual instance before transforming. - var info = new InstanceInfo(objectContext.Instance, objectContext.Descriptor); - objectContext.Properties.Add(InstanceInfoKey, info); + // Ignore collections flagged as having non-identifiable items + if (!AreCollectionItemsIdentifiable(ref objectContext)) + return; - if (objectContext.SerializerContext.IsSerializing && objectContext.Instance != null) - { - // Store deleted items in the context - CollectionItemIdentifiers identifier; - if (CollectionItemIdHelper.TryGetCollectionItemIds(objectContext.Instance, out identifier)) - { - var deletedItems = identifier.DeletedItems.ToList(); - deletedItems.Sort(); - objectContext.Properties.Add(DeletedItemsKey, deletedItems); - } - // We're serializing, transform the collection to a dictionary of - objectContext.Instance = TransformForSerialization(objectContext.Descriptor, objectContext.Instance); - } - else + // Store the information on the actual instance before transforming. + var info = new InstanceInfo(objectContext.Instance, objectContext.Descriptor); + objectContext.Properties.Add(InstanceInfoKey, info); + + if (objectContext.SerializerContext.IsSerializing && objectContext.Instance != null) + { + // Store deleted items in the context + if (CollectionItemIdHelper.TryGetCollectionItemIds(objectContext.Instance, out var identifier)) { - // We're deserializing, create an empty dictionary of - objectContext.Instance = CreatEmptyContainer(objectContext.Descriptor); + var deletedItems = identifier.DeletedItems.ToList(); + deletedItems.Sort(); + objectContext.Properties.Add(DeletedItemsKey, deletedItems); } + // We're serializing, transform the collection to a dictionary of + objectContext.Instance = TransformForSerialization(objectContext.Descriptor, objectContext.Instance); } - - /// - /// Reads the dictionary items key-values. - /// - /// - protected override void ReadDictionaryItems(ref ObjectContext objectContext) + else { - var dictionaryDescriptor = (DictionaryDescriptor)objectContext.Descriptor; + // We're deserializing, create an empty dictionary of + objectContext.Instance = CreatEmptyContainer(objectContext.Descriptor); + } + } - var deletedItems = new HashSet(); + /// + /// Reads the dictionary items key-values. + /// + /// + protected override void ReadDictionaryItems(ref ObjectContext objectContext) + { + var dictionaryDescriptor = (DictionaryDescriptor)objectContext.Descriptor; - var reader = objectContext.Reader; - while (!reader.Accept()) - { - var currentDepth = objectContext.Reader.CurrentDepth; - var startParsingEvent = objectContext.Reader.Parser.Current; + var deletedItems = new HashSet(); - try + var reader = objectContext.Reader; + while (!reader.Accept()) + { + var currentDepth = objectContext.Reader.CurrentDepth; + var startParsingEvent = objectContext.Reader.Parser.Current; + + try + { + // Read key and value + var keyValue = ReadDictionaryItem(ref objectContext, new KeyValuePair(dictionaryDescriptor.KeyType, dictionaryDescriptor.ValueType)); + if (!Equals(keyValue.Value, YamlDeletedKey) || keyValue.Key is not ItemId id) { - // Read key and value - var keyValue = ReadDictionaryItem(ref objectContext, new KeyValuePair(dictionaryDescriptor.KeyType, dictionaryDescriptor.ValueType)); - if (!Equals(keyValue.Value, YamlDeletedKey) || !(keyValue.Key is ItemId)) - { - dictionaryDescriptor.AddToDictionary(objectContext.Instance, keyValue.Key, keyValue.Value); - } - else - { - deletedItems.Add((ItemId)keyValue.Key); - } + dictionaryDescriptor.AddToDictionary(objectContext.Instance, keyValue.Key, keyValue.Value); } - catch (YamlException ex) + else { - if (objectContext.SerializerContext.AllowErrors) - { - var logger = objectContext.SerializerContext.Logger; - logger?.Warning($"{ex.Message}, this dictionary item will be ignored", ex); - objectContext.Reader.Skip(currentDepth, objectContext.Reader.Parser.Current == startParsingEvent); - } - else throw; + deletedItems.Add(id); } } - - objectContext.Properties.Add(DeletedItemsKey, deletedItems); + catch (YamlException ex) + { + if (objectContext.SerializerContext.AllowErrors) + { + var logger = objectContext.SerializerContext.Logger; + logger?.Warning($"{ex.Message}, this dictionary item will be ignored", ex); + objectContext.Reader.Skip(currentDepth, objectContext.Reader.Parser.Current == startParsingEvent); + } + else throw; + } } - protected override void WriteDictionaryItems(ref ObjectContext objectContext) - { - var dictionaryDescriptor = (DictionaryDescriptor)objectContext.Descriptor; - var keyValues = dictionaryDescriptor.GetEnumerator(objectContext.Instance).ToList(); + objectContext.Properties.Add(DeletedItemsKey, deletedItems); + } - // Not sorting the keys here, they should be already properly sorted when we arrive here - // TODO: Allow to disable sorting externally, to avoid overriding this method. NOTE: tampering with Settings.SortKeyForMapping is not an option, it is not local but applied to all children. (ParameterKeyDictionarySerializer is doing that and is buggy) + protected override void WriteDictionaryItems(ref ObjectContext objectContext) + { + var dictionaryDescriptor = (DictionaryDescriptor)objectContext.Descriptor; + var keyValues = dictionaryDescriptor.GetEnumerator(objectContext.Instance).ToList(); - var keyValueType = new KeyValuePair(dictionaryDescriptor.KeyType, dictionaryDescriptor.ValueType); + // Not sorting the keys here, they should be already properly sorted when we arrive here + // TODO: Allow to disable sorting externally, to avoid overriding this method. NOTE: tampering with Settings.SortKeyForMapping is not an option, it is not local but applied to all children. (ParameterKeyDictionarySerializer is doing that and is buggy) - foreach (var keyValue in keyValues) - { - WriteDictionaryItem(ref objectContext, keyValue, keyValueType); - } + var keyValueType = new KeyValuePair(dictionaryDescriptor.KeyType, dictionaryDescriptor.ValueType); - // Store deleted items in the context - WriteDeletedItems(ref objectContext); + foreach (var keyValue in keyValues) + { + WriteDictionaryItem(ref objectContext, keyValue, keyValueType); } - protected override KeyValuePair ReadDictionaryItem(ref ObjectContext objectContext, KeyValuePair keyValueTypes) + // Store deleted items in the context + WriteDeletedItems(ref objectContext); + } + + protected override KeyValuePair ReadDictionaryItem(ref ObjectContext objectContext, KeyValuePair keyValueTypes) + { + var keyResult = objectContext.ObjectSerializerBackend.ReadDictionaryKey(ref objectContext, keyValueTypes.Key); + var peek = objectContext.SerializerContext.Reader.Peek(); + if (Equals(peek?.Value, YamlDeletedKey)) { - var keyResult = objectContext.ObjectSerializerBackend.ReadDictionaryKey(ref objectContext, keyValueTypes.Key); - var peek = objectContext.SerializerContext.Reader.Peek(); - if (Equals(peek?.Value, YamlDeletedKey)) - { - return ReadDeletedDictionaryItem(ref objectContext, keyResult); - } - var valueResult = objectContext.ObjectSerializerBackend.ReadDictionaryValue(ref objectContext, keyValueTypes.Value, keyResult); - return new KeyValuePair(keyResult, valueResult); + return ReadDeletedDictionaryItem(ref objectContext, keyResult); } + var valueResult = objectContext.ObjectSerializerBackend.ReadDictionaryValue(ref objectContext, keyValueTypes.Value, keyResult); + return new KeyValuePair(keyResult, valueResult); + } - protected abstract KeyValuePair ReadDeletedDictionaryItem(ref ObjectContext objectContext, object keyResult); + protected abstract KeyValuePair ReadDeletedDictionaryItem(ref ObjectContext objectContext, object keyResult); - protected override bool CheckIsSequence(ref ObjectContext objectContext) - { - var collectionDescriptor = objectContext.Descriptor as CollectionDescriptor; + protected override bool CheckIsSequence(ref ObjectContext objectContext) + { + // If the dictionary is pure, we can directly output a sequence instead of a mapping + return objectContext.Descriptor is CollectionDescriptor collectionDescriptor && collectionDescriptor.IsPureCollection; + } - // If the dictionary is pure, we can directly output a sequence instead of a mapping - return collectionDescriptor != null && collectionDescriptor.IsPureCollection; - } + protected virtual void ReadYamlAfterTransform(ref ObjectContext objectContext, bool transformed) + { + ReadMembers(ref objectContext); + } - protected virtual void ReadYamlAfterTransform(ref ObjectContext objectContext, bool transformed) - { - ReadMembers(ref objectContext); - } + protected virtual void WriteYamlAfterTransform(ref ObjectContext objectContext, bool transformed) + { + var type = objectContext.Instance.GetType(); + var context = objectContext.SerializerContext; - protected virtual void WriteYamlAfterTransform(ref ObjectContext objectContext, bool transformed) - { - var type = objectContext.Instance.GetType(); - var context = objectContext.SerializerContext; + // Resolve the style, use default style if not defined. + var style = GetStyle(ref objectContext); - // Resolve the style, use default style if not defined. - var style = GetStyle(ref objectContext); + context.Writer.Emit(new MappingStartEventInfo(objectContext.Instance, type) { Tag = objectContext.Tag, Anchor = objectContext.Anchor, Style = style }); + WriteMembers(ref objectContext); + //WriteDeleted(ref objectContext); + context.Writer.Emit(new MappingEndEventInfo(objectContext.Instance, type)); + } - context.Writer.Emit(new MappingStartEventInfo(objectContext.Instance, type) { Tag = objectContext.Tag, Anchor = objectContext.Anchor, Style = style }); - WriteMembers(ref objectContext); - //WriteDeleted(ref objectContext); - context.Writer.Emit(new MappingEndEventInfo(objectContext.Instance, type)); - } + /// + /// Transforms the given collection or dictionary into a dictionary of (ids, items) or a dictionary of (ids & keys, items). + /// + /// The type descriptor of the collection. + /// The collection for which to create the mapping dictionary. + /// A dictionary mapping the id to the element of the initial collection. + protected abstract object TransformForSerialization(ITypeDescriptor descriptor, object collection); - /// - /// Transforms the given collection or dictionary into a dictionary of (ids, items) or a dictionary of (ids & keys, items). - /// - /// The type descriptor of the collection. - /// The collection for which to create the mapping dictionary. - /// A dictionary mapping the id to the element of the initial collection. - protected abstract object TransformForSerialization(ITypeDescriptor descriptor, object collection); - - /// - /// Creates an empty dictionary that can store the mapping of ids to items of the collection. - /// - /// The type descriptor of the collection for which to create the dictionary. - /// An empty dictionary for mapping ids to elements. - protected abstract IDictionary CreatEmptyContainer(ITypeDescriptor descriptor); - - /// - /// Transforms a dictionary containing the mapping of ids to items into the actual collection, and store the ids in the . - /// - /// The dictionary mapping ids to item. - /// The type descriptor of the actual collection to fill. - /// The instance of the actual collection to fill. - /// A collection of items that are marked as deleted. Can be null. - protected abstract void TransformAfterDeserialization(IDictionary container, ITypeDescriptor targetDescriptor, object targetCollection, ICollection deletedItems = null); - - protected abstract void WriteDeletedItems(ref ObjectContext objectContext); - - protected static bool AreCollectionItemsIdentifiable(ref ObjectContext objectContext) - { - bool nonIdentifiableItems; + /// + /// Creates an empty dictionary that can store the mapping of ids to items of the collection. + /// + /// The type descriptor of the collection for which to create the dictionary. + /// An empty dictionary for mapping ids to elements. + protected abstract IDictionary CreatEmptyContainer(ITypeDescriptor descriptor); - // Check in the serializer context first, for disabling of item identifiers at parent type level - if (objectContext.SerializerContext.Properties.TryGetValue(NonIdentifiableCollectionItemsKey, out nonIdentifiableItems) && nonIdentifiableItems) - return false; + /// + /// Transforms a dictionary containing the mapping of ids to items into the actual collection, and store the ids in the . + /// + /// The dictionary mapping ids to item. + /// The type descriptor of the actual collection to fill. + /// The instance of the actual collection to fill. + /// A collection of items that are marked as deleted. Can be null. + protected abstract void TransformAfterDeserialization(IDictionary container, ITypeDescriptor targetDescriptor, object targetCollection, ICollection? deletedItems = null); - // Then check locally for disabling of item identifiers at member level - if (objectContext.Properties.TryGetValue(NonIdentifiableCollectionItemsKey, out nonIdentifiableItems) && nonIdentifiableItems) - return false; + protected abstract void WriteDeletedItems(ref ObjectContext objectContext); - return true; - } + protected static bool AreCollectionItemsIdentifiable(ref ObjectContext objectContext) + { + + // Check in the serializer context first, for disabling of item identifiers at parent type level + if (objectContext.SerializerContext.Properties.TryGetValue(NonIdentifiableCollectionItemsKey, out var nonIdentifiableItems) && nonIdentifiableItems) + return false; + + // Then check locally for disabling of item identifiers at member level + if (objectContext.Properties.TryGetValue(NonIdentifiableCollectionItemsKey, out nonIdentifiableItems) && nonIdentifiableItems) + return false; + + return true; } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/CollectionWithItemIds.cs b/sources/assets/Stride.Core.Assets/Yaml/CollectionWithItemIds.cs index 21ecd6814a..c83776da47 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/CollectionWithItemIds.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/CollectionWithItemIds.cs @@ -1,16 +1,16 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Reflection; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// A container used to serialize collection whose items have identifiers. +/// +/// The type of item contained in the collection. +[DataContract] +public class CollectionWithItemIds : OrderedDictionary { - /// - /// A container used to serialize collection whose items have identifiers. - /// - /// The type of item contained in the collection. - [DataContract] - public class CollectionWithItemIds : OrderedDictionary - { - } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/ContextAttributeSerializer.cs b/sources/assets/Stride.Core.Assets/Yaml/ContextAttributeSerializer.cs index 786ff56641..c36a90f8a7 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/ContextAttributeSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/ContextAttributeSerializer.cs @@ -1,67 +1,67 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System.Reflection; using Stride.Core.Annotations; using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// A serializer that is run before the and allow to push and pop contextual properties. +/// +internal class ContextAttributeSerializer : ChainedSerializer { /// - /// A serializer that is run before the and allow to push and pop contextual properties. + /// A structure containing all the information needed to pop contextual properties after serialization. /// - internal class ContextAttributeSerializer : ChainedSerializer + private struct ContextToken { /// - /// A structure containing all the information needed to pop contextual properties after serialization. + /// Indicates if we entered a context where no collection should be identifiable anymore, indicated by the . /// - private struct ContextToken - { - /// - /// Indicates if we entered a context where no collection should be identifiable anymore, indicated by the . - /// - public bool NonIdentifiableItems; - } + public bool NonIdentifiableItems; + } - /// - public override void WriteYaml(ref ObjectContext objectContext) - { - var token = PreSerialize(ref objectContext); - base.WriteYaml(ref objectContext); - PostSerialize(ref objectContext, token); - } + /// + public override void WriteYaml(ref ObjectContext objectContext) + { + var token = PreSerialize(ref objectContext); + base.WriteYaml(ref objectContext); + PostSerialize(ref objectContext, token); + } - /// - public override object ReadYaml(ref ObjectContext objectContext) - { - var token = PreSerialize(ref objectContext); - var result = base.ReadYaml(ref objectContext); - PostSerialize(ref objectContext, token); - return result; - } + /// + public override object ReadYaml(ref ObjectContext objectContext) + { + var token = PreSerialize(ref objectContext); + var result = base.ReadYaml(ref objectContext); + PostSerialize(ref objectContext, token); + return result; + } - private static ContextToken PreSerialize(ref ObjectContext objectContext) + private static ContextToken PreSerialize(ref ObjectContext objectContext) + { + var token = new ContextToken(); + // Check if we enter a context where collection items are not identifiable anymore (see doc of the related attribute) + if (objectContext.Descriptor.Type.GetCustomAttribute(true) != null) { - var token = new ContextToken(); - // Check if we enter a context where collection items are not identifiable anymore (see doc of the related attribute) - if (objectContext.Descriptor.Type.GetCustomAttribute(true) != null) + if (!objectContext.SerializerContext.Properties.ContainsKey(CollectionWithIdsSerializerBase.NonIdentifiableCollectionItemsKey)) { - if (!objectContext.SerializerContext.Properties.ContainsKey(CollectionWithIdsSerializerBase.NonIdentifiableCollectionItemsKey)) - { - token.NonIdentifiableItems = true; - objectContext.SerializerContext.Properties.Add(CollectionWithIdsSerializerBase.NonIdentifiableCollectionItemsKey, true); - } + token.NonIdentifiableItems = true; + objectContext.SerializerContext.Properties.Add(CollectionWithIdsSerializerBase.NonIdentifiableCollectionItemsKey, true); } - return token; } + return token; + } - private static void PostSerialize(ref ObjectContext objectContext, ContextToken contextToken) + private static void PostSerialize(ref ObjectContext objectContext, ContextToken contextToken) + { + // Restore contexts that must be restored + if (contextToken.NonIdentifiableItems) { - // Restore contexts that must be restored - if (contextToken.NonIdentifiableItems) - { - objectContext.SerializerContext.Properties.Remove(CollectionWithIdsSerializerBase.NonIdentifiableCollectionItemsKey); - } + objectContext.SerializerContext.Properties.Remove(CollectionWithIdsSerializerBase.NonIdentifiableCollectionItemsKey); } } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/DeletedKeyWithId.cs b/sources/assets/Stride.Core.Assets/Yaml/DeletedKeyWithId.cs index 6b5df64581..a8ac7c8260 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/DeletedKeyWithId.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/DeletedKeyWithId.cs @@ -1,36 +1,39 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Reflection; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// A generic structure that implements the interface for keys that are deleted. +/// +/// The type of the key. +public readonly struct DeletedKeyWithId : IKeyWithId { /// - /// A generic structure that implements the interface for keys that are deleted. + /// Initializes a new instance of the structure. /// - /// The type of the key. - public struct DeletedKeyWithId : IKeyWithId + /// The associated to the deleted key. + public DeletedKeyWithId(ItemId id) { - /// - /// Initializes a new instance of the structure. - /// - /// The associated to the deleted key. - public DeletedKeyWithId(ItemId id) - { - Id = id; - } - - /// - /// The associated to the key. - /// - public readonly ItemId Id; - /// - ItemId IKeyWithId.Id => Id; - /// - object IKeyWithId.Key => default(TKey); - /// - bool IKeyWithId.IsDeleted => true; - /// - Type IKeyWithId.KeyType => typeof(TKey); + Id = id; } + + /// + /// The associated to the key. + /// + public readonly ItemId Id; + + /// + readonly ItemId IKeyWithId.Id => Id; + + /// + readonly object IKeyWithId.Key => default(TKey); + + /// + readonly bool IKeyWithId.IsDeleted => true; + + /// + readonly Type IKeyWithId.KeyType => typeof(TKey); } diff --git a/sources/assets/Stride.Core.Assets/Yaml/DictionaryWithIdsSerializer.cs b/sources/assets/Stride.Core.Assets/Yaml/DictionaryWithIdsSerializer.cs index fbfaa818ef..df845a16f6 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/DictionaryWithIdsSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/DictionaryWithIdsSerializer.cs @@ -1,146 +1,140 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; using System.Reflection; using Stride.Core.Annotations; using Stride.Core.Reflection; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// An implementation of for dictionaries. +/// +[YamlSerializerFactory("Assets")] +public class DictionaryWithIdsSerializer : CollectionWithIdsSerializerBase { - /// - /// An implementation of for dictionaries. - /// - [YamlSerializerFactory("Assets")] - public class DictionaryWithIdsSerializer : CollectionWithIdsSerializerBase + /// + public override IYamlSerializable? TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) { - /// - public override IYamlSerializable TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) + if (typeDescriptor is DictionaryDescriptor) { - if (typeDescriptor is DictionaryDescriptor) - { - var identifiable = typeDescriptor.Type.GetCustomAttribute(); - if (identifiable != null) - return null; + var identifiable = typeDescriptor.Type.GetCustomAttribute(); + if (identifiable != null) + return null; - var dataStyle = typeDescriptor.Type.GetCustomAttribute(); - if (dataStyle == null || dataStyle.Style != DataStyle.Compact) - return this; - } - return null; + var dataStyle = typeDescriptor.Type.GetCustomAttribute(); + if (dataStyle == null || dataStyle.Style != DataStyle.Compact) + return this; } + return null; + } - /// - protected override void TransformObjectAfterRead(ref ObjectContext objectContext) + /// + protected override void TransformObjectAfterRead(ref ObjectContext objectContext) + { + if (!AreCollectionItemsIdentifiable(ref objectContext)) { - if (!AreCollectionItemsIdentifiable(ref objectContext)) - { - base.TransformObjectAfterRead(ref objectContext); - return; - } - - var info = (InstanceInfo)objectContext.Properties[InstanceInfoKey]; - - // This is to be backward compatible with previous serialization. We fetch ids from the ~Id member of each item - if (info.Instance != null) - { - ICollection deletedItems; - objectContext.Properties.TryGetValue(DeletedItemsKey, out deletedItems); - TransformAfterDeserialization((IDictionary)objectContext.Instance, info.Descriptor, info.Instance, deletedItems); - } - objectContext.Instance = info.Instance; - base.TransformObjectAfterRead(ref objectContext); + return; } - /// - protected override object TransformForSerialization(ITypeDescriptor descriptor, object collection) + var info = (InstanceInfo)objectContext.Properties[InstanceInfoKey]; + + // This is to be backward compatible with previous serialization. We fetch ids from the ~Id member of each item + if (info.Instance != null) { - var dictionaryDescriptor = (DictionaryDescriptor)descriptor; - var instance = CreatEmptyContainer(descriptor); + objectContext.Properties.TryGetValue(DeletedItemsKey, out var deletedItems); + TransformAfterDeserialization((IDictionary)objectContext.Instance, info.Descriptor, info.Instance, deletedItems); + } + objectContext.Instance = info.Instance; - CollectionItemIdentifiers identifier; - if (!CollectionItemIdHelper.TryGetCollectionItemIds(collection, out identifier)) - { - identifier = new CollectionItemIdentifiers(); - } - var keyWithIdType = typeof(KeyWithId<>).MakeGenericType(dictionaryDescriptor.KeyType); - foreach (var item in dictionaryDescriptor.GetEnumerator(collection)) - { - ItemId id; - if (!identifier.TryGet(item.Key, out id)) - { - id = ItemId.New(); - identifier.Add(item.Key, id); - } - var keyWithId = Activator.CreateInstance(keyWithIdType, id, item.Key); - instance.Add(keyWithId, item.Value); - } + base.TransformObjectAfterRead(ref objectContext); + } - return instance; - } + /// + protected override object TransformForSerialization(ITypeDescriptor descriptor, object collection) + { + var dictionaryDescriptor = (DictionaryDescriptor)descriptor; + var instance = CreatEmptyContainer(descriptor); - /// - protected override IDictionary CreatEmptyContainer(ITypeDescriptor descriptor) + if (!CollectionItemIdHelper.TryGetCollectionItemIds(collection, out var identifier)) { - var dictionaryDescriptor = (DictionaryDescriptor)descriptor; - var type = typeof(DictionaryWithItemIds<,>).MakeGenericType(dictionaryDescriptor.KeyType, dictionaryDescriptor.ValueType); - if (type.GetConstructor(Type.EmptyTypes) == null) - throw new InvalidOperationException("The type of dictionary does not have a parameterless constructor."); - return (IDictionary)Activator.CreateInstance(type); + identifier = []; } - - /// - protected override void TransformAfterDeserialization(IDictionary container, ITypeDescriptor targetDescriptor, object targetCollection, ICollection deletedItems = null) + var keyWithIdType = typeof(KeyWithId<>).MakeGenericType(dictionaryDescriptor.KeyType); + foreach (var item in dictionaryDescriptor.GetEnumerator(collection)) { - var dictionaryDescriptor = (DictionaryDescriptor)targetDescriptor; - var type = typeof(DictionaryWithItemIds<,>).MakeGenericType(dictionaryDescriptor.KeyType, dictionaryDescriptor.ValueType); - if (!type.IsInstanceOfType(container)) - throw new InvalidOperationException("The given container does not match the expected type."); - var identifier = CollectionItemIdHelper.GetCollectionItemIds(targetCollection); - identifier.Clear(); - var enumerator = container.GetEnumerator(); - while (enumerator.MoveNext()) - { - var keyWithId = (IKeyWithId)enumerator.Key; - dictionaryDescriptor.AddToDictionary(targetCollection, keyWithId.Key, enumerator.Value); - identifier.Add(keyWithId.Key, keyWithId.Id); - } - if (deletedItems != null) + if (!identifier.TryGet(item.Key, out var id)) { - foreach (var deletedItem in deletedItems) - { - identifier.MarkAsDeleted(deletedItem); - } + id = ItemId.New(); + identifier.Add(item.Key, id); } + var keyWithId = Activator.CreateInstance(keyWithIdType, id, item.Key)!; + instance.Add(keyWithId, item.Value); } - protected override void WriteDeletedItems(ref ObjectContext objectContext) + return instance; + } + + /// + protected override IDictionary CreatEmptyContainer(ITypeDescriptor descriptor) + { + var dictionaryDescriptor = (DictionaryDescriptor)descriptor; + var type = typeof(DictionaryWithItemIds<,>).MakeGenericType(dictionaryDescriptor.KeyType, dictionaryDescriptor.ValueType); + if (type.GetConstructor(Type.EmptyTypes) == null) + throw new InvalidOperationException("The type of dictionary does not have a parameterless constructor."); + return (IDictionary)Activator.CreateInstance(type)!; + } + + /// + protected override void TransformAfterDeserialization(IDictionary container, ITypeDescriptor targetDescriptor, object targetCollection, ICollection? deletedItems = null) + { + var dictionaryDescriptor = (DictionaryDescriptor)targetDescriptor; + var type = typeof(DictionaryWithItemIds<,>).MakeGenericType(dictionaryDescriptor.KeyType, dictionaryDescriptor.ValueType); + if (!type.IsInstanceOfType(container)) + throw new InvalidOperationException("The given container does not match the expected type."); + var identifier = CollectionItemIdHelper.GetCollectionItemIds(targetCollection); + identifier.Clear(); + var enumerator = container.GetEnumerator(); + while (enumerator.MoveNext()) + { + var keyWithId = (IKeyWithId)enumerator.Key; + dictionaryDescriptor.AddToDictionary(targetCollection, keyWithId.Key, enumerator.Value); + identifier.Add(keyWithId.Key, keyWithId.Id); + } + if (deletedItems != null) { - ICollection deletedItems; - objectContext.Properties.TryGetValue(DeletedItemsKey, out deletedItems); - if (deletedItems != null) + foreach (var deletedItem in deletedItems) { - var dictionaryDescriptor = (DictionaryDescriptor)objectContext.Descriptor; - var keyWithIdType = typeof(DeletedKeyWithId<>).MakeGenericType(dictionaryDescriptor.KeyType); - var keyValueType = new KeyValuePair(keyWithIdType, typeof(string)); - foreach (var deletedItem in deletedItems) - { - // Add a ~ to allow to parse it back as a KeyWithId. - var keyWithId = Activator.CreateInstance(keyWithIdType, deletedItem); - var entry = new KeyValuePair(keyWithId, YamlDeletedKey); - WriteDictionaryItem(ref objectContext, entry, keyValueType); - } + identifier.MarkAsDeleted(deletedItem); } } + } - protected override KeyValuePair ReadDeletedDictionaryItem(ref ObjectContext objectContext, object keyResult) + protected override void WriteDeletedItems(ref ObjectContext objectContext) + { + objectContext.Properties.TryGetValue(DeletedItemsKey, out var deletedItems); + if (deletedItems != null) { - var valueResult = objectContext.ObjectSerializerBackend.ReadDictionaryValue(ref objectContext, typeof(string), keyResult); - var id = ((IKeyWithId)keyResult).Id; - return new KeyValuePair(id, valueResult); + var dictionaryDescriptor = (DictionaryDescriptor)objectContext.Descriptor; + var keyWithIdType = typeof(DeletedKeyWithId<>).MakeGenericType(dictionaryDescriptor.KeyType); + var keyValueType = new KeyValuePair(keyWithIdType, typeof(string)); + foreach (var deletedItem in deletedItems) + { + // Add a ~ to allow to parse it back as a KeyWithId. + var keyWithId = Activator.CreateInstance(keyWithIdType, deletedItem)!; + var entry = new KeyValuePair(keyWithId, YamlDeletedKey); + WriteDictionaryItem(ref objectContext, entry, keyValueType); + } } } + + protected override KeyValuePair ReadDeletedDictionaryItem(ref ObjectContext objectContext, object keyResult) + { + var valueResult = objectContext.ObjectSerializerBackend.ReadDictionaryValue(ref objectContext, typeof(string), keyResult); + var id = ((IKeyWithId)keyResult).Id; + return new KeyValuePair(id, valueResult); + } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/DictionaryWithItemIds.cs b/sources/assets/Stride.Core.Assets/Yaml/DictionaryWithItemIds.cs index 9596b35053..f0ca11794c 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/DictionaryWithItemIds.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/DictionaryWithItemIds.cs @@ -1,17 +1,14 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Yaml -{ - /// - /// A container used to serialize dictionary whose entries have identifiers. - /// - /// The type of key contained in the dictionary. - /// The type of value contained in the dictionary. - [DataContract] - public class DictionaryWithItemIds : OrderedDictionary, TValue> - { +namespace Stride.Core.Yaml; - } -} +/// +/// A container used to serialize dictionary whose entries have identifiers. +/// +/// The type of key contained in the dictionary. +/// The type of value contained in the dictionary. +[DataContract] +public class DictionaryWithItemIds : OrderedDictionary, TValue>; diff --git a/sources/assets/Stride.Core.Assets/Yaml/ErrorRecoverySerializer.cs b/sources/assets/Stride.Core.Assets/Yaml/ErrorRecoverySerializer.cs index f190063bf9..ef5a062b80 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/ErrorRecoverySerializer.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/ErrorRecoverySerializer.cs @@ -1,168 +1,161 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using Stride.Core.Diagnostics; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// Objects that can't be loaded as valid Yaml will be converted to a proxy object implementing . +/// +class ErrorRecoverySerializer : ChainedSerializer { - /// - /// Objects that can't be loaded as valid Yaml will be converted to a proxy object implementing . - /// - class ErrorRecoverySerializer : ChainedSerializer + public override object ReadYaml(ref ObjectContext objectContext) { - public override object ReadYaml(ref ObjectContext objectContext) + // Check if we already have a memory parser + var previousReader = objectContext.SerializerContext.Reader; + EventReader reader; + if (previousReader.Parser is not MemoryParser) { - // Check if we already have a memory parser - var previousReader = objectContext.SerializerContext.Reader; - EventReader reader; - if (!(previousReader.Parser is MemoryParser)) - { - // Switch to a memory parser so that we can easily rollback in case of error - // Read all events from this node - var parsingEvents = new List(); - previousReader.ReadCurrent(parsingEvents); + // Switch to a memory parser so that we can easily rollback in case of error + // Read all events from this node + var parsingEvents = new List(); + previousReader.ReadCurrent(parsingEvents); - objectContext.SerializerContext.Reader = reader = new EventReader(new MemoryParser(parsingEvents)); - } - else - { - reader = previousReader; - previousReader = null; - } + objectContext.SerializerContext.Reader = reader = new EventReader(new MemoryParser(parsingEvents)); + } + else + { + reader = previousReader; + previousReader = null; + } - // Get start position in case we need to recover - var memoryParser = (MemoryParser)reader.Parser; - var startDepth = reader.CurrentDepth; - var startPosition = memoryParser.Position; + // Get start position in case we need to recover + var memoryParser = (MemoryParser)reader.Parser; + var startDepth = reader.CurrentDepth; + var startPosition = memoryParser.Position; - // Allow errors to happen - var previousAllowErrors = objectContext.SerializerContext.AllowErrors; - objectContext.SerializerContext.AllowErrors = true; + // Allow errors to happen + var previousAllowErrors = objectContext.SerializerContext.AllowErrors; + objectContext.SerializerContext.AllowErrors = true; - try - { - // Deserialize normally - return base.ReadYaml(ref objectContext); - } - catch (Exception ex) when (ex is YamlException || ex is DefaultObjectFactory.InstanceCreationException) // TODO: Filter only TagTypeSerializer TypeFromTag decoding errors? or more? + try + { + // Deserialize normally + return base.ReadYaml(ref objectContext); + } + catch (Exception ex) when (ex is YamlException || ex is DefaultObjectFactory.InstanceCreationException) // TODO: Filter only TagTypeSerializer TypeFromTag decoding errors? or more? + { + // Find the parsing range for this object + // Skipping is also important to make sure the depth is properly updated + reader.Skip(startDepth, startPosition == memoryParser.Position); + var endPosition = memoryParser.Position; + + // TODO: Only type from user assemblies (and plugins?) + var type = objectContext.Descriptor?.Type; + if (type != null) { - // Find the parsing range for this object - // Skipping is also important to make sure the depth is properly updated - reader.Skip(startDepth, startPosition == memoryParser.Position); - var endPosition = memoryParser.Position; - - // TODO: Only type from user assemblies (and plugins?) - var type = objectContext.Descriptor?.Type; - if (type != null) + // Dump the range of Yaml for this object + var parsingEvents = new List(); + for (int i = startPosition; i < endPosition; ++i) + parsingEvents.Add(memoryParser.ParsingEvents[i]); + + // Get typename (if available) and temporarily erase it from the parsing events for partial deserialization of base type + string tag = "Unknown"; + var firstNode = memoryParser.ParsingEvents[startPosition] as NodeEvent; + if (firstNode != null) { - // Dump the range of Yaml for this object - var parsingEvents = new List(); - for (int i = startPosition; i < endPosition; ++i) - parsingEvents.Add(memoryParser.ParsingEvents[i]); - - // Get typename (if available) and temporarily erase it from the parsing events for partial deserialization of base type - string tag = "Unknown"; - var firstNode = memoryParser.ParsingEvents[startPosition] as NodeEvent; - if (firstNode != null) - { - if (firstNode.Tag != null) - tag = firstNode.Tag; - - // Temporarily recreate the node without its tag, so that we can try deserializing as many members as possible still - // TODO: Replace this with switch pattern matching (C# 7.0) - var mappingStart = firstNode as MappingStart; - var sequenceStart = firstNode as SequenceStart; - var scalar = firstNode as Scalar; - if (mappingStart != null) - { - memoryParser.ParsingEvents[startPosition] = new MappingStart(mappingStart.Anchor, null, mappingStart.IsImplicit, mappingStart.Style, mappingStart.Start, mappingStart.End); - } - else if (sequenceStart != null) - { - memoryParser.ParsingEvents[startPosition] = new SequenceStart(sequenceStart.Anchor, null, sequenceStart.IsImplicit, sequenceStart.Style, sequenceStart.Start, sequenceStart.End); - } - else if (scalar != null) - { - memoryParser.ParsingEvents[startPosition] = new Scalar(scalar.Anchor, null, scalar.Value, scalar.Style, scalar.IsPlainImplicit, scalar.IsQuotedImplicit, scalar.Start, scalar.End); - } - else - { - throw new NotImplementedException("Unknown node type"); - } - } + if (firstNode.Tag != null) + tag = firstNode.Tag; - string typeName = null; - string assemblyName = null; - if (tag != null) + // Temporarily recreate the node without its tag, so that we can try deserializing as many members as possible still + // TODO: Replace this with switch pattern matching (C# 7.0) + if (firstNode is MappingStart mappingStart) { - var tagAsType = tag.StartsWith('!') ? tag[1..] : tag; - objectContext.SerializerContext.ParseType(tagAsType, out typeName, out assemblyName); + memoryParser.ParsingEvents[startPosition] = new MappingStart(mappingStart.Anchor, null, mappingStart.IsImplicit, mappingStart.Style, mappingStart.Start, mappingStart.End); } - - var log = objectContext.SerializerContext.Logger; - log?.Warning($"Could not deserialize object of type '{typeName ?? tag}'; replacing it with an object implementing {nameof(IUnloadable)}", ex); - - var unloadableObject = UnloadableObjectInstantiator.CreateUnloadableObject(type, typeName, assemblyName, ex.Message, parsingEvents); - objectContext.Instance = unloadableObject; - objectContext.Descriptor = objectContext.SerializerContext.FindTypeDescriptor(unloadableObject.GetType()); - - // Restore parser position at beginning of object - memoryParser.Position = startPosition; - reader.RefreshParserState(); - - try + else if (firstNode is SequenceStart sequenceStart) { - // Here, we try again to deserialize the object in the proxy - // Since we erase the tag, it shouldn't try to resolve the unknown type anymore (it will deserialize properties that exist in the base type) - unloadableObject = (IUnloadable)base.ReadYaml(ref objectContext); + memoryParser.ParsingEvents[startPosition] = new SequenceStart(sequenceStart.Anchor, null, sequenceStart.IsImplicit, sequenceStart.Style, sequenceStart.Start, sequenceStart.End); } - catch (YamlException) + else if (firstNode is Scalar scalar) { - // Mute exceptions when trying to deserialize the proxy - // (in most case, we can do fine with incomplete objects) + memoryParser.ParsingEvents[startPosition] = new Scalar(scalar.Anchor, null, scalar.Value, scalar.Style, scalar.IsPlainImplicit, scalar.IsQuotedImplicit, scalar.Start, scalar.End); } - finally + else { - // Read until end of object (in case it failed again) - // Skipping is also important to make sure the depth is properly updated - reader.Skip(startDepth, startPosition == memoryParser.Position); - if (firstNode != null) - memoryParser.ParsingEvents[startPosition] = firstNode; + throw new NotImplementedException("Unknown node type"); } + } - return unloadableObject; + string? typeName = null; + string? assemblyName = null; + if (tag != null) + { + var tagAsType = tag.StartsWith('!') ? tag[1..] : tag; + objectContext.SerializerContext.ParseType(tagAsType, out typeName, out assemblyName); + } + + var log = objectContext.SerializerContext.Logger; + log?.Warning($"Could not deserialize object of type '{typeName ?? tag}'; replacing it with an object implementing {nameof(IUnloadable)}", ex); + + var unloadableObject = UnloadableObjectInstantiator.CreateUnloadableObject(type, typeName, assemblyName, ex.Message, parsingEvents); + objectContext.Instance = unloadableObject; + objectContext.Descriptor = objectContext.SerializerContext.FindTypeDescriptor(unloadableObject.GetType()); + + // Restore parser position at beginning of object + memoryParser.Position = startPosition; + reader.RefreshParserState(); + + try + { + // Here, we try again to deserialize the object in the proxy + // Since we erase the tag, it shouldn't try to resolve the unknown type anymore (it will deserialize properties that exist in the base type) + unloadableObject = (IUnloadable)base.ReadYaml(ref objectContext); + } + catch (YamlException) + { + // Mute exceptions when trying to deserialize the proxy + // (in most case, we can do fine with incomplete objects) + } + finally + { + // Read until end of object (in case it failed again) + // Skipping is also important to make sure the depth is properly updated + reader.Skip(startDepth, startPosition == memoryParser.Position); + if (firstNode != null) + memoryParser.ParsingEvents[startPosition] = firstNode; } - throw; - } - finally - { - // Restore reader - if (previousReader != null) - objectContext.SerializerContext.Reader = previousReader; - // Restore states - objectContext.SerializerContext.AllowErrors = previousAllowErrors; + return unloadableObject; } + throw; } - - public override void WriteYaml(ref ObjectContext objectContext) + finally { - // If it's a IUnloadable, serialize the yaml events as is - var proxy = objectContext.Instance as IUnloadable; - if (proxy != null) - { - // TODO: Do we want to save values on the base type that might have changed? - foreach (var parsingEvent in proxy.ParsingEvents) - objectContext.Writer.Emit(parsingEvent); - return; - } + // Restore reader + if (previousReader != null) + objectContext.SerializerContext.Reader = previousReader; - base.WriteYaml(ref objectContext); + // Restore states + objectContext.SerializerContext.AllowErrors = previousAllowErrors; } } + + public override void WriteYaml(ref ObjectContext objectContext) + { + // If it's a IUnloadable, serialize the yaml events as is + if (objectContext.Instance is IUnloadable proxy) + { + // TODO: Do we want to save values on the base type that might have changed? + foreach (var parsingEvent in proxy.ParsingEvents) + objectContext.Writer.Emit(parsingEvent); + return; + } + + base.WriteYaml(ref objectContext); + } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/IKeyWithId.cs b/sources/assets/Stride.Core.Assets/Yaml/IKeyWithId.cs index dfef51bd00..3152730dfb 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/IKeyWithId.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/IKeyWithId.cs @@ -1,30 +1,29 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Reflection; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// An interface representing an association between an and the key of a dictionary. +/// +public interface IKeyWithId { /// - /// An interface representing an association between an and the key of a dictionary. + /// The associated to the key. + /// + ItemId Id { get; } + /// + /// The key of the dictionary. + /// + object Key { get; } + /// + /// The type of the key. + /// + Type KeyType { get; } + /// + /// Indicates whether this key is considered to be deleted in the dictionary, and kept around for reconcilation purpose. /// - public interface IKeyWithId - { - /// - /// The associated to the key. - /// - ItemId Id { get; } - /// - /// The key of the dictionary. - /// - object Key { get; } - /// - /// The type of the key. - /// - Type KeyType { get; } - /// - /// Indicates whether this key is considered to be deleted in the dictionary, and kept around for reconcilation purpose. - /// - bool IsDeleted { get; } - } + bool IsDeleted { get; } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/IUnloadable.cs b/sources/assets/Stride.Core.Assets/Yaml/IUnloadable.cs index eef840e4fb..d30851fc2a 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/IUnloadable.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/IUnloadable.cs @@ -1,21 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; + using Stride.Core.Yaml.Events; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// Objects that can't be loaded as valid Yaml will be converted to a proxy object implementing this interface by . +/// +public interface IUnloadable { - /// - /// Objects that can't be loaded as valid Yaml will be converted to a proxy object implementing this interface by . - /// - public interface IUnloadable - { - string TypeName { get; } + string TypeName { get; } - string AssemblyName { get; } + string AssemblyName { get; } - string Error { get; } + string Error { get; } - List ParsingEvents { get; } - } + List ParsingEvents { get; } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/IYamlAssetMetadata.cs b/sources/assets/Stride.Core.Assets/Yaml/IYamlAssetMetadata.cs index 6777397f71..85a93194ab 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/IYamlAssetMetadata.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/IYamlAssetMetadata.cs @@ -1,41 +1,37 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using System.Collections; -using System.Collections.Generic; -using Stride.Core.Annotations; -namespace Stride.Core.Assets.Yaml +namespace Stride.Core.Assets.Yaml; + +/// +/// An interface representing a container used to transfer metadata between the asset and the YAML serializer. +/// +internal interface IYamlAssetMetadata : IEnumerable { /// - /// An interface representing a container used to transfer metadata between the asset and the YAML serializer. + /// Notifies that this metadata has been attached and cannot be modified anymore. /// - internal interface IYamlAssetMetadata : IEnumerable - { - /// - /// Notifies that this metadata has been attached and cannot be modified anymore. - /// - void Attach(); + void Attach(); - /// - /// Attaches the given metadata value to the given YAML path. - /// - /// The path at which to attach metadata. - /// The metadata to attach. - void Set([NotNull] YamlAssetPath path, object value); + /// + /// Attaches the given metadata value to the given YAML path. + /// + /// The path at which to attach metadata. + /// The metadata to attach. + void Set(YamlAssetPath path, object value); - /// - /// Removes attached metadata from the given YAML path. - /// - /// The path at which to remove metadata. - void Remove([NotNull] YamlAssetPath path); + /// + /// Removes attached metadata from the given YAML path. + /// + /// The path at which to remove metadata. + void Remove(YamlAssetPath path); - /// - /// Tries to retrieve the metadata for the given path. - /// - /// The path at which to retrieve metadata. - /// The metadata attached to the given path, or the default value of the underlying type if no metadata is attached at the given path. - object TryGet([NotNull] YamlAssetPath path); - } + /// + /// Tries to retrieve the metadata for the given path. + /// + /// The path at which to retrieve metadata. + /// The metadata attached to the given path, or the default value of the underlying type if no metadata is attached at the given path. + object? TryGet(YamlAssetPath path); } diff --git a/sources/assets/Stride.Core.Assets/Yaml/ItemIdSerializer.cs b/sources/assets/Stride.Core.Assets/Yaml/ItemIdSerializer.cs index 0dcffc7375..d2032d6a05 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/ItemIdSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/ItemIdSerializer.cs @@ -1,31 +1,29 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Reflection; using Stride.Core.Storage; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// A Yaml serializer for without associated data. +/// +[YamlSerializerFactory("Assets")] // TODO: use YamlAssetProfile.Name +internal class ItemIdSerializer : ItemIdSerializerBase { - /// - /// A Yaml serializer for without associated data. - /// - [YamlSerializerFactory("Assets")] // TODO: use YamlAssetProfile.Name - internal class ItemIdSerializer : ItemIdSerializerBase + /// + public override bool CanVisit(Type type) { - /// - public override bool CanVisit(Type type) - { - return type == typeof(ItemId); - } + return type == typeof(ItemId); + } - /// - public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) - { - ObjectId id; - ObjectId.TryParse(fromScalar.Value, out id); - return new ItemId(id); - } + /// + public override object ConvertFrom(ref ObjectContext context, Scalar fromScalar) + { + _ = ObjectId.TryParse(fromScalar.Value, out var id); + return new ItemId(id); } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/ItemIdSerializerBase.cs b/sources/assets/Stride.Core.Assets/Yaml/ItemIdSerializerBase.cs index 8afa494380..028aecdfce 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/ItemIdSerializerBase.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/ItemIdSerializerBase.cs @@ -1,32 +1,30 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Reflection; using Stride.Core.Yaml.Serialization; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// A base class to serialize . +/// +public abstract class ItemIdSerializerBase : AssetScalarSerializerBase { /// - /// A base class to serialize . + /// A key used in properties of serialization contexts to notify whether an override flag should be appened when serializing the related . /// - public abstract class ItemIdSerializerBase : AssetScalarSerializerBase - { - /// - /// A key used in properties of serialization contexts to notify whether an override flag should be appened when serializing the related . - /// - public static PropertyKey OverrideInfoKey = new PropertyKey("OverrideInfo", typeof(ItemIdSerializer)); + public static PropertyKey OverrideInfoKey = new("OverrideInfo", typeof(ItemIdSerializer)); - /// - public override string ConvertTo(ref ObjectContext objectContext) + /// + public override string ConvertTo(ref ObjectContext objectContext) + { + var result = ((ItemId)objectContext.Instance).ToString(); + if (objectContext.SerializerContext.Properties.TryGetValue(OverrideInfoKey, out var overrideInfo)) { - var result = ((ItemId)objectContext.Instance).ToString(); - string overrideInfo; - if (objectContext.SerializerContext.Properties.TryGetValue(OverrideInfoKey, out overrideInfo)) - { - result += overrideInfo; - objectContext.SerializerContext.Properties.Remove(OverrideInfoKey); - } - return result; + result += overrideInfo; + objectContext.SerializerContext.Properties.Remove(OverrideInfoKey); } - + return result; } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/KeyValuePairSerializer.cs b/sources/assets/Stride.Core.Assets/Yaml/KeyValuePairSerializer.cs index be4ca4f94e..a9185cb48e 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/KeyValuePairSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/KeyValuePairSerializer.cs @@ -1,63 +1,61 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using Stride.Core.Reflection; using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// A Yaml Serializer for . +/// Because this type is immutable we need to implement a special serializer. +/// +[YamlSerializerFactory(YamlSerializerFactoryAttribute.Default)] +internal class KeyValuePairSerializer : ObjectSerializer { - /// - /// A Yaml Serializer for . - /// Because this type is immutable we need to implement a special serializer. - /// - [YamlSerializerFactory(YamlSerializerFactoryAttribute.Default)] - internal class KeyValuePairSerializer : ObjectSerializer + private struct MutableKeyValuePair { - private struct MutableKeyValuePair + public MutableKeyValuePair(KeyValuePair kv) { - public MutableKeyValuePair(KeyValuePair kv) - { - Key = kv.Key; - Value = kv.Value; - } - - public TKey Key { get; set; } - - public TValue Value { get; set; } + Key = kv.Key; + Value = kv.Value; } + + public TKey Key { get; set; } - /// - public override IYamlSerializable TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) - { - if (!typeDescriptor.Type.IsGenericType) - return null; + public TValue Value { get; set; } + } - var genericTypeDefinition = typeDescriptor.Type.GetGenericTypeDefinition(); - return genericTypeDefinition == typeof(KeyValuePair<,>) ? this : null; - } + /// + public override IYamlSerializable? TryCreate(SerializerContext context, ITypeDescriptor typeDescriptor) + { + if (!typeDescriptor.Type.IsGenericType) + return null; - /// - protected override void CreateOrTransformObject(ref ObjectContext objectContext) - { - var keyValueType = objectContext.Descriptor.Type; - var typeArguments = keyValueType.GetGenericArguments(); - var mutableKeyValueType = typeof(MutableKeyValuePair<,>).MakeGenericType(typeArguments); - objectContext.Instance = objectContext.SerializerContext.IsSerializing - ? Activator.CreateInstance(mutableKeyValueType, objectContext.Instance) - : Activator.CreateInstance(mutableKeyValueType); - } + var genericTypeDefinition = typeDescriptor.Type.GetGenericTypeDefinition(); + return genericTypeDefinition == typeof(KeyValuePair<,>) ? this : null; + } - /// - protected override void TransformObjectAfterRead(ref ObjectContext objectContext) - { - var mutableKeyValueType = objectContext.Descriptor.Type; - var typeArguments = mutableKeyValueType.GetGenericArguments(); - var keyValueType = typeof(KeyValuePair<,>).MakeGenericType(typeArguments); - var key = objectContext.Descriptor["Key"].Get(objectContext.Instance); - var value = objectContext.Descriptor["Value"].Get(objectContext.Instance); - objectContext.Instance = Activator.CreateInstance(keyValueType, key, value); - } + /// + protected override void CreateOrTransformObject(ref ObjectContext objectContext) + { + var keyValueType = objectContext.Descriptor.Type; + var typeArguments = keyValueType.GetGenericArguments(); + var mutableKeyValueType = typeof(MutableKeyValuePair<,>).MakeGenericType(typeArguments); + objectContext.Instance = objectContext.SerializerContext.IsSerializing + ? Activator.CreateInstance(mutableKeyValueType, objectContext.Instance) + : Activator.CreateInstance(mutableKeyValueType); + } + + /// + protected override void TransformObjectAfterRead(ref ObjectContext objectContext) + { + var mutableKeyValueType = objectContext.Descriptor.Type; + var typeArguments = mutableKeyValueType.GetGenericArguments(); + var keyValueType = typeof(KeyValuePair<,>).MakeGenericType(typeArguments); + var key = objectContext.Descriptor["Key"].Get(objectContext.Instance); + var value = objectContext.Descriptor["Value"].Get(objectContext.Instance); + objectContext.Instance = Activator.CreateInstance(keyValueType, key, value); } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/KeyWithId.cs b/sources/assets/Stride.Core.Assets/Yaml/KeyWithId.cs index 1b4ac62118..8a3474371f 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/KeyWithId.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/KeyWithId.cs @@ -1,42 +1,45 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Reflection; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// A generic structure that implements the interface for keys that are not deleted. +/// +/// The type of the key. +public readonly struct KeyWithId : IKeyWithId { /// - /// A generic structure that implements the interface for keys that are not deleted. + /// Initializes a new instance of the structure. /// - /// The type of the key. - public struct KeyWithId : IKeyWithId + /// The associated to the key. + /// The key of the dictionary. + public KeyWithId(ItemId id, TKey key) { - /// - /// Initializes a new instance of the structure. - /// - /// The associated to the key. - /// The key of the dictionary. - public KeyWithId(ItemId id, TKey key) - { - Id = id; - Key = key; - } - - /// - /// The associated to the key. - /// - public readonly ItemId Id; - /// - /// The key of the dictionary. - /// - public readonly TKey Key; - /// - ItemId IKeyWithId.Id => Id; - /// - object IKeyWithId.Key => Key; - /// - bool IKeyWithId.IsDeleted => false; - /// - Type IKeyWithId.KeyType => typeof(TKey); + Id = id; + Key = key; } + + /// + /// The associated to the key. + /// + public readonly ItemId Id; + /// + /// The key of the dictionary. + /// + public readonly TKey Key; + + /// + readonly ItemId IKeyWithId.Id => Id; + + /// + readonly object IKeyWithId.Key => Key; + + /// + readonly bool IKeyWithId.IsDeleted => false; + + /// + readonly Type IKeyWithId.KeyType => typeof(TKey); } diff --git a/sources/assets/Stride.Core.Assets/Yaml/KeyWithIdSerializer.cs b/sources/assets/Stride.Core.Assets/Yaml/KeyWithIdSerializer.cs index a43b46885c..8fcc2d9438 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/KeyWithIdSerializer.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/KeyWithIdSerializer.cs @@ -1,89 +1,85 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Reflection; using Stride.Core.Yaml.Events; using Stride.Core.Yaml.Serialization; using Stride.Core.Yaml.Serialization.Serializers; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +/// +/// A Yaml serializer for . +/// +[YamlSerializerFactory("Assets")] // TODO: use YamlAssetProfile.Name +public class KeyWithIdSerializer : ItemIdSerializerBase { /// - /// A Yaml serializer for . + /// A key used in properties of serialization contexts to notify whether an override flag should be appened when serializing the key of the related . /// - [YamlSerializerFactory("Assets")] // TODO: use YamlAssetProfile.Name - public class KeyWithIdSerializer : ItemIdSerializerBase - { - /// - /// A key used in properties of serialization contexts to notify whether an override flag should be appened when serializing the key of the related . - /// - public static PropertyKey OverrideKeyInfoKey = new PropertyKey("OverrideKeyInfo", typeof(KeyWithIdSerializer)); + public static PropertyKey OverrideKeyInfoKey = new("OverrideKeyInfo", typeof(KeyWithIdSerializer)); - /// - public override object ConvertFrom(ref ObjectContext objectContext, Scalar fromScalar) + /// + public override object? ConvertFrom(ref ObjectContext objectContext, Scalar fromScalar) + { + var idIndex = fromScalar.Value.IndexOf('~'); + var id = ItemId.Empty; + var keyString = fromScalar.Value; + if (idIndex >= 0) { - var idIndex = fromScalar.Value.IndexOf('~'); - var id = ItemId.Empty; - var keyString = fromScalar.Value; - if (idIndex >= 0) - { - var idString = fromScalar.Value.Substring(0, idIndex); - keyString = fromScalar.Value.Substring(idIndex + 1); - id = ItemId.Parse(idString); - } - var keyType = objectContext.Descriptor.Type.GetGenericArguments()[0]; - var keyDescriptor = objectContext.SerializerContext.FindTypeDescriptor(keyType); - var keySerializer = objectContext.SerializerContext.Serializer.GetSerializer(objectContext.SerializerContext, keyDescriptor); - var scalarKeySerializer = keySerializer as ScalarSerializerBase; - // TODO: deserialize non-scalar keys! - if (scalarKeySerializer == null) - throw new InvalidOperationException("Non-scalar key not yet supported!"); - - var context = new ObjectContext(objectContext.SerializerContext, null, keyDescriptor); - var key = scalarKeySerializer.ConvertFrom(ref context, new Scalar(keyString)); - var result = Activator.CreateInstance(typeof(KeyWithId<>).MakeGenericType(keyType), id, key); - return result; + var idString = fromScalar.Value[..idIndex]; + keyString = fromScalar.Value[(idIndex + 1)..]; + id = ItemId.Parse(idString); } + var keyType = objectContext.Descriptor.Type.GetGenericArguments()[0]; + var keyDescriptor = objectContext.SerializerContext.FindTypeDescriptor(keyType); + var keySerializer = objectContext.SerializerContext.Serializer.GetSerializer(objectContext.SerializerContext, keyDescriptor); + // TODO: deserialize non-scalar keys! + if (keySerializer is not ScalarSerializerBase scalarKeySerializer) + throw new InvalidOperationException("Non-scalar key not yet supported!"); - /// - public override string ConvertTo(ref ObjectContext objectContext) - { - var key = (IKeyWithId)objectContext.Instance; - var keyDescriptor = objectContext.SerializerContext.FindTypeDescriptor(key.KeyType); - var keySerializer = objectContext.SerializerContext.Serializer.GetSerializer(objectContext.SerializerContext, keyDescriptor); + var context = new ObjectContext(objectContext.SerializerContext, null, keyDescriptor); + var key = scalarKeySerializer.ConvertFrom(ref context, new Scalar(keyString)); + var result = Activator.CreateInstance(typeof(KeyWithId<>).MakeGenericType(keyType), id, key); + return result; + } - // TODO: serialize non-scalar keys! - // Guid: - // Key: {Key} - // Value: {Value} + /// + public override string ConvertTo(ref ObjectContext objectContext) + { + var key = (IKeyWithId)objectContext.Instance; + var keyDescriptor = objectContext.SerializerContext.FindTypeDescriptor(key.KeyType); + var keySerializer = objectContext.SerializerContext.Serializer.GetSerializer(objectContext.SerializerContext, keyDescriptor); - var scalarKeySerializer = keySerializer as ScalarSerializerBase; - if (scalarKeySerializer == null) - throw new InvalidOperationException("Non-scalar key not yet supported!"); + // TODO: serialize non-scalar keys! + // Guid: + // Key: {Key} + // Value: {Value} - var context = new ObjectContext(objectContext.SerializerContext, key.Key, keyDescriptor); + if (keySerializer is not ScalarSerializerBase scalarKeySerializer) + throw new InvalidOperationException("Non-scalar key not yet supported!"); - objectContext.Instance = key.Id; - var itemIdPart = base.ConvertTo(ref objectContext); - objectContext.Instance = key; + var context = new ObjectContext(objectContext.SerializerContext, key.Key, keyDescriptor); - if (key.IsDeleted) - return $"{itemIdPart}~"; + objectContext.Instance = key.Id; + var itemIdPart = base.ConvertTo(ref objectContext); + objectContext.Instance = key; - var keyString = scalarKeySerializer.ConvertTo(ref context); - string overrideInfo; - if (objectContext.SerializerContext.Properties.TryGetValue(OverrideKeyInfoKey, out overrideInfo)) - { - keyString += overrideInfo; - objectContext.SerializerContext.Properties.Remove(OverrideKeyInfoKey); - } - return $"{itemIdPart}~{keyString}"; - } + if (key.IsDeleted) + return $"{itemIdPart}~"; - /// - public override bool CanVisit(Type type) + var keyString = scalarKeySerializer.ConvertTo(ref context); + if (objectContext.SerializerContext.Properties.TryGetValue(OverrideKeyInfoKey, out var overrideInfo)) { - return typeof(IKeyWithId).IsAssignableFrom(type); + keyString += overrideInfo; + objectContext.SerializerContext.Properties.Remove(OverrideKeyInfoKey); } + return $"{itemIdPart}~{keyString}"; + } + + /// + public override bool CanVisit(Type type) + { + return typeof(IKeyWithId).IsAssignableFrom(type); } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/UnloadableObjectInstantiator.cs b/sources/assets/Stride.Core.Assets/Yaml/UnloadableObjectInstantiator.cs index c70b303fdd..8ff33be5ad 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/UnloadableObjectInstantiator.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/UnloadableObjectInstantiator.cs @@ -1,118 +1,115 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using System.Reflection; using System.Reflection.Emit; using Stride.Core.Annotations; using Stride.Core.Reflection; using Stride.Core.Yaml.Events; -namespace Stride.Core.Yaml +namespace Stride.Core.Yaml; + +public static class UnloadableObjectInstantiator { - public static class UnloadableObjectInstantiator + private static readonly Dictionary proxyTypes = []; + + public delegate void ProcessProxyTypeDelegate(Type baseType, TypeBuilder typeBuilder); + + /// + /// Callback to perform additional changes to the generated proxy object. + /// + public static ProcessProxyTypeDelegate? ProcessProxyType; + + /// + /// Creates an object that implements the given and . + /// + /// + /// + /// + /// + public static IUnloadable CreateUnloadableObject(Type baseType, string typeName, string assemblyName, string error, List parsingEvents) { - private static Dictionary proxyTypes = new Dictionary(); - - public delegate void ProcessProxyTypeDelegate(Type baseType, TypeBuilder typeBuilder); - - /// - /// Callback to perform additional changes to the generated proxy object. - /// - public static ProcessProxyTypeDelegate ProcessProxyType; - - /// - /// Creates an object that implements the given and . - /// - /// - /// - /// - /// - public static IUnloadable CreateUnloadableObject(Type baseType, string typeName, string assemblyName, string error, List parsingEvents) + Type? proxyType; + lock (proxyTypes) { - Type proxyType; - lock (proxyTypes) + if (!proxyTypes.TryGetValue(baseType, out proxyType)) { - if (!proxyTypes.TryGetValue(baseType, out proxyType)) + var asmName = new AssemblyName($"YamlProxy_{Guid.NewGuid():N}"); + + // Create assembly (in memory) + var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run); + var moduleBuilder = asmBuilder.DefineDynamicModule("DynamicModule"); + + // Create type + var typeBuilder = moduleBuilder.DefineType($"{baseType}YamlProxy"); + AbstractObjectInstantiator.InitializeTypeBuilderFromType(typeBuilder, baseType); + + // Add DisplayAttribute + var displayAttributeCtor = typeof(DisplayAttribute).GetConstructor([typeof(string), typeof(string)])!; + var displayAttribute = new CustomAttributeBuilder(displayAttributeCtor, ["Error: unable to load this object", null]); + typeBuilder.SetCustomAttribute(displayAttribute); + + // Add NonInstantiableAttribute + var nonInstantiableAttributeCtor = typeof(NonInstantiableAttribute).GetConstructor(Type.EmptyTypes)!; + var nonInstantiableAttribute = new CustomAttributeBuilder(nonInstantiableAttributeCtor, []); + typeBuilder.SetCustomAttribute(nonInstantiableAttribute); + + // Implement IUnloadable + typeBuilder.AddInterfaceImplementation(typeof(IUnloadable)); + + var backingFields = new List(); + foreach (var property in new[] { new { Name = nameof(IUnloadable.TypeName), Type = typeof(string) }, new { Name = nameof(IUnloadable.AssemblyName), Type = typeof(string) }, new { Name = nameof(IUnloadable.Error), Type = typeof(string) }, new { Name = nameof(IUnloadable.ParsingEvents), Type = typeof(List) } }) { - var asmName = new AssemblyName($"YamlProxy_{Guid.NewGuid():N}"); - - // Create assembly (in memory) - var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run); - var moduleBuilder = asmBuilder.DefineDynamicModule("DynamicModule"); - - // Create type - var typeBuilder = moduleBuilder.DefineType($"{baseType}YamlProxy"); - AbstractObjectInstantiator.InitializeTypeBuilderFromType(typeBuilder, baseType); - - // Add DisplayAttribute - var displayAttributeCtor = typeof(DisplayAttribute).GetConstructor(new Type[] { typeof(string), typeof(string) }); - var displayAttribute = new CustomAttributeBuilder(displayAttributeCtor, new object[] { "Error: unable to load this object", null }); - typeBuilder.SetCustomAttribute(displayAttribute); - - // Add NonInstantiableAttribute - var nonInstantiableAttributeCtor = typeof(NonInstantiableAttribute).GetConstructor(Type.EmptyTypes); - var nonInstantiableAttribute = new CustomAttributeBuilder(nonInstantiableAttributeCtor, new object[0]); - typeBuilder.SetCustomAttribute(nonInstantiableAttribute); - - // Implement IUnloadable - typeBuilder.AddInterfaceImplementation(typeof(IUnloadable)); - - var backingFields = new List(); - foreach (var property in new[] { new { Name = nameof(IUnloadable.TypeName), Type = typeof(string) }, new { Name = nameof(IUnloadable.AssemblyName), Type = typeof(string) }, new { Name = nameof(IUnloadable.Error), Type = typeof(string) }, new { Name = nameof(IUnloadable.ParsingEvents), Type = typeof(List) } }) - { - // Add backing field - var backingField = typeBuilder.DefineField($"{property.Name.ToLowerInvariant()}", property.Type, FieldAttributes.Private); - backingFields.Add(backingField); - - // Create property - var propertyBuilder = typeBuilder.DefineProperty(property.Name, PropertyAttributes.HasDefault, property.Type, Type.EmptyTypes); - - // Create getter method - var propertyGetter = typeBuilder.DefineMethod($"get_{property.Name}", - MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual, property.Type, Type.EmptyTypes); - var propertyGetterIL = propertyGetter.GetILGenerator(); - propertyGetterIL.Emit(OpCodes.Ldarg_0); - propertyGetterIL.Emit(OpCodes.Ldfld, backingField); - propertyGetterIL.Emit(OpCodes.Ret); - propertyBuilder.SetGetMethod(propertyGetter); - - // Add DataMemberIgnoreAttribute - var dataMemberIgnoreAttributeCtor = typeof(DataMemberIgnoreAttribute).GetConstructor(Type.EmptyTypes); - var dataMemberIgnoreAttribute = new CustomAttributeBuilder(dataMemberIgnoreAttributeCtor, new object[0]); - propertyBuilder.SetCustomAttribute(dataMemberIgnoreAttribute); - } - - // .ctor (initialize backing fields too) - var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, backingFields.Select(x => x.FieldType).ToArray()); - var ctorIL = ctor.GetILGenerator(); - // Call parent ctor (if one without parameters exist) - var defaultCtor = baseType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); - if (defaultCtor != null) - { - ctorIL.Emit(OpCodes.Ldarg_0); - ctorIL.Emit(OpCodes.Call, defaultCtor); - } - // Initialize fields - for (var index = 0; index < backingFields.Count; index++) - { - var backingField = backingFields[index]; - ctorIL.Emit(OpCodes.Ldarg_0); - ctorIL.Emit(OpCodes.Ldarg, index + 1); - ctorIL.Emit(OpCodes.Stfld, backingField); - } - ctorIL.Emit(OpCodes.Ret); - - // User-registered callbacks - ProcessProxyType?.Invoke(baseType, typeBuilder); - - proxyType = typeBuilder.CreateTypeInfo(); - proxyTypes.Add(baseType, proxyType); + // Add backing field + var backingField = typeBuilder.DefineField($"{property.Name.ToLowerInvariant()}", property.Type, FieldAttributes.Private); + backingFields.Add(backingField); + + // Create property + var propertyBuilder = typeBuilder.DefineProperty(property.Name, PropertyAttributes.HasDefault, property.Type, Type.EmptyTypes); + + // Create getter method + var propertyGetter = typeBuilder.DefineMethod($"get_{property.Name}", + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual, property.Type, Type.EmptyTypes); + var propertyGetterIL = propertyGetter.GetILGenerator(); + propertyGetterIL.Emit(OpCodes.Ldarg_0); + propertyGetterIL.Emit(OpCodes.Ldfld, backingField); + propertyGetterIL.Emit(OpCodes.Ret); + propertyBuilder.SetGetMethod(propertyGetter); + + // Add DataMemberIgnoreAttribute + var dataMemberIgnoreAttributeCtor = typeof(DataMemberIgnoreAttribute).GetConstructor(Type.EmptyTypes)!; + var dataMemberIgnoreAttribute = new CustomAttributeBuilder(dataMemberIgnoreAttributeCtor, []); + propertyBuilder.SetCustomAttribute(dataMemberIgnoreAttribute); } - } - return (IUnloadable)Activator.CreateInstance(proxyType, typeName, assemblyName, error, parsingEvents); + // .ctor (initialize backing fields too) + var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, backingFields.Select(x => x.FieldType).ToArray()); + var ctorIL = ctor.GetILGenerator(); + // Call parent ctor (if one without parameters exist) + var defaultCtor = baseType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); + if (defaultCtor != null) + { + ctorIL.Emit(OpCodes.Ldarg_0); + ctorIL.Emit(OpCodes.Call, defaultCtor); + } + // Initialize fields + for (var index = 0; index < backingFields.Count; index++) + { + var backingField = backingFields[index]; + ctorIL.Emit(OpCodes.Ldarg_0); + ctorIL.Emit(OpCodes.Ldarg, index + 1); + ctorIL.Emit(OpCodes.Stfld, backingField); + } + ctorIL.Emit(OpCodes.Ret); + + // User-registered callbacks + ProcessProxyType?.Invoke(baseType, typeBuilder); + + proxyType = typeBuilder.CreateTypeInfo(); + proxyTypes.Add(baseType, proxyType); + } } + + return (IUnloadable)Activator.CreateInstance(proxyType, typeName, assemblyName, error, parsingEvents)!; } } diff --git a/sources/assets/Stride.Core.Assets/Yaml/YamlAssetMetadata.cs b/sources/assets/Stride.Core.Assets/Yaml/YamlAssetMetadata.cs index 71d3631ea2..6ff7804f4e 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/YamlAssetMetadata.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/YamlAssetMetadata.cs @@ -1,98 +1,95 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Stride.Core.Annotations; -namespace Stride.Core.Assets.Yaml +namespace Stride.Core.Assets.Yaml; + +/// +/// Equality comparer for hwne used as a key in a hashing collection (e.g. . +/// +/// +/// To stay valid the compared must not change while used as keys in the hashing collection. +/// +public class YamlAssetPathComparer : EqualityComparer +{ + public static new YamlAssetPathComparer Default { get; } = new YamlAssetPathComparer(); + + /// + public override bool Equals(YamlAssetPath? x, YamlAssetPath? y) + { + if (ReferenceEquals(x, y)) return true; + if (x is null || y is null) return false; + return x.Match(y); + } + + /// + public override int GetHashCode(YamlAssetPath obj) + { + return obj?.Elements.Aggregate(0, (hashCode, element) => (hashCode * 397) ^ element.GetHashCode()) ?? 0; + } +} + +/// +/// A container class to transfer metadata between the asset and the YAML serializer. +/// +/// The type of metadata. +public class YamlAssetMetadata : IYamlAssetMetadata, IEnumerable> { + + private readonly Dictionary metadata = new(YamlAssetPathComparer.Default); + private bool isAttached; + /// - /// Equality comparer for hwne used as a key in a hashing collection (e.g. . + /// Gets the number of key/value pairs contained in the /// - /// - /// To stay valid the compared must not change while used as keys in the hashing collection. - /// - public class YamlAssetPathComparer : EqualityComparer + public int Count => metadata.Count; + + /// + /// Attaches the given metadata value to the given YAML path. + /// + /// The path at which to attach metadata. + /// The metadata to attach. + public void Set(YamlAssetPath path, T value) { - public new static YamlAssetPathComparer Default { get; } = new YamlAssetPathComparer(); - - /// - public override bool Equals(YamlAssetPath x, YamlAssetPath y) - { - if (ReferenceEquals(x, y)) return true; - return x.Match(y); - } - - /// - public override int GetHashCode(YamlAssetPath obj) - { - return obj?.Elements.Aggregate(0, (hashCode, element) => (hashCode * 397) ^ element.GetHashCode()) ?? 0; - } + if (isAttached) throw new InvalidOperationException("Cannot modify a YamlAssetMetadata after it has been attached."); + metadata[path] = value; } /// - /// A container class to transfer metadata between the asset and the YAML serializer. + /// Removes attached metadata from the given YAML path. /// - /// The type of metadata. - public class YamlAssetMetadata : IYamlAssetMetadata, IEnumerable> + /// The path at which to remove metadata. + public void Remove(YamlAssetPath path) { + if (isAttached) throw new InvalidOperationException("Cannot modify a YamlAssetMetadata after it has been attached."); + metadata.Remove(path); + } - private readonly Dictionary metadata = new Dictionary(YamlAssetPathComparer.Default); - private bool isAttached; - - /// - /// Gets the number of key/value pairs contained in the - /// - public int Count => metadata.Count; - - /// - /// Attaches the given metadata value to the given YAML path. - /// - /// The path at which to attach metadata. - /// The metadata to attach. - public void Set([NotNull] YamlAssetPath path, T value) - { - if (isAttached) throw new InvalidOperationException("Cannot modify a YamlAssetMetadata after it has been attached."); - metadata[path] = value; - } - - /// - /// Removes attached metadata from the given YAML path. - /// - /// The path at which to remove metadata. - public void Remove(YamlAssetPath path) - { - if (isAttached) throw new InvalidOperationException("Cannot modify a YamlAssetMetadata after it has been attached."); - metadata.Remove(path); - } - - /// - /// Tries to retrieve the metadata for the given path. - /// - /// The path at which to retrieve metadata. - /// The metadata attached to the given path, or the default value of if no metadata is attached at the given path. - public T TryGet([NotNull] YamlAssetPath path) - { - metadata.TryGetValue(path, out T value); - return value; - } - - /// - void IYamlAssetMetadata.Set(YamlAssetPath path, object value) => Set(path, (T)value); - - /// - object IYamlAssetMetadata.TryGet(YamlAssetPath path) => TryGet(path); - - /// - void IYamlAssetMetadata.Attach() - { - isAttached = true; - } - - IEnumerator IEnumerable.GetEnumerator() => ((IDictionary)metadata).GetEnumerator(); - - public IEnumerator> GetEnumerator() => metadata.GetEnumerator(); + /// + /// Tries to retrieve the metadata for the given path. + /// + /// The path at which to retrieve metadata. + /// The metadata attached to the given path, or the default value of if no metadata is attached at the given path. + public T? TryGet(YamlAssetPath path) + { + metadata.TryGetValue(path, out var value); + return value; } + + /// + void IYamlAssetMetadata.Set(YamlAssetPath path, object value) => Set(path, (T)value); + + /// + object? IYamlAssetMetadata.TryGet(YamlAssetPath path) => TryGet(path); + + /// + void IYamlAssetMetadata.Attach() + { + isAttached = true; + } + + IEnumerator IEnumerable.GetEnumerator() => ((IDictionary)metadata).GetEnumerator(); + + public IEnumerator> GetEnumerator() => metadata.GetEnumerator(); } diff --git a/sources/assets/Stride.Core.Assets/Yaml/YamlAssetPath.cs b/sources/assets/Stride.Core.Assets/Yaml/YamlAssetPath.cs index 8969f7d6f9..4a1af44413 100644 --- a/sources/assets/Stride.Core.Assets/Yaml/YamlAssetPath.cs +++ b/sources/assets/Stride.Core.Assets/Yaml/YamlAssetPath.cs @@ -1,247 +1,250 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using System.Diagnostics.Contracts; -using System.Linq; using System.Text; -using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.Reflection; using Stride.Core.Yaml; -namespace Stride.Core.Assets.Yaml +namespace Stride.Core.Assets.Yaml; + +/// +/// A class representing the path of a member or item of an Asset as it is created/consumed by the YAML asset serializers. +/// +[DataContract] +public sealed class YamlAssetPath { /// - /// A class representing the path of a member or item of an Asset as it is created/consumed by the YAML asset serializers. + /// An enum representing the type of an element of the path. /// - [DataContract] - public sealed class YamlAssetPath + public enum ElementType { /// - /// An enum representing the type of an element of the path. + /// An element that is a member. /// - public enum ElementType - { - /// - /// An element that is a member. - /// - Member, - /// - /// An element that is an index or a key. - /// - Index, - /// - /// An element that is an item identifier of a collection with ids - /// - /// - ItemId - } - + Member, /// - /// A structure representing an element of a . + /// An element that is an index or a key. /// - public struct Element : IEquatable - { - /// - /// The type of the element. - /// - public readonly ElementType Type; - /// - /// The value of the element, corresonding to its . - /// - public readonly object Value; - - /// - /// Initializes a new instance of the structure. - /// - /// The type of element. - /// The value of the element. - public Element(ElementType type, object value) - { - Type = type; - Value = value; - } - - /// - /// Fetches the name of the member, considering this element is a . - /// - /// The name of the member. - public string AsMember() { if (Type != ElementType.Member) throw new InvalidOperationException("This item is not a Member"); return (string)Value; } - /// - /// Returns the of this element, considering this element is a . - /// - /// The of the item. - public ItemId AsItemId() { if (Type != ElementType.ItemId) throw new InvalidOperationException("This item is not a item Id"); return (ItemId)Value; } - - /// - public bool Equals(Element other) - { - return Type == other.Type && Equals(Value, other.Value); - } - - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is Element && Equals((Element)obj); - } - - /// - public override int GetHashCode() - { - unchecked { return ((int)Type*397) ^ (Value?.GetHashCode() ?? 0); } - } - - public static bool operator ==(Element left, Element right) - { - return left.Equals(right); - } - - public static bool operator !=(Element left, Element right) - { - return !left.Equals(right); - } - } - - private readonly List elements = new List(16); - + Index, /// - /// Initializes a new instance of the class. + /// An element that is an item identifier of a collection with ids /// - public YamlAssetPath() - { - } + /// + ItemId + } + /// + /// A structure representing an element of a . + /// + public readonly struct Element : IEquatable + { /// - /// Initializes a new instance of the class. + /// The type of the element. /// - /// The elements constituting this path, in proper order. - public YamlAssetPath([NotNull] IEnumerable elements) - { - if (elements == null) throw new ArgumentNullException(nameof(elements)); - this.elements.AddRange(elements); - } - + public readonly ElementType Type; /// - /// The elements constituting this path. + /// The value of the element, corresonding to its . /// - [DataMember] - public IReadOnlyList Elements => elements; + public readonly object Value; /// - /// Indicates whether the current path represents the same path of another object. + /// Initializes a new instance of the structure. /// - /// An object to compare with this path. - /// true if the current path matches the parameter; otherwise, false. - public bool Match(YamlAssetPath other) + /// The type of element. + /// The value of the element. + public Element(ElementType type, object value) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - if (Elements.Count != other.Elements.Count) return false; - - return Elements.SequenceEqual(other.Elements); + Type = type; + Value = value; } /// - /// Adds an additional element to the path representing an access to a member of an object. + /// Fetches the name of the member, considering this element is a . /// - /// The name of the member. - public void PushMember(string memberName) + /// The name of the member. + public readonly string AsMember() { - elements.Add(new Element(ElementType.Member, memberName)); + if (Type != ElementType.Member) throw new InvalidOperationException("This item is not a Member"); + return (string)Value; } /// - /// Adds an additional element to the path representing an access to an item of a collection or a value of a dictionary that does not use . + /// Returns the of this element, considering this element is a . /// - /// The index of the item. - /// - /// - public void PushIndex(object index) + /// The of the item. + public readonly ItemId AsItemId() { - elements.Add(new Element(ElementType.Index, index)); + if (Type != ElementType.ItemId) throw new InvalidOperationException("This item is not a item Id"); + return (ItemId)Value; } - /// - /// Adds an additional element to the path representing an access to an item of an collection or a value of a dictionary. - /// - /// The of the item. - public void PushItemId(ItemId itemId) + /// + public readonly bool Equals(Element other) { - elements.Add(new Element(ElementType.ItemId, itemId)); + return Type == other.Type && Equals(Value, other.Value); } - /// - /// Adds an additional element. - /// - /// The to add. - public void Push(Element element) + /// + public override readonly bool Equals(object? obj) { - elements.Add(element); + if (ReferenceEquals(null, obj)) return false; + return obj is Element element && Equals(element); } - /// - /// Appends the given to this instance. - /// - /// The - /// A new instance of corresonding to the given instance appended to this instance. - [NotNull, Pure] - public YamlAssetPath Append([CanBeNull] YamlAssetPath other) + /// + public override readonly int GetHashCode() { - var result = new YamlAssetPath(elements); - if (other != null) - { - result.elements.AddRange(other.elements); - } - return result; + return HashCode.Combine(Type, Value); } - /// - /// Creates a clone of this instance. - /// - /// A new copy of this . - [NotNull] - public YamlAssetPath Clone() + public static bool operator ==(Element left, Element right) { - var clone = new YamlAssetPath(elements); - return clone; + return left.Equals(right); } - /// - /// Convert this into a . - /// - /// The actual instance that is root of this path. - /// An instance of corresponding to the same target than this . - [NotNull, Pure] - public MemberPath ToMemberPath(object root) + public static bool operator !=(Element left, Element right) { - var currentObject = root; - var memberPath = new MemberPath(); - foreach (var item in Elements) - { - if (currentObject == null) - throw new InvalidOperationException($"The path [{ToString()}] contains access to a member of a null object."); + return !left.Equals(right); + } + } - switch (item.Type) - { - case ElementType.Member: + private readonly List elements = new(16); + + /// + /// Initializes a new instance of the class. + /// + public YamlAssetPath() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The elements constituting this path, in proper order. + public YamlAssetPath(IEnumerable elements) + { + ArgumentNullException.ThrowIfNull(elements); + this.elements.AddRange(elements); + } + + /// + /// The elements constituting this path. + /// + [DataMember] + public IReadOnlyList Elements => elements; + + /// + /// Indicates whether the current path represents the same path of another object. + /// + /// An object to compare with this path. + /// true if the current path matches the parameter; otherwise, false. + public bool Match(YamlAssetPath other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + if (Elements.Count != other.Elements.Count) return false; + + return Elements.SequenceEqual(other.Elements); + } + + /// + /// Adds an additional element to the path representing an access to a member of an object. + /// + /// The name of the member. + public void PushMember(string memberName) + { + elements.Add(new Element(ElementType.Member, memberName)); + } + + /// + /// Adds an additional element to the path representing an access to an item of a collection or a value of a dictionary that does not use . + /// + /// The index of the item. + /// + /// + public void PushIndex(object index) + { + elements.Add(new Element(ElementType.Index, index)); + } + + /// + /// Adds an additional element to the path representing an access to an item of an collection or a value of a dictionary. + /// + /// The of the item. + public void PushItemId(ItemId itemId) + { + elements.Add(new Element(ElementType.ItemId, itemId)); + } + + /// + /// Adds an additional element. + /// + /// The to add. + public void Push(Element element) + { + elements.Add(element); + } + + /// + /// Appends the given to this instance. + /// + /// The + /// A new instance of corresonding to the given instance appended to this instance. + [Pure] + public YamlAssetPath Append(YamlAssetPath? other) + { + var result = new YamlAssetPath(elements); + if (other != null) + { + result.elements.AddRange(other.elements); + } + return result; + } + + /// + /// Creates a clone of this instance. + /// + /// A new copy of this . + public YamlAssetPath Clone() + { + var clone = new YamlAssetPath(elements); + return clone; + } + + /// + /// Convert this into a . + /// + /// The actual instance that is root of this path. + /// An instance of corresponding to the same target than this . + [Pure] + public MemberPath ToMemberPath(object root) + { + var currentObject = root; + var memberPath = new MemberPath(); + foreach (var item in Elements) + { + if (currentObject is null) + throw new InvalidOperationException($"The path [{ToString()}] contains access to a member of a null object."); + + switch (item.Type) + { + case ElementType.Member: { var typeDescriptor = TypeDescriptorFactory.Default.Find(currentObject.GetType()); var name = item.AsMember(); - var memberDescriptor = typeDescriptor.Members.FirstOrDefault(x => x.Name == name); - if (memberDescriptor == null) throw new InvalidOperationException($"The path [{ToString()}] contains access to non-existing member [{name}]."); + var memberDescriptor = typeDescriptor.Members.FirstOrDefault(x => x.Name == name) + ?? throw new InvalidOperationException($"The path [{ToString()}] contains access to non-existing member [{name}]."); memberPath.Push(memberDescriptor); currentObject = memberDescriptor.Get(currentObject); break; } - case ElementType.Index: + case ElementType.Index: { var typeDescriptor = TypeDescriptorFactory.Default.Find(currentObject.GetType()); if (typeDescriptor is ArrayDescriptor arrayDescriptor) { - if (!(item.Value is int)) + if (item.Value is not int) { throw new InvalidOperationException($"The path [{ToString()}] contains non-integer index on an array."); } @@ -253,7 +256,7 @@ public MemberPath ToMemberPath(object root) { if (collectionDescriptor is SetDescriptor setDescriptor) { - if (item.Value == null && !collectionDescriptor.ElementType.IsNullable()) + if (item.Value is null && !collectionDescriptor.ElementType.IsNullable()) { throw new InvalidOperationException($"The path [{ToString()}] contains a null item on a set."); } @@ -266,7 +269,7 @@ public MemberPath ToMemberPath(object root) } else { - if (!(item.Value is int)) + if (item.Value is not int) { throw new InvalidOperationException($"The path [{ToString()}] contains non-integer index on a collection."); } @@ -276,20 +279,20 @@ public MemberPath ToMemberPath(object root) } else if (typeDescriptor is DictionaryDescriptor dictionaryDescriptor) { - if (item.Value == null) throw new InvalidOperationException($"The path [{ToString()}] contains a null key on an dictionary."); + if (item.Value is null) throw new InvalidOperationException($"The path [{ToString()}] contains a null key on an dictionary."); memberPath.Push(dictionaryDescriptor, item.Value); currentObject = dictionaryDescriptor.GetValue(currentObject, item.Value); } break; } - case ElementType.ItemId: + case ElementType.ItemId: { var ids = CollectionItemIdHelper.GetCollectionItemIds(currentObject); var key = ids.GetKey(item.AsItemId()); var typeDescriptor = TypeDescriptorFactory.Default.Find(currentObject.GetType()); if (typeDescriptor is ArrayDescriptor arrayDescriptor) { - if (!(key is int)) throw new InvalidOperationException($"The path [{ToString()}] contains a non-valid item id on an array."); + if (key is not int) throw new InvalidOperationException($"The path [{ToString()}] contains a non-valid item id on an array."); int keyInt = (int)key; memberPath.Push(arrayDescriptor, keyInt); currentObject = arrayDescriptor.GetValue(currentObject, keyInt); @@ -298,14 +301,14 @@ public MemberPath ToMemberPath(object root) { if (collectionDescriptor.Category == DescriptorCategory.Set) { - if (item.Value == null && !collectionDescriptor.ElementType.IsNullable()) + if (item.Value is null && !collectionDescriptor.ElementType.IsNullable()) { throw new InvalidOperationException($"The path [{ToString()}] contains a null item on a set."); } } else { - if (!(key is int)) + if (key is not int) { throw new InvalidOperationException($"The path [{ToString()}] contains a non-valid item id on a collection."); } @@ -315,152 +318,150 @@ public MemberPath ToMemberPath(object root) } else if (typeDescriptor is DictionaryDescriptor dictionaryDescriptor) { - if (key == null) throw new InvalidOperationException($"The path [{ToString()}] contains a non-valid item id on an dictionary."); + if (key is null) throw new InvalidOperationException($"The path [{ToString()}] contains a non-valid item id on an dictionary."); memberPath.Push(dictionaryDescriptor, key); currentObject = dictionaryDescriptor.GetValue(currentObject, key); } break; } - default: - throw new ArgumentOutOfRangeException(); - } + default: + throw new ArgumentOutOfRangeException(); } - - return memberPath; } - /// - /// Creates a out of a instance. - /// - /// The from which to create a . - /// The root object of the given . - /// An instance of corresponding to the same target than the given . - [NotNull] - public static YamlAssetPath FromMemberPath([NotNull] MemberPath path, object root) + return memberPath; + } + + /// + /// Creates a out of a instance. + /// + /// The from which to create a . + /// The root object of the given . + /// An instance of corresponding to the same target than the given . + public static YamlAssetPath FromMemberPath(MemberPath path, object root) + { + ArgumentNullException.ThrowIfNull(path); + var result = new YamlAssetPath(); + var clone = new MemberPath(); + foreach (var item in path.Decompose()) { - if (path == null) throw new ArgumentNullException(nameof(path)); - var result = new YamlAssetPath(); - var clone = new MemberPath(); - foreach (var item in path.Decompose()) + if (item.MemberDescriptor != null) { - if (item.MemberDescriptor != null) + clone.Push(item.MemberDescriptor); + var member = item.MemberDescriptor.Name; + result.PushMember(member); + } + else + { + object? index = null; + if (item is MemberPath.ArrayPathItem arrayItem) + { + clone.Push(arrayItem.Descriptor, arrayItem.Index); + index = arrayItem.Index; + } + else if (item is MemberPath.CollectionPathItem collectionItem) + { + clone.Push(collectionItem.Descriptor, collectionItem.Index); + index = collectionItem.Index; + } + else if (item is MemberPath.DictionaryPathItem dictionaryItem) { - clone.Push(item.MemberDescriptor); - var member = item.MemberDescriptor.Name; - result.PushMember(member); + clone.Push(dictionaryItem.Descriptor, dictionaryItem.Key); + index = dictionaryItem.Key; + } + else if (item is MemberPath.SetPathItem setItem) + { + clone.Push(setItem.Descriptor, setItem.Index); + index = setItem.Index; + } + if (!CollectionItemIdHelper.TryGetCollectionItemIds(clone.GetValue(root), out var ids)) + { + result.PushIndex(index); } else { - object index = null; - if (item is MemberPath.ArrayPathItem arrayItem) - { - clone.Push(arrayItem.Descriptor, arrayItem.Index); - index = arrayItem.Index; - } - else if (item is MemberPath.CollectionPathItem collectionItem) - { - clone.Push(collectionItem.Descriptor, collectionItem.Index); - index = collectionItem.Index; - } - else if (item is MemberPath.DictionaryPathItem dictionaryItem) - { - clone.Push(dictionaryItem.Descriptor, dictionaryItem.Key); - index = dictionaryItem.Key; - } - else if (item is MemberPath.SetPathItem setItem) - { - clone.Push(setItem.Descriptor, setItem.Index); - index = setItem.Index; - } - if (!CollectionItemIdHelper.TryGetCollectionItemIds(clone.GetValue(root), out CollectionItemIdentifiers ids)) - { - result.PushIndex(index); - } - else - { - var id = ids[index]; - // Create a new id if we don't have any so far - if (id == ItemId.Empty) - id = ItemId.New(); - result.PushItemId(id); - } + var id = ids[index]; + // Create a new id if we don't have any so far + if (id == ItemId.Empty) + id = ItemId.New(); + result.PushItemId(id); } } - return result; } + return result; + } + + public bool StartsWith(YamlAssetPath path) + { + ArgumentNullException.ThrowIfNull(path); + if (path.elements.Count > elements.Count) + return false; - public bool StartsWith([NotNull] YamlAssetPath path) + for (var i = 0; i < path.Elements.Count; ++i) { - if (path == null) throw new ArgumentNullException(nameof(path)); - if (path.elements.Count > elements.Count) + if (!Elements[i].Equals(path.Elements[i])) return false; - - for (var i = 0; i < path.Elements.Count; ++i) - { - if (!Elements[i].Equals(path.Elements[i])) - return false; - } - - return true; } - /// - public override string ToString() + return true; + } + + /// + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("(object)"); + foreach (var item in elements) { - var sb = new StringBuilder(); - sb.Append("(object)"); - foreach (var item in elements) + switch (item.Type) { - switch (item.Type) - { - case ElementType.Member: - sb.Append('.'); - sb.Append(item.Value); - break; - case ElementType.Index: - sb.Append('['); - sb.Append(item.Value); - sb.Append(']'); - break; - case ElementType.ItemId: - sb.Append('{'); - sb.Append(item.Value); - sb.Append('}'); - break; - default: - throw new ArgumentOutOfRangeException(); - } + case ElementType.Member: + sb.Append('.'); + sb.Append(item.Value); + break; + case ElementType.Index: + sb.Append('['); + sb.Append(item.Value); + sb.Append(']'); + break; + case ElementType.ItemId: + sb.Append('{'); + sb.Append(item.Value); + sb.Append('}'); + break; + default: + throw new ArgumentOutOfRangeException(); } - return sb.ToString(); } + return sb.ToString(); + } - internal static bool IsCollectionWithIdType([NotNull] Type type, object key, out ItemId id, out object actualKey) + internal static bool IsCollectionWithIdType(Type type, object key, out ItemId id, out object actualKey) + { + if (type.IsGenericType) { - if (type.IsGenericType) + if (type.GetGenericTypeDefinition() == typeof(CollectionWithItemIds<>)) { - if (type.GetGenericTypeDefinition() == typeof(CollectionWithItemIds<>)) - { - id = (ItemId)key; - actualKey = key; - return true; - } - if (type.GetGenericTypeDefinition() == typeof(DictionaryWithItemIds<,>)) - { - var keyWithId = (IKeyWithId)key; - id = keyWithId.Id; - actualKey = keyWithId.Key; - return true; - } + id = (ItemId)key; + actualKey = key; + return true; + } + if (type.GetGenericTypeDefinition() == typeof(DictionaryWithItemIds<,>)) + { + var keyWithId = (IKeyWithId)key; + id = keyWithId.Id; + actualKey = keyWithId.Key; + return true; } - - id = ItemId.Empty; - actualKey = key; - return false; } - internal static bool IsCollectionWithIdType([NotNull] Type type, object key, out ItemId id) - { - return IsCollectionWithIdType(type, key, out id, out object _); - } + id = ItemId.Empty; + actualKey = key; + return false; + } + + internal static bool IsCollectionWithIdType(Type type, object key, out ItemId id) + { + return IsCollectionWithIdType(type, key, out id, out object _); } } diff --git a/sources/assets/Stride.Core.Packages/ConstraintProvider.cs b/sources/assets/Stride.Core.Packages/ConstraintProvider.cs index a07bb58782..403036c7a7 100644 --- a/sources/assets/Stride.Core.Packages/ConstraintProvider.cs +++ b/sources/assets/Stride.Core.Packages/ConstraintProvider.cs @@ -1,50 +1,44 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; +namespace Stride.Core.Packages; -namespace Stride.Core.Packages +/// +/// Collection of constraints associated to some packages expressed as version ranges. +/// +public class ConstraintProvider { /// - /// Collection of constraints associated to some packages expressed as version ranges. + /// Store constraints associated to a given package. /// - public class ConstraintProvider - { - /// - /// Store constraints associated to a given package. - /// - private readonly Dictionary constraints = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary constraints = new(StringComparer.OrdinalIgnoreCase); - /// - /// Does current instance have constraints? - /// - public bool HasConstraints => constraints.Count > 0; + /// + /// Does current instance have constraints? + /// + public bool HasConstraints => constraints.Count > 0; - /// - /// Add constraint to package ID . - /// - /// Package on which constraint will be applied. - /// Range of constraint. - public void AddConstraint(string packageId, PackageVersionRange range) - { - constraints[packageId] = range; - } + /// + /// Add constraint to package ID . + /// + /// Package on which constraint will be applied. + /// Range of constraint. + public void AddConstraint(string packageId, PackageVersionRange range) + { + constraints[packageId] = range; + } - /// - /// Retrieve constraint associated with if any. - /// - /// Id of package being queried. - /// Constraint if any, null otherwise. - internal PackageVersionRange GetConstraint(string packageId) + /// + /// Retrieve constraint associated with if any. + /// + /// Id of package being queried. + /// Constraint if any, null otherwise. + internal PackageVersionRange? GetConstraint(string packageId) + { + if (constraints.TryGetValue(packageId, out var versionRange)) { - PackageVersionRange versionRange; - if (constraints.TryGetValue(packageId, out versionRange)) - { - return versionRange; - } - return null; + return versionRange; } + return null; } } diff --git a/sources/assets/Stride.Core.Packages/INugetDownloadProgress.cs b/sources/assets/Stride.Core.Packages/INugetDownloadProgress.cs index 9c5a1d94c8..f1da3e3873 100644 --- a/sources/assets/Stride.Core.Packages/INugetDownloadProgress.cs +++ b/sources/assets/Stride.Core.Packages/INugetDownloadProgress.cs @@ -1,7 +1,6 @@ -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +interface INugetDownloadProgress { - interface INugetDownloadProgress - { - void DownloadProgress(long contentPosition, long contentLength); - } + void DownloadProgress(long contentPosition, long contentLength); } diff --git a/sources/assets/Stride.Core.Packages/IPackagesLogger.cs b/sources/assets/Stride.Core.Packages/IPackagesLogger.cs index 832a2273fb..2117722290 100644 --- a/sources/assets/Stride.Core.Packages/IPackagesLogger.cs +++ b/sources/assets/Stride.Core.Packages/IPackagesLogger.cs @@ -1,27 +1,24 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Threading.Tasks; +namespace Stride.Core.Packages; -namespace Stride.Core.Packages +/// +/// Generic interface for logging. See for various level of logging. +/// +public interface IPackagesLogger { /// - /// Generic interface for logging. See for various level of logging. + /// Logs the using the log . /// - public interface IPackagesLogger - { - /// - /// Logs the using the log . - /// - /// The level of the logged message. - /// The message to log. - void Log(MessageLevel level, string message); + /// The level of the logged message. + /// The message to log. + void Log(MessageLevel level, string message); - /// - /// Logs the using the log . - /// - /// The level of the logged message. - /// The message to log. - Task LogAsync(MessageLevel level, string message); - } + /// + /// Logs the using the log . + /// + /// The level of the logged message. + /// The message to log. + Task LogAsync(MessageLevel level, string message); } diff --git a/sources/assets/Stride.Core.Packages/ManifestDependency.cs b/sources/assets/Stride.Core.Packages/ManifestDependency.cs index e6fee9247b..8a6128bab5 100644 --- a/sources/assets/Stride.Core.Packages/ManifestDependency.cs +++ b/sources/assets/Stride.Core.Packages/ManifestDependency.cs @@ -1,23 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; +namespace Stride.Core.Packages; -namespace Stride.Core.Packages +/// +/// Representation of a dependency in a package manifest. +/// +public class ManifestDependency { /// - /// Representation of a dependency in a package manifest. + /// Name of package dependency. /// - public class ManifestDependency - { - /// - /// Name of package dependency. - /// - public string Id { get; set; } + public string Id { get; set; } - /// - /// Version of package dependency. - /// - public PackageVersionRange Version { get; set; } - } + /// + /// Version of package dependency. + /// + public PackageVersionRange Version { get; set; } } diff --git a/sources/assets/Stride.Core.Packages/ManifestFile.cs b/sources/assets/Stride.Core.Packages/ManifestFile.cs index 39f336f8fc..4cf6140396 100644 --- a/sources/assets/Stride.Core.Packages/ManifestFile.cs +++ b/sources/assets/Stride.Core.Packages/ManifestFile.cs @@ -1,28 +1,27 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +/// +/// Describe a file in a package by giving the of a file or set of files, the destination where they will be copied +/// with some exclude rules . +/// Both Source and Exclude can use regular expressions. +/// +public class ManifestFile { /// - /// Describe a file in a package by giving the of a file or set of files, the destination where they will be copied - /// with some exclude rules . - /// Both Source and Exclude can use regular expressions. + /// Set of source files that will be copied to . /// - public class ManifestFile - { - /// - /// Set of source files that will be copied to . - /// - public string Source { get; set; } + public string Source { get; set; } - /// - /// Target location where files described by will be copied. - /// - public string Target { get; set; } + /// + /// Target location where files described by will be copied. + /// + public string Target { get; set; } - /// - /// Rules excluding copies of files from . - /// - public string Exclude { get; set; } - } + /// + /// Rules excluding copies of files from . + /// + public string Exclude { get; set; } } diff --git a/sources/assets/Stride.Core.Packages/ManifestMetadata.cs b/sources/assets/Stride.Core.Packages/ManifestMetadata.cs index d5295c44a2..8b8ed65514 100644 --- a/sources/assets/Stride.Core.Packages/ManifestMetadata.cs +++ b/sources/assets/Stride.Core.Packages/ManifestMetadata.cs @@ -1,46 +1,42 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using Stride.Core; +namespace Stride.Core.Packages; -namespace Stride.Core.Packages +public class ManifestMetadata { - public class ManifestMetadata + public ManifestMetadata() { - public ManifestMetadata() - { - Dependencies = new List(); - } + Dependencies = []; + } - public string MinClientVersionString { get; set; } - public string Id { get; set; } - public string Version { get; set; } - public string Title { get; set; } - public IEnumerable Authors { get; set; } - public IEnumerable Owners { get; set; } - public string LicenseUrl { get; set; } - public string ProjectUrl { get; set; } - public string IconUrl { get; set; } - public bool RequireLicenseAcceptance { get; set; } - public bool DevelopmentDependency { get; set; } - public string Description { get; set; } - public string Summary { get; set; } - public string ReleaseNotes { get; set; } - public string Copyright { get; set; } - public string Language { get; set; } - public string Tags { get; set; } - public List Dependencies { get; set; } + public string MinClientVersionString { get; set; } + public string Id { get; set; } + public string Version { get; set; } + public string Title { get; set; } + public IEnumerable Authors { get; set; } + public IEnumerable Owners { get; set; } + public string LicenseUrl { get; set; } + public string ProjectUrl { get; set; } + public string IconUrl { get; set; } + public bool RequireLicenseAcceptance { get; set; } + public bool DevelopmentDependency { get; set; } + public string Description { get; set; } + public string Summary { get; set; } + public string ReleaseNotes { get; set; } + public string Copyright { get; set; } + public string Language { get; set; } + public string Tags { get; set; } + public List Dependencies { get; set; } - /// - /// Add new dependency to package name with version to - /// the first set if it exists already, otherwise create a new sets where dependency will be added to. - /// - /// Name of package to add to - /// Version range accepted for package to add to - public void AddDependency(string name, PackageVersionRange v) - { - Dependencies.Add(new ManifestDependency() { Id = name, Version = v }); - } + /// + /// Add new dependency to package name with version to + /// the first set if it exists already, otherwise create a new sets where dependency will be added to. + /// + /// Name of package to add to + /// Version range accepted for package to add to + public void AddDependency(string name, PackageVersionRange v) + { + Dependencies.Add(new ManifestDependency() { Id = name, Version = v }); } } diff --git a/sources/assets/Stride.Core.Packages/MessageLevel.cs b/sources/assets/Stride.Core.Packages/MessageLevel.cs index f3a05e4602..8e24da415c 100644 --- a/sources/assets/Stride.Core.Packages/MessageLevel.cs +++ b/sources/assets/Stride.Core.Packages/MessageLevel.cs @@ -1,20 +1,19 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +/// +/// Possible level of logging used by . +/// +public enum MessageLevel { - /// - /// Possible level of logging used by . - /// - public enum MessageLevel - { - Debug, - Verbose, - Info, - Minimal, - Warning, - Error, - InfoSummary, - ErrorSummary - } + Debug, + Verbose, + Info, + Minimal, + Warning, + Error, + InfoSummary, + ErrorSummary } diff --git a/sources/assets/Stride.Core.Packages/NuGet3Extensions.cs b/sources/assets/Stride.Core.Packages/NuGet3Extensions.cs index fd401db947..45ae9d4479 100644 --- a/sources/assets/Stride.Core.Packages/NuGet3Extensions.cs +++ b/sources/assets/Stride.Core.Packages/NuGet3Extensions.cs @@ -1,9 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; using NuGet.Versioning; using NuGet.Frameworks; using NuGet.Packaging; @@ -11,120 +8,119 @@ using NuGetManifestFile = NuGet.Packaging.ManifestFile; using NuGetManifestMetadata = NuGet.Packaging.ManifestMetadata; -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +public static class NuGet3Extensions { - public static class NuGet3Extensions + /// + /// Converts a into a . + /// + /// The source of conversion. + /// A new instance of corresponding to . + public static PackageVersionRange ToPackageVersionRange(this VersionRange range) { - /// - /// Converts a into a . - /// - /// The source of conversion. - /// A new instance of corresponding to . - public static PackageVersionRange ToPackageVersionRange(this VersionRange range) - { - if (range == null) throw new ArgumentNullException(nameof(range)); + ArgumentNullException.ThrowIfNull(range); - return new PackageVersionRange(range.MinVersion?.ToPackageVersion(), range.IsMinInclusive, range.MaxVersion?.ToPackageVersion(), range.IsMaxInclusive); - } + return new PackageVersionRange(range.MinVersion?.ToPackageVersion(), range.IsMinInclusive, range.MaxVersion?.ToPackageVersion(), range.IsMaxInclusive); + } - /// - /// Converts a into a . - /// - /// The source of conversion. - /// A new instance of corresponding to . - public static PackageVersion ToPackageVersion(this NuGetVersion version) - { - if (version == null) throw new ArgumentNullException(nameof(version)); + /// + /// Converts a into a . + /// + /// The source of conversion. + /// A new instance of corresponding to . + public static PackageVersion ToPackageVersion(this NuGetVersion version) + { + ArgumentNullException.ThrowIfNull(version); - return new PackageVersion(version.Version, version.Release); - } + return new PackageVersion(version.Version, version.Release); + } - /// - /// Converts a into a . - /// - /// The source of conversion. - /// A new instance of corresponding to . - public static VersionRange ToVersionRange(this PackageVersionRange range) - { - if (range == null) throw new ArgumentNullException(nameof(range)); + /// + /// Converts a into a . + /// + /// The source of conversion. + /// A new instance of corresponding to . + public static VersionRange ToVersionRange(this PackageVersionRange range) + { + ArgumentNullException.ThrowIfNull(range); - return new VersionRange(range.MinVersion?.ToNuGetVersion(), range.IsMinInclusive, range.MaxVersion?.ToNuGetVersion(), range.IsMaxInclusive); - } + return new VersionRange(range.MinVersion?.ToNuGetVersion(), range.IsMinInclusive, range.MaxVersion?.ToNuGetVersion(), range.IsMaxInclusive); + } - /// - /// Converts a into a . - /// - /// The source of conversion. - /// A new instance of corresponding to . - public static NuGetVersion ToNuGetVersion(this PackageVersion version) - { - if (version == null) throw new ArgumentNullException(nameof(version)); + /// + /// Converts a into a . + /// + /// The source of conversion. + /// A new instance of corresponding to . + public static NuGetVersion ToNuGetVersion(this PackageVersion version) + { + ArgumentNullException.ThrowIfNull(version); - return new NuGetVersion(version.Version, version.SpecialVersion); - } + return new NuGetVersion(version.Version, version.SpecialVersion); + } - /// - /// Converts a into a . - /// - /// The manifest file source of conversion. - /// A new instance of corresponding to . - public static NuGetManifestFile ToManifestFile(this ManifestFile file) + /// + /// Converts a into a . + /// + /// The manifest file source of conversion. + /// A new instance of corresponding to . + public static NuGetManifestFile ToManifestFile(this ManifestFile file) + { + ArgumentNullException.ThrowIfNull(file); + + return new NuGetManifestFile() { - if (file == null) throw new ArgumentNullException(nameof(file)); + Source = file.Source, + Exclude = file.Exclude, + Target = file.Target + }; + } - return new NuGetManifestFile() - { - Source = file.Source, - Exclude = file.Exclude, - Target = file.Target - }; - } + /// + /// Converts a into a . + /// + /// The metadata source of conversion. + /// A new instance of corresponding to . + public static NuGetManifestMetadata ToManifestMetadata(this ManifestMetadata metadata) + { + ArgumentNullException.ThrowIfNull(metadata); - /// - /// Converts a into a . - /// - /// The metadata source of conversion. - /// A new instance of corresponding to . - public static NuGetManifestMetadata ToManifestMetadata(this ManifestMetadata metadata) + var nugetMetadata = new NuGetManifestMetadata() { - if (metadata == null) throw new ArgumentNullException(nameof(metadata)); + Id = metadata.Id, + Authors = metadata.Authors, + Description = metadata.Description, + Copyright = metadata.Copyright, + DevelopmentDependency = metadata.DevelopmentDependency, + Version = new NuGetVersion(metadata.Version), + Owners = metadata.Owners, + Language = metadata.Language, + MinClientVersionString = metadata.MinClientVersionString, + ReleaseNotes = metadata.ReleaseNotes, + RequireLicenseAcceptance = metadata.RequireLicenseAcceptance, + Summary = metadata.Summary, + Tags = metadata.Tags, + Title = metadata.Title + }; + // Setting properties without a setter. + nugetMetadata.SetIconUrl(metadata.IconUrl); + nugetMetadata.SetLicenseUrl(metadata.LicenseUrl); + nugetMetadata.SetProjectUrl(metadata.ProjectUrl); - var nugetMetadata = new NuGetManifestMetadata() - { - Id = metadata.Id, - Authors = metadata.Authors, - Description = metadata.Description, - Copyright = metadata.Copyright, - DevelopmentDependency = metadata.DevelopmentDependency, - Version = new NuGetVersion(metadata.Version), - Owners = metadata.Owners, - Language = metadata.Language, - MinClientVersionString = metadata.MinClientVersionString, - ReleaseNotes = metadata.ReleaseNotes, - RequireLicenseAcceptance = metadata.RequireLicenseAcceptance, - Summary = metadata.Summary, - Tags = metadata.Tags, - Title = metadata.Title - }; - // Setting properties without a setter. - nugetMetadata.SetIconUrl(metadata.IconUrl); - nugetMetadata.SetLicenseUrl(metadata.LicenseUrl); - nugetMetadata.SetProjectUrl(metadata.ProjectUrl); - - // Updating dependencies - if (metadata.Dependencies.Count != 0) + // Updating dependencies + if (metadata.Dependencies.Count != 0) + { + var packages = new List(); + foreach (var dependency in metadata.Dependencies) { - var packages = new List(); - foreach (var dependency in metadata.Dependencies) - { - packages.Add(new PackageDependency(dependency.Id, dependency.Version.ToVersionRange())); - } - // We are .NET agnostic - var group = new PackageDependencyGroup(NuGetFramework.AgnosticFramework, packages); - nugetMetadata.DependencyGroups = new [] {group}; + packages.Add(new PackageDependency(dependency.Id, dependency.Version.ToVersionRange())); } - - return nugetMetadata; + // We are .NET agnostic + var group = new PackageDependencyGroup(NuGetFramework.AgnosticFramework, packages); + nugetMetadata.DependencyGroups = [group]; } + + return nugetMetadata; } } diff --git a/sources/assets/Stride.Core.Packages/NugetLocalPackage.cs b/sources/assets/Stride.Core.Packages/NugetLocalPackage.cs index b42f845504..26447bdddd 100644 --- a/sources/assets/Stride.Core.Packages/NugetLocalPackage.cs +++ b/sources/assets/Stride.Core.Packages/NugetLocalPackage.cs @@ -1,73 +1,69 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // This file is distributed under GPL v3. See LICENSE.md for details -using System; -using System.Collections.Generic; -using System.IO; using NuGet.Protocol; -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +/// +/// Description of a package that has been installed locally. +/// +public class NugetLocalPackage : NugetPackage { /// - /// Description of a package that has been installed locally. + /// A new instance of initialized from . /// - public class NugetLocalPackage : NugetPackage + /// The NuGet local information about the package. + internal NugetLocalPackage(LocalPackageInfo info) : base(new LocalPackageSearchMetadata(info)) { - /// - /// A new instance of initialized from . - /// - /// The NuGet local information about the package. - internal NugetLocalPackage(LocalPackageInfo info) : base(new LocalPackageSearchMetadata(info)) - { - Info = info; - } + Info = info; + } - /// - /// The copyright of the current local package. - /// - public string Copyright => Info.Nuspec.GetCopyright(); + /// + /// The copyright of the current local package. + /// + public string Copyright => Info.Nuspec.GetCopyright(); - /// - /// The release notes of the current local package. - /// - public string ReleaseNotes => Info.Nuspec.GetReleaseNotes(); + /// + /// The release notes of the current local package. + /// + public string ReleaseNotes => Info.Nuspec.GetReleaseNotes(); - /// - /// The language of the current local package. - /// - public string Language => Info.Nuspec.GetLanguage(); + /// + /// The language of the current local package. + /// + public string Language => Info.Nuspec.GetLanguage(); - /// - /// Nupkg path. - /// - public string NupkgPath => Info.IsNupkg ? Info.Path : null; + /// + /// Nupkg path. + /// + public string? NupkgPath => Info.IsNupkg ? Info.Path : null; - /// - /// Folder containing nupkg and extracted package. - /// - public string Path => Info.IsNupkg ? Directory.GetParent(Info.Path).FullName : Info.Path; + /// + /// Folder containing nupkg and extracted package. + /// + public string Path => Info.IsNupkg ? Directory.GetParent(Info.Path)!.FullName : Info.Path; - /// - /// Gets the list of files that make up the current local package. - /// - /// The list of files making up the current local package. - public IEnumerable GetFiles() + /// + /// Gets the list of files that make up the current local package. + /// + /// The list of files making up the current local package. + public IEnumerable GetFiles() + { + var res = new List(); + var files = Info.GetReader().GetFiles(); + if (files != null) { - var res = new List(); - var files = Info.GetReader().GetFiles(); - if (files != null) + foreach (var file in files) { - foreach (var file in files) - { - res.Add(new PackageFile(Path, file)); - } + res.Add(new PackageFile(Path, file)); } - return res; } - - /// - /// The reader of the associated .nuspec file of the current local package. - /// - protected LocalPackageInfo Info { get; } + return res; } + + /// + /// The reader of the associated .nuspec file of the current local package. + /// + protected LocalPackageInfo Info { get; } } diff --git a/sources/assets/Stride.Core.Packages/NugetLogger.cs b/sources/assets/Stride.Core.Packages/NugetLogger.cs index 64604363ed..36cbe0bf37 100644 --- a/sources/assets/Stride.Core.Packages/NugetLogger.cs +++ b/sources/assets/Stride.Core.Packages/NugetLogger.cs @@ -1,181 +1,171 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Threading.Tasks; using NuGet.Common; using ILogger = NuGet.Common.ILogger; -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +/// +/// Implementation of the interface using our interface. +/// +internal class NugetLogger : ILogger { + private readonly IPackagesLogger logger; + /// - /// Implementation of the interface using our interface. + /// Initialize new instance of NugetLogger. /// - internal class NugetLogger : ILogger + /// The instance to use to implement + public NugetLogger(IPackagesLogger logger) { - private readonly IPackagesLogger logger; - - /// - /// Initialize new instance of NugetLogger. - /// - /// The instance to use to implement - public NugetLogger(IPackagesLogger logger) - { - this.logger = logger; - } + this.logger = logger; + } - #region ILogger implementation + #region ILogger implementation - /// - /// Logs a debug message . - /// - /// The message to log. - public void LogDebug(string data) - { - logger.Log(MessageLevel.Debug, data); - } - - /// - /// Logs a verbose message . - /// - /// The message to log. - public void LogVerbose(string data) - { - logger.Log(MessageLevel.Verbose, data); - } + /// + /// Logs a debug message . + /// + /// The message to log. + public void LogDebug(string data) + { + logger.Log(MessageLevel.Debug, data); + } - /// - /// Logs an information message . - /// - /// The message to log. - public void LogInformation(string data) - { - logger.Log(MessageLevel.Info, data); - } + /// + /// Logs a verbose message . + /// + /// The message to log. + public void LogVerbose(string data) + { + logger.Log(MessageLevel.Verbose, data); + } - /// - /// Logs a minimal message . - /// - /// The message to log. - public void LogMinimal(string data) - { - logger.Log(MessageLevel.Minimal, data); - } + /// + /// Logs an information message . + /// + /// The message to log. + public void LogInformation(string data) + { + logger.Log(MessageLevel.Info, data); + } - /// - /// Logs a warning message . - /// - /// The message to log. - public void LogWarning(string data) - { - logger.Log(MessageLevel.Warning, data); - } + /// + /// Logs a minimal message . + /// + /// The message to log. + public void LogMinimal(string data) + { + logger.Log(MessageLevel.Minimal, data); + } - /// - /// Logs an error message . - /// - /// The message to log. - public void LogError(string data) - { - logger.Log(MessageLevel.Error, data); - } + /// + /// Logs a warning message . + /// + /// The message to log. + public void LogWarning(string data) + { + logger.Log(MessageLevel.Warning, data); + } - /// - /// Logs an information summary message . - /// - /// The message to log. - public void LogInformationSummary(string data) - { - logger.Log(MessageLevel.InfoSummary, data); - } + /// + /// Logs an error message . + /// + /// The message to log. + public void LogError(string data) + { + logger.Log(MessageLevel.Error, data); + } - /// - /// Logs an error summary message . - /// - /// The message to log. - public void LogErrorSummary(string data) - { - logger.Log(MessageLevel.ErrorSummary, data); - } + /// + /// Logs an information summary message . + /// + /// The message to log. + public void LogInformationSummary(string data) + { + logger.Log(MessageLevel.InfoSummary, data); + } - /// - /// Logs a message using the log . - /// - /// The level of the logged message. - /// The message to log. - /// is not a valid log level. - public void Log(LogLevel level, string data) - { - switch (level) - { - case LogLevel.Debug: - LogDebug(data); - break; - case LogLevel.Verbose: - LogVerbose(data); - break; - case LogLevel.Information: - LogInformation(data); - break; - case LogLevel.Minimal: - LogMinimal(data); - break; - case LogLevel.Warning: - LogWarning(data); - break; - case LogLevel.Error: - LogError(data); - break; - default: - throw new ArgumentOutOfRangeException(nameof(level), level, null); - } - } + /// + /// Logs an error summary message . + /// + /// The message to log. + public void LogErrorSummary(string data) + { + logger.Log(MessageLevel.ErrorSummary, data); + } - /// - /// Logs a message using the log . - /// - /// The level of the logged message. - /// The message to log. - /// is not a valid log level. - public Task LogAsync(LogLevel level, string data) + /// + /// Logs a message using the log . + /// + /// The level of the logged message. + /// The message to log. + /// is not a valid log level. + public void Log(LogLevel level, string data) + { + switch (level) { - switch (level) - { case LogLevel.Debug: - return logger.LogAsync(MessageLevel.Debug, data); + LogDebug(data); + break; case LogLevel.Verbose: - return logger.LogAsync(MessageLevel.Verbose, data); + LogVerbose(data); + break; case LogLevel.Information: - return logger.LogAsync(MessageLevel.Info, data); + LogInformation(data); + break; case LogLevel.Minimal: - return logger.LogAsync(MessageLevel.Minimal, data); + LogMinimal(data); + break; case LogLevel.Warning: - return logger.LogAsync(MessageLevel.Warning, data); + LogWarning(data); + break; case LogLevel.Error: - return logger.LogAsync(MessageLevel.Error, data); + LogError(data); + break; default: throw new ArgumentOutOfRangeException(nameof(level), level, null); - } } + } - /// - /// Logs a message . - /// - /// The message to log. - public void Log(ILogMessage message) + /// + /// Logs a message using the log . + /// + /// The level of the logged message. + /// The message to log. + /// is not a valid log level. + public Task LogAsync(LogLevel level, string data) + { + return level switch { - Log(message.Level, message.Message); - } + LogLevel.Debug => logger.LogAsync(MessageLevel.Debug, data), + LogLevel.Verbose => logger.LogAsync(MessageLevel.Verbose, data), + LogLevel.Information => logger.LogAsync(MessageLevel.Info, data), + LogLevel.Minimal => logger.LogAsync(MessageLevel.Minimal, data), + LogLevel.Warning => logger.LogAsync(MessageLevel.Warning, data), + LogLevel.Error => logger.LogAsync(MessageLevel.Error, data), + _ => throw new ArgumentOutOfRangeException(nameof(level), level, null), + }; + } - /// - /// Logs a message . - /// - /// The message to log. - public Task LogAsync(ILogMessage message) - { - return LogAsync(message.Level, message.Message); - } + /// + /// Logs a message . + /// + /// The message to log. + public void Log(ILogMessage message) + { + Log(message.Level, message.Message); + } - #endregion + /// + /// Logs a message . + /// + /// The message to log. + public Task LogAsync(ILogMessage message) + { + return LogAsync(message.Level, message.Message); } + + #endregion } diff --git a/sources/assets/Stride.Core.Packages/NugetPackage.cs b/sources/assets/Stride.Core.Packages/NugetPackage.cs index ed643e845d..d3da2f78f9 100644 --- a/sources/assets/Stride.Core.Packages/NugetPackage.cs +++ b/sources/assets/Stride.Core.Packages/NugetPackage.cs @@ -1,225 +1,216 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; -using System.Linq; using NuGet.Packaging.Core; using NuGet.Protocol.Core.Types; using NuGet.Versioning; -using Stride.Core.Annotations; using Constants = NuGet.ProjectManagement.Constants; -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +/// +/// Nuget abstraction of a package. +/// +public abstract class NugetPackage : IEquatable { /// - /// Nuget abstraction of a package. + /// Initializes a new instance of using some NuGet data. /// - public abstract class NugetPackage : IEquatable + /// The NuGet metadata we will use to construct the current instance. + internal NugetPackage(IPackageSearchMetadata package) { - /// - /// Initializes a new instance of using some NuGet data. - /// - /// The NuGet metadata we will use to construct the current instance. - internal NugetPackage([NotNull] IPackageSearchMetadata package) - { - packageMetadata = package ?? throw new ArgumentNullException(nameof(package)); - } + packageMetadata = package ?? throw new ArgumentNullException(nameof(package)); + } - /// - /// Storage for the NuGet metatadata. - /// - private readonly IPackageSearchMetadata packageMetadata; + /// + /// Storage for the NuGet metatadata. + /// + private readonly IPackageSearchMetadata packageMetadata; - /// - public bool Equals(NugetPackage other) - { - return packageMetadata.Identity.Equals(other.packageMetadata.Identity); - } + /// + public bool Equals(NugetPackage? other) + { + return packageMetadata.Identity.Equals(other?.packageMetadata.Identity); + } - /// - public override bool Equals(object other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - if (other.GetType() != GetType()) return false; - return Equals((NugetPackage)other); - } + /// + public override bool Equals(object? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + if (other.GetType() != GetType()) return false; + return Equals((NugetPackage)other); + } - /// - public override int GetHashCode() - { - return packageMetadata.GetHashCode(); - } + /// + public override int GetHashCode() + { + return packageMetadata.GetHashCode(); + } - /// - /// Determines whether two specified objects are equal. - /// - /// The first object. - /// The second object. - /// true if is equal to , false otherwise. - public static bool operator ==(NugetPackage left, NugetPackage right) - { - return Equals(left, right); - } + /// + /// Determines whether two specified objects are equal. + /// + /// The first object. + /// The second object. + /// true if is equal to , false otherwise. + public static bool operator ==(NugetPackage? left, NugetPackage? right) + { + return Equals(left, right); + } - /// - /// Determines whether two specified objects are not equal. - /// - /// The first object. - /// The second object. - /// true if is not equal to , false otherwise. - public static bool operator !=(NugetPackage left, NugetPackage right) - { - return !Equals(left, right); - } + /// + /// Determines whether two specified objects are not equal. + /// + /// The first object. + /// The second object. + /// true if is not equal to , false otherwise. + public static bool operator !=(NugetPackage? left, NugetPackage? right) + { + return !Equals(left, right); + } + + /// + /// Version of current package. + /// + public PackageVersion Version => packageMetadata.Identity.Version.ToPackageVersion(); + + /// + /// The of this package's version. + /// + /// Internal since it exposes a NuGet type. + internal NuGetVersion NuGetVersion => packageMetadata.Identity.Version; + + /// + /// The of this package. + /// + /// Internal since it exposes a NuGet type. + internal PackageIdentity Identity => packageMetadata.Identity; + + /// + /// The Id of this package. + /// + public string Id => packageMetadata.Identity.Id; + + /// + /// The listed status of this package. + /// + public bool Listed => !Published.HasValue || Published > Constants.Unpublished; + + /// + /// The date of publication if present. + /// + public DateTimeOffset? Published => packageMetadata.Published; + + /// + /// The title of this package. + /// + public string Title => packageMetadata.Title; + + /// + /// The list of authors of this package. + /// + public IEnumerable Authors => [packageMetadata.Authors]; + + /// + /// The list of owners of this package. + /// + public IEnumerable Owners => [packageMetadata.Owners]; + + /// + /// The URL of this package's icon. + /// + public Uri IconUrl => packageMetadata.IconUrl; - /// - /// Version of current package. - /// - public PackageVersion Version => packageMetadata.Identity.Version.ToPackageVersion(); - - /// - /// The of this package's version. - /// - /// Internal since it exposes a NuGet type. - internal NuGetVersion NuGetVersion => packageMetadata.Identity.Version; - - /// - /// The of this package. - /// - /// Internal since it exposes a NuGet type. - internal PackageIdentity Identity => packageMetadata.Identity; - - /// - /// The Id of this package. - /// - public string Id => packageMetadata.Identity.Id; - - /// - /// The listed status of this package. - /// - public bool Listed => !Published.HasValue || Published > Constants.Unpublished; - - /// - /// The date of publication if present. - /// - public DateTimeOffset? Published => packageMetadata.Published; - - /// - /// The title of this package. - /// - public string Title => packageMetadata.Title; - - /// - /// The list of authors of this package. - /// - public IEnumerable Authors => new List(1) { packageMetadata.Authors }; - - /// - /// The list of owners of this package. - /// - public IEnumerable Owners => new List(1) { packageMetadata.Owners }; - - /// - /// The URL of this package's icon. - /// - public Uri IconUrl => packageMetadata.IconUrl; - - /// - /// The URL of this package's license. - /// - public Uri LicenseUrl => packageMetadata.LicenseUrl; - - /// - /// The URL of this package's project. - /// - public Uri ProjectUrl => packageMetadata.ProjectUrl; - - /// - /// Determines if this package requires a license acceptance. - /// - public bool RequireLicenseAcceptance => packageMetadata.RequireLicenseAcceptance; - - /// - /// The description of this package. - /// - public string Description => packageMetadata.Description; - - /// - /// The summary description of this package. - /// - public string Summary => packageMetadata.Summary; - - /// - /// The list of tags of this package separated by spaces. - /// - public string Tags => packageMetadata.Tags; - - /// - /// The list of dependencies of this package. - /// - /// Internal since it exposes a NuGet type. - internal IEnumerable DependencySets => packageMetadata.DependencySets; - - /// - /// The number of downloads for this package. It is specific to the version of this package. - /// - public long DownloadCount => VersionInfo.DownloadCount ?? 0; - - /// - /// The URL to report abused on this package. - /// - public Uri ReportAbuseUrl => packageMetadata.ReportAbuseUrl; - - /// - /// The number of dependency sets. - /// - public int DependencySetsCount => DependencySets?.Count() ?? 0; - - /// - /// List of supported target frameworks. - /// - public IEnumerable TargetFrameworks => packageMetadata.DependencySets.Select(x => x.TargetFramework.GetShortFolderName()); - - /// - /// Computed the list of dependencies of this package. - /// - public IEnumerable> Dependencies + /// + /// The URL of this package's license. + /// + public Uri LicenseUrl => packageMetadata.LicenseUrl; + + /// + /// The URL of this package's project. + /// + public Uri ProjectUrl => packageMetadata.ProjectUrl; + + /// + /// Determines if this package requires a license acceptance. + /// + public bool RequireLicenseAcceptance => packageMetadata.RequireLicenseAcceptance; + + /// + /// The description of this package. + /// + public string Description => packageMetadata.Description; + + /// + /// The summary description of this package. + /// + public string Summary => packageMetadata.Summary; + + /// + /// The list of tags of this package separated by spaces. + /// + public string Tags => packageMetadata.Tags; + + /// + /// The list of dependencies of this package. + /// + /// Internal since it exposes a NuGet type. + internal IEnumerable DependencySets => packageMetadata.DependencySets; + + /// + /// The number of downloads for this package. It is specific to the version of this package. + /// + public long DownloadCount => VersionInfo.DownloadCount ?? 0; + + /// + /// The URL to report abused on this package. + /// + public Uri ReportAbuseUrl => packageMetadata.ReportAbuseUrl; + + /// + /// The number of dependency sets. + /// + public int DependencySetsCount => DependencySets?.Count() ?? 0; + + /// + /// List of supported target frameworks. + /// + public IEnumerable TargetFrameworks => packageMetadata.DependencySets.Select(x => x.TargetFramework.GetShortFolderName()); + + /// + /// Computed the list of dependencies of this package. + /// + public IEnumerable> Dependencies + { + get { - get + var res = new List>(); + var set = DependencySets.FirstOrDefault(); + if (set != null) { - var res = new List>(); - var set = DependencySets.FirstOrDefault(); - if (set != null) + foreach (var dependency in set.Packages) { - foreach (var dependency in set.Packages) - { - res.Add(new Tuple(dependency.Id, dependency.VersionRange.ToPackageVersionRange())); - } + res.Add(new Tuple(dependency.Id, dependency.VersionRange.ToPackageVersionRange())); } - return res; } + return res; } + } - /// - /// The associated with this package. - /// - private VersionInfo VersionInfo + /// + /// The associated with this package. + /// + private VersionInfo VersionInfo + { + get { - get - { - if (versionInfo == null) - { - // Get all versions of the current package and filter on the current package's version. - versionInfo = packageMetadata.GetVersionsAsync().Result.First(v=>v.Version.Equals(Version.ToNuGetVersion())); - } - return versionInfo; - } + // Get all versions of the current package and filter on the current package's version. + versionInfo ??= packageMetadata.GetVersionsAsync().Result.First(v => v.Version.Equals(Version.ToNuGetVersion())); + return versionInfo; } + } - private VersionInfo versionInfo; + private VersionInfo versionInfo; - } } diff --git a/sources/assets/Stride.Core.Packages/NugetPackageBuilder.cs b/sources/assets/Stride.Core.Packages/NugetPackageBuilder.cs index 216cf1202a..c132ee1a59 100644 --- a/sources/assets/Stride.Core.Packages/NugetPackageBuilder.cs +++ b/sources/assets/Stride.Core.Packages/NugetPackageBuilder.cs @@ -1,230 +1,222 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; using System.Text; -using System.Linq; -using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.IO; using NuGet.Packaging; -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +/// +/// Abstraction to build a NuGet package. +/// +public sealed class NugetPackageBuilder : IEquatable { /// - /// Abstraction to build a NuGet package. + /// Initializes a new instance of . /// - public sealed class NugetPackageBuilder : IEquatable + public NugetPackageBuilder() { - /// - /// Initializes a new instance of . - /// - public NugetPackageBuilder() - { - Builder = new PackageBuilder(); - } - - /// - /// Internal NuGet helper used to build a package. - /// - internal PackageBuilder Builder { get; } - - /// - /// Determines whether the object is equal to the current object. - /// - /// The object to compare against the current object. - /// true if is equal to the current object, false otherwise. - public bool Equals(NugetPackageBuilder other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(Builder, other.Builder); - } + Builder = new PackageBuilder(); + } - /// - public override bool Equals(object other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(other as NugetPackageBuilder); - } + /// + /// Internal NuGet helper used to build a package. + /// + internal PackageBuilder Builder { get; } - /// - public override int GetHashCode() - { - return Builder.GetHashCode(); - } + /// + /// Determines whether the object is equal to the current object. + /// + /// The object to compare against the current object. + /// true if is equal to the current object, false otherwise. + public bool Equals(NugetPackageBuilder? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Builder, other.Builder); + } - /// - /// Determines whether two specified objects are equal. - /// - /// The first object. - /// The second object. - /// true if is equal to , false otherwise. - public static bool operator ==(NugetPackageBuilder left, NugetPackageBuilder right) - { - return Equals(left, right); - } + /// + public override bool Equals(object? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(other as NugetPackageBuilder); + } - /// - /// Determines whether two specified objects are equal. - /// - /// The first object. - /// The second object. - /// true if is not equal to , false otherwise. - public static bool operator !=(NugetPackageBuilder left, NugetPackageBuilder right) - { - return !Equals(left, right); - } + /// + public override int GetHashCode() + { + return Builder.GetHashCode(); + } - /// - /// The authors of this new package. - /// - public IEnumerable Authors => Builder.Authors; - - /// - /// The copyright of this new package. - /// - public string Copyright => Builder.Copyright; - - /// - /// The description of this new package. - /// - public string Description => Builder.Description; - - /// - /// Determines if this new package is used for development purpose and should not be listed as a dependency. - /// - public bool DevelopmentDependency => Builder.DevelopmentDependency; - - public IEnumerable Files => Builder.Files.Select(x => new PackageFile(x)); - - /// - /// The URL of this new package's icon. - /// - public Uri IconUrl => Builder.IconUrl; - - /// - /// The Id of this new package. - /// - public string Id => Builder.Id; - - /// - /// The language of this new package. - /// - public string Language => Builder.Language; - - /// - /// The URL of this new package's license. - /// - public Uri LicenseUrl => Builder.LicenseUrl; - - /// - /// The minimum client supported by this new package. - /// - public Version MinClientVersion => Builder.MinClientVersion; - - /// - /// The owners of this new package. - /// - public IEnumerable Owners => Builder.Owners; - - /// - /// The URL of this new package's project. - /// - public Uri ProjectUrl => Builder.ProjectUrl; - - /// - /// The release notes of this new package. - /// - public string ReleaseNotes => Builder.ReleaseNotes; - - /// - /// Determines if this new package requires a license acceptance. - /// - public bool RequireLicenseAcceptance => Builder.RequireLicenseAcceptance; - - /// - /// The summary description of this new package. - /// - public string Summary => Builder.Summary; - - /// - /// The list of tags of this package separated by spaces. - /// - public string Tags - { - get - { - var s = new StringBuilder(); - foreach (var tag in Builder.Tags) - { - s.Append(tag); - s.Append(' '); - } - return s.ToString(); - } - } + /// + /// Determines whether two specified objects are equal. + /// + /// The first object. + /// The second object. + /// true if is equal to , false otherwise. + public static bool operator ==(NugetPackageBuilder left, NugetPackageBuilder right) + { + return Equals(left, right); + } - /// - /// The title of this new package. - /// - public string Title => Builder.Title; + /// + /// Determines whether two specified objects are equal. + /// + /// The first object. + /// The second object. + /// true if is not equal to , false otherwise. + public static bool operator !=(NugetPackageBuilder left, NugetPackageBuilder right) + { + return !Equals(left, right); + } - public PackageVersion Version => Builder.Version.ToPackageVersion(); + /// + /// The authors of this new package. + /// + public IEnumerable Authors => Builder.Authors; - /// - /// Saves this new package in . - /// - /// The stream where package will be saved. - public void Save(Stream stream) - { - Builder.Save(stream); - } + /// + /// The copyright of this new package. + /// + public string Copyright => Builder.Copyright; - /// - /// Fills the builder with the manifest metadata containing all the information about this new package. - /// - /// The manifest metadata. - public void Populate(ManifestMetadata meta) - { - Builder.Populate(meta.ToManifestMetadata()); - } + /// + /// The description of this new package. + /// + public string Description => Builder.Description; - /// - /// Fills the builder with the list of files that are part of this new package. - /// - /// The root location where files are located. - /// The files to include to the builder. - public void PopulateFiles(UDirectory rootDirectory, List files) - { - Builder.PopulateFiles(rootDirectory, ToManifsetFiles(files)); - } + /// + /// Determines if this new package is used for development purpose and should not be listed as a dependency. + /// + public bool DevelopmentDependency => Builder.DevelopmentDependency; - /// - /// Removes the files previously added by . - /// - public void ClearFiles() - { - Builder.Files.Clear(); - } + public IEnumerable Files => Builder.Files.Select(x => new PackageFile(x)); - /// - /// Converts a list of into a list of . - /// - /// The list to convert. - /// A new list of - [NotNull] - private static IEnumerable ToManifsetFiles(IEnumerable list) + /// + /// The URL of this new package's icon. + /// + public Uri IconUrl => Builder.IconUrl; + + /// + /// The Id of this new package. + /// + public string Id => Builder.Id; + + /// + /// The language of this new package. + /// + public string Language => Builder.Language; + + /// + /// The URL of this new package's license. + /// + public Uri LicenseUrl => Builder.LicenseUrl; + + /// + /// The minimum client supported by this new package. + /// + public Version MinClientVersion => Builder.MinClientVersion; + + /// + /// The owners of this new package. + /// + public IEnumerable Owners => Builder.Owners; + + /// + /// The URL of this new package's project. + /// + public Uri ProjectUrl => Builder.ProjectUrl; + + /// + /// The release notes of this new package. + /// + public string ReleaseNotes => Builder.ReleaseNotes; + + /// + /// Determines if this new package requires a license acceptance. + /// + public bool RequireLicenseAcceptance => Builder.RequireLicenseAcceptance; + + /// + /// The summary description of this new package. + /// + public string Summary => Builder.Summary; + + /// + /// The list of tags of this package separated by spaces. + /// + public string Tags + { + get { - var res = new List(); - foreach (var entry in list) + var s = new StringBuilder(); + foreach (var tag in Builder.Tags) { - res.Add(entry.ToManifestFile()); + s.Append(tag); + s.Append(' '); } - return res; + return s.ToString(); + } + } + + /// + /// The title of this new package. + /// + public string Title => Builder.Title; + + public PackageVersion Version => Builder.Version.ToPackageVersion(); + + /// + /// Saves this new package in . + /// + /// The stream where package will be saved. + public void Save(Stream stream) + { + Builder.Save(stream); + } + + /// + /// Fills the builder with the manifest metadata containing all the information about this new package. + /// + /// The manifest metadata. + public void Populate(ManifestMetadata meta) + { + Builder.Populate(meta.ToManifestMetadata()); + } + + /// + /// Fills the builder with the list of files that are part of this new package. + /// + /// The root location where files are located. + /// The files to include to the builder. + public void PopulateFiles(UDirectory rootDirectory, List files) + { + Builder.PopulateFiles(rootDirectory, ToManifsetFiles(files)); + } + + /// + /// Removes the files previously added by . + /// + public void ClearFiles() + { + Builder.Files.Clear(); + } + + /// + /// Converts a list of into a list of . + /// + /// The list to convert. + /// A new list of + private static List ToManifsetFiles(IEnumerable list) + { + var res = new List(); + foreach (var entry in list) + { + res.Add(entry.ToManifestFile()); } + return res; } } diff --git a/sources/assets/Stride.Core.Packages/NugetServerPackage.cs b/sources/assets/Stride.Core.Packages/NugetServerPackage.cs index c1c1c25ef5..9aa1bc5544 100644 --- a/sources/assets/Stride.Core.Packages/NugetServerPackage.cs +++ b/sources/assets/Stride.Core.Packages/NugetServerPackage.cs @@ -1,16 +1,13 @@ using NuGet.Protocol.Core.Types; -using Stride.Core.Annotations; -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +public class NugetServerPackage : NugetPackage { - public class NugetServerPackage : NugetPackage + public NugetServerPackage(IPackageSearchMetadata package, string source) : base(package) { - public NugetServerPackage([NotNull] IPackageSearchMetadata package, [NotNull] string source) : base(package) - { - Source = source; - } - - [NotNull] - public string Source { get; } + Source = source; } + + public string Source { get; } } diff --git a/sources/assets/Stride.Core.Packages/NugetSourceRepositoryProvider.cs b/sources/assets/Stride.Core.Packages/NugetSourceRepositoryProvider.cs index 59ebf2e53a..bccc71f79e 100644 --- a/sources/assets/Stride.Core.Packages/NugetSourceRepositoryProvider.cs +++ b/sources/assets/Stride.Core.Packages/NugetSourceRepositoryProvider.cs @@ -1,63 +1,57 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; using NuGet.Configuration; using NuGet.Protocol; using NuGet.Protocol.Core.Types; -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +/// +/// Copy of from Nuget with the only change being adding V2 in the list +/// of resource providers. +/// +internal class NugetSourceRepositoryProvider : ISourceRepositoryProvider { - /// - /// Copy of from Nuget with the only change being adding V2 in the list - /// of resource providers. - /// - internal class NugetSourceRepositoryProvider : ISourceRepositoryProvider + private readonly List> _resourceProviders; + private readonly List _repositories; + + // There should only be one instance of the source repository for each package source. + private static readonly ConcurrentDictionary _cachedSources = []; + + public NugetSourceRepositoryProvider(IPackageSourceProvider packageSourceProvider, INugetDownloadProgress downloadProgress) + { + PackageSourceProvider = packageSourceProvider; + + _resourceProviders = [.. Repository.Provider.GetCoreV3()]; + + // Create repositories + _repositories = PackageSourceProvider.LoadPackageSources() + .Where(s => s.IsEnabled) + .Select(CreateRepository) + .ToList(); + } + + /// + public IEnumerable GetRepositories() { - private readonly List> _resourceProviders; - private readonly List _repositories; - - // There should only be one instance of the source repository for each package source. - private static readonly ConcurrentDictionary _cachedSources - = new ConcurrentDictionary(); - - public NugetSourceRepositoryProvider(IPackageSourceProvider packageSourceProvider, INugetDownloadProgress downloadProgress) - { - PackageSourceProvider = packageSourceProvider; - - _resourceProviders = new List>(); - _resourceProviders.AddRange(Repository.Provider.GetCoreV3()); - - // Create repositories - _repositories = PackageSourceProvider.LoadPackageSources() - .Where(s => s.IsEnabled) - .Select(CreateRepository) - .ToList(); - } - - /// - public IEnumerable GetRepositories() - { - return _repositories; - } - - /// - public SourceRepository CreateRepository(PackageSource source) - { - var feedTypeSource = source as FeedTypePackageSource; - return CreateRepository(source, feedTypeSource?.FeedType ?? FeedType.Undefined); - } - - /// - public SourceRepository CreateRepository(PackageSource source, FeedType type) - { - return _cachedSources.GetOrAdd(source, new SourceRepository(source, _resourceProviders, type)); - } - - /// - public IPackageSourceProvider PackageSourceProvider { get; } + return _repositories; } + + /// + public SourceRepository CreateRepository(PackageSource source) + { + var feedTypeSource = source as FeedTypePackageSource; + return CreateRepository(source, feedTypeSource?.FeedType ?? FeedType.Undefined); + } + + /// + public SourceRepository CreateRepository(PackageSource source, FeedType type) + { + return _cachedSources.GetOrAdd(source, new SourceRepository(source, _resourceProviders, type)); + } + + /// + public IPackageSourceProvider PackageSourceProvider { get; } } diff --git a/sources/assets/Stride.Core.Packages/NugetStore.cs b/sources/assets/Stride.Core.Packages/NugetStore.cs index fdb0158d30..38e245cce1 100644 --- a/sources/assets/Stride.Core.Packages/NugetStore.cs +++ b/sources/assets/Stride.Core.Packages/NugetStore.cs @@ -1,15 +1,9 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Text; using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; using NuGet.Commands; using NuGet.Common; using NuGet.Configuration; @@ -30,876 +24,868 @@ using PackageSource = NuGet.Configuration.PackageSource; using PackageSourceProvider = NuGet.Configuration.PackageSourceProvider; -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +/// +/// Abstraction to interact with a store backed by the NuGet infrastructure. +/// +public partial class NugetStore : INugetDownloadProgress { - /// - /// Abstraction to interact with a store backed by the NuGet infrastructure. - /// - public class NugetStore : INugetDownloadProgress - { - private IPackagesLogger logger; - private readonly ISettings settings; - private ProgressReport currentProgressReport; + private IPackagesLogger? logger; + private readonly ISettings settings; + private ProgressReport? currentProgressReport; - private readonly string oldRootDirectory; + private readonly string? oldRootDirectory; - private static Regex powerShellProgressRegex = new Regex(@".*\[ProgressReport:\s*(\d*)%\].*"); + private static readonly Regex powerShellProgressRegex = GetPowerShellProgressRegex(); - /// - /// Initialize a new instance of . - /// - /// The location of the Nuget store. - public NugetStore(string oldRootDirectory) - { - // Used only for versions before 3.0 - this.oldRootDirectory = oldRootDirectory; + /// + /// Initialize a new instance of . + /// + /// The location of the Nuget store. + public NugetStore(string? oldRootDirectory) + { + // Used only for versions before 3.0 + this.oldRootDirectory = oldRootDirectory; - settings = NuGet.Configuration.Settings.LoadDefaultSettings(null); + settings = NuGet.Configuration.Settings.LoadDefaultSettings(null); - // Remove obsolete sources - RemoveDeletedSources(settings, "Xenko Dev"); - // Note the space: we want to keep "Stride Dev" but not "Stride Dev {PATH}\bin\packages" anymore - RemoveSources(settings, "Stride Dev "); + // Remove obsolete sources + RemoveDeletedSources(settings, "Xenko Dev"); + // Note the space: we want to keep "Stride Dev" but not "Stride Dev {PATH}\bin\packages" anymore + RemoveSources(settings, "Stride Dev "); - settings.SaveToDisk(); + settings.SaveToDisk(); - InstallPath = SettingsUtility.GetGlobalPackagesFolder(settings); + InstallPath = SettingsUtility.GetGlobalPackagesFolder(settings); - var pathContext = NuGetPathContext.Create(settings); - InstalledPathResolver = new FallbackPackagePathResolver(pathContext.UserPackageFolder, oldRootDirectory != null ? pathContext.FallbackPackageFolders.Concat(new[] { oldRootDirectory }) : pathContext.FallbackPackageFolders); - var packageSourceProvider = new PackageSourceProvider(settings); + var pathContext = NuGetPathContext.Create(settings); + InstalledPathResolver = new FallbackPackagePathResolver(pathContext.UserPackageFolder, oldRootDirectory != null ? pathContext.FallbackPackageFolders.Concat([oldRootDirectory]) : pathContext.FallbackPackageFolders); + var packageSourceProvider = new PackageSourceProvider(settings); - var availableSources = packageSourceProvider.LoadPackageSources().Where(source => source.IsEnabled); - var packageSources = new List(); - packageSources.AddRange(availableSources); - PackageSources = packageSources; + var availableSources = packageSourceProvider.LoadPackageSources().Where(source => source.IsEnabled); + var packageSources = new List(); + packageSources.AddRange(availableSources); + PackageSources = packageSources; - // Setup source provider as a V3 only. - sourceRepositoryProvider = new NugetSourceRepositoryProvider(packageSourceProvider, this); - } + // Setup source provider as a V3 only. + sourceRepositoryProvider = new NugetSourceRepositoryProvider(packageSourceProvider, this); + } - private static void RemoveSources(ISettings settings, string prefixName) + private static void RemoveSources(ISettings settings, string prefixName) + { + var packageSources = settings.GetSection("packageSources"); + if (packageSources != null) { - var packageSources = settings.GetSection("packageSources"); - if (packageSources != null) + foreach (var packageSource in packageSources.Items.OfType().ToList()) { - foreach (var packageSource in packageSources.Items.OfType().ToList()) + _ = packageSource.GetValueAsPath(); + if (packageSource.Key.StartsWith(prefixName, StringComparison.Ordinal)) { - var path = packageSource.GetValueAsPath(); - - if (packageSource.Key.StartsWith(prefixName, StringComparison.Ordinal)) - { - // Remove entry from packageSources - settings.Remove("packageSources", packageSource); - } + // Remove entry from packageSources + settings.Remove("packageSources", packageSource); } } } + } - private static void RemoveDeletedSources(ISettings settings, string prefixName) + private static void RemoveDeletedSources(ISettings settings, string prefixName) + { + var packageSources = settings.GetSection("packageSources"); + if (packageSources != null) { - var packageSources = settings.GetSection("packageSources"); - if (packageSources != null) + foreach (var packageSource in packageSources.Items.OfType().ToList()) { - foreach (var packageSource in packageSources.Items.OfType().ToList()) - { - var path = packageSource.GetValueAsPath(); + var path = packageSource.GetValueAsPath(); - if (packageSource.Key.StartsWith(prefixName, StringComparison.Ordinal) - && Uri.TryCreate(path, UriKind.Absolute, out var uri) && uri.IsFile // make sure it's a valid file URI - && !Directory.Exists(path)) // detect if directory has been deleted - { - // Remove entry from packageSources - settings.Remove("packageSources", packageSource); - } + if (packageSource.Key.StartsWith(prefixName, StringComparison.Ordinal) + && Uri.TryCreate(path, UriKind.Absolute, out var uri) && uri.IsFile // make sure it's a valid file URI + && !Directory.Exists(path)) // detect if directory has been deleted + { + // Remove entry from packageSources + settings.Remove("packageSources", packageSource); } } } + } - public static bool CheckPackageSource(ISettings settings, string name) + public static bool CheckPackageSource(ISettings settings, string name) + { + var packageSources = settings.GetSection("packageSources"); + if (packageSources != null) { - var packageSources = settings.GetSection("packageSources"); - if (packageSources != null) + foreach (var packageSource in packageSources.Items.OfType().ToList()) { - foreach (var packageSource in packageSources.Items.OfType().ToList()) + if (packageSource.Key == name) { - if (packageSource.Key == name) - { - return true; - } + return true; } } - - return false; } - public static void UpdatePackageSource(ISettings settings, string name, string url) + return false; + } + + public static void UpdatePackageSource(ISettings settings, string name, string url) + { + settings.AddOrUpdate("packageSources", new SourceItem(name, url)); + } + + private readonly NugetSourceRepositoryProvider sourceRepositoryProvider; + + /// + /// Path where all packages are installed. + /// Usually `InstallPath = RootDirectory/RepositoryPath`. + /// + public string InstallPath { get; } + + /// + /// List of package Ids under which the main package is known. Usually just one entry, but + /// we could have several in case there is a product name change. + /// + public IReadOnlyCollection MainPackageIds { get; } = ["Stride.GameStudio", "Xenko.GameStudio", "Xenko"]; + + /// + /// Package Id of the Visual Studio Integration plugin. + /// + public string VsixPackageId { get; } = "Stride.VisualStudio.Package"; + + /// + /// The different supported versions of Visual Studio + /// + public enum VsixSupportedVsVersion + { + VS2019, + VS2022 + } + + /// + /// A mapping of the supported versions of VS to a Stride release version range. + /// For each supported VS release, the first Version represents the included earliest Stride version eligible for the VSIX and the second Version is the excluded upper bound. + /// + public IReadOnlyDictionary VsixVersionToStrideRelease { get; } = new Dictionary + { + // The VSIX for VS2019 is avaliable in Stride packages of version 4.0.x + {VsixSupportedVsVersion.VS2019, (new PackageVersion("4.0"), new PackageVersion("4.1")) }, + + // The VSIX for VS2022 is available in Stride packages of version 4.1.x and later. + {VsixSupportedVsVersion.VS2022, (new PackageVersion("4.1"), new PackageVersion(int.MaxValue,0,0,0)) }, + }; + + /// + /// Logger for all operations of the package manager. + /// + public IPackagesLogger Logger + { + get { - settings.AddOrUpdate("packageSources", new SourceItem(name, url)); + return logger ?? NullPackagesLogger.Instance; } - private readonly NugetSourceRepositoryProvider sourceRepositoryProvider; - - /// - /// Path where all packages are installed. - /// Usually `InstallPath = RootDirectory/RepositoryPath`. - /// - public string InstallPath { get; } - - /// - /// List of package Ids under which the main package is known. Usually just one entry, but - /// we could have several in case there is a product name change. - /// - public IReadOnlyCollection MainPackageIds { get; } = new[] { "Stride.GameStudio", "Xenko.GameStudio", "Xenko" }; - - /// - /// Package Id of the Visual Studio Integration plugin. - /// - public string VsixPackageId { get; } = "Stride.VisualStudio.Package"; - - /// - /// The different supported versions of Visual Studio - /// - public enum VsixSupportedVsVersion + set { - VS2019, - VS2022 + logger = value; } + } - /// - /// A mapping of the supported versions of VS to a Stride release version range. - /// For each supported VS release, the first Version represents the included earliest Stride version eligible for the VSIX and the second Version is the excluded upper bound. - /// - public IReadOnlyDictionary VsixVersionToStrideRelease { get; } = new Dictionary - { - // The VSIX for VS2019 is avaliable in Stride packages of version 4.0.x - {VsixSupportedVsVersion.VS2019, (new PackageVersion("4.0"), new PackageVersion("4.1")) }, + private ILogger NativeLogger => new NugetLogger(Logger); - // The VSIX for VS2022 is available in Stride packages of version 4.1.x and later. - {VsixSupportedVsVersion.VS2022, (new PackageVersion("4.1"), new PackageVersion(int.MaxValue,0,0,0)) }, - }; + private IEnumerable PackageSources { get; } - /// - /// Logger for all operations of the package manager. - /// - public IPackagesLogger Logger + /// + /// Helper to locate packages. + /// + private FallbackPackagePathResolver InstalledPathResolver { get; } + + /// + /// Event executed when a package's installation has completed. + /// + public event EventHandler? NugetPackageInstalled; + + /// + /// Event executed when a package's uninstallation has completed. + /// + public event EventHandler? NugetPackageUninstalled; + + /// + /// Event executed when a package's uninstallation is in progress. + /// + public event EventHandler? NugetPackageUninstalling; + + /// + /// Installation path of + /// + /// Id of package to query. + /// Version of package to query. + /// The installation path if installed, null otherwise. + public string GetInstalledPath(string id, PackageVersion version) + { + return InstalledPathResolver.GetPackageDirectory(id, version.ToNuGetVersion()); + } + + /// + /// Get the most recent version associated to . To make sense + /// it is assumed that packageIds represent the same package under a different name. + /// + /// List of Ids representing a package name. + /// The most recent version of `GetPackagesInstalled (packageIds)`. + public NugetLocalPackage? GetLatestPackageInstalled(IEnumerable packageIds) + { + return GetPackagesInstalled(packageIds).FirstOrDefault(); + } + + /// + /// List of all packages represented by . The list is ordered + /// from the most recent version to the oldest. + /// + /// List of Ids representing the package names to retrieve. + /// The list of packages sorted from the most recent to the oldest. + public IList GetPackagesInstalled(IEnumerable packageIds) + { + return [.. packageIds.SelectMany(GetLocalPackages).OrderByDescending(p => p.Version)]; + } + + /// + /// List of all installed packages. + /// + /// A list of packages. + public IEnumerable GetLocalPackages(string packageId) + { + var res = new List(); + + // We also scan rootDirectory for 1.x/2.x + foreach (var installPath in new[] { InstallPath, oldRootDirectory }) { - get - { - return logger ?? NullPackagesLogger.Instance; - } + // oldRootDirectory might be null + if (installPath == null) + continue; - set + var localResource = new FindLocalPackagesResourceV3(installPath); + var packages = localResource.FindPackagesById(packageId, NativeLogger, CancellationToken.None); + foreach (var package in packages) { - logger = value; + res.Add(new NugetLocalPackage(package)); } } - private ILogger NativeLogger => new NugetLogger(Logger); - - private IEnumerable PackageSources { get; } - - /// - /// Helper to locate packages. - /// - private FallbackPackagePathResolver InstalledPathResolver { get; } - - /// - /// Event executed when a package's installation has completed. - /// - public event EventHandler NugetPackageInstalled; - - /// - /// Event executed when a package's uninstallation has completed. - /// - public event EventHandler NugetPackageUninstalled; - - /// - /// Event executed when a package's uninstallation is in progress. - /// - public event EventHandler NugetPackageUninstalling; - - /// - /// Installation path of - /// - /// Id of package to query. - /// Version of package to query. - /// The installation path if installed, null otherwise. - public string GetInstalledPath(string id, PackageVersion version) - { - return InstalledPathResolver.GetPackageDirectory(id, version.ToNuGetVersion()); - } + return res; + } - /// - /// Get the most recent version associated to . To make sense - /// it is assumed that packageIds represent the same package under a different name. - /// - /// List of Ids representing a package name. - /// The most recent version of `GetPackagesInstalled (packageIds)`. - public NugetLocalPackage GetLatestPackageInstalled(IEnumerable packageIds) - { - return GetPackagesInstalled(packageIds).FirstOrDefault(); - } + /// + /// Name of variable used to hold the version of . + /// + /// The package Id. + /// The name of the variable holding the version of . + public static string GetPackageVersionVariable(string packageId, string packageVariablePrefix = "StridePackage") + { + ArgumentNullException.ThrowIfNull(packageId); + var newPackageId = packageId.Replace(".", string.Empty); + return packageVariablePrefix + newPackageId + "Version"; + } - /// - /// List of all packages represented by . The list is ordered - /// from the most recent version to the oldest. - /// - /// List of Ids representing the package names to retrieve. - /// The list of packages sorted from the most recent to the oldest. - public IList GetPackagesInstalled(IEnumerable packageIds) - { - return packageIds.SelectMany(GetLocalPackages).OrderByDescending(p => p.Version).ToList(); - } + /// + /// Lock to ensure atomicity of updates to the local repository. + /// + /// A Lock. + private static FileLock? GetLocalRepositoryLock() + { + return FileLock.Wait("nuget.lock"); + } - /// - /// List of all installed packages. - /// - /// A list of packages. - public IEnumerable GetLocalPackages(string packageId) + #region Manager + /// + /// Fetch, if not already downloaded, and install the package represented by + /// (, ). + /// + /// It is safe to call it concurrently be cause we operations are done using the FileLock. + /// Name of package to install. + /// Version of package to install. + public async Task InstallPackage(string packageId, PackageVersion version, IEnumerable targetFrameworks, ProgressReport progress) + { + using (GetLocalRepositoryLock()) { - var res = new List(); - - // We also scan rootDirectory for 1.x/2.x - foreach (var installPath in new[] { InstallPath, oldRootDirectory }) + currentProgressReport = progress; + try { - // oldRootDirectory might be null - if (installPath == null) - continue; + var identity = new PackageIdentity(packageId, version.ToNuGetVersion()); - var localResource = new FindLocalPackagesResourceV3(installPath); - var packages = localResource.FindPackagesById(packageId, NativeLogger, CancellationToken.None); - foreach (var package in packages) - { - res.Add(new NugetLocalPackage(package)); - } - } + var resolutionContext = new ResolutionContext( + DependencyBehavior.Lowest, + true, + true, + VersionConstraints.None); - return res; - } + var repositories = PackageSources.Select(sourceRepositoryProvider.CreateRepository).ToArray(); - /// - /// Name of variable used to hold the version of . - /// - /// The package Id. - /// The name of the variable holding the version of . - public static string GetPackageVersionVariable(string packageId, string packageVariablePrefix = "StridePackage") - { - if (packageId == null) throw new ArgumentNullException(nameof(packageId)); - var newPackageId = packageId.Replace(".", string.Empty); - return packageVariablePrefix + newPackageId + "Version"; - } + var projectContext = new EmptyNuGetProjectContext() + { + ActionType = NuGetActionType.Install, + PackageExtractionContext = new PackageExtractionContext(PackageSaveMode.Defaultv3, XmlDocFileSaveMode.Skip, null, NativeLogger), + }; - /// - /// Lock to ensure atomicity of updates to the local repository. - /// - /// A Lock. - private IDisposable GetLocalRepositoryLock() - { - return FileLock.Wait("nuget.lock"); - } + ActivityCorrelationId.StartNew(); -#region Manager - /// - /// Fetch, if not already downloaded, and install the package represented by - /// (, ). - /// - /// It is safe to call it concurrently be cause we operations are done using the FileLock. - /// Name of package to install. - /// Version of package to install. - public async Task InstallPackage(string packageId, PackageVersion version, IEnumerable targetFrameworks, ProgressReport progress) - { - using (GetLocalRepositoryLock()) - { - currentProgressReport = progress; - try { - var identity = new PackageIdentity(packageId, version.ToNuGetVersion()); + var installPath = SettingsUtility.GetGlobalPackagesFolder(settings); - var resolutionContext = new ResolutionContext( - DependencyBehavior.Lowest, - true, - true, - VersionConstraints.None); + // In case it's a package without any TFM (i.e. Visual Studio plugin), we still need to specify one + if (!targetFrameworks.Any()) + targetFrameworks = ["net8.0"]; - var repositories = PackageSources.Select(sourceRepositoryProvider.CreateRepository).ToArray(); + // Old version expects to be installed in GamePackages + if (packageId == "Xenko" && version < new PackageVersion(3, 0, 0, 0) && oldRootDirectory != null) + { + installPath = oldRootDirectory; + } - var projectContext = new EmptyNuGetProjectContext() + var projectPath = Path.Combine("StrideLauncher.json"); + var spec = new PackageSpec() { - ActionType = NuGetActionType.Install, - PackageExtractionContext = new PackageExtractionContext(PackageSaveMode.Defaultv3, XmlDocFileSaveMode.Skip, null, NativeLogger), + Name = Path.GetFileNameWithoutExtension(projectPath), // make sure this package never collides with a dependency + FilePath = projectPath, + Dependencies = + [ + new() + { + LibraryRange = new LibraryRange(packageId, new VersionRange(version.ToNuGetVersion()), LibraryDependencyTarget.Package), + } + ], + RestoreMetadata = new ProjectRestoreMetadata + { + ProjectPath = projectPath, + ProjectName = Path.GetFileNameWithoutExtension(projectPath), + ProjectStyle = ProjectStyle.PackageReference, + ProjectUniqueName = projectPath, + OutputPath = Path.Combine(Path.GetTempPath(), $"StrideLauncher-{packageId}-{version}"), + OriginalTargetFrameworks = targetFrameworks.ToList(), + ConfigFilePaths = settings.GetConfigFilePaths(), + PackagesPath = installPath, + Sources = SettingsUtility.GetEnabledSources(settings).ToList(), + FallbackFolders = [.. SettingsUtility.GetFallbackPackageFolders(settings)] + }, }; + foreach (var targetFramework in targetFrameworks) + { + spec.TargetFrameworks.Add(new TargetFrameworkInformation { FrameworkName = NuGetFramework.Parse(targetFramework) }); + } - ActivityCorrelationId.StartNew(); - + using (var context = new SourceCacheContext { MaxAge = DateTimeOffset.UtcNow }) { - var installPath = SettingsUtility.GetGlobalPackagesFolder(settings); + context.IgnoreFailedSources = true; - // In case it's a package without any TFM (i.e. Visual Studio plugin), we still need to specify one - if (!targetFrameworks.Any()) - targetFrameworks = new string[] { "net8.0" }; + var dependencyGraphSpec = new DependencyGraphSpec(); - // Old version expects to be installed in GamePackages - if (packageId == "Xenko" && version < new PackageVersion(3, 0, 0, 0) && oldRootDirectory != null) - { - installPath = oldRootDirectory; - } + dependencyGraphSpec.AddProject(spec); - var projectPath = Path.Combine("StrideLauncher.json"); - var spec = new PackageSpec() - { - Name = Path.GetFileNameWithoutExtension(projectPath), // make sure this package never collides with a dependency - FilePath = projectPath, - Dependencies = new List() - { - new LibraryDependency - { - LibraryRange = new LibraryRange(packageId, new VersionRange(version.ToNuGetVersion()), LibraryDependencyTarget.Package), - } - }, - RestoreMetadata = new ProjectRestoreMetadata - { - ProjectPath = projectPath, - ProjectName = Path.GetFileNameWithoutExtension(projectPath), - ProjectStyle = ProjectStyle.PackageReference, - ProjectUniqueName = projectPath, - OutputPath = Path.Combine(Path.GetTempPath(), $"StrideLauncher-{packageId}-{version.ToString()}"), - OriginalTargetFrameworks = targetFrameworks.ToList(), - ConfigFilePaths = settings.GetConfigFilePaths(), - PackagesPath = installPath, - Sources = SettingsUtility.GetEnabledSources(settings).ToList(), - FallbackFolders = SettingsUtility.GetFallbackPackageFolders(settings).ToList() - }, - }; - foreach (var targetFramework in targetFrameworks) - { - spec.TargetFrameworks.Add(new TargetFrameworkInformation { FrameworkName = NuGetFramework.Parse(targetFramework) }); - } + dependencyGraphSpec.AddRestore(spec.RestoreMetadata.ProjectUniqueName); - using (var context = new SourceCacheContext { MaxAge = DateTimeOffset.UtcNow }) + var requestProvider = new DependencyGraphSpecRequestProvider(new RestoreCommandProvidersCache(), dependencyGraphSpec); + var restoreArgs = new RestoreArgs { - context.IgnoreFailedSources = true; + AllowNoOp = true, + CacheContext = context, + CachingSourceProvider = new CachingSourceProvider(new PackageSourceProvider(settings)), + Log = NativeLogger, + }; - var dependencyGraphSpec = new DependencyGraphSpec(); + // Create requests from the arguments + var requests = requestProvider.CreateRequests(restoreArgs).Result; - dependencyGraphSpec.AddProject(spec); + foreach (var request in requests) + { + // Limit concurrency to avoid timeout + request.Request.MaxDegreeOfConcurrency = 4; - dependencyGraphSpec.AddRestore(spec.RestoreMetadata.ProjectUniqueName); + var command = new RestoreCommand(request.Request); - IPreLoadedRestoreRequestProvider requestProvider = new DependencyGraphSpecRequestProvider(new RestoreCommandProvidersCache(), dependencyGraphSpec); + // Act + var result = await command.ExecuteAsync(); - var restoreArgs = new RestoreArgs + if (!result.Success) { - AllowNoOp = true, - CacheContext = context, - CachingSourceProvider = new CachingSourceProvider(new PackageSourceProvider(settings)), - Log = NativeLogger, - }; - - // Create requests from the arguments - var requests = requestProvider.CreateRequests(restoreArgs).Result; - - foreach (var request in requests) + throw new InvalidOperationException($"Could not restore package {packageId}"); + } + foreach (var install in result.RestoreGraphs.Last().Install) { - // Limit concurrency to avoid timeout - request.Request.MaxDegreeOfConcurrency = 4; - - var command = new RestoreCommand(request.Request); - - // Act - var result = await command.ExecuteAsync(); - - if (!result.Success) + var package = result.LockFile.Libraries.FirstOrDefault(x => x.Name == install.Library.Name && x.Version == install.Library.Version); + if (package != null) { - throw new InvalidOperationException($"Could not restore package {packageId}"); - } - foreach (var install in result.RestoreGraphs.Last().Install) - { - var package = result.LockFile.Libraries.FirstOrDefault(x => x.Name == install.Library.Name && x.Version == install.Library.Version); - if (package != null) - { - var packagePath = Path.Combine(installPath, package.Path); - OnPackageInstalled(this, new PackageOperationEventArgs(new PackageName(install.Library.Name, install.Library.Version.ToPackageVersion()), packagePath)); - } + var packagePath = Path.Combine(installPath, package.Path); + OnPackageInstalled(this, new PackageOperationEventArgs(new PackageName(install.Library.Name, install.Library.Version.ToPackageVersion()), packagePath)); } } } - - if (packageId == "Xenko" && version < new PackageVersion(3, 0, 0, 0)) - { - UpdateTargetsHelper(); - } } - // Load the recently installed package - var installedPackages = GetPackagesInstalled(new[] { packageId }); - return installedPackages.FirstOrDefault(p => p.Version == version); - } - finally - { - currentProgressReport = null; + if (packageId == "Xenko" && version < new PackageVersion(3, 0, 0, 0)) + { + UpdateTargetsHelper(); + } } + + // Load the recently installed package + var installedPackages = GetPackagesInstalled([packageId]); + return installedPackages.FirstOrDefault(p => p.Version == version); + } + finally + { + currentProgressReport = null; } } - /// - /// Uninstall , while still keeping the downloaded file in the cache. - /// - /// It is safe to call it concurrently be cause we operations are done using the FileLock. - /// Package to uninstall. - public async Task UninstallPackage(NugetPackage package, ProgressReport progress) + void OnPackageInstalled(object? sender, PackageOperationEventArgs args) { + var packageInstallPath = Path.Combine(args.InstallPath, "tools\\packageinstall.exe"); + if (File.Exists(packageInstallPath)) + { + RunPackageInstall(packageInstallPath, "/install", currentProgressReport); + } + + NugetPackageInstalled?.Invoke(sender, args); + } + } + + /// + /// Uninstall , while still keeping the downloaded file in the cache. + /// + /// It is safe to call it concurrently be cause we operations are done using the FileLock. + /// Package to uninstall. + public async Task UninstallPackage(NugetPackage package, ProgressReport progress) + { #if DEBUG - var installedPackages = GetPackagesInstalled(new [] {package.Id}); - Debug.Assert(installedPackages.FirstOrDefault(p => p.Equals(package)) != null); + var installedPackages = GetPackagesInstalled([package.Id]); + Debug.Assert(installedPackages.FirstOrDefault(p => p.Equals(package)) is not null); #endif - using (GetLocalRepositoryLock()) + using (GetLocalRepositoryLock()) + { + currentProgressReport = progress; + try { - currentProgressReport = progress; - try - { - var identity = new PackageIdentity(package.Id, package.Version.ToNuGetVersion()); + var identity = new PackageIdentity(package.Id, package.Version.ToNuGetVersion()); - // Notify that uninstallation started. - var installPath = GetInstalledPath(identity.Id, identity.Version.ToPackageVersion()); - if (installPath == null) - throw new InvalidOperationException($"Could not find installation path for package {identity}"); - OnPackageUninstalling(this, new PackageOperationEventArgs(new PackageName(package.Id, package.Version), installPath)); + // Notify that uninstallation started. + var installPath = GetInstalledPath(identity.Id, identity.Version.ToPackageVersion()) + ?? throw new InvalidOperationException($"Could not find installation path for package {identity}"); + OnPackageUninstalling(this, new PackageOperationEventArgs(new PackageName(package.Id, package.Version), installPath)); - var projectContext = new EmptyNuGetProjectContext() - { - ActionType = NuGetActionType.Uninstall, - PackageExtractionContext = new PackageExtractionContext(PackageSaveMode.Defaultv3, XmlDocFileSaveMode.Skip, null, NativeLogger), - }; + var projectContext = new EmptyNuGetProjectContext() + { + ActionType = NuGetActionType.Uninstall, + PackageExtractionContext = new PackageExtractionContext(PackageSaveMode.Defaultv3, XmlDocFileSaveMode.Skip, null, NativeLogger), + }; - // Simply delete the installed package and its .nupkg installed in it. - await Task.Run(() => FileSystemUtility.DeleteDirectorySafe(installPath, true, projectContext)); - - // Notify that uninstallation completed. - OnPackageUninstalled(this, new PackageOperationEventArgs(new PackageName(package.Id, package.Version), installPath)); - //currentProgressReport = progress; - //try - //{ - // manager.UninstallPackage(package.IPackage); - //} - //finally - //{ - // currentProgressReport = null; - //} - - if (package.Id == "Xenko" && package.Version < new PackageVersion(3, 0, 0, 0)) - { - UpdateTargetsHelper(); - } - } - finally + // Simply delete the installed package and its .nupkg installed in it. + await Task.Run(() => FileSystemUtility.DeleteDirectorySafe(installPath, true, projectContext)); + + // Notify that uninstallation completed. + OnPackageUninstalled(this, new PackageOperationEventArgs(new PackageName(package.Id, package.Version), installPath)); + //currentProgressReport = progress; + //try + //{ + // manager.UninstallPackage(package.IPackage); + //} + //finally + //{ + // currentProgressReport = null; + //} + + if (package.Id == "Xenko" && package.Version < new PackageVersion(3, 0, 0, 0)) { - currentProgressReport = null; + UpdateTargetsHelper(); } } + finally + { + currentProgressReport = null; + } } - /// - /// Find the installed package using the version if not null, otherwise the if specified. - /// If no constraints are specified, the first found entry, whatever it means for NuGet, is used. - /// - /// Name of the package. - /// The version range. - /// The package constraint provider. - /// if set to true [allow prelease version]. - /// if set to true [allow unlisted]. - /// A Package matching the search criterion or null if not found. - /// packageIdentity - /// - public NugetPackage FindLocalPackage(string packageId, PackageVersion version = null, ConstraintProvider constraintProvider = null, bool allowPrereleaseVersions = true, bool allowUnlisted = false) + void OnPackageUninstalling(object? sender, PackageOperationEventArgs args) { - var versionRange = new PackageVersionRange(version); - return FindLocalPackage(packageId, versionRange, constraintProvider, allowPrereleaseVersions, allowUnlisted); - } + NugetPackageUninstalling?.Invoke(sender, args); - /// - /// Find the installed package using the version if not null, otherwise the if specified. - /// If no constraints are specified, the first found entry, whatever it means for NuGet, is used. - /// - /// Name of the package. - /// The version range. - /// The package constraint provider. - /// if set to true [allow prelease version]. - /// if set to true [allow unlisted]. - /// A Package matching the search criterion or null if not found. - /// packageIdentity - /// - public NugetLocalPackage FindLocalPackage(string packageId, PackageVersionRange versionRange = null, ConstraintProvider constraintProvider = null, bool allowPrereleaseVersions = true, bool allowUnlisted = false) - { - // if an explicit version is specified, disregard the 'allowUnlisted' argument - // and always allow unlisted packages. - if (versionRange != null) + try { - allowUnlisted = true; + var packageInstallPath = Path.Combine(args.InstallPath, "tools\\packageinstall.exe"); + if (File.Exists(packageInstallPath)) + { + RunPackageInstall(packageInstallPath, "/uninstall", currentProgressReport); + } } - else if (!allowUnlisted && ((constraintProvider == null) || !constraintProvider.HasConstraints)) + catch (Exception) { - // Simple case, we just get the most recent version based on `allowPrereleaseVersions`. - return GetPackagesInstalled(new[] { packageId }).FirstOrDefault(p => allowPrereleaseVersions || string.IsNullOrEmpty(p.Version.SpecialVersion)); + // We mute errors during uninstall since they are usually non-fatal (OTOH, if we don't catch the exception, the NuGet package isn't uninstalled, which is probably not what we want) + // If we really wanted to deal with them at some point, we should use another mechanism than exception (i.e. log) } + } - var packages = GetLocalPackages(packageId); + void OnPackageUninstalled(object? sender, PackageOperationEventArgs args) + { + NugetPackageUninstalled?.Invoke(sender, args); + } - if (!allowUnlisted) - { - packages = packages.Where(p=>p.Listed); - } + } - if (constraintProvider != null) - { - versionRange = constraintProvider.GetConstraint(packageId) ?? versionRange; - } - if (versionRange != null) - { - packages = packages.Where(p => versionRange.Contains(p.Version)); - } - return packages?.FirstOrDefault(p => allowPrereleaseVersions || string.IsNullOrEmpty(p.Version.SpecialVersion)); + /// + /// Find the installed package using the version if not null, otherwise the if specified. + /// If no constraints are specified, the first found entry, whatever it means for NuGet, is used. + /// + /// Name of the package. + /// The version range. + /// The package constraint provider. + /// if set to true [allow prelease version]. + /// if set to true [allow unlisted]. + /// A Package matching the search criterion or null if not found. + /// packageIdentity + /// + public NugetPackage? FindLocalPackage(string packageId, PackageVersion? version = null, ConstraintProvider? constraintProvider = null, bool allowPrereleaseVersions = true, bool allowUnlisted = false) + { + var versionRange = new PackageVersionRange(version); + return FindLocalPackage(packageId, versionRange, constraintProvider, allowPrereleaseVersions, allowUnlisted); + } + + /// + /// Find the installed package using the version if not null, otherwise the if specified. + /// If no constraints are specified, the first found entry, whatever it means for NuGet, is used. + /// + /// Name of the package. + /// The version range. + /// The package constraint provider. + /// if set to true [allow prelease version]. + /// if set to true [allow unlisted]. + /// A Package matching the search criterion or null if not found. + /// packageIdentity + /// + public NugetLocalPackage? FindLocalPackage(string packageId, PackageVersionRange? versionRange = null, ConstraintProvider? constraintProvider = null, bool allowPrereleaseVersions = true, bool allowUnlisted = false) + { + // if an explicit version is specified, disregard the 'allowUnlisted' argument + // and always allow unlisted packages. + if (versionRange != null) + { + allowUnlisted = true; + } + else if (!allowUnlisted && (constraintProvider?.HasConstraints != true)) + { + // Simple case, we just get the most recent version based on `allowPrereleaseVersions`. + return GetPackagesInstalled([packageId]).FirstOrDefault(p => allowPrereleaseVersions || string.IsNullOrEmpty(p.Version.SpecialVersion)); + } + + var packages = GetLocalPackages(packageId); + + if (!allowUnlisted) + { + packages = packages.Where(p => p.Listed); } - /// - /// Find available packages from source ith Ids matching . - /// - /// List of package Ids we are looking for. - /// A cancellation token. - /// A list of packages matching or an empty list if none is found. - public async Task> FindSourcePackages(IReadOnlyCollection packageIds, CancellationToken cancellationToken) + if (constraintProvider != null) { - var repositories = PackageSources.Select(sourceRepositoryProvider.CreateRepository).ToArray(); - var res = new List(); - foreach (var packageId in packageIds) - { - await FindSourcePackagesByIdHelper(packageId, res, repositories, cancellationToken); - } - return res; + versionRange = constraintProvider.GetConstraint(packageId) ?? versionRange; + } + if (versionRange != null) + { + packages = packages.Where(p => versionRange.Contains(p.Version)); } + return packages?.FirstOrDefault(p => allowPrereleaseVersions || string.IsNullOrEmpty(p.Version.SpecialVersion)); + } - /// - /// Find available packages from source with Id matching . - /// - /// Id of package we are looking for. - /// A cancellation token. - /// A list of packages matching or an empty list if none is found. - public async Task> FindSourcePackagesById(string packageId, CancellationToken cancellationToken) + /// + /// Find available packages from source ith Ids matching . + /// + /// List of package Ids we are looking for. + /// A cancellation token. + /// A list of packages matching or an empty list if none is found. + public async Task> FindSourcePackages(IReadOnlyCollection packageIds, CancellationToken cancellationToken) + { + var repositories = PackageSources.Select(sourceRepositoryProvider.CreateRepository).ToArray(); + var res = new List(); + foreach (var packageId in packageIds) { - var repositories = PackageSources.Select(sourceRepositoryProvider.CreateRepository).ToArray(); - var res = new List(); await FindSourcePackagesByIdHelper(packageId, res, repositories, cancellationToken); - return res; } + return res; + } - private async Task FindSourcePackagesByIdHelper(string packageId, List resultList, SourceRepository [] repositories, CancellationToken cancellationToken) + /// + /// Find available packages from source with Id matching . + /// + /// Id of package we are looking for. + /// A cancellation token. + /// A list of packages matching or an empty list if none is found. + public async Task> FindSourcePackagesById(string packageId, CancellationToken cancellationToken) + { + var repositories = PackageSources.Select(sourceRepositoryProvider.CreateRepository).ToArray(); + var res = new List(); + await FindSourcePackagesByIdHelper(packageId, res, repositories, cancellationToken); + return res; + } + + private async Task FindSourcePackagesByIdHelper(string packageId, List resultList, SourceRepository[] repositories, CancellationToken cancellationToken) + { + using var sourceCacheContext = new SourceCacheContext { MaxAge = DateTimeOffset.UtcNow }; + foreach (var repo in repositories) { - using (var sourceCacheContext = new SourceCacheContext { MaxAge = DateTimeOffset.UtcNow }) + try { - foreach (var repo in repositories) + var metadataResource = await repo.GetResourceAsync(CancellationToken.None); + var metadataList = await metadataResource.GetMetadataAsync(packageId, true, true, sourceCacheContext, NativeLogger, cancellationToken); + foreach (var metadata in metadataList) { - try - { - var metadataResource = await repo.GetResourceAsync(CancellationToken.None); - var metadataList = await metadataResource.GetMetadataAsync(packageId, true, true, sourceCacheContext, NativeLogger, cancellationToken); - foreach (var metadata in metadataList) - { - if (metadata.IsListed) - resultList.Add(new NugetServerPackage(metadata, repo.PackageSource.Source)); - } - } - catch (FatalProtocolException) - { - // Ignore 404/403 etc... (invalid sources) - } + if (metadata.IsListed) + resultList.Add(new NugetServerPackage(metadata, repo.PackageSource.Source)); } } + catch (FatalProtocolException) + { + // Ignore 404/403 etc... (invalid sources) + } } + } - /// - /// Look for available packages from source containing in either the Id or description of the package. - /// - /// Term used for search. - /// Are we looking in pre-release versions too? - /// A list of packages matching . - public async Task> SourceSearch(string searchTerm, bool allowPrereleaseVersions) + /// + /// Look for available packages from source containing in either the Id or description of the package. + /// + /// Term used for search. + /// Are we looking in pre-release versions too? + /// A list of packages matching . + public async Task> SourceSearch(string searchTerm, bool allowPrereleaseVersions) + { + var repositories = PackageSources.Select(sourceRepositoryProvider.CreateRepository).ToArray(); + var res = new List(); + foreach (var repo in repositories) { - var repositories = PackageSources.Select(sourceRepositoryProvider.CreateRepository).ToArray(); - var res = new List(); - foreach (var repo in repositories) + try { - try + var searchResource = await repo.GetResourceAsync(CancellationToken.None); + + if (searchResource != null) { - var searchResource = await repo.GetResourceAsync(CancellationToken.None); + var searchResults = await searchResource.SearchAsync(searchTerm, new SearchFilter(includePrerelease: false), 0, 0, NativeLogger, CancellationToken.None); - if (searchResource != null) + if (searchResults != null) { - var searchResults = await searchResource.SearchAsync(searchTerm, new SearchFilter(includePrerelease: false), 0, 0, NativeLogger, CancellationToken.None); + var packages = searchResults.ToArray(); - if (searchResults != null) + foreach (var package in packages) { - var packages = searchResults.ToArray(); - - foreach (var package in packages) - { - if (package.IsListed) - res.Add(new NugetServerPackage(package, repo.PackageSource.Source)); - } + if (package.IsListed) + res.Add(new NugetServerPackage(package, repo.PackageSource.Source)); } } } - catch (FatalProtocolException) - { - // Ignore 404/403 etc... (invalid sources) - } } - return res.AsQueryable(); + catch (FatalProtocolException) + { + // Ignore 404/403 etc... (invalid sources) + } } + return res.AsQueryable(); + } - /// - /// Returns updates for packages from the repository - /// - /// Package to look for updates - /// Indicates whether to consider prerelease updates. - /// Indicates whether to include all versions of an update as opposed to only including the latest version. - /// A cancellation token. - /// - public async Task> GetUpdates(PackageName packageName, bool includePrerelease, bool includeAllVersions, CancellationToken cancellationToken) - { - var resolutionContext = new ResolutionContext( - DependencyBehavior.Lowest, - includePrerelease, - true, - includeAllVersions ? VersionConstraints.None : VersionConstraints.ExactMajor | VersionConstraints.ExactMinor); + /// + /// Returns updates for packages from the repository + /// + /// Package to look for updates + /// Indicates whether to consider prerelease updates. + /// Indicates whether to include all versions of an update as opposed to only including the latest version. + /// A cancellation token. + /// + public async Task> GetUpdates(PackageName packageName, bool includePrerelease, bool includeAllVersions, CancellationToken cancellationToken) + { + var resolutionContext = new ResolutionContext( + DependencyBehavior.Lowest, + includePrerelease, + true, + includeAllVersions ? VersionConstraints.None : VersionConstraints.ExactMajor | VersionConstraints.ExactMinor); - var repositories = PackageSources.Select(sourceRepositoryProvider.CreateRepository).ToArray(); + var repositories = PackageSources.Select(sourceRepositoryProvider.CreateRepository).ToArray(); - var res = new List(); - using (var context = new SourceCacheContext { MaxAge = DateTimeOffset.UtcNow }) + var res = new List(); + using (var context = new SourceCacheContext { MaxAge = DateTimeOffset.UtcNow }) + { + foreach (var repo in repositories) { - foreach (var repo in repositories) + try { - try - { - var metadataResource = await repo.GetResourceAsync(cancellationToken); - var metadataList = await metadataResource.GetMetadataAsync(packageName.Id, includePrerelease, includeAllVersions, context, NativeLogger, cancellationToken); - foreach (var metadata in metadataList) - { - if (metadata.IsListed) - res.Add(new NugetServerPackage(metadata, repo.PackageSource.Source)); - } - } - catch (FatalProtocolException) + var metadataResource = await repo.GetResourceAsync(cancellationToken); + var metadataList = await metadataResource.GetMetadataAsync(packageName.Id, includePrerelease, includeAllVersions, context, NativeLogger, cancellationToken); + foreach (var metadata in metadataList) { - // Ignore 404/403 etc... (invalid sources) + if (metadata.IsListed) + res.Add(new NugetServerPackage(metadata, repo.PackageSource.Source)); } } + catch (FatalProtocolException) + { + // Ignore 404/403 etc... (invalid sources) + } } - return res; } -#endregion + return res; + } + #endregion - /// - /// Clean all temporary files created thus far during store operations. - /// - public void PurgeCache() - { - } + /// + /// Clean all temporary files created thus far during store operations. + /// + public void PurgeCache() + { + } - public string GetRealPath(NugetLocalPackage package) + public string GetRealPath(NugetLocalPackage package) + { + if (IsDevRedirectPackage(package) && package.Version < new PackageVersion(3, 1, 0, 0)) { - if (IsDevRedirectPackage(package) && package.Version < new PackageVersion(3, 1, 0, 0)) - { - var realPath = File.ReadAllText(GetRedirectFile(package)); - if (!Directory.Exists(realPath)) - throw new DirectoryNotFoundException(); - return realPath; - } - - return package.Path; + var realPath = File.ReadAllText(GetRedirectFile(package)); + if (!Directory.Exists(realPath)) + throw new DirectoryNotFoundException(); + return realPath; } - public string GetRedirectFile(NugetLocalPackage package) - { - return Path.Combine(package.Path, $"{package.Id}.redirect"); - } + return package.Path; + } - public bool IsDevRedirectPackage(NugetLocalPackage package) - { - return package.Version < new PackageVersion(3, 1, 0, 0) - ? File.Exists(GetRedirectFile(package)) - : (package.Version.SpecialVersion != null && package.Version.SpecialVersion.StartsWith("dev", StringComparison.Ordinal) && !package.Version.SpecialVersion.Contains('.')); - } + public string GetRedirectFile(NugetLocalPackage package) + { + return Path.Combine(package.Path, $"{package.Id}.redirect"); + } - public bool IsDevRedirectPackage(NugetServerPackage package) - { - return (package.Version.SpecialVersion != null && package.Version.SpecialVersion.StartsWith("dev", StringComparison.Ordinal) && !package.Version.SpecialVersion.Contains('.')); - } + public bool IsDevRedirectPackage(NugetLocalPackage package) + { + return package.Version < new PackageVersion(3, 1, 0, 0) + ? File.Exists(GetRedirectFile(package)) + : (package.Version.SpecialVersion?.StartsWith("dev", StringComparison.Ordinal) == true && !package.Version.SpecialVersion.Contains('.')); + } - private void OnPackageInstalled(object sender, PackageOperationEventArgs args) - { - var packageInstallPath = Path.Combine(args.InstallPath, "tools\\packageinstall.exe"); - if (File.Exists(packageInstallPath)) - { - RunPackageInstall(packageInstallPath, "/install", currentProgressReport); - } + public bool IsDevRedirectPackage(NugetServerPackage package) + { + return package.Version.SpecialVersion?.StartsWith("dev", StringComparison.Ordinal) == true && !package.Version.SpecialVersion.Contains('.'); + } - NugetPackageInstalled?.Invoke(sender, args); - } + void INugetDownloadProgress.DownloadProgress(long contentPosition, long contentLength) + { + currentProgressReport?.UpdateProgress(ProgressAction.Download, (int)(contentPosition * 100 / contentLength)); + } - private void OnPackageUninstalling(object sender, PackageOperationEventArgs args) + private static void RunPackageInstall(string packageInstall, string arguments, ProgressReport progress) + { + // Run packageinstall.exe + using var process = Process.Start(new ProcessStartInfo(packageInstall, arguments) { - NugetPackageUninstalling?.Invoke(sender, args); + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardOutput = true, + WorkingDirectory = Path.GetDirectoryName(packageInstall), + }) ?? throw new InvalidOperationException($"Could not start install package process [{packageInstall}] with options {arguments}"); + var errorOutput = new StringBuilder(); - try + process.OutputDataReceived += (_, args) => + { + if (!string.IsNullOrEmpty(args.Data)) { - var packageInstallPath = Path.Combine(args.InstallPath, "tools\\packageinstall.exe"); - if (File.Exists(packageInstallPath)) + var matches = powerShellProgressRegex.Match(args.Data); + if (matches.Success && int.TryParse(matches.Groups[1].Value, out var percentageResult)) { - RunPackageInstall(packageInstallPath, "/uninstall", currentProgressReport); + // Report progress + progress?.UpdateProgress(ProgressAction.Install, percentageResult); + } + else + { + lock (process) + { + errorOutput.AppendLine(args.Data); + } } } - catch (Exception) + }; + process.ErrorDataReceived += (_, args) => + { + if (!string.IsNullOrEmpty(args.Data)) { - // We mute errors during uninstall since they are usually non-fatal (OTOH, if we don't catch the exception, the NuGet package isn't uninstalled, which is probably not what we want) - // If we really wanted to deal with them at some point, we should use another mechanism than exception (i.e. log) + // Save errors + lock (process) + { + errorOutput.AppendLine(args.Data); + } } - } + }; - private void OnPackageUninstalled(object sender, PackageOperationEventArgs args) - { - NugetPackageUninstalled?.Invoke(sender, args); - } + // Process output and wait for exit + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); - void INugetDownloadProgress.DownloadProgress(long contentPosition, long contentLength) + // Check exit code + var exitCode = process.ExitCode; + if (exitCode != 0) { - currentProgressReport?.UpdateProgress(ProgressAction.Download, (int)(contentPosition * 100 / contentLength)); + throw new InvalidOperationException($"Error code {exitCode} while running install package process [{packageInstall}]\n\n" + errorOutput); } + } - private static void RunPackageInstall(string packageInstall, string arguments, ProgressReport progress) - { - // Run packageinstall.exe - using (var process = Process.Start(new ProcessStartInfo(packageInstall, arguments) - { - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardError = true, - RedirectStandardOutput = true, - WorkingDirectory = Path.GetDirectoryName(packageInstall), - })) - { - if (process == null) - throw new InvalidOperationException($"Could not start install package process [{packageInstall}] with options {arguments}"); - - var errorOutput = new StringBuilder(); - - process.OutputDataReceived += (_, args) => - { - if (!string.IsNullOrEmpty(args.Data)) - { - var matches = powerShellProgressRegex.Match(args.Data); - int percentageResult; - if (matches.Success && int.TryParse(matches.Groups[1].Value, out percentageResult)) - { - // Report progress - progress?.UpdateProgress(ProgressAction.Install, percentageResult); - } - else - { - lock (process) - { - errorOutput.AppendLine(args.Data); - } - } - } - }; - process.ErrorDataReceived += (_, args) => - { - if (!string.IsNullOrEmpty(args.Data)) - { - // Save errors - lock (process) - { - errorOutput.AppendLine(args.Data); - } - } - }; - - // Process output and wait for exit - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - process.WaitForExit(); + // Used only for Stride 1.x and 2.x + private void UpdateTargetsHelper() + { + if (oldRootDirectory == null) + return; - // Check exit code - var exitCode = process.ExitCode; - if (exitCode != 0) - { - throw new InvalidOperationException($"Error code {exitCode} while running install package process [{packageInstall}]\n\n" + errorOutput); - } - } - } + // Get latest package only for each MainPackageIds (up to 2.x) + var strideOldPackages = GetLocalPackages("Xenko").Where(package => package.Tags?.Contains("internal") != true).Where(x => x.Version.Version.Major < 3).ToList(); - // Used only for Stride 1.x and 2.x - private void UpdateTargetsHelper() - { - if (oldRootDirectory == null) - return; + // Generate target file + var targetGenerator = new TargetGenerator(this, strideOldPackages, "SiliconStudioPackage"); + var targetFileContent = targetGenerator.TransformText(); + var targetFile = Path.Combine(oldRootDirectory, @"Targets\SiliconStudio.Common.targets"); - // Get latest package only for each MainPackageIds (up to 2.x) - var strideOldPackages = GetLocalPackages("Xenko").Where(package => !((package.Tags != null) && package.Tags.Contains("internal"))).Where(x => x.Version.Version.Major < 3).ToList(); + var targetFilePath = Path.GetDirectoryName(targetFile); - // Generate target file - var targetGenerator = new TargetGenerator(this, strideOldPackages, "SiliconStudioPackage"); - var targetFileContent = targetGenerator.TransformText(); - var targetFile = Path.Combine(oldRootDirectory, @"Targets\SiliconStudio.Common.targets"); + // Make sure directory exists + if (targetFilePath != null && !Directory.Exists(targetFilePath)) + Directory.CreateDirectory(targetFilePath); - var targetFilePath = Path.GetDirectoryName(targetFile); + File.WriteAllText(targetFile, targetFileContent, Encoding.UTF8); + } - // Make sure directory exists - if (targetFilePath != null && !Directory.Exists(targetFilePath)) - Directory.CreateDirectory(targetFilePath); + private class PackagePathResolverV3 : PackagePathResolver + { + private readonly VersionFolderPathResolver pathResolver; - File.WriteAllText(targetFile, targetFileContent, Encoding.UTF8); + public PackagePathResolverV3(string rootDirectory) : base(rootDirectory, true) + { + pathResolver = new VersionFolderPathResolver(rootDirectory); } - private class PackagePathResolverV3 : PackagePathResolver + public override string GetPackageDirectoryName(PackageIdentity packageIdentity) { - private VersionFolderPathResolver pathResolver; - - public PackagePathResolverV3(string rootDirectory) : base(rootDirectory, true) - { - pathResolver = new VersionFolderPathResolver(rootDirectory); - } - - public override string GetPackageDirectoryName(PackageIdentity packageIdentity) - { - return pathResolver.GetPackageDirectory(packageIdentity.Id, packageIdentity.Version); - } + return pathResolver.GetPackageDirectory(packageIdentity.Id, packageIdentity.Version); + } - public override string GetPackageFileName(PackageIdentity packageIdentity) - { - return pathResolver.GetPackageFileName(packageIdentity.Id, packageIdentity.Version); - } + public override string GetPackageFileName(PackageIdentity packageIdentity) + { + return pathResolver.GetPackageFileName(packageIdentity.Id, packageIdentity.Version); + } - public override string GetInstallPath(PackageIdentity packageIdentity) - { - return pathResolver.GetInstallPath(packageIdentity.Id, packageIdentity.Version); - } + public override string GetInstallPath(PackageIdentity packageIdentity) + { + return pathResolver.GetInstallPath(packageIdentity.Id, packageIdentity.Version); + } - public override string GetInstalledPath(PackageIdentity packageIdentity) - { - var installPath = GetInstallPath(packageIdentity); - var installPackagePath = GetInstalledPackageFilePath(packageIdentity); - return File.Exists(installPackagePath) ? installPath : null; - } + public override string? GetInstalledPath(PackageIdentity packageIdentity) + { + var installPath = GetInstallPath(packageIdentity); + var installPackagePath = GetInstalledPackageFilePath(packageIdentity); + return File.Exists(installPackagePath) ? installPath : null; + } - public override string GetInstalledPackageFilePath(PackageIdentity packageIdentity) - { - var installPath = GetInstallPath(packageIdentity); - var installPackagePath = Path.Combine(installPath, packageIdentity.ToString().ToLower() + PackagingCoreConstants.NupkgExtension); - return File.Exists(installPackagePath) ? installPackagePath : null; - } + public override string? GetInstalledPackageFilePath(PackageIdentity packageIdentity) + { + var installPath = GetInstallPath(packageIdentity); + var installPackagePath = Path.Combine(installPath, packageIdentity.ToString().ToLower() + PackagingCoreConstants.NupkgExtension); + return File.Exists(installPackagePath) ? installPackagePath : null; } } + + [GeneratedRegex(@"\[ProgressReport:\s*(\d*)%\]")] + private static partial Regex GetPowerShellProgressRegex(); } diff --git a/sources/assets/Stride.Core.Packages/NullPackagesLogger.cs b/sources/assets/Stride.Core.Packages/NullPackagesLogger.cs index 8f196ef20c..da33d47087 100644 --- a/sources/assets/Stride.Core.Packages/NullPackagesLogger.cs +++ b/sources/assets/Stride.Core.Packages/NullPackagesLogger.cs @@ -1,24 +1,21 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Threading.Tasks; +namespace Stride.Core.Packages; -namespace Stride.Core.Packages +/// +/// Null implementation of . +/// +public class NullPackagesLogger : IPackagesLogger { - /// - /// Null implementation of . - /// - public class NullPackagesLogger : IPackagesLogger - { - public static IPackagesLogger Instance { get; } = new NullPackagesLogger(); + public static IPackagesLogger Instance { get; } = new NullPackagesLogger(); - public void Log(MessageLevel level, string message) - { - } + public void Log(MessageLevel level, string message) + { + } - public Task LogAsync(MessageLevel level, string message) - { - return Task.CompletedTask; - } + public Task LogAsync(MessageLevel level, string message) + { + return Task.CompletedTask; } } diff --git a/sources/assets/Stride.Core.Packages/PackageConstants.cs b/sources/assets/Stride.Core.Packages/PackageConstants.cs index d893f20e68..dbd9d66a04 100644 --- a/sources/assets/Stride.Core.Packages/PackageConstants.cs +++ b/sources/assets/Stride.Core.Packages/PackageConstants.cs @@ -1,10 +1,9 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +public static class PackageConstants { - public static class PackageConstants - { - public static readonly string PackageExtension = ".nupkg"; - } + public static readonly string PackageExtension = ".nupkg"; } diff --git a/sources/assets/Stride.Core.Packages/PackageFile.cs b/sources/assets/Stride.Core.Packages/PackageFile.cs index 156186c38f..f00ef50e6c 100644 --- a/sources/assets/Stride.Core.Packages/PackageFile.cs +++ b/sources/assets/Stride.Core.Packages/PackageFile.cs @@ -1,59 +1,57 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.IO; using NuGet.Packaging; -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +/// +/// Representation of a file in a package. +/// TODO: Verify if this needs updating for NuGet 3.0 +/// +public class PackageFile { + private readonly string packagePath; + private readonly IPackageFile? packageFile; + + /// + /// Initializes a new instance of located in . + /// + /// Path of the file in the package. + public PackageFile(string packagePath, string path) + { + this.packagePath = packagePath; + Path = path; + } + + public PackageFile(IPackageFile x) + { + Path = x.Path; + SourcePath = (x as PhysicalPackageFile)?.SourcePath; + packageFile = x; + } + + /// + /// Gets the full path of the file on the HDD. + /// + public string FullPath => System.IO.Path.Combine(packagePath, Path); + + /// + /// Gets the path of the file inside the package. + /// + public string Path { get; } + + /// + /// Gets the source path of the file on the hard drive (if it has a source). + /// + public string? SourcePath { get; } + /// - /// Representation of a file in a package. - /// TODO: Verify if this needs updating for NuGet 3.0 + /// Access to the stream content in read mode. /// - public class PackageFile + /// A new stream reading file pointed by . + public Stream GetStream() { - private readonly string packagePath; - private readonly IPackageFile packageFile; - - /// - /// Initializes a new instance of located in . - /// - /// Path of the file in the package. - public PackageFile(string packagePath, string path) - { - this.packagePath = packagePath; - Path = path; - } - - public PackageFile(IPackageFile x) - { - Path = x.Path; - SourcePath = (x as PhysicalPackageFile)?.SourcePath; - packageFile = x; - } - - /// - /// Gets the full path of the file on the HDD. - /// - public string FullPath => System.IO.Path.Combine(packagePath, Path); - - /// - /// Gets the path of the file inside the package. - /// - public string Path { get; } - - /// - /// Gets the source path of the file on the hard drive (if it has a source). - /// - public string SourcePath { get; } - - /// - /// Access to the stream content in read mode. - /// - /// A new stream reading file pointed by . - public Stream GetStream() - { - return packageFile?.GetStream() ?? File.OpenRead(FullPath); - } + return packageFile?.GetStream() ?? File.OpenRead(FullPath); } } diff --git a/sources/assets/Stride.Core.Packages/PackageName.cs b/sources/assets/Stride.Core.Packages/PackageName.cs index 1fe30d4a2e..a0754394a2 100644 --- a/sources/assets/Stride.Core.Packages/PackageName.cs +++ b/sources/assets/Stride.Core.Packages/PackageName.cs @@ -1,60 +1,53 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core; -using Stride.Core.Annotations; +namespace Stride.Core.Packages; -namespace Stride.Core.Packages +/// +/// Representation of the name of a package made of an Id and a version. +/// +public sealed class PackageName : IEquatable { /// - /// Representation of the name of a package made of an Id and a version. + /// Initializes a new instance of . /// - public sealed class PackageName : IEquatable + /// Id of package. + /// Version of package. + public PackageName(string id, PackageVersion version) { - /// - /// Initializes a new instance of . - /// - /// Id of package. - /// Version of package. - public PackageName([NotNull] string id, [NotNull] PackageVersion version) - { - Id = id ?? throw new ArgumentNullException(nameof(id)); - Version = version ?? throw new ArgumentNullException(nameof(version)); - } + Id = id ?? throw new ArgumentNullException(nameof(id)); + Version = version ?? throw new ArgumentNullException(nameof(version)); + } - /// - /// Identity of the package. - /// - [NotNull] - public string Id { get; } + /// + /// Identity of the package. + /// + public string Id { get; } - /// - /// Version of the package. - /// - [NotNull] - public PackageVersion Version { get; } + /// + /// Version of the package. + /// + public PackageVersion Version { get; } - /// - public override int GetHashCode() - { - return (Id.GetHashCode() * 397) ^ Version.GetHashCode(); - } + /// + public override int GetHashCode() + { + return (Id.GetHashCode() * 397) ^ Version.GetHashCode(); + } - /// - public bool Equals(PackageName other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(Id, other.Id) && Equals(Version, other.Version); - } + /// + public bool Equals(PackageName? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Id, other.Id) && Equals(Version, other.Version); + } - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return Equals(obj as PackageName); - } + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as PackageName); } } diff --git a/sources/assets/Stride.Core.Packages/PackageOperationEventArgs.cs b/sources/assets/Stride.Core.Packages/PackageOperationEventArgs.cs index 9c3fa7953c..dfa4fbc6a3 100644 --- a/sources/assets/Stride.Core.Packages/PackageOperationEventArgs.cs +++ b/sources/assets/Stride.Core.Packages/PackageOperationEventArgs.cs @@ -1,32 +1,31 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Packages +namespace Stride.Core.Packages; + +public class PackageOperationEventArgs { - public class PackageOperationEventArgs + /// + /// Initialize a new instance of using the corresponding NuGet abstraction. + /// + internal PackageOperationEventArgs(PackageName name, string installPath) { - /// - /// Initialize a new instance of using the corresponding NuGet abstraction. - /// - internal PackageOperationEventArgs(PackageName name, string installPath) - { - Name = name; - InstallPath = installPath; - } + Name = name; + InstallPath = installPath; + } - /// - /// Name of package being installed/uninstalled. - /// - public PackageName Name { get; } + /// + /// Name of package being installed/uninstalled. + /// + public PackageName Name { get; } - /// - /// Id of . - /// - public string Id => Name.Id; + /// + /// Id of . + /// + public string Id => Name.Id; - /// - /// Location where package is installed to/uninstalled from. - /// - public string InstallPath { get; } - } + /// + /// Location where package is installed to/uninstalled from. + /// + public string InstallPath { get; } } diff --git a/sources/assets/Stride.Core.Packages/ProgressAction.cs b/sources/assets/Stride.Core.Packages/ProgressAction.cs index b4e0250afc..aae596383e 100644 --- a/sources/assets/Stride.Core.Packages/ProgressAction.cs +++ b/sources/assets/Stride.Core.Packages/ProgressAction.cs @@ -1,11 +1,11 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Packages + +namespace Stride.Core.Packages; + +public enum ProgressAction { - public enum ProgressAction - { - Download, - Install, - Delete, - } + Download, + Install, + Delete, } diff --git a/sources/assets/Stride.Core.Packages/ProgressReport.cs b/sources/assets/Stride.Core.Packages/ProgressReport.cs index 79dad8fe92..695a094bbf 100644 --- a/sources/assets/Stride.Core.Packages/ProgressReport.cs +++ b/sources/assets/Stride.Core.Packages/ProgressReport.cs @@ -1,66 +1,61 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Linq; -using NuGet; +namespace Stride.Core.Packages; -namespace Stride.Core.Packages +public class ProgressReport : IDisposable { - public class ProgressReport : IDisposable - { - private readonly NugetStore store; - //private readonly string version; - private ProgressAction action; - private int progress; - - public ProgressReport(NugetStore store, NugetPackage package) - { - if (store == null) throw new ArgumentNullException(nameof(store)); - this.store = store; - //version = package.Version.ToSemanticVersion().ToNormalizedString(); + private readonly NugetStore store; + //private readonly string version; + private ProgressAction action; + private int progress; - //foreach (var progressProvider in store.SourceRepository.Repositories.OfType()) - //{ - // progressProvider.ProgressAvailable += OnProgressAvailable; - //} - } + public ProgressReport(NugetStore store, NugetPackage package) + { + ArgumentNullException.ThrowIfNull(store); + this.store = store; + //version = package.Version.ToSemanticVersion().ToNormalizedString(); - public event Action ProgressChanged; + //foreach (var progressProvider in store.SourceRepository.Repositories.OfType()) + //{ + // progressProvider.ProgressAvailable += OnProgressAvailable; + //} + } - public void UpdateProgress(ProgressAction action, int progress) - { - if (this.action != action || this.progress != progress) - { - this.action = action; - this.progress = progress; - ProgressChanged?.Invoke(action, progress); - } - } + public event Action ProgressChanged; - public void Dispose() + public void UpdateProgress(ProgressAction action, int progress) + { + if (this.action != action || this.progress != progress) { - //foreach (var progressProvider in store.SourceRepository.Repositories.OfType()) - //{ - // progressProvider.ProgressAvailable -= OnProgressAvailable; - //} + this.action = action; + this.progress = progress; + ProgressChanged?.Invoke(action, progress); } + } - //private void OnProgressAvailable(object sender, ProgressEventArgs e) + public void Dispose() + { + //foreach (var progressProvider in store.SourceRepository.Repositories.OfType()) //{ - // if (version == null) - // return; - // - // if (e.Operation == null || e.Operation.Contains(version)) - // { - // var percentComplete = e.PercentComplete; - // if (progress != percentComplete) - // { - // progress = percentComplete; - // // seems like NuGet is only returning download progress so far - // ProgressChanged?.Invoke(ProgressAction.Download, progress); - // } - // } + // progressProvider.ProgressAvailable -= OnProgressAvailable; //} } + + //private void OnProgressAvailable(object sender, ProgressEventArgs e) + //{ + // if (version == null) + // return; + // + // if (e.Operation == null || e.Operation.Contains(version)) + // { + // var percentComplete = e.PercentComplete; + // if (progress != percentComplete) + // { + // progress = percentComplete; + // // seems like NuGet is only returning download progress so far + // ProgressChanged?.Invoke(ProgressAction.Download, progress); + // } + // } + //} } diff --git a/sources/assets/Stride.Core.Packages/Stride.Core.Packages.csproj b/sources/assets/Stride.Core.Packages/Stride.Core.Packages.csproj index 6cb4fb3a30..290b46c7b5 100644 --- a/sources/assets/Stride.Core.Packages/Stride.Core.Packages.csproj +++ b/sources/assets/Stride.Core.Packages/Stride.Core.Packages.csproj @@ -3,6 +3,9 @@ true $(StrideXplatEditorTargetFramework) + enable + latest + enable true --auto-module-initializer --serialization @@ -36,4 +39,4 @@ - \ No newline at end of file + diff --git a/sources/assets/Stride.Core.Packages/TargetGenerator.Members.cs b/sources/assets/Stride.Core.Packages/TargetGenerator.Members.cs index a52a9205cd..ab6b4cffcc 100644 --- a/sources/assets/Stride.Core.Packages/TargetGenerator.Members.cs +++ b/sources/assets/Stride.Core.Packages/TargetGenerator.Members.cs @@ -1,21 +1,18 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; +namespace Stride.Core.Packages; -namespace Stride.Core.Packages +partial class TargetGenerator { - partial class TargetGenerator - { - private readonly NugetStore store; - private readonly List packages; - private readonly string packageVersionPrefix; + private readonly NugetStore store; + private readonly List packages; + private readonly string packageVersionPrefix; - internal TargetGenerator(NugetStore store, List packages, string packageVersionPrefix) - { - this.store = store; - this.packages = packages; - this.packageVersionPrefix = packageVersionPrefix; - } + internal TargetGenerator(NugetStore store, List packages, string packageVersionPrefix) + { + this.store = store; + this.packages = packages; + this.packageVersionPrefix = packageVersionPrefix; } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/AnonymousBuildStepProvider.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/AnonymousBuildStepProvider.cs index 34001eb49a..ef5995e724 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/AnonymousBuildStepProvider.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/AnonymousBuildStepProvider.cs @@ -1,31 +1,28 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; +namespace Stride.Core.BuildEngine; -namespace Stride.Core.BuildEngine +/// +/// An implementation of the interface that allows to create a build step provider +/// from an anonymous function. +/// +public class AnonymousBuildStepProvider : IBuildStepProvider { + private readonly Func providerFunction; + /// - /// An implementation of the interface that allows to create a build step provider - /// from an anonymous function. + /// Initializes a new instance of the class. /// - public class AnonymousBuildStepProvider : IBuildStepProvider + /// The function that provides build steps. + public AnonymousBuildStepProvider(Func providerFunction) { - private readonly Func providerFunction; - - /// - /// Initializes a new instance of the class. - /// - /// The function that provides build steps. - public AnonymousBuildStepProvider(Func providerFunction) - { - if (providerFunction == null) throw new ArgumentNullException("providerFunction"); - this.providerFunction = providerFunction; - } + ArgumentNullException.ThrowIfNull(providerFunction); + this.providerFunction = providerFunction; + } - /// - public BuildStep GetNextBuildStep(int maxPriority) - { - return providerFunction(maxPriority); - } + /// + public BuildStep GetNextBuildStep(int maxPriority) + { + return providerFunction(maxPriority); } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/AssemblyHash.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/AssemblyHash.cs index 4e3a66c3ea..6605890e93 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/AssemblyHash.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/AssemblyHash.cs @@ -1,71 +1,68 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using System.Reflection; using System.Text; using Stride.Core.Diagnostics; using Stride.Core.Storage; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +internal static class AssemblyHash { - internal static class AssemblyHash - { - private static readonly Logger Log = GlobalLogger.GetLogger("AssemblyHash"); + private static readonly Logger Log = GlobalLogger.GetLogger("AssemblyHash"); - /// - /// Computes the hash from an assembly based on its AssemblyFileVersion. Recurse to all assembly dependencies. - /// - /// The assembly. - /// A full hash of this assembly, including all its dependencies. - public static string ComputeAssemblyHash(Assembly assembly) + /// + /// Computes the hash from an assembly based on its AssemblyFileVersion. Recurse to all assembly dependencies. + /// + /// The assembly. + /// A full hash of this assembly, including all its dependencies. + public static string ComputeAssemblyHash(Assembly assembly) + { + string? hash; + lock (assemblyToHash) { - string hash; - lock (assemblyToHash) + if (!assemblyToHash.TryGetValue(assembly, out hash)) { - if (!assemblyToHash.TryGetValue(assembly, out hash)) - { - var assemblies = new HashSet(); - var text = new StringBuilder(); - ComputeAssemblyHash(assembly, assemblies, text); - hash = ObjectId.FromBytes(Encoding.UTF8.GetBytes(text.ToString())).ToString(); - assemblyToHash.Add(assembly, hash); - Log.Debug($"Assembly Hash [{assembly.GetName().Name}] => [{hash}]"); - } + var assemblies = new HashSet(); + var text = new StringBuilder(); + ComputeAssemblyHash(assembly, assemblies, text); + hash = ObjectId.FromBytes(Encoding.UTF8.GetBytes(text.ToString())).ToString(); + assemblyToHash.Add(assembly, hash); + Log.Debug($"Assembly Hash [{assembly.GetName().Name}] => [{hash}]"); } - return hash; } + return hash; + } - private static readonly Dictionary assemblyToHash = new Dictionary(); + private static readonly Dictionary assemblyToHash = []; - private static void ComputeAssemblyHash(Assembly assembly, HashSet assemblies, StringBuilder outputString) - { - if (assemblies.Contains(assembly)) - return; + private static void ComputeAssemblyHash(Assembly assembly, HashSet assemblies, StringBuilder outputString) + { + if (assemblies.Contains(assembly)) + return; - outputString.Append(assembly.FullName); + outputString.Append(assembly.FullName); - var attribute = assembly.GetCustomAttribute(); - if (attribute != null) - { - outputString.Append(",").Append(attribute.Version); - outputString.AppendLine(); - } + var attribute = assembly.GetCustomAttribute(); + if (attribute != null) + { + outputString.Append(',').Append(attribute.Version); + outputString.AppendLine(); + } - assemblies.Add(assembly); - foreach (var referencedAssemblyName in assembly.GetReferencedAssemblies()) - { - // Avoid processing system assemblies - // TODO: Scan what is actually in framework folders (and unify it with ProcessDataSerializerGlobalAttributes) - if (referencedAssemblyName.Name == "mscorlib" || referencedAssemblyName.Name.StartsWith("System", StringComparison.Ordinal) - || referencedAssemblyName.FullName.Contains("PublicKeyToken=31bf3856ad364e35")) // Signed with Microsoft public key (likely part of system libraries) - continue; + assemblies.Add(assembly); + foreach (var referencedAssemblyName in assembly.GetReferencedAssemblies()) + { + // Avoid processing system assemblies + // TODO: Scan what is actually in framework folders (and unify it with ProcessDataSerializerGlobalAttributes) + if (referencedAssemblyName.Name == "mscorlib" || (referencedAssemblyName.Name?.StartsWith("System", StringComparison.Ordinal) ?? false) + || referencedAssemblyName.FullName.Contains("PublicKeyToken=31bf3856ad364e35")) // Signed with Microsoft public key (likely part of system libraries) + continue; - var assemblyRef = Assembly.Load(referencedAssemblyName); - ComputeAssemblyHash(assemblyRef, assemblies, outputString); - } + var assemblyRef = Assembly.Load(referencedAssemblyName); + ComputeAssemblyHash(assemblyRef, assemblies, outputString); } } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/BuildResultCode.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/BuildResultCode.cs index 9396ccfd77..a16c3a1607 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/BuildResultCode.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/BuildResultCode.cs @@ -1,19 +1,12 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace Stride.Core.BuildEngine -{ - public enum BuildResultCode - { - Successful = 0, - BuildError = 1, - CommandLineError = 2, - Cancelled = 100 - } +namespace Stride.Core.BuildEngine; +public enum BuildResultCode +{ + Successful = 0, + BuildError = 1, + CommandLineError = 2, + Cancelled = 100 } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStep.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStep.cs index b70bb38658..324cfbffb8 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStep.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStep.cs @@ -2,213 +2,207 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using Stride.Core.Diagnostics; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Stride.Core.Serialization.Contents; using Stride.Core.Storage; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public abstract class BuildStep { - public abstract class BuildStep - { - internal bool ProcessedDependencies = false; + internal bool ProcessedDependencies = false; - public delegate void TransformExecuteContextLoggerDelegate(ref Logger logger); + public delegate void TransformExecuteContextLoggerDelegate(ref Logger logger); - protected BuildStep(ResultStatus status = ResultStatus.NotProcessed) - { - Status = status; - } + protected BuildStep(ResultStatus status = ResultStatus.NotProcessed) + { + Status = status; + } - /// - /// Callback that can transform the . - /// - /// The module. - public TransformExecuteContextLoggerDelegate TransformExecuteContextLogger { get; set; } - - /// - /// Gets or sets the priority amongst other build steps. - /// - /// - /// The priority. - /// - public int? Priority { get; set; } - - /// - /// Title of the build step. Intended to be short - /// - public abstract string Title { get; } - - /// - /// Description of the build step. Intended to be longer and more descriptive than the - /// - public string Description => ToString(); - - /// - /// The status of the result. - /// - public ResultStatus Status { get; private set; } - - /// - /// Indicate whether this command has already been processed (ie. executed or skipped) by the Builder - /// - public bool Processed => Status != ResultStatus.NotProcessed; - - /// - /// Indicate whether the result corresponds to a successful execution (even if the command has not been triggered) - /// - public bool Succeeded => Status == ResultStatus.Successful || Status == ResultStatus.NotTriggeredWasSuccessful; - - /// - /// Indicate whether the result corresponds to a failed execution (even if the command has not been triggered) - /// - public bool Failed => Status == ResultStatus.Failed || Status == ResultStatus.NotTriggeredPrerequisiteFailed; - - /// - /// A tag property that can contain anything useful for tools based on this build Engine. - /// - public object Tag { get; set; } - - /// - /// List of commands that must be executed prior this one (direct dependence only). - /// - public HashSet PrerequisiteSteps { get; } = new HashSet(); - - /// - /// The parent build step, which will be the instigator of the step - /// - public BuildStep Parent { get => parent; protected internal set { if (parent != null && value != null) throw new InvalidOperationException("BuildStep already has a parent"); parent = value; } } - - private BuildStep parent; - - /// - /// An unique id during a build execution, assigned once the build step is scheduled. - /// - public long ExecutionId { get; internal set; } - - /// - /// Indicate whether all prerequisite commands have been processed - /// - public bool ArePrerequisitesCompleted { get { return PrerequisiteSteps.All(x => x.Processed); } } - - /// - /// Indicate whether all prerequisite commands have been processed and are in a successful state - /// - public bool ArePrerequisitesSuccessful { get { return PrerequisiteSteps.All(x => x.Succeeded); } } - - /// - /// Gets the logger for the current build step. - /// - public LoggerResult Logger { get; } = new LoggerResult(); - - /// - /// The object this build step write (if any). - /// - public virtual string OutputLocation => null; - - /// - /// The list of objects generated by this build step. - /// - public virtual IEnumerable> OutputObjectIds => throw new NotImplementedException(); - - /// - /// Event raised when the command is processed (even if it has been skipped or if it failed) - /// - public event EventHandler StepProcessed; - - /// - /// Execute the BuildStep, usually resulting in scheduling tasks in the scheduler - /// - /// The execute context - /// The builder context - /// A task returning indicating weither the execution has successed or failed. - public abstract Task Execute(IExecuteContext executeContext, BuilderContext builderContext); - - /// - /// Clean the build, deleting the command cache which is used to determine wheither a command has already been executed, and deleting the output objects if asked. - /// - /// The execute context - /// The builder context - /// if true, every output object is also deleted, in addition of the command cache. - public virtual void Clean(IExecuteContext executeContext, BuilderContext builderContext, bool deleteOutput) - { - // By default, do the same as Execute. This will apply for flow control steps (lists, enumerations...) - // Specific implementation exists for CommandBuildStep - Execute(executeContext, builderContext); - } + /// + /// Callback that can transform the . + /// + /// The module. + public TransformExecuteContextLoggerDelegate TransformExecuteContextLogger { get; set; } + + /// + /// Gets or sets the priority amongst other build steps. + /// + /// + /// The priority. + /// + public int? Priority { get; set; } + + /// + /// Title of the build step. Intended to be short + /// + public abstract string Title { get; } + + /// + /// Description of the build step. Intended to be longer and more descriptive than the + /// + public string Description => ToString(); + + /// + /// The status of the result. + /// + public ResultStatus Status { get; private set; } + + /// + /// Indicate whether this command has already been processed (ie. executed or skipped) by the Builder + /// + public bool Processed => Status != ResultStatus.NotProcessed; + + /// + /// Indicate whether the result corresponds to a successful execution (even if the command has not been triggered) + /// + public bool Succeeded => Status == ResultStatus.Successful || Status == ResultStatus.NotTriggeredWasSuccessful; + + /// + /// Indicate whether the result corresponds to a failed execution (even if the command has not been triggered) + /// + public bool Failed => Status == ResultStatus.Failed || Status == ResultStatus.NotTriggeredPrerequisiteFailed; + + /// + /// A tag property that can contain anything useful for tools based on this build Engine. + /// + public object Tag { get; set; } + + /// + /// List of commands that must be executed prior this one (direct dependence only). + /// + public HashSet PrerequisiteSteps { get; } = []; + + /// + /// The parent build step, which will be the instigator of the step + /// + public BuildStep? Parent { get => parent; protected internal set { if (parent != null && value != null) throw new InvalidOperationException("BuildStep already has a parent"); parent = value; } } + + private BuildStep? parent; + + /// + /// An unique id during a build execution, assigned once the build step is scheduled. + /// + public long ExecutionId { get; internal set; } + + /// + /// Indicate whether all prerequisite commands have been processed + /// + public bool ArePrerequisitesCompleted { get { return PrerequisiteSteps.All(x => x.Processed); } } + + /// + /// Indicate whether all prerequisite commands have been processed and are in a successful state + /// + public bool ArePrerequisitesSuccessful { get { return PrerequisiteSteps.All(x => x.Succeeded); } } + + /// + /// Gets the logger for the current build step. + /// + public LoggerResult Logger { get; } = new LoggerResult(); + + /// + /// The object this build step write (if any). + /// + public virtual string? OutputLocation => null; + + /// + /// The list of objects generated by this build step. + /// + public virtual IEnumerable> OutputObjectIds => throw new NotImplementedException(); + + /// + /// Event raised when the command is processed (even if it has been skipped or if it failed) + /// + public event EventHandler StepProcessed; + + /// + /// Execute the BuildStep, usually resulting in scheduling tasks in the scheduler + /// + /// The execute context + /// The builder context + /// A task returning indicating weither the execution has successed or failed. + public abstract Task Execute(IExecuteContext executeContext, BuilderContext builderContext); + + /// + /// Clean the build, deleting the command cache which is used to determine wheither a command has already been executed, and deleting the output objects if asked. + /// + /// The execute context + /// The builder context + /// if true, every output object is also deleted, in addition of the command cache. + public virtual void Clean(IExecuteContext executeContext, BuilderContext builderContext, bool deleteOutput) + { + // By default, do the same as Execute. This will apply for flow control steps (lists, enumerations...) + // Specific implementation exists for CommandBuildStep + Execute(executeContext, builderContext); + } - public abstract override string ToString(); + public abstract override string ToString(); - public static void LinkBuildSteps(BuildStep parent, BuildStep child) + public static void LinkBuildSteps(BuildStep parent, BuildStep child) + { + lock (child.PrerequisiteSteps) { - lock (child.PrerequisiteSteps) - { - child.PrerequisiteSteps.Add(parent); - } + child.PrerequisiteSteps.Add(parent); } + } - public Task ExecutedAsync() - { - // Already processed? - if (Processed) - return Task.FromResult(this); + public Task ExecutedAsync() + { + // Already processed? + if (Processed) + return Task.FromResult(this); - var tcs = new TaskCompletionSource(); - StepProcessed += (sender, e) => tcs.TrySetResult(e.Step); - return tcs.Task; - } + var tcs = new TaskCompletionSource(); + StepProcessed += (sender, e) => tcs.TrySetResult(e.Step); + return tcs.Task; + } - /// - /// Associate the given object as the result of the current step and execute the event. - /// - /// The execute context. - /// The result status. - internal void RegisterResult(IExecuteContext executeContext, ResultStatus status) - { - Status = status; + /// + /// Associate the given object as the result of the current step and execute the event. + /// + /// The execute context. + /// The result status. + internal void RegisterResult(IExecuteContext executeContext, ResultStatus status) + { + Status = status; - //executeContext.Logger.Debug("Step timer for {0}: callbacks: {1}ms, total: {2}ms", this, CallbackWatch.ElapsedMilliseconds, MicroThreadWatch.ElapsedMilliseconds); + //executeContext.Logger.Debug("Step timer for {0}: callbacks: {1}ms, total: {2}ms", this, CallbackWatch.ElapsedMilliseconds, MicroThreadWatch.ElapsedMilliseconds); - if (StepProcessed != null) + if (StepProcessed != null) + { + try { - try - { - var outputObjectsGroups = executeContext.GetOutputObjectsGroups(); - MicrothreadLocalDatabases.MountDatabase(outputObjectsGroups); - StepProcessed(this, new BuildStepEventArgs(this, executeContext.Logger)); - } - catch (Exception ex) - { - executeContext.Logger.Error("Exception in command " + this + ": " + ex); - } - finally - { - MicrothreadLocalDatabases.UnmountDatabase(); - } + var outputObjectsGroups = executeContext.GetOutputObjectsGroups(); + MicrothreadLocalDatabases.MountDatabase(outputObjectsGroups); + StepProcessed(this, new BuildStepEventArgs(this, executeContext.Logger)); + } + catch (Exception ex) + { + executeContext.Logger.Error("Exception in command " + this + ": " + ex); + } + finally + { + MicrothreadLocalDatabases.UnmountDatabase(); } } + } - public IEnumerable> GetOutputObjectsGroups() + public IEnumerable> GetOutputObjectsGroups() + { + var currentBuildStep = this; + while (currentBuildStep != null) { - var currentBuildStep = this; - while (currentBuildStep != null) - { - var enumBuildStep = currentBuildStep as ListBuildStep; - if (enumBuildStep != null) - yield return enumBuildStep.OutputObjects; + if (currentBuildStep is ListBuildStep enumBuildStep) + yield return enumBuildStep.OutputObjects; - foreach (var prerequisiteStep in currentBuildStep.PrerequisiteSteps) + foreach (var prerequisiteStep in currentBuildStep.PrerequisiteSteps) + { + foreach (var outputObjectsGroup in prerequisiteStep.GetOutputObjectsGroups()) { - foreach (var outputObjectsGroup in prerequisiteStep.GetOutputObjectsGroups()) - { - yield return outputObjectsGroup; - } + yield return outputObjectsGroup; } - - currentBuildStep = currentBuildStep.Parent; } + + currentBuildStep = currentBuildStep.Parent; } } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStepEventArgs.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStepEventArgs.cs index faac6c913d..d9386cdf7d 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStepEventArgs.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStepEventArgs.cs @@ -1,21 +1,19 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using Stride.Core.Diagnostics; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public class BuildStepEventArgs : EventArgs { - public class BuildStepEventArgs : EventArgs + public BuildStepEventArgs(BuildStep step, ILogger logger) { - public BuildStepEventArgs(BuildStep step, ILogger logger) - { - Step = step; - Logger = logger; - } + Step = step; + Logger = logger; + } - public BuildStep Step { get; private set; } + public BuildStep Step { get; private set; } - public ILogger Logger { get; set; } - } + public ILogger Logger { get; set; } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStepExtensions.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStepExtensions.cs index 9b4900f8fb..de8b20da52 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStepExtensions.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStepExtensions.cs @@ -1,47 +1,40 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using Stride.Core.Extensions; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +/// +/// This class contains extension methods related to the class. +/// +public static class BuildStepExtensions { /// - /// This class contains extension methods related to the class. + /// Enumerates this build step, and all its inner build steps recursively when they are themselves . /// - public static class BuildStepExtensions + /// The build step to enumerates with its inner build steps + /// An object enumerating this build step, and all its inner build steps recursively. + public static IEnumerable EnumerateRecursively(this BuildStep buildStep) { - /// - /// Enumerates this build step, and all its inner build steps recursively when they are themselves . - /// - /// The build step to enumerates with its inner build steps - /// An object enumerating this build step, and all its inner build steps recursively. - public static IEnumerable EnumerateRecursively(this BuildStep buildStep) - { - var enumerableBuildStep = buildStep as ListBuildStep; - if (enumerableBuildStep == null) - return buildStep.Yield(); + if (buildStep is not ListBuildStep enumerableBuildStep) + return buildStep.Yield()!; - return enumerableBuildStep.Steps.SelectDeep(x => x is ListBuildStep && ((ListBuildStep)x).Steps != null ? ((ListBuildStep)x).Steps : Enumerable.Empty()); - } + return enumerableBuildStep.Steps.SelectDeep(static x => x is ListBuildStep step && step.Steps != null ? step.Steps : []); + } - public static void Print(this BuildStep buildStep, TextWriter stream = null) - { - PrintRecursively(buildStep, 0, stream ?? Console.Out); - } + public static void Print(this BuildStep buildStep, TextWriter? stream = null) + { + PrintRecursively(buildStep, 0, stream ?? Console.Out); + } - private static void PrintRecursively(this BuildStep buildStep, int offset, TextWriter stream) + private static void PrintRecursively(this BuildStep buildStep, int offset, TextWriter stream) + { + stream.WriteLine("".PadLeft(offset) + buildStep); + if (buildStep is ListBuildStep enumerable) { - stream.WriteLine("".PadLeft(offset) + buildStep); - var enumerable = buildStep as ListBuildStep; - if (enumerable != null) - { - foreach (var child in enumerable.Steps) - PrintRecursively(child, offset + 2, stream); - } + foreach (var child in enumerable.Steps) + PrintRecursively(child, offset + 2, stream); } } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStepLogger.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStepLogger.cs index 660ffcb12d..45b1b01276 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStepLogger.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/BuildStepLogger.cs @@ -1,34 +1,32 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using Stride.Core.Diagnostics; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public class BuildStepLogger : Logger { - public class BuildStepLogger : Logger - { - private readonly BuildStep buildStep; - private readonly ILogger mainLogger; - public readonly TimestampLocalLogger StepLogger; + private readonly BuildStep buildStep; + private readonly ILogger mainLogger; + public readonly TimestampLocalLogger StepLogger; - public BuildStepLogger(BuildStep buildStep, ILogger mainLogger, DateTime startTime) - { - this.buildStep = buildStep; - this.mainLogger = mainLogger; - StepLogger = new TimestampLocalLogger(startTime); - // Let's receive all level messages, each logger will filter them itself - ActivateLog(LogMessageType.Debug); - // StepLogger messages will be forwarded to the monitor, which will also filter itself - StepLogger.ActivateLog(LogMessageType.Debug); - } + public BuildStepLogger(BuildStep buildStep, ILogger mainLogger, DateTime startTime) + { + this.buildStep = buildStep; + this.mainLogger = mainLogger; + StepLogger = new TimestampLocalLogger(startTime); + // Let's receive all level messages, each logger will filter them itself + ActivateLog(LogMessageType.Debug); + // StepLogger messages will be forwarded to the monitor, which will also filter itself + StepLogger.ActivateLog(LogMessageType.Debug); + } - protected override void LogRaw(ILogMessage logMessage) - { - buildStep.Logger.Log(logMessage); + protected override void LogRaw(ILogMessage logMessage) + { + buildStep.Logger.Log(logMessage); - mainLogger?.Log(logMessage); - StepLogger?.Log(logMessage); - } + mainLogger?.Log(logMessage); + StepLogger?.Log(logMessage); } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/BuildTransaction.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/BuildTransaction.cs index 91976dd57a..fa4511db01 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/BuildTransaction.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/BuildTransaction.cs @@ -1,131 +1,125 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; + using Stride.Core.Serialization.Contents; using Stride.Core.Storage; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +internal class BuildTransaction { - internal class BuildTransaction + private readonly Dictionary transactionOutputObjects = []; + private readonly IContentIndexMap? contentIndexMap; + private readonly IEnumerable> outputObjectsGroups; + + public BuildTransaction(IContentIndexMap? contentIndexMap, IEnumerable> outputObjectsGroups) { - private readonly Dictionary transactionOutputObjects = new Dictionary(); - private readonly IContentIndexMap contentIndexMap; - private readonly IEnumerable> outputObjectsGroups; + this.contentIndexMap = contentIndexMap; + this.outputObjectsGroups = outputObjectsGroups; + } - public BuildTransaction(IContentIndexMap contentIndexMap, IEnumerable> outputObjectsGroups) - { - this.contentIndexMap = contentIndexMap; - this.outputObjectsGroups = outputObjectsGroups; - } + public IEnumerable> GetTransactionIdMap() + { + return transactionOutputObjects; + } - public IEnumerable> GetTransactionIdMap() + public IEnumerable> SearchValues(Func, bool> predicate) + { + lock (transactionOutputObjects) { - return transactionOutputObjects; + return transactionOutputObjects.Select(x => new KeyValuePair(x.Key.Path, x.Value)).Where(predicate).ToList(); } + } - public IEnumerable> SearchValues(Func, bool> predicate) - { - lock (transactionOutputObjects) - { - return transactionOutputObjects.Select(x => new KeyValuePair(x.Key.Path, x.Value)).Where(predicate).ToList(); - } - } + public bool TryGetValue(string url, out ObjectId objectId) + { + var objUrl = new ObjectUrl(UrlType.Content, url); - public bool TryGetValue(string url, out ObjectId objectId) + // Lock TransactionAssetIndexMap + lock (transactionOutputObjects) { - var objUrl = new ObjectUrl(UrlType.Content, url); + if (transactionOutputObjects.TryGetValue(objUrl, out objectId)) + return true; - // Lock TransactionAssetIndexMap - lock (transactionOutputObjects) + foreach (var outputObjects in outputObjectsGroups) { - if (transactionOutputObjects.TryGetValue(objUrl, out objectId)) - return true; - - foreach (var outputObjects in outputObjectsGroups) + // Lock underlying EnumerableBuildStep.OutputObjects + lock (outputObjects) { - // Lock underlying EnumerableBuildStep.OutputObjects - lock (outputObjects) + if (outputObjects.TryGetValue(objUrl, out var outputObject)) { - OutputObject outputObject; - if (outputObjects.TryGetValue(objUrl, out outputObject)) - { - objectId = outputObject.ObjectId; - return true; - } + objectId = outputObject.ObjectId; + return true; } } + } - // Check asset index map (if set) - if (contentIndexMap != null) - { - if (contentIndexMap.TryGetValue(url, out objectId)) - return true; - } + // Check asset index map (if set) + if (contentIndexMap != null) + { + if (contentIndexMap.TryGetValue(url, out objectId)) + return true; } + } + + objectId = ObjectId.Empty; + return false; + } + + internal class DatabaseContentIndexMap : IContentIndexMap + { + private readonly BuildTransaction buildTransaction; - objectId = ObjectId.Empty; - return false; + public DatabaseContentIndexMap(BuildTransaction buildTransaction) + { + this.buildTransaction = buildTransaction; } - internal class DatabaseContentIndexMap : IContentIndexMap + public bool TryGetValue(string url, out ObjectId objectId) { - private readonly BuildTransaction buildTransaction; + return buildTransaction.TryGetValue(url, out objectId); + } - public DatabaseContentIndexMap(BuildTransaction buildTransaction) - { - this.buildTransaction = buildTransaction; - } + public bool Contains(string url) + { + return TryGetValue(url, out var _); + } - public bool TryGetValue(string url, out ObjectId objectId) + public ObjectId this[string url] + { + get { - return buildTransaction.TryGetValue(url, out objectId); - } + if (!TryGetValue(url, out var objectId)) + throw new KeyNotFoundException(); - public bool Contains(string url) - { - ObjectId objectId; - return TryGetValue(url, out objectId); + return objectId; } - - public ObjectId this[string url] + set { - get - { - ObjectId objectId; - if (!TryGetValue(url, out objectId)) - throw new KeyNotFoundException(); - - return objectId; - } - set + lock (buildTransaction.transactionOutputObjects) { - lock (buildTransaction.transactionOutputObjects) - { - buildTransaction.transactionOutputObjects[new ObjectUrl(UrlType.Content, url)] = value; - } + buildTransaction.transactionOutputObjects[new ObjectUrl(UrlType.Content, url)] = value; } } + } - public IEnumerable> SearchValues(Func, bool> predicate) - { - return buildTransaction.SearchValues(predicate); - } + public IEnumerable> SearchValues(Func, bool> predicate) + { + return buildTransaction.SearchValues(predicate); + } - public void WaitPendingOperations() - { - } + public void WaitPendingOperations() + { + } - public IEnumerable> GetMergedIdMap() - { - // Shouldn't be used - throw new NotImplementedException(); - } + public IEnumerable> GetMergedIdMap() + { + // Shouldn't be used + throw new NotImplementedException(); + } - public void Dispose() - { - } + public void Dispose() + { } } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/Builder.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/Builder.cs index 02ec78c36a..9b0de1631f 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/Builder.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/Builder.cs @@ -1,13 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Stride.Core.Diagnostics; using Stride.Core.Extensions; using Stride.Core.IO; @@ -15,758 +9,737 @@ using Stride.Core.Serialization.Contents; using Stride.Core.Storage; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public class Builder : IDisposable { - public class Builder : IDisposable - { - public const int ExpectedVersion = 4; - public static readonly string DoNotCompressTag = "DoNotCompress"; - public static readonly string DoNotPackTag = "DoNotPack"; + public const int ExpectedVersion = 4; + public static readonly string DoNotCompressTag = "DoNotCompress"; + public static readonly string DoNotPackTag = "DoNotPack"; - #region Public Members + #region Public Members - public const string MonitorPipeName = "Stride/BuildEngine/Monitor"; + public const string MonitorPipeName = "Stride/BuildEngine/Monitor"; - public readonly ISet DisableCompressionIds = new HashSet(); + public readonly ISet DisableCompressionIds = new HashSet(); - /// - /// Gets the in which built objects are written. - /// - public static ObjectDatabase ObjectDatabase { get; private set; } + /// + /// Gets the in which built objects are written. + /// + public static ObjectDatabase? ObjectDatabase { get; private set; } - /// - /// The assigned to the builder. - /// - public Guid BuilderId { get; } + /// + /// The assigned to the builder. + /// + public Guid BuilderId { get; } - /// - /// Builder name - /// - public string BuilderName { get; set; } + /// + /// Builder name + /// + public string BuilderName { get; set; } - /// - /// Indicate whether the build has been canceled - /// - public bool Cancelled { get; protected set; } + /// + /// Indicate whether the build has been canceled + /// + public bool Cancelled { get; protected set; } - public IDictionary InitialVariables { get; } + public IDictionary InitialVariables { get; } - /// - /// Indicate whether this builder is currently running. - /// - public bool IsRunning { get; protected set; } + /// + /// Indicate whether this builder is currently running. + /// + public bool IsRunning { get; protected set; } - /// - /// Logger used by the builder and the commands - /// - public ILogger Logger { get; } + /// + /// Logger used by the builder and the commands + /// + public ILogger Logger { get; } - public List MonitorPipeNames { get; } + public List MonitorPipeNames { get; } + /// + /// The root build step of the builder defining the builds to perform. + /// + public ListBuildStep Root { get; private set; } + + /// + /// Number of working threads to create + /// + public int ThreadCount { get; set; } + + public CommandBuildStep.TryExecuteRemoteDelegate TryExecuteRemote { get; set; } + + /// + /// Indicate which mode to use with this builder + /// + public enum Mode + { /// - /// The root build step of the builder defining the builds to perform. + /// Build the script /// - public ListBuildStep Root { get; private set; } + Build, /// - /// Number of working threads to create + /// Clean the command cache used to determine wheither a command has already been triggered. /// - public int ThreadCount { get; set; } - - public CommandBuildStep.TryExecuteRemoteDelegate TryExecuteRemote { get; set; } + Clean, /// - /// Indicate which mode to use with this builder + /// Clean the command cache and delete every output objects /// - public enum Mode - { - /// - /// Build the script - /// - Build, - - /// - /// Clean the command cache used to determine wheither a command has already been triggered. - /// - Clean, - - /// - /// Clean the command cache and delete every output objects - /// - CleanAndDelete, - } + CleanAndDelete, + } - #endregion Public Members + #endregion Public Members - #region Private Members + #region Private Members - /// - /// The path on the disk where to perform the build - /// - private readonly string buildPath; + /// + /// The path on the disk where to perform the build + /// + private readonly string buildPath; - /// - /// The name on the disk of the index file name. - /// - private readonly string indexName; + /// + /// The name on the disk of the index file name. + /// + private readonly string indexName; - private readonly CommandIOMonitor ioMonitor; + private readonly CommandIOMonitor ioMonitor; - private readonly DateTime startTime; + private readonly DateTime startTime; - private readonly StepCounter stepCounter = new StepCounter(); + private readonly StepCounter stepCounter = new(); - /// - /// Cancellation token source used for cancellation. - /// - private CancellationTokenSource cancellationTokenSource; + /// + /// Cancellation token source used for cancellation. + /// + private CancellationTokenSource cancellationTokenSource; - /// - /// A map containing results of each commands, indexed by command hashes. When the builder is running, this map if filled with the result of the commands of the current execution. - /// - private ObjectDatabase resultMap; + /// + /// A map containing results of each commands, indexed by command hashes. When the builder is running, this map if filled with the result of the commands of the current execution. + /// + private ObjectDatabase? resultMap; - /// - /// The build mode of the current run execution - /// - private Mode runMode; + /// + /// The build mode of the current run execution + /// + private Mode runMode; - private Scheduler scheduler; + private Scheduler scheduler; - #endregion Private Members + #endregion Private Members - /// - /// The full path of the index file from the build directory. - /// - private string IndexFileFullPath => indexName != null ? VirtualFileSystem.ApplicationDatabasePath + VirtualFileSystem.DirectorySeparatorChar + indexName : null; + /// + /// The full path of the index file from the build directory. + /// + private string? IndexFileFullPath => indexName != null ? VirtualFileSystem.ApplicationDatabasePath + VirtualFileSystem.DirectorySeparatorChar + indexName : null; - #region Methods + #region Methods - public Builder(ILogger logger, string buildPath, string indexName) - { - MonitorPipeNames = new List(); - startTime = DateTime.Now; - this.indexName = indexName; - Logger = logger; - this.buildPath = buildPath ?? throw new ArgumentNullException(nameof(buildPath)); - Root = new ListBuildStep(); - ioMonitor = new CommandIOMonitor(Logger); - ThreadCount = Environment.ProcessorCount; - BuilderId = Guid.NewGuid(); - InitialVariables = new Dictionary(); - } + public Builder(ILogger logger, string buildPath, string indexName) + { + MonitorPipeNames = []; + startTime = DateTime.Now; + this.indexName = indexName; + Logger = logger; + this.buildPath = buildPath ?? throw new ArgumentNullException(nameof(buildPath)); + Root = new ListBuildStep(); + ioMonitor = new CommandIOMonitor(Logger); + ThreadCount = Environment.ProcessorCount; + BuilderId = Guid.NewGuid(); + InitialVariables = new Dictionary(); + } - public static void CloseObjectDatabase() - { - var db = ObjectDatabase; - ObjectDatabase = null; - db?.Dispose(); - } + public static void CloseObjectDatabase() + { + var db = ObjectDatabase; + ObjectDatabase = null; + db?.Dispose(); + } - public static void OpenObjectDatabase(string buildPath, string indexName) - { - // Mount build path - ((FileSystemProvider)VirtualFileSystem.ApplicationData).ChangeBasePath(buildPath); - if (ObjectDatabase == null) - { - // Note: this has to be done after VFS.ChangeBasePath - ObjectDatabase = new ObjectDatabase(VirtualFileSystem.ApplicationDatabasePath, indexName, null, false); - } - } + public static void OpenObjectDatabase(string buildPath, string indexName) + { + // Mount build path + ((FileSystemProvider)VirtualFileSystem.ApplicationData).ChangeBasePath(buildPath); + // Note: this has to be done after VFS.ChangeBasePath + ObjectDatabase ??= new ObjectDatabase(VirtualFileSystem.ApplicationDatabasePath, indexName, null, false); + } - /// - /// Cancel the currently executing build. - /// - public void CancelBuild() + /// + /// Cancel the currently executing build. + /// + public void CancelBuild() + { + if (IsRunning) { - if (IsRunning) - { - Cancelled = true; - cancellationTokenSource.Cancel(); - } + Cancelled = true; + cancellationTokenSource.Cancel(); } + } - public void Dispose() - { - CloseObjectDatabase(); - } + public void Dispose() + { + CloseObjectDatabase(); + } - /// - /// Discard the current build step and initialize a new empty one. - /// - public void Reset() - { - Root = new ListBuildStep(); - stepCounter.Clear(); - } + /// + /// Discard the current build step and initialize a new empty one. + /// + public void Reset() + { + Root = new ListBuildStep(); + stepCounter.Clear(); + } - /// - /// Runs this instance. - /// - public BuildResultCode Run(Mode mode, bool writeIndexFile = true) - { - // When we setup the database ourself we have to take responsibility to close it after - var shouldCloseDatabase = ObjectDatabase == null; - OpenObjectDatabase(buildPath, indexName); + /// + /// Runs this instance. + /// + public BuildResultCode Run(Mode mode, bool writeIndexFile = true) + { + // When we setup the database ourself we have to take responsibility to close it after + var shouldCloseDatabase = ObjectDatabase == null; + OpenObjectDatabase(buildPath, indexName); - PreRun(); + PreRun(); - runMode = mode; + runMode = mode; - if (IsRunning) - throw new InvalidOperationException("An instance of this Builder is already running."); + if (IsRunning) + throw new InvalidOperationException("An instance of this Builder is already running."); - // reset build cache from previous build run - cancellationTokenSource = new CancellationTokenSource(); - Cancelled = false; - IsRunning = true; - DisableCompressionIds.Clear(); + // reset build cache from previous build run + cancellationTokenSource = new CancellationTokenSource(); + Cancelled = false; + IsRunning = true; + DisableCompressionIds.Clear(); - // Reseting result map - var inputHashes = FileVersionTracker.GetDefault(); - { - var builderContext = new BuilderContext(inputHashes, TryExecuteRemote); + // Reseting result map + var inputHashes = FileVersionTracker.GetDefault(); + { + var builderContext = new BuilderContext(inputHashes, TryExecuteRemote); - resultMap = ObjectDatabase; + resultMap = ObjectDatabase; - scheduler = new Scheduler(); + scheduler = new Scheduler(); - // Schedule the build - ScheduleBuildStep(builderContext, null, Root, InitialVariables); + // Schedule the build + ScheduleBuildStep(builderContext, null, Root, InitialVariables); - // Create threads - var threads = Enumerable.Range(0, ThreadCount).Select(x => new Thread(SafeAction.Wrap(RunUntilEnd)) { IsBackground = true }).ToArray(); + // Create threads + var threads = Enumerable.Range(0, ThreadCount).Select(x => new Thread(SafeAction.Wrap(RunUntilEnd)) { IsBackground = true }).ToArray(); - // Start threads - int threadId = 0; - foreach (var thread in threads) - { - thread.Name = (BuilderName ?? "Builder") + " worker thread " + (++threadId); - thread.Start(); - } + // Start threads + int threadId = 0; + foreach (var thread in threads) + { + thread.Name = (BuilderName ?? "Builder") + " worker thread " + (++threadId); + thread.Start(); + } - // Wait for all threads to finish - foreach (var thread in threads) - { - thread.Join(); - } + // Wait for all threads to finish + foreach (var thread in threads) + { + thread.Join(); } + } - BuildResultCode result; + BuildResultCode result; - if (runMode == Mode.Build) + if (runMode == Mode.Build) + { + if (cancellationTokenSource.IsCancellationRequested) { - if (cancellationTokenSource.IsCancellationRequested) - { - Logger.Error("Build cancelled."); - result = BuildResultCode.Cancelled; - } - else if (stepCounter.Get(ResultStatus.Failed) > 0 || stepCounter.Get(ResultStatus.NotTriggeredPrerequisiteFailed) > 0) - { - Logger.Error($"Build finished in {stepCounter.Total} steps. Command results: {stepCounter.Get(ResultStatus.Successful)} succeeded, {stepCounter.Get(ResultStatus.NotTriggeredWasSuccessful)} up-to-date, {stepCounter.Get(ResultStatus.Failed)} failed, {stepCounter.Get(ResultStatus.NotTriggeredPrerequisiteFailed)} not triggered due to previous failure."); - Logger.Error("Build failed."); - result = BuildResultCode.BuildError; - } - else - { - Logger.Info($"Build finished in {stepCounter.Total} steps. Command results: {stepCounter.Get(ResultStatus.Successful)} succeeded, {stepCounter.Get(ResultStatus.NotTriggeredWasSuccessful)} up-to-date, {stepCounter.Get(ResultStatus.Failed)} failed, {stepCounter.Get(ResultStatus.NotTriggeredPrerequisiteFailed)} not triggered due to previous failure."); - Logger.Info("Build is successful."); - result = BuildResultCode.Successful; - } + Logger.Error("Build cancelled."); + result = BuildResultCode.Cancelled; + } + else if (stepCounter.Get(ResultStatus.Failed) > 0 || stepCounter.Get(ResultStatus.NotTriggeredPrerequisiteFailed) > 0) + { + Logger.Error($"Build finished in {stepCounter.Total} steps. Command results: {stepCounter.Get(ResultStatus.Successful)} succeeded, {stepCounter.Get(ResultStatus.NotTriggeredWasSuccessful)} up-to-date, {stepCounter.Get(ResultStatus.Failed)} failed, {stepCounter.Get(ResultStatus.NotTriggeredPrerequisiteFailed)} not triggered due to previous failure."); + Logger.Error("Build failed."); + result = BuildResultCode.BuildError; } else { - string modeName; - switch (runMode) - { - case Mode.Clean: - modeName = "Clean"; - break; - - case Mode.CleanAndDelete: - modeName = "Clean-and-delete"; - break; - - default: - throw new InvalidOperationException("Builder executed in unknown mode."); - } - - if (cancellationTokenSource.IsCancellationRequested) - { - Logger.Error(modeName + " has been cancelled."); - result = BuildResultCode.Cancelled; - } - else if (stepCounter.Get(ResultStatus.Failed) > 0 || stepCounter.Get(ResultStatus.NotTriggeredPrerequisiteFailed) > 0) - { - Logger.Error(modeName + " has failed."); - result = BuildResultCode.BuildError; - } - else - { - Logger.Error(modeName + " has been successfully completed."); - result = BuildResultCode.Successful; - } + Logger.Info($"Build finished in {stepCounter.Total} steps. Command results: {stepCounter.Get(ResultStatus.Successful)} succeeded, {stepCounter.Get(ResultStatus.NotTriggeredWasSuccessful)} up-to-date, {stepCounter.Get(ResultStatus.Failed)} failed, {stepCounter.Get(ResultStatus.NotTriggeredPrerequisiteFailed)} not triggered due to previous failure."); + Logger.Info("Build is successful."); + result = BuildResultCode.Successful; } - scheduler = null; - resultMap = null; - IsRunning = false; - - if (shouldCloseDatabase) + } + else + { + var modeName = runMode switch + { + Mode.Clean => "Clean", + Mode.CleanAndDelete => "Clean-and-delete", + _ => throw new InvalidOperationException("Builder executed in unknown mode."), + }; + if (cancellationTokenSource.IsCancellationRequested) + { + Logger.Error(modeName + " has been cancelled."); + result = BuildResultCode.Cancelled; + } + else if (stepCounter.Get(ResultStatus.Failed) > 0 || stepCounter.Get(ResultStatus.NotTriggeredPrerequisiteFailed) > 0) { - CloseObjectDatabase(); + Logger.Error(modeName + " has failed."); + result = BuildResultCode.BuildError; } + else + { + Logger.Error(modeName + " has been successfully completed."); + result = BuildResultCode.Successful; + } + } + scheduler = null; + resultMap = null; + IsRunning = false; - return result; + if (shouldCloseDatabase) + { + CloseObjectDatabase(); } - /// - /// Write the generated objects into the index map file. - /// - /// Indicate if old values must be deleted or merged - public void WriteIndexFile(bool mergeWithCurrentIndexFile) + return result; + } + + /// + /// Write the generated objects into the index map file. + /// + /// Indicate if old values must be deleted or merged + public void WriteIndexFile(bool mergeWithCurrentIndexFile) + { + if (!mergeWithCurrentIndexFile) { - if (!mergeWithCurrentIndexFile) - { - VirtualFileSystem.FileDelete(IndexFileFullPath); - } + VirtualFileSystem.FileDelete(IndexFileFullPath); + } - using (var indexFile = ContentIndexMap.NewTool(indexName)) - { - // Filter database Location - indexFile.AddValues( - Root.OutputObjects.Where(x => x.Key.Type == UrlType.Content) - .Select(x => new KeyValuePair(x.Key.Path, x.Value.ObjectId))); + using var indexFile = ContentIndexMap.NewTool(indexName); + // Filter database Location + indexFile.AddValues( + Root.OutputObjects.Where(x => x.Key.Type == UrlType.Content) + .Select(x => new KeyValuePair(x.Key.Path, x.Value.ObjectId))); - foreach (var outputObject in Root.OutputObjects.Where(x => x.Key.Type == UrlType.Content).Select(x => x.Value)) - { - if (outputObject.Tags.Contains(DoNotCompressTag)) - DisableCompressionIds.Add(outputObject.ObjectId); - } - } + foreach (var outputObject in Root.OutputObjects.Where(x => x.Key.Type == UrlType.Content).Select(x => x.Value)) + { + if (outputObject.Tags.Contains(DoNotCompressTag)) + DisableCompressionIds.Add(outputObject.ObjectId); } + } - private static IEnumerable CollectCommandSteps(BuildStep step) + private static IEnumerable CollectCommandSteps(BuildStep step) + { + if (step is CommandBuildStep commandBuildStep) { - var commandBuildStep = step as CommandBuildStep; - if (commandBuildStep != null) - { - yield return commandBuildStep; - } + yield return commandBuildStep; + } - // NOTE: We assume that only EnumerableBuildStep is the base class for sub-steps and that ContentReferencable BuildStep are accessible from them (not through dynamic build step) - var enumerateBuildStep = step as ListBuildStep; - if (enumerateBuildStep?.Steps != null) + // NOTE: We assume that only EnumerableBuildStep is the base class for sub-steps and that ContentReferencable BuildStep are accessible from them (not through dynamic build step) + var enumerateBuildStep = step as ListBuildStep; + if (enumerateBuildStep?.Steps != null) + { + foreach (var subStep in enumerateBuildStep.Steps) { - foreach (var subStep in enumerateBuildStep.Steps) + foreach (var command in CollectCommandSteps(subStep)) { - foreach (var command in CollectCommandSteps(subStep)) - { - yield return command; - } + yield return command; } } } + } - private static void CollectContentReferenceDependencies(BuildStep step, HashSet locations) + private static void CollectContentReferenceDependencies(BuildStep step, HashSet locations) + { + // For each CommandStep for the current build step, collects all dependencies to ContenrReference-BuildStep + foreach (var commandStep in CollectCommandSteps(step)) { - // For each CommandStep for the current build step, collects all dependencies to ContenrReference-BuildStep - foreach (var commandStep in CollectCommandSteps(step)) + foreach (var inputFile in commandStep.Command.GetInputFiles()) { - foreach (var inputFile in commandStep.Command.GetInputFiles()) + if (inputFile.Type == UrlType.Content) { - if (inputFile.Type == UrlType.Content) - { - locations.Add(inputFile.Path); - } + locations.Add(inputFile.Path); } } } + } - /// - /// Collects dependencies between BuildStep. See remarks. - /// - /// The step to compute the dependencies for - /// A cache of content reference location to buildsteps - /// - /// Each BuildStep inheriting from is considered as a top-level dependency step that can have depedencies - /// on other top-level dependency. We are collecting all of them here. - /// - private static void PrepareDependencyGraph(BuildStep step, Dictionary>> contentBuildSteps) - { - step.ProcessedDependencies = true; + /// + /// Collects dependencies between BuildStep. See remarks. + /// + /// The step to compute the dependencies for + /// A cache of content reference location to buildsteps + /// + /// Each BuildStep inheriting from is considered as a top-level dependency step that can have depedencies + /// on other top-level dependency. We are collecting all of them here. + /// + private static void PrepareDependencyGraph(BuildStep step, Dictionary>> contentBuildSteps) + { + step.ProcessedDependencies = true; - var outputLocation = step.OutputLocation; - if (outputLocation != null) + var outputLocation = step.OutputLocation; + if (outputLocation != null) + { + var dependencies = new HashSet(); + if (!contentBuildSteps.ContainsKey(outputLocation)) { - var dependencies = new HashSet(); - if (!contentBuildSteps.ContainsKey(outputLocation)) + contentBuildSteps.Add(outputLocation, new KeyValuePair>(step, dependencies)); + CollectContentReferenceDependencies(step, dependencies); + foreach (var prerequisiteStep in step.PrerequisiteSteps) { - contentBuildSteps.Add(outputLocation, new KeyValuePair>(step, dependencies)); - CollectContentReferenceDependencies(step, dependencies); - foreach (var prerequisiteStep in step.PrerequisiteSteps) - { - PrepareDependencyGraph(prerequisiteStep, contentBuildSteps); - } + PrepareDependencyGraph(prerequisiteStep, contentBuildSteps); } - - // If we have a reference, we don't need to iterate further - return; } - // NOTE: We assume that only ListBuildStep is the base class for sub-steps and that ContentReferencable BuildStep are accessible from them (not through dynamic build step) - var enumerateBuildStep = step as ListBuildStep; - if (enumerateBuildStep?.Steps != null) + // If we have a reference, we don't need to iterate further + return; + } + + // NOTE: We assume that only ListBuildStep is the base class for sub-steps and that ContentReferencable BuildStep are accessible from them (not through dynamic build step) + var enumerateBuildStep = step as ListBuildStep; + if (enumerateBuildStep?.Steps != null) + { + foreach (var subStep in enumerateBuildStep.Steps) { - foreach (var subStep in enumerateBuildStep.Steps) - { - PrepareDependencyGraph(subStep, contentBuildSteps); - } + PrepareDependencyGraph(subStep, contentBuildSteps); } } + } - private void ComputeDependencyGraph(Dictionary>> contentBuildSteps) + private void ComputeDependencyGraph(Dictionary>> contentBuildSteps) + { + foreach (var item in contentBuildSteps) { - foreach (var item in contentBuildSteps) + var step = item.Value.Key; + var dependencies = item.Value.Value; + foreach (var dependency in dependencies) { - var step = item.Value.Key; - var dependencies = item.Value.Value; - foreach (var dependency in dependencies) + if (contentBuildSteps.TryGetValue(dependency, out var deps)) { - KeyValuePair> deps; - if (contentBuildSteps.TryGetValue(dependency, out deps)) - { - BuildStep.LinkBuildSteps(deps.Key, step); - } - else - { - // TODO: Either something is wrong, or it's because dependencies added afterwise (incremental) are not supported yet - Logger.Error($"BuildStep [{step}] depends on [{dependency}] but nothing that generates it could be found (or maybe incremental dependencies need to be implemented)"); - } + BuildStep.LinkBuildSteps(deps.Key, step); + } + else + { + // TODO: Either something is wrong, or it's because dependencies added afterwise (incremental) are not supported yet + Logger.Error($"BuildStep [{step}] depends on [{dependency}] but nothing that generates it could be found (or maybe incremental dependencies need to be implemented)"); } } } + } - /// - /// Collects dependencies between and fill the accordingly. - /// - /// The root BuildStep - private void GenerateDependencies(BuildStep rootStep) - { - // TODO: Support proper incremental dependecies - if (rootStep.ProcessedDependencies) - return; + /// + /// Collects dependencies between and fill the accordingly. + /// + /// The root BuildStep + private void GenerateDependencies(BuildStep rootStep) + { + // TODO: Support proper incremental dependecies + if (rootStep.ProcessedDependencies) + return; - rootStep.ProcessedDependencies = true; + rootStep.ProcessedDependencies = true; - var contentBuildSteps = new Dictionary>>(); - PrepareDependencyGraph(rootStep, contentBuildSteps); - ComputeDependencyGraph(contentBuildSteps); - } + var contentBuildSteps = new Dictionary>>(); + PrepareDependencyGraph(rootStep, contentBuildSteps); + ComputeDependencyGraph(contentBuildSteps); + } - private void PreRun() - { - var objectDatabase = Builder.ObjectDatabase; + private void PreRun() + { + var objectDatabase = Builder.ObjectDatabase; - // Check current database version, and erase it if too old - int currentVersion = ExpectedVersion; - var versionFile = Path.Combine(VirtualFileSystem.GetAbsolutePath(VirtualFileSystem.ApplicationDatabasePath), "version"); - if (File.Exists(versionFile)) + // Check current database version, and erase it if too old + int currentVersion = ExpectedVersion; + var versionFile = Path.Combine(VirtualFileSystem.GetAbsolutePath(VirtualFileSystem.ApplicationDatabasePath), "version"); + if (File.Exists(versionFile)) + { + try { - try - { - var versionText = File.ReadAllText(versionFile); - currentVersion = int.Parse(versionText); - } - catch (Exception e) - { - e.Ignore(); - currentVersion = 0; - } + var versionText = File.ReadAllText(versionFile); + currentVersion = int.Parse(versionText); } - - // Prepare data base directories - var databasePathSplits = VirtualFileSystem.ApplicationDatabasePath.Split('/'); - var accumulatorPath = "/"; - foreach (var pathPart in databasePathSplits.Where(x => x != "")) + catch (Exception e) { - accumulatorPath += pathPart + "/"; - VirtualFileSystem.CreateDirectory(accumulatorPath); + e.Ignore(); + currentVersion = 0; } + } - if (currentVersion != ExpectedVersion) + // Prepare data base directories + var databasePathSplits = VirtualFileSystem.ApplicationDatabasePath.Split('/'); + var accumulatorPath = "/"; + foreach (var pathPart in databasePathSplits.Where(x => x != "")) + { + accumulatorPath += pathPart + "/"; + VirtualFileSystem.CreateDirectory(accumulatorPath); + } + + if (currentVersion != ExpectedVersion) + { + var looseObjects = objectDatabase.EnumerateLooseObjects().ToArray(); + + if (looseObjects.Length > 0) { - var looseObjects = objectDatabase.EnumerateLooseObjects().ToArray(); + Logger.Info($"Database version number has been updated from {currentVersion} to {ExpectedVersion}, erasing all objects..."); - if (looseObjects.Length > 0) + // Database version has been updated, let's clean it + foreach (var objectId in looseObjects) { - Logger.Info($"Database version number has been updated from {currentVersion} to {ExpectedVersion}, erasing all objects..."); - - // Database version has been updated, let's clean it - foreach (var objectId in looseObjects) + try + { + objectDatabase.Delete(objectId); + } + catch (IOException) { - try - { - objectDatabase.Delete(objectId); - } - catch (IOException) - { - } } } - - // Create directory - File.WriteAllText(versionFile, ExpectedVersion.ToString(CultureInfo.InvariantCulture)); } + + // Create directory + File.WriteAllText(versionFile, ExpectedVersion.ToString(CultureInfo.InvariantCulture)); } + } + + private void RunUntilEnd() + { - private void RunUntilEnd() + while (true) { + scheduler.Run(); - while (true) + // Exit loop if no more micro threads + lock (scheduler.MicroThreads) { - scheduler.Run(); - - // Exit loop if no more micro threads - lock (scheduler.MicroThreads) - { - if (scheduler.MicroThreads.Count == 0) - break; - } - - // TODO: improve how we wait for work. Thread.Sleep(0) uses too much CPU. - Thread.Sleep(1); + if (scheduler.MicroThreads.Count == 0) + break; } + + // TODO: improve how we wait for work. Thread.Sleep(0) uses too much CPU. + Thread.Sleep(1); } + } - private void ScheduleBuildStep(BuilderContext builderContext, BuildStep instigator, BuildStep buildStep, IDictionary variables) + private void ScheduleBuildStep(BuilderContext builderContext, BuildStep instigator, BuildStep buildStep, IDictionary variables) + { + if (buildStep.ExecutionId == 0) { - if (buildStep.ExecutionId == 0) - { - if (buildStep.Parent != null && buildStep.Parent != instigator) - throw new InvalidOperationException("Scheduling a BuildStep with a different instigator that its parent"); - if (buildStep.Parent == null) - { - buildStep.Parent = instigator; - } + if (buildStep.Parent != null && buildStep.Parent != instigator) + throw new InvalidOperationException("Scheduling a BuildStep with a different instigator that its parent"); + buildStep.Parent ??= instigator; - // Compute content dependencies before scheduling the build - GenerateDependencies(buildStep); + // Compute content dependencies before scheduling the build + GenerateDependencies(buildStep); - // TODO: Big review of the log infrastructure of CompilerApp & BuildEngine! - // Create a logger that redirects to various places (BuildStep.Logger, timestampped log, global log, etc...) - var buildStepLogger = new BuildStepLogger(buildStep, Logger, startTime); - var logger = (Logger)buildStepLogger; - // Apply user-registered callbacks to the logger - buildStep.TransformExecuteContextLogger?.Invoke(ref logger); + // TODO: Big review of the log infrastructure of CompilerApp & BuildEngine! + // Create a logger that redirects to various places (BuildStep.Logger, timestampped log, global log, etc...) + var buildStepLogger = new BuildStepLogger(buildStep, Logger, startTime); + var logger = (Logger)buildStepLogger; + // Apply user-registered callbacks to the logger + buildStep.TransformExecuteContextLogger?.Invoke(ref logger); - // Create execute context - var executeContext = new ExecuteContext(this, builderContext, buildStep, logger) { Variables = new Dictionary(variables) }; - //buildStep.ExpandStrings(executeContext); + // Create execute context + var executeContext = new ExecuteContext(this, builderContext, buildStep, logger) { Variables = new Dictionary(variables) }; + //buildStep.ExpandStrings(executeContext); - if (runMode == Mode.Build) + if (runMode == Mode.Build) + { + MicroThread microThread = scheduler.Create(); + + // Set priority from this build step, if we have one. + if (buildStep.Priority.HasValue) { - MicroThread microThread = scheduler.Create(); + microThread.Priority = buildStep.Priority.Value; + } - // Set priority from this build step, if we have one. - if (buildStep.Priority.HasValue) - { - microThread.Priority = buildStep.Priority.Value; - } + buildStep.ExecutionId = microThread.Id; - buildStep.ExecutionId = microThread.Id; + microThread.Name = buildStep.ToString(); - microThread.Name = buildStep.ToString(); + // Default: + // Schedule continuations as early as possible to help EnumerableBuildStep finish when all its task are finished. + // Otherwise, it would wait for all leaf to finish first before finishing parent EnumerableBuildStep. + // This should also reduce memory usage, and might improve cache coherency as well. + microThread.ScheduleMode = ScheduleMode.First; - // Default: - // Schedule continuations as early as possible to help EnumerableBuildStep finish when all its task are finished. - // Otherwise, it would wait for all leaf to finish first before finishing parent EnumerableBuildStep. - // This should also reduce memory usage, and might improve cache coherency as well. - microThread.ScheduleMode = ScheduleMode.First; + microThread.Start(async () => + { + // Wait for prerequisites + await Task.WhenAll(buildStep.PrerequisiteSteps.Select(x => x.ExecutedAsync()).ToArray()); - microThread.Start(async () => - { - // Wait for prerequisites - await Task.WhenAll(buildStep.PrerequisiteSteps.Select(x => x.ExecutedAsync()).ToArray()); + // Check for failed prerequisites + var status = ResultStatus.NotProcessed; - // Check for failed prerequisites - var status = ResultStatus.NotProcessed; + if (buildStep.ArePrerequisitesSuccessful) + { + try + { + var outputObjectsGroups = executeContext.GetOutputObjectsGroups(); + MicrothreadLocalDatabases.MountDatabase(outputObjectsGroups); - if (buildStep.ArePrerequisitesSuccessful) + // Execute + status = await buildStep.Execute(executeContext, builderContext); + } + catch (TaskCanceledException e) { - try - { - var outputObjectsGroups = executeContext.GetOutputObjectsGroups(); - MicrothreadLocalDatabases.MountDatabase(outputObjectsGroups); - - // Execute - status = await buildStep.Execute(executeContext, builderContext); - } - catch (TaskCanceledException e) - { - // Benlitz: I'm NOT SURE this is the correct explanation, it might be a more subtle race condition, but I can't manage to reproduce it again - executeContext.Logger.Warning("A child task of build step " + buildStep + " triggered a TaskCanceledException that was not caught by the parent task. The command has not handled cancellation gracefully."); - executeContext.Logger.Warning(e.Message); - status = ResultStatus.Cancelled; - } - catch (Exception e) - { - executeContext.Logger.Error("Exception in command " + buildStep + ": " + e); - status = ResultStatus.Failed; - } - finally - { - MicrothreadLocalDatabases.UnmountDatabase(); - - // Ensure the command set at least the result status - if (status == ResultStatus.NotProcessed) - throw new InvalidDataException("The build step " + buildStep + " returned ResultStatus.NotProcessed after completion."); - } - if (microThread.Exception != null) - { - executeContext.Logger.Error("Exception in command " + buildStep + ": " + microThread.Exception); - status = ResultStatus.Failed; - } + // Benlitz: I'm NOT SURE this is the correct explanation, it might be a more subtle race condition, but I can't manage to reproduce it again + executeContext.Logger.Warning("A child task of build step " + buildStep + " triggered a TaskCanceledException that was not caught by the parent task. The command has not handled cancellation gracefully."); + executeContext.Logger.Warning(e.Message); + status = ResultStatus.Cancelled; } - else + catch (Exception e) { - status = ResultStatus.NotTriggeredPrerequisiteFailed; + executeContext.Logger.Error("Exception in command " + buildStep + ": " + e); + status = ResultStatus.Failed; } - - //if (completedTask.IsCanceled) - //{ - // completedStep.Status = ResultStatus.Cancelled; - //} - var logType = LogMessageType.Info; - string logText = null; - - switch (status) + finally { - case ResultStatus.Successful: - logType = LogMessageType.Verbose; - logText = "BuildStep {0} was successful.".ToFormat(buildStep.ToString()); - break; - - case ResultStatus.Failed: - logType = LogMessageType.Error; - logText = "BuildStep {0} failed.".ToFormat(buildStep.ToString()); - break; - - case ResultStatus.NotTriggeredPrerequisiteFailed: - logType = LogMessageType.Error; - logText = "BuildStep {0} failed of previous failed prerequisites.".ToFormat(buildStep.ToString()); - break; - - case ResultStatus.Cancelled: - logType = LogMessageType.Warning; - logText = "BuildStep {0} cancelled.".ToFormat(buildStep.ToString()); - break; - - case ResultStatus.NotTriggeredWasSuccessful: - logType = LogMessageType.Verbose; - logText = "BuildStep {0} is up-to-date and has been skipped".ToFormat(buildStep.ToString()); - break; - - case ResultStatus.NotProcessed: - throw new InvalidDataException("BuildStep has neither succeeded, failed, nor been cancelled"); + MicrothreadLocalDatabases.UnmountDatabase(); + + // Ensure the command set at least the result status + if (status == ResultStatus.NotProcessed) + throw new InvalidDataException("The build step " + buildStep + " returned ResultStatus.NotProcessed after completion."); } - if (logText != null) + if (microThread.Exception != null) { - var logMessage = new LogMessage(null, logType, logText); - executeContext.Logger.Log(logMessage); + executeContext.Logger.Error("Exception in command " + buildStep + ": " + microThread.Exception); + status = ResultStatus.Failed; } + } + else + { + status = ResultStatus.NotTriggeredPrerequisiteFailed; + } - buildStep.RegisterResult(executeContext, status); - stepCounter.AddStepResult(status); - }); - } - else - { - buildStep.Clean(executeContext, builderContext, runMode == Mode.CleanAndDelete); - } - } - } + //if (completedTask.IsCanceled) + //{ + // completedStep.Status = ResultStatus.Cancelled; + //} + var logType = LogMessageType.Info; + string? logText = null; - #endregion Methods + switch (status) + { + case ResultStatus.Successful: + logType = LogMessageType.Verbose; + logText = "BuildStep {0} was successful.".ToFormat(buildStep.ToString()); + break; + + case ResultStatus.Failed: + logType = LogMessageType.Error; + logText = "BuildStep {0} failed.".ToFormat(buildStep.ToString()); + break; + + case ResultStatus.NotTriggeredPrerequisiteFailed: + logType = LogMessageType.Error; + logText = "BuildStep {0} failed of previous failed prerequisites.".ToFormat(buildStep.ToString()); + break; + + case ResultStatus.Cancelled: + logType = LogMessageType.Warning; + logText = "BuildStep {0} cancelled.".ToFormat(buildStep.ToString()); + break; + + case ResultStatus.NotTriggeredWasSuccessful: + logType = LogMessageType.Verbose; + logText = "BuildStep {0} is up-to-date and has been skipped".ToFormat(buildStep.ToString()); + break; + + case ResultStatus.NotProcessed: + throw new InvalidDataException("BuildStep has neither succeeded, failed, nor been cancelled"); + } + if (logText != null) + { + var logMessage = new LogMessage(null, logType, logText); + executeContext.Logger.Log(logMessage); + } - private class ExecuteContext : IExecuteContext - { - private readonly Builder builder; - private readonly BuilderContext builderContext; - private readonly BuildStep buildStep; - private readonly BuildTransaction buildTransaction; - public CancellationTokenSource CancellationTokenSource => builder.cancellationTokenSource; + buildStep.RegisterResult(executeContext, status); + stepCounter.AddStepResult(status); + }); + } + else + { + buildStep.Clean(executeContext, builderContext, runMode == Mode.CleanAndDelete); + } + } + } - public Logger Logger { get; } + #endregion Methods - public ObjectDatabase ResultMap => builder.resultMap; + private class ExecuteContext : IExecuteContext + { + private readonly Builder builder; + private readonly BuilderContext builderContext; + private readonly BuildStep buildStep; + private readonly BuildTransaction buildTransaction; + public CancellationTokenSource CancellationTokenSource => builder.cancellationTokenSource; - public Dictionary Variables { get; set; } + public Logger Logger { get; } - public ExecuteContext(Builder builder, BuilderContext builderContext, BuildStep buildStep, Logger logger) - { - Logger = logger; - this.builderContext = builderContext; - this.builder = builder; - this.buildStep = buildStep; - buildTransaction = new BuildTransaction(null, buildStep.GetOutputObjectsGroups()); - } + public ObjectDatabase ResultMap => builder.resultMap; - public ObjectId ComputeInputHash(UrlType type, string filePath) - { - var hash = ObjectId.Empty; + public Dictionary Variables { get; set; } - switch (type) - { - case UrlType.File: - hash = builderContext.InputHashes.ComputeFileHash(filePath); - break; - - case UrlType.Content: - if (!buildTransaction.TryGetValue(filePath, out hash)) - Logger.Warning("Location " + filePath + " does not exist currently and is required to compute the current command hash. The build cache will not work for this command!"); - break; - } + public ExecuteContext(Builder builder, BuilderContext builderContext, BuildStep buildStep, Logger logger) + { + Logger = logger; + this.builderContext = builderContext; + this.builder = builder; + this.buildStep = buildStep; + buildTransaction = new BuildTransaction(null, buildStep.GetOutputObjectsGroups()); + } - return hash; - } + public ObjectId ComputeInputHash(UrlType type, string filePath) + { + var hash = ObjectId.Empty; - public IEnumerable> GetOutputObjectsGroups() + switch (type) { - return buildStep.GetOutputObjectsGroups(); + case UrlType.File: + hash = builderContext.InputHashes.ComputeFileHash(filePath); + break; + + case UrlType.Content: + if (!buildTransaction.TryGetValue(filePath, out hash)) + Logger.Warning("Location " + filePath + " does not exist currently and is required to compute the current command hash. The build cache will not work for this command!"); + break; } - public CommandBuildStep IsCommandCurrentlyRunning(ObjectId commandHash) - { - lock (builderContext.CommandsInProgress) - { - CommandBuildStep step; - builderContext.CommandsInProgress.TryGetValue(commandHash, out step); - return step; - } - } + return hash; + } + + public IEnumerable> GetOutputObjectsGroups() + { + return buildStep.GetOutputObjectsGroups(); + } - public void NotifyCommandBuildStepFinished(CommandBuildStep commandBuildStep, ObjectId commandHash) + public CommandBuildStep? IsCommandCurrentlyRunning(ObjectId commandHash) + { + lock (builderContext.CommandsInProgress) { - lock (builderContext.CommandsInProgress) - { - builderContext.CommandsInProgress.Remove(commandHash); - builder.ioMonitor.CommandEnded(commandBuildStep); - } + builderContext.CommandsInProgress.TryGetValue(commandHash, out var step); + return step; } + } - public void NotifyCommandBuildStepStarted(CommandBuildStep commandBuildStep, ObjectId commandHash) + public void NotifyCommandBuildStepFinished(CommandBuildStep commandBuildStep, ObjectId commandHash) + { + lock (builderContext.CommandsInProgress) { - lock (builderContext.CommandsInProgress) - { - builderContext.CommandsInProgress.TryAdd(commandHash, commandBuildStep); - builder.ioMonitor.CommandStarted(commandBuildStep); - } + builderContext.CommandsInProgress.Remove(commandHash); + builder.ioMonitor.CommandEnded(commandBuildStep); } + } - public void ScheduleBuildStep(BuildStep step) + public void NotifyCommandBuildStepStarted(CommandBuildStep commandBuildStep, ObjectId commandHash) + { + lock (builderContext.CommandsInProgress) { - builder.ScheduleBuildStep(builderContext, buildStep, step, Variables); + builderContext.CommandsInProgress.TryAdd(commandHash, commandBuildStep); + builder.ioMonitor.CommandStarted(commandBuildStep); } } + + public void ScheduleBuildStep(BuildStep step) + { + builder.ScheduleBuildStep(builderContext, buildStep, step, Variables); + } } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/BuilderContext.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/BuilderContext.cs index 724d8f332d..be0292c5fb 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/BuilderContext.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/BuilderContext.cs @@ -1,24 +1,21 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Threading; using Stride.Core.Storage; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public class BuilderContext { - public class BuilderContext - { - internal readonly Dictionary CommandsInProgress = new Dictionary(); + internal readonly Dictionary CommandsInProgress = []; - internal FileVersionTracker InputHashes { get; private set; } + internal FileVersionTracker InputHashes { get; private set; } - public CommandBuildStep.TryExecuteRemoteDelegate TryExecuteRemote { get; } + public CommandBuildStep.TryExecuteRemoteDelegate TryExecuteRemote { get; } - public BuilderContext(FileVersionTracker inputHashes, CommandBuildStep.TryExecuteRemoteDelegate tryExecuteRemote) - { - InputHashes = inputHashes; - TryExecuteRemote = tryExecuteRemote; - } + public BuilderContext(FileVersionTracker inputHashes, CommandBuildStep.TryExecuteRemoteDelegate tryExecuteRemote) + { + InputHashes = inputHashes; + TryExecuteRemote = tryExecuteRemote; } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/Command.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/Command.cs index c9b0c9275a..e73c3e66ef 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/Command.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/Command.cs @@ -1,204 +1,196 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; using Stride.Core.Storage; using Stride.Core.Serialization; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using Stride.Core.Serialization.Contents; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +[DataContract(Inherited = true), Serializable] +public abstract class Command { - [DataContract(Inherited = true), Serializable] - public abstract class Command + /// + /// The command cache version, should be bumped when binary serialization format changes (so that cache gets invalidated) + /// + protected const int CommandCacheVersion = 1; + + /// + /// Title (short description) of the command + /// + public abstract string Title { get; } + + /// + /// The object this command writes (if any). + /// + public virtual string? OutputLocation => null; + + /// + /// Safeguard to ensure inheritance will always call base.PreCommand + /// + internal bool BasePreCommandCalled; + + /// + /// Safeguard to ensure inheritance will always call base.PostCommand + /// + internal bool BasePostCommandCalled; + + /// + /// Cancellation Token. Must be checked frequently by the implementation in order to interrupt the command while running + /// + public CancellationToken CancellationToken; + + /// + /// The method to override containing the actual command code. It is called by the function + /// + /// + protected abstract Task DoCommandOverride(ICommandContext commandContext); + + /// + /// The method that indirectly call to execute the actual command code. + /// It is called by the current when the command is triggered + /// + /// + public Task DoCommand(ICommandContext commandContext) { - /// - /// The command cache version, should be bumped when binary serialization format changes (so that cache gets invalidated) - /// - protected const int CommandCacheVersion = 1; - - /// - /// Title (short description) of the command - /// - public abstract string Title { get; } - - /// - /// The object this command writes (if any). - /// - public virtual string OutputLocation => null; - - /// - /// Safeguard to ensure inheritance will always call base.PreCommand - /// - internal bool BasePreCommandCalled; - - /// - /// Safeguard to ensure inheritance will always call base.PostCommand - /// - internal bool BasePostCommandCalled; - - /// - /// Cancellation Token. Must be checked frequently by the implementation in order to interrupt the command while running - /// - public CancellationToken CancellationToken; - - /// - /// The method to override containing the actual command code. It is called by the function - /// - /// - protected abstract Task DoCommandOverride(ICommandContext commandContext); - - /// - /// The method that indirectly call to execute the actual command code. - /// It is called by the current when the command is triggered - /// - /// - public Task DoCommand(ICommandContext commandContext) - { - if (CancellationToken.IsCancellationRequested) - return Task.FromResult(ResultStatus.Cancelled); + if (CancellationToken.IsCancellationRequested) + return Task.FromResult(ResultStatus.Cancelled); - return DoCommandOverride(commandContext); - } + return DoCommandOverride(commandContext); + } - public virtual void PreCommand(ICommandContext commandContext) - { - // Safeguard, will throw an exception if a inherited command does not call base.PreCommand - BasePreCommandCalled = true; - } + public virtual void PreCommand(ICommandContext commandContext) + { + // Safeguard, will throw an exception if a inherited command does not call base.PreCommand + BasePreCommandCalled = true; + } - public virtual void PostCommand(ICommandContext commandContext, ResultStatus status) - { - // Safeguard, will throw an exception if a inherited command does not call base.PostCommand - BasePostCommandCalled = true; + public virtual void PostCommand(ICommandContext commandContext, ResultStatus status) + { + // Safeguard, will throw an exception if a inherited command does not call base.PostCommand + BasePostCommandCalled = true; - commandContext.RegisterCommandLog(commandContext.Logger.Messages); - } + commandContext.RegisterCommandLog(commandContext.Logger.Messages); + } - public Command Clone() + public Command Clone() + { + var copy = (Command)Activator.CreateInstance(GetType())!; + foreach (PropertyInfo property in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) { - var copy = (Command)Activator.CreateInstance(GetType()); - foreach (PropertyInfo property in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + if (property.GetSetMethod() != null) { - if (property.GetSetMethod() != null) - { - var value = property.GetValue(this); - property.SetValue(copy, value); - } + var value = property.GetValue(this); + property.SetValue(copy, value); } - return copy; } + return copy; + } - /// - public abstract override string ToString(); + /// + public abstract override string ToString(); - /// - /// Gets the list of input files (that can be deduced without running the command, only from command parameters). - /// - /// - public virtual IEnumerable GetInputFiles() - { - return InputFilesGetter?.Invoke() ?? Enumerable.Empty(); - } + /// + /// Gets the list of input files (that can be deduced without running the command, only from command parameters). + /// + /// + public virtual IEnumerable GetInputFiles() + { + return InputFilesGetter?.Invoke() ?? []; + } - public Func> InputFilesGetter; + public Func>? InputFilesGetter; - /// - /// Check some conditions that determine if the command should be executed. This method may not be called if some previous check determinated that it already needs to be executed. - /// - /// true if the command should be executed - public virtual bool ShouldForceExecution() - { - return false; - } + /// + /// Check some conditions that determine if the command should be executed. This method may not be called if some previous check determinated that it already needs to be executed. + /// + /// true if the command should be executed + public virtual bool ShouldForceExecution() + { + return false; + } - public virtual bool ShouldSpawnNewProcess() - { - return false; - } + public virtual bool ShouldSpawnNewProcess() + { + return false; + } - /// - /// Callback called by . It can be useful for commands in a blocking call that can be unblocked from here. - /// - public virtual void Cancel() - { - // Do nothing by default - } + /// + /// Callback called by . It can be useful for commands in a blocking call that can be unblocked from here. + /// + public virtual void Cancel() + { + // Do nothing by default + } - protected virtual void ComputeParameterHash(BinarySerializationWriter writer) - { - // Do nothing by default - } + protected virtual void ComputeParameterHash(BinarySerializationWriter writer) + { + // Do nothing by default + } - protected void ComputeInputFilesHash(BinarySerializationWriter writer, IPrepareContext prepareContext) - { - var inputFiles = GetInputFiles(); - if (inputFiles == null) - return; + protected void ComputeInputFilesHash(BinarySerializationWriter writer, IPrepareContext prepareContext) + { + var inputFiles = GetInputFiles(); + if (inputFiles == null) + return; - foreach (var inputFile in inputFiles) + foreach (var inputFile in inputFiles) + { + var hash = prepareContext.ComputeInputHash(inputFile.Type, inputFile.Path); + if (hash == ObjectId.Empty) + { + writer.UnderlyingStream.WriteByte(0); + } + else { - var hash = prepareContext.ComputeInputHash(inputFile.Type, inputFile.Path); - if (hash == ObjectId.Empty) - { - writer.UnderlyingStream.WriteByte(0); - } - else - { - writer.UnderlyingStream.Write((byte[])hash, 0, ObjectId.HashSize); - } + writer.UnderlyingStream.Write((byte[])hash, 0, ObjectId.HashSize); } } + } - public void ComputeCommandHash(Stream stream, IPrepareContext prepareContext) - { - var writer = new BinarySerializationWriter(stream) { Context = { SerializerSelector = SerializerSelector.AssetWithReuse } }; + public void ComputeCommandHash(Stream stream, IPrepareContext prepareContext) + { + var writer = new BinarySerializationWriter(stream) { Context = { SerializerSelector = SerializerSelector.AssetWithReuse } }; - writer.Write(CommandCacheVersion); + writer.Write(CommandCacheVersion); - // Compute assembly hash - ComputeAssemblyHash(writer); + // Compute assembly hash + ComputeAssemblyHash(writer); - // Compute parameters hash - ComputeParameterHash(writer); + // Compute parameters hash + ComputeParameterHash(writer); - // Compute static input files hash (parameter dependent) - ComputeInputFilesHash(writer, prepareContext); - } + // Compute static input files hash (parameter dependent) + ComputeInputFilesHash(writer, prepareContext); + } - protected virtual void ComputeAssemblyHash(BinarySerializationWriter writer) - { - // Use binary format version (bumping it forces everything to be reevaluated) - writer.Write(DataSerializer.BinaryFormatVersion); + protected virtual void ComputeAssemblyHash(BinarySerializationWriter writer) + { + // Use binary format version (bumping it forces everything to be reevaluated) + writer.Write(DataSerializer.BinaryFormatVersion); - // Gets the hash of the assembly of the command - //writer.Write(AssemblyHash.ComputeAssemblyHash(GetType().Assembly)); - } + // Gets the hash of the assembly of the command + //writer.Write(AssemblyHash.ComputeAssemblyHash(GetType().Assembly)); + } - /// - /// Computes the command hash. If an error occurred, the hash is - /// - /// The prepare context. - /// Hash of the command. - internal ObjectId ComputeCommandHash(IPrepareContext prepareContext) + /// + /// Computes the command hash. If an error occurred, the hash is + /// + /// The prepare context. + /// Hash of the command. + internal ObjectId ComputeCommandHash(IPrepareContext prepareContext) + { + var stream = new DigestStream(Stream.Null); + try { - var stream = new DigestStream(Stream.Null); - try - { - ComputeCommandHash(stream, prepareContext); - return stream.CurrentHash; - } - catch (Exception ex) - { - prepareContext.Logger.Error($"Unexpected error while computing the command hash for [{GetType().Name}].", ex); - } - return ObjectId.Empty; + ComputeCommandHash(stream, prepareContext); + return stream.CurrentHash; + } + catch (Exception ex) + { + prepareContext.Logger.Error($"Unexpected error while computing the command hash for [{GetType().Name}].", ex); } + return ObjectId.Empty; } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/CommandBuildStep.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/CommandBuildStep.cs index 937c2f8d50..5f00bd39a5 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/CommandBuildStep.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/CommandBuildStep.cs @@ -1,328 +1,322 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; + using Stride.Core.Storage; using Stride.Core.IO; using Stride.Core.Serialization.Contents; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public class CommandBuildStep : BuildStep { - public class CommandBuildStep : BuildStep + public delegate Task TryExecuteRemoteDelegate(Command command, BuilderContext builderContext, IExecuteContext executeContext, LocalCommandContext commandContext); + + public CommandBuildStep(Command command) { - public delegate Task TryExecuteRemoteDelegate(Command command, BuilderContext builderContext, IExecuteContext executeContext, LocalCommandContext commandContext); + Command = command; + } - public CommandBuildStep(Command command) - { - Command = command; - } + /// + public override string Title => Command != null ? Command.Title : ""; - /// - public override string Title => Command != null ? Command.Title : ""; + public Command Command { get; } - public Command Command { get; } + /// + /// Command Result, set only after step completion. Not thread safe, should not be modified + /// + public CommandResultEntry Result { get; private set; } - /// - /// Command Result, set only after step completion. Not thread safe, should not be modified - /// - public CommandResultEntry Result { get; private set; } + /// + public override string OutputLocation => Command.OutputLocation; - /// - public override string OutputLocation => Command.OutputLocation; + /// + public override IEnumerable> OutputObjectIds => Result.OutputObjects; - /// - public override IEnumerable> OutputObjectIds => Result.OutputObjects; + public override string ToString() + { + return Command.ToString(); + } - public override string ToString() + public override void Clean(IExecuteContext executeContext, BuilderContext builderContext, bool deleteOutput) + { + // try to retrieve result from one of the object store + var commandHash = Command.ComputeCommandHash(executeContext); + // If there was an error computing the hash, early exit + if (commandHash == ObjectId.Empty) { - return Command.ToString(); + return; + } + + var commandResultsFileStream = executeContext.ResultMap.OpenStream(commandHash, VirtualFileMode.OpenOrCreate, VirtualFileAccess.ReadWrite, VirtualFileShare.ReadWrite); + var commandResultEntries = new ListStore(commandResultsFileStream) { AutoLoadNewValues = false }; + commandResultEntries.LoadNewValues(); + commandResultsFileStream.Close(); + + CommandResultEntry? matchingResult = FindMatchingResult(executeContext, commandResultEntries.GetValues()); + if (matchingResult != null) + { + if (deleteOutput) + { + foreach (KeyValuePair outputObject in matchingResult.OutputObjects) + { + switch (outputObject.Key.Type) + { + case UrlType.File: + try + { + if (File.Exists(outputObject.Key.Path)) + File.Delete(outputObject.Key.Path); + } + catch (Exception) + { + executeContext.Logger.Error("Unable to delete file: " + outputObject.Key.Path); + } + break; + case UrlType.Content: + executeContext.ResultMap.Delete(outputObject.Value); + break; + } + } + } } - public override void Clean(IExecuteContext executeContext, BuilderContext builderContext, bool deleteOutput) + executeContext.ResultMap.Delete(commandHash); + } + + public override async Task Execute(IExecuteContext executeContext, BuilderContext builderContext) + { + ListStore commandResultEntries; + + // Prevent several command build step to evaluate wheither they should start at the same time. This increase the efficiency of the builder by avoiding the same command to be executed several time in parallel + // NOTE: Careful here, there's no try/finally block around the monitor Enter/Exit, so no non-fatal exception is allowed! + Monitor.Enter(executeContext); + bool monitorExited = false; + var status = ResultStatus.NotProcessed; + // if any external input has changed since the last execution (or if we don't have a successful execution in cache, trigger the command + CommandResultEntry? matchingResult; + try { // try to retrieve result from one of the object store var commandHash = Command.ComputeCommandHash(executeContext); - // If there was an error computing the hash, early exit + // Early exit if the hash of the command failed if (commandHash == ObjectId.Empty) { - return; + return ResultStatus.Failed; } var commandResultsFileStream = executeContext.ResultMap.OpenStream(commandHash, VirtualFileMode.OpenOrCreate, VirtualFileAccess.ReadWrite, VirtualFileShare.ReadWrite); - var commandResultEntries = new ListStore(commandResultsFileStream) { AutoLoadNewValues = false }; + commandResultEntries = new ListStore(commandResultsFileStream) { AutoLoadNewValues = false }; commandResultEntries.LoadNewValues(); - commandResultsFileStream.Close(); - CommandResultEntry matchingResult = FindMatchingResult(executeContext, commandResultEntries.GetValues()); - if (matchingResult != null) + if (ShouldExecute(executeContext, commandResultEntries.GetValues(), commandHash, out matchingResult)) { - if (deleteOutput) + CommandBuildStep? stepInProgress = executeContext.IsCommandCurrentlyRunning(commandHash); + if (stepInProgress != null) { - foreach (KeyValuePair outputObject in matchingResult.OutputObjects) - { - switch (outputObject.Key.Type) - { - case UrlType.File: - try - { - if (File.Exists(outputObject.Key.Path)) - File.Delete(outputObject.Key.Path); - } - catch (Exception) - { - executeContext.Logger.Error("Unable to delete file: " + outputObject.Key.Path); - } - break; - case UrlType.Content: - executeContext.ResultMap.Delete(outputObject.Value); - break; - } - } + Monitor.Exit(executeContext); + monitorExited = true; + executeContext.Logger.Debug($"Command {Command} delayed because it is currently running..."); + status = (await stepInProgress.ExecutedAsync()).Status; + matchingResult = stepInProgress.Result; } - } + else + { + executeContext.NotifyCommandBuildStepStarted(this, commandHash); + Monitor.Exit(executeContext); + monitorExited = true; - executeContext.ResultMap.Delete(commandHash); - } + executeContext.Logger.Debug($"Command {Command} scheduled..."); - public override async Task Execute(IExecuteContext executeContext, BuilderContext builderContext) - { - ListStore commandResultEntries; - - // Prevent several command build step to evaluate wheither they should start at the same time. This increase the efficiency of the builder by avoiding the same command to be executed several time in parallel - // NOTE: Careful here, there's no try/finally block around the monitor Enter/Exit, so no non-fatal exception is allowed! - Monitor.Enter(executeContext); - bool monitorExited = false; - var status = ResultStatus.NotProcessed; - // if any external input has changed since the last execution (or if we don't have a successful execution in cache, trigger the command - CommandResultEntry matchingResult; - try - { - // try to retrieve result from one of the object store - var commandHash = Command.ComputeCommandHash(executeContext); - // Early exit if the hash of the command failed - if (commandHash == ObjectId.Empty) - { - return ResultStatus.Failed; - } + // Register the cancel callback + var cancellationTokenSource = executeContext.CancellationTokenSource; + cancellationTokenSource.Token.Register(x => ((Command?)x)?.Cancel(), Command); - var commandResultsFileStream = executeContext.ResultMap.OpenStream(commandHash, VirtualFileMode.OpenOrCreate, VirtualFileAccess.ReadWrite, VirtualFileShare.ReadWrite); - commandResultEntries = new ListStore(commandResultsFileStream) { AutoLoadNewValues = false }; - commandResultEntries.LoadNewValues(); + Command.CancellationToken = cancellationTokenSource.Token; - if (ShouldExecute(executeContext, commandResultEntries.GetValues(), commandHash, out matchingResult)) - { - CommandBuildStep stepInProgress = executeContext.IsCommandCurrentlyRunning(commandHash); - if (stepInProgress != null) + try { - Monitor.Exit(executeContext); - monitorExited = true; - executeContext.Logger.Debug($"Command {Command} delayed because it is currently running..."); - status = (await stepInProgress.ExecutedAsync()).Status; - matchingResult = stepInProgress.Result; + status = await StartCommand(executeContext, commandResultEntries, builderContext); } - else + finally { - executeContext.NotifyCommandBuildStepStarted(this, commandHash); - Monitor.Exit(executeContext); - monitorExited = true; - - executeContext.Logger.Debug($"Command {Command} scheduled..."); - - // Register the cancel callback - var cancellationTokenSource = executeContext.CancellationTokenSource; - cancellationTokenSource.Token.Register(x => ((Command)x).Cancel(), Command); - - Command.CancellationToken = cancellationTokenSource.Token; - - try - { - status = await StartCommand(executeContext, commandResultEntries, builderContext); - } - finally - { - // Restore cancellation token (to avoid memory leak due to previous CancellationToken.Register - Command.CancellationToken = CancellationToken.None; - } - - executeContext.NotifyCommandBuildStepFinished(this, commandHash); + // Restore cancellation token (to avoid memory leak due to previous CancellationToken.Register + Command.CancellationToken = CancellationToken.None; } + + executeContext.NotifyCommandBuildStepFinished(this, commandHash); } } - finally + } + finally + { + if (!monitorExited) { - if (!monitorExited) - { - Monitor.Exit(executeContext); - } + Monitor.Exit(executeContext); } + } - // The command has not been executed - if (matchingResult != null) + // The command has not been executed + if (matchingResult != null) + { + using (commandResultEntries) { - using (commandResultEntries) + // Re-output command log messages + foreach (var message in matchingResult.LogMessages) { - // Re-output command log messages - foreach (var message in matchingResult.LogMessages) - { - executeContext.Logger.Log(message); - } - - status = ResultStatus.NotTriggeredWasSuccessful; - RegisterCommandResult(commandResultEntries, matchingResult, status); + executeContext.Logger.Log(message); } - } - return status; + status = ResultStatus.NotTriggeredWasSuccessful; + RegisterCommandResult(commandResultEntries, matchingResult, status); + } } - private void RegisterCommandResult(ListStore commandResultEntries, CommandResultEntry result, ResultStatus status) - { - //foreach (var outputObject in result.OutputObjects.Where(outputObject => outputObject.Key.Type == UrlType.Internal)) - //{ - // builderContext.contentIndexMap[outputObject.Key.Path] = outputObject.Value; - //} + return status; + } - Result = result; + private void RegisterCommandResult(ListStore commandResultEntries, CommandResultEntry result, ResultStatus status) + { + //foreach (var outputObject in result.OutputObjects.Where(outputObject => outputObject.Key.Type == UrlType.Internal)) + //{ + // builderContext.contentIndexMap[outputObject.Key.Path] = outputObject.Value; + //} - // Only save to build cache if compilation was done and successful - if (status == ResultStatus.Successful) - { - commandResultEntries.AddValue(result); - } - } + Result = result; - internal bool ShouldExecute(IExecuteContext executeContext, CommandResultEntry[] previousResultCollection, ObjectId commandHash, out CommandResultEntry matchingResult) + // Only save to build cache if compilation was done and successful + if (status == ResultStatus.Successful) { - var outputObjectsGroups = executeContext.GetOutputObjectsGroups(); - MicrothreadLocalDatabases.MountDatabase(outputObjectsGroups); - try - { - matchingResult = FindMatchingResult(executeContext, previousResultCollection); - } - finally - { - MicrothreadLocalDatabases.UnmountDatabase(); - } - - if (matchingResult == null || Command.ShouldForceExecution()) - { - // Ensure we ignore existing results if the execution is forced - matchingResult = null; - return true; - } + commandResultEntries.AddValue(result); + } + } - return false; + internal bool ShouldExecute(IExecuteContext executeContext, CommandResultEntry[] previousResultCollection, ObjectId commandHash, out CommandResultEntry? matchingResult) + { + var outputObjectsGroups = executeContext.GetOutputObjectsGroups(); + MicrothreadLocalDatabases.MountDatabase(outputObjectsGroups); + try + { + matchingResult = FindMatchingResult(executeContext, previousResultCollection); + } + finally + { + MicrothreadLocalDatabases.UnmountDatabase(); } - internal CommandResultEntry FindMatchingResult(IPrepareContext prepareContext, CommandResultEntry[] commandResultCollection) + if (matchingResult == null || Command.ShouldForceExecution()) { - if (commandResultCollection == null) - return null; + // Ensure we ignore existing results if the execution is forced + matchingResult = null; + return true; + } - // Then check input dependencies and output versions - //builderContext.contentIndexMap.LoadNewValues(); + return false; + } - foreach (CommandResultEntry entry in commandResultCollection) - { - bool entryMatch = true; + internal static CommandResultEntry? FindMatchingResult(IPrepareContext prepareContext, CommandResultEntry[] commandResultCollection) + { + if (commandResultCollection == null) + return null; - foreach (var inputDepVersion in entry.InputDependencyVersions) - { - var hash = prepareContext.ComputeInputHash(inputDepVersion.Key.Type, inputDepVersion.Key.Path); - if (hash != inputDepVersion.Value) - { - entryMatch = false; - break; - } - } + // Then check input dependencies and output versions + //builderContext.contentIndexMap.LoadNewValues(); - if (!entryMatch) - continue; + foreach (CommandResultEntry entry in commandResultCollection) + { + bool entryMatch = true; - if (entry.OutputObjects.Any(outputObject => !VirtualFileSystem.FileExists(FileOdbBackend.BuildUrl(VirtualFileSystem.ApplicationDatabasePath, outputObject.Value)))) + foreach (var inputDepVersion in entry.InputDependencyVersions) + { + var hash = prepareContext.ComputeInputHash(inputDepVersion.Key.Type, inputDepVersion.Key.Path); + if (hash != inputDepVersion.Value) { entryMatch = false; + break; } + } - if (!entryMatch) - continue; - - // TODO/Benlitz: check matching spawned commands if needed + if (!entryMatch) + continue; - return entry; + if (entry.OutputObjects.Any(outputObject => !VirtualFileSystem.FileExists(FileOdbBackend.BuildUrl(VirtualFileSystem.ApplicationDatabasePath, outputObject.Value)))) + { + entryMatch = false; } - return null; + if (!entryMatch) + continue; + + // TODO/Benlitz: check matching spawned commands if needed + + return entry; } - private async Task StartCommand(IExecuteContext executeContext, ListStore commandResultEntries, BuilderContext builderContext) - { - var logger = executeContext.Logger; + return null; + } - //await Scheduler.Yield(); + private async Task StartCommand(IExecuteContext executeContext, ListStore commandResultEntries, BuilderContext builderContext) + { + var logger = executeContext.Logger; - ResultStatus status; + //await Scheduler.Yield(); - using (commandResultEntries) - { - logger.Debug($"Starting command {Command}..."); + ResultStatus status; - // Creating the CommandResult object - var commandContext = new LocalCommandContext(executeContext, this, builderContext); + using (commandResultEntries) + { + logger.Debug($"Starting command {Command}..."); - // Try to execute remotely - if (!(Command.ShouldSpawnNewProcess() - && builderContext.TryExecuteRemote != null - && (status = await builderContext.TryExecuteRemote(Command, builderContext, executeContext, commandContext)) != ResultStatus.NotProcessed)) - { - Command.PreCommand(commandContext); - if (!Command.BasePreCommandCalled) - throw new InvalidOperationException("base.PreCommand not called in command " + Command); + // Creating the CommandResult object + var commandContext = new LocalCommandContext(executeContext, this, builderContext); - try + // Try to execute remotely + if (!(Command.ShouldSpawnNewProcess() + && builderContext.TryExecuteRemote != null + && (status = await builderContext.TryExecuteRemote(Command, builderContext, executeContext, commandContext)) != ResultStatus.NotProcessed)) + { + Command.PreCommand(commandContext); + if (!Command.BasePreCommandCalled) + throw new InvalidOperationException("base.PreCommand not called in command " + Command); + + try + { + // Merge results from prerequisites + // TODO: This will prevent us from overwriting this asset with different content as it will result in a write conflict + // At some point we _might_ want to get rid of WaitBuildStep/ListBuildStep system and write a fully stateless input/output-based system; probably need further discussions + var fileProvider = MicrothreadLocalDatabases.DatabaseFileProvider; + if (fileProvider != null) { - // Merge results from prerequisites - // TODO: This will prevent us from overwriting this asset with different content as it will result in a write conflict - // At some point we _might_ want to get rid of WaitBuildStep/ListBuildStep system and write a fully stateless input/output-based system; probably need further discussions - var fileProvider = MicrothreadLocalDatabases.DatabaseFileProvider; - if (fileProvider != null) + var assetIndexMap = fileProvider.ContentIndexMap; + foreach (var prerequisiteStep in PrerequisiteSteps) { - var assetIndexMap = fileProvider.ContentIndexMap; - foreach (var prerequisiteStep in PrerequisiteSteps) + foreach (var output in prerequisiteStep.OutputObjectIds) { - foreach (var output in prerequisiteStep.OutputObjectIds) - { - assetIndexMap[output.Key.Path] = output.Value; - } + assetIndexMap[output.Key.Path] = output.Value; } } - - status = await Command.DoCommand(commandContext); - } - catch (Exception ex) - { - executeContext.Logger.Error("Exception in command " + this + ": " + ex); - status = ResultStatus.Failed; } - Command.PostCommand(commandContext, status); - if (!Command.BasePostCommandCalled) - throw new InvalidOperationException("base.PostCommand not called in command " + Command); + status = await Command.DoCommand(commandContext); + } + catch (Exception ex) + { + executeContext.Logger.Error("Exception in command " + this + ": " + ex); + status = ResultStatus.Failed; } - // Ensure the command set at least the result status - if (status == ResultStatus.NotProcessed) - throw new InvalidDataException("The command " + Command + " returned ResultStatus.NotProcessed after completion."); - - // Registering the result to the build cache - RegisterCommandResult(commandResultEntries, commandContext.ResultEntry, status); + Command.PostCommand(commandContext, status); + if (!Command.BasePostCommandCalled) + throw new InvalidOperationException("base.PostCommand not called in command " + Command); } - return status; + // Ensure the command set at least the result status + if (status == ResultStatus.NotProcessed) + throw new InvalidDataException("The command " + Command + " returned ResultStatus.NotProcessed after completion."); + + // Registering the result to the build cache + RegisterCommandResult(commandResultEntries, commandContext.ResultEntry, status); } + + return status; } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/CommandContextBase.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/CommandContextBase.cs index 77c40415b9..4f2e8d9b88 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/CommandContextBase.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/CommandContextBase.cs @@ -1,55 +1,53 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; + using Stride.Core.Storage; -using System.Threading.Tasks; using Stride.Core.Diagnostics; using Stride.Core.Serialization.Contents; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public abstract class CommandContextBase : ICommandContext { - public abstract class CommandContextBase : ICommandContext - { - public Command CurrentCommand { get; } + public Command CurrentCommand { get; } - public abstract LoggerResult Logger { get; } + public abstract LoggerResult Logger { get; } - protected internal readonly CommandResultEntry ResultEntry; + protected internal readonly CommandResultEntry ResultEntry; - public abstract IEnumerable> GetOutputObjectsGroups(); + public abstract IEnumerable> GetOutputObjectsGroups(); - public abstract ObjectId ComputeInputHash(UrlType type, string filePath); + public abstract ObjectId ComputeInputHash(UrlType type, string filePath); - protected CommandContextBase(Command command, BuilderContext builderContext) - { - CurrentCommand = command; - ResultEntry = new CommandResultEntry(); - } + protected CommandContextBase(Command command, BuilderContext builderContext) + { + CurrentCommand = command; + ResultEntry = new CommandResultEntry(); + } - public void RegisterInputDependency(ObjectUrl url) - { - ResultEntry.InputDependencyVersions.Add(url, ComputeInputHash(url.Type, url.Path)); - } + public void RegisterInputDependency(ObjectUrl url) + { + ResultEntry.InputDependencyVersions.Add(url, ComputeInputHash(url.Type, url.Path)); + } - public void RegisterOutput(ObjectUrl url, ObjectId hash) - { - ResultEntry.OutputObjects.Add(url, hash); - } + public void RegisterOutput(ObjectUrl url, ObjectId hash) + { + ResultEntry.OutputObjects.Add(url, hash); + } - public void RegisterCommandLog(IEnumerable logMessages) + public void RegisterCommandLog(IEnumerable logMessages) + { + foreach (var message in logMessages) { - foreach (var message in logMessages) - { - ResultEntry.LogMessages.Add(message as SerializableLogMessage ?? new SerializableLogMessage((LogMessage)message)); - } + ResultEntry.LogMessages.Add(message as SerializableLogMessage ?? new SerializableLogMessage((LogMessage)message)); } + } - public void AddTag(ObjectUrl url, string tag) - { - ResultEntry.TagSymbols.Add(new KeyValuePair(url, tag)); - } + public void AddTag(ObjectUrl url, string tag) + { + ResultEntry.TagSymbols.Add(new KeyValuePair(url, tag)); + } - } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/CommandIOMonitor.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/CommandIOMonitor.cs index 01c7f9b1bb..befa2dde15 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/CommandIOMonitor.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/CommandIOMonitor.cs @@ -1,160 +1,153 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; + using System.Diagnostics; -using System.Linq; using Stride.Core.Diagnostics; using Stride.Core.Serialization.Contents; using Stride.Core.Storage; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +/// +/// This class monitors input/output access from every BuildStep execution, and display an error message if an object url is the input of a command and the output of another command running at the same time. +/// +internal class CommandIOMonitor { /// - /// This class monitors input/output access from every BuildStep execution, and display an error message if an object url is the input of a command and the output of another command running at the same time. + /// A dictionary containing read and write access timings (value) of a given object url (key) /// - internal class CommandIOMonitor - { - /// - /// A dictionary containing read and write access timings (value) of a given object url (key) - /// - private readonly Dictionary objectsAccesses = new Dictionary(); + private readonly Dictionary objectsAccesses = []; - /// - /// A dictionary containing execution intervals of BuildStep - /// - private readonly Dictionary commandExecutionIntervals = new Dictionary(); + /// + /// A dictionary containing execution intervals of BuildStep + /// + private readonly Dictionary commandExecutionIntervals = []; - private readonly Dictionary> commandInputFiles = new Dictionary>(); + private readonly Dictionary> commandInputFiles = []; - private readonly ILogger logger; + private readonly ILogger logger; - private readonly object lockObject = new object(); + private readonly object lockObject = new(); - private readonly Stopwatch stopWatch = new Stopwatch(); + private readonly Stopwatch stopWatch = new(); - // Store earliest start time of command still running (to clean up accesses as time goes) - private long earliestCommandAliveStartTime; + // Store earliest start time of command still running (to clean up accesses as time goes) + private long earliestCommandAliveStartTime; - public CommandIOMonitor(ILogger logger) - { - this.logger = logger; - stopWatch.Start(); - } + public CommandIOMonitor(ILogger logger) + { + this.logger = logger; + stopWatch.Start(); + } - public void CommandStarted(CommandBuildStep command) + public void CommandStarted(CommandBuildStep command) + { + lock (lockObject) { - lock (lockObject) - { - long startTime = stopWatch.ElapsedTicks; - commandExecutionIntervals.Add(command, new TimeInterval(startTime)); + long startTime = stopWatch.ElapsedTicks; + commandExecutionIntervals.Add(command, new TimeInterval(startTime)); - // Get a list of unique input files - var inputFiles = command.Command.GetInputFiles().Distinct().ToList(); - // Store it aside, so that we're sure to remove the same entries during CommandEnded - commandInputFiles.Add(command, inputFiles); + // Get a list of unique input files + var inputFiles = command.Command.GetInputFiles().Distinct().ToList(); + // Store it aside, so that we're sure to remove the same entries during CommandEnded + commandInputFiles.Add(command, inputFiles); - // Setup start read time for each file entry - var inputHash = new HashSet(); - foreach (ObjectUrl inputUrl in inputFiles) - { - if (inputHash.Contains(inputUrl)) - logger.Error($"The command '{command.Title}' has several times the file '{inputUrl.Path}' as input. Input Files must not be duplicated"); - inputHash.Add(inputUrl); + // Setup start read time for each file entry + var inputHash = new HashSet(); + foreach (ObjectUrl inputUrl in inputFiles) + { + if (inputHash.Contains(inputUrl)) + logger.Error($"The command '{command.Title}' has several times the file '{inputUrl.Path}' as input. Input Files must not be duplicated"); + inputHash.Add(inputUrl); - ObjectAccesses inputAccesses; - if (!objectsAccesses.TryGetValue(inputUrl, out inputAccesses)) - { - objectsAccesses.Add(inputUrl, inputAccesses = new ObjectAccesses()); - } - inputAccesses.Reads.Add(new TimeInterval(command, startTime)); + if (!objectsAccesses.TryGetValue(inputUrl, out var inputAccesses)) + { + objectsAccesses.Add(inputUrl, inputAccesses = new ObjectAccesses()); } + inputAccesses.Reads.Add(new TimeInterval(command, startTime)); } } + } - public void CommandEnded(CommandBuildStep command) + public void CommandEnded(CommandBuildStep command) + { + lock (lockObject) { - lock (lockObject) - { - TimeInterval commandInterval = commandExecutionIntervals[command]; + TimeInterval commandInterval = commandExecutionIntervals[command]; - long startTime = commandInterval.StartTime; - long endTime = stopWatch.ElapsedTicks; - commandInterval.End(endTime); + long startTime = commandInterval.StartTime; + long endTime = stopWatch.ElapsedTicks; + commandInterval.End(endTime); - commandExecutionIntervals.Remove(command); + commandExecutionIntervals.Remove(command); - foreach (var outputObject in command.Result.OutputObjects) + foreach (var outputObject in command.Result.OutputObjects) + { + var outputUrl = outputObject.Key; + if (objectsAccesses.TryGetValue(outputUrl, out var inputAccess)) { - var outputUrl = outputObject.Key; - ObjectAccesses inputAccess; - if (objectsAccesses.TryGetValue(outputUrl, out inputAccess)) - { - foreach (TimeInterval input in inputAccess.Reads.Where(input => input.Object != command && input.Overlap(startTime, endTime))) - { - logger.Error($"Command {command} is writing {outputUrl} while command {input.Object} is reading it"); - } - } - - ObjectAccesses outputAccess; - if (!objectsAccesses.TryGetValue(outputUrl, out outputAccess)) + foreach (TimeInterval input in inputAccess.Reads.Where(input => input.Object != command && input.Overlap(startTime, endTime))) { - objectsAccesses.Add(outputUrl, outputAccess = new ObjectAccesses()); - } - - foreach (var output in outputAccess.Writes.Where(output => output.Object.Key != command && output.Overlap(startTime, endTime))) - { - if (outputObject.Value != output.Object.Value) - logger.Error($"Commands {command} and {output.Object} are both writing {outputUrl} at the same time, but they are different objects"); + logger.Error($"Command {command} is writing {outputUrl} while command {input.Object} is reading it"); } + } - outputAccess.Writes.Add(new TimeInterval>(new KeyValuePair(command, outputObject.Value), startTime, endTime)); + if (!objectsAccesses.TryGetValue(outputUrl, out var outputAccess)) + { + objectsAccesses.Add(outputUrl, outputAccess = new ObjectAccesses()); } - foreach (ObjectUrl inputUrl in command.Result.InputDependencyVersions.Keys) + foreach (var output in outputAccess.Writes.Where(output => output.Object.Key != command && output.Overlap(startTime, endTime))) { - ObjectAccesses outputAccess; - if (objectsAccesses.TryGetValue(inputUrl, out outputAccess)) - { - foreach (TimeInterval> output in outputAccess.Writes.Where(output => output.Object.Key != command && output.Overlap(startTime, endTime))) - { - logger.Error($"Command {output.Object} is writing {inputUrl} while command {command} is reading it"); - } - } + if (outputObject.Value != output.Object.Value) + logger.Error($"Commands {command} and {output.Object} are both writing {outputUrl} at the same time, but they are different objects"); } - // Notify that we're done reading input files - List inputFiles; - if (commandInputFiles.TryGetValue(command, out inputFiles)) + outputAccess.Writes.Add(new TimeInterval>(new KeyValuePair(command, outputObject.Value), startTime, endTime)); + } + + foreach (ObjectUrl inputUrl in command.Result.InputDependencyVersions.Keys) + { + if (objectsAccesses.TryGetValue(inputUrl, out var outputAccess)) { - commandInputFiles.Remove(command); - foreach (ObjectUrl input in inputFiles) + foreach (TimeInterval> output in outputAccess.Writes.Where(output => output.Object.Key != command && output.Overlap(startTime, endTime))) { - objectsAccesses[input].Reads.Single(x => x.Object == command).End(endTime); + logger.Error($"Command {output.Object} is writing {inputUrl} while command {command} is reading it"); } } + } - // "Garbage collection" of accesses - var newEarliestCommandAliveStartTime = commandExecutionIntervals.Count > 0 ? commandExecutionIntervals.Min(x => x.Value.StartTime) : endTime; - if (newEarliestCommandAliveStartTime > earliestCommandAliveStartTime) + // Notify that we're done reading input files + if (commandInputFiles.TryGetValue(command, out var inputFiles)) + { + commandInputFiles.Remove(command); + foreach (ObjectUrl input in inputFiles) { - earliestCommandAliveStartTime = newEarliestCommandAliveStartTime; + objectsAccesses[input].Reads.Single(x => x.Object == command).End(endTime); + } + } - // We can remove objects whose all R/W accesses are "completed" (EndTime is set) - // and happened before all the current running commands started, since they won't affect us - foreach (var objectAccesses in objectsAccesses.ToList()) - { - if (objectAccesses.Value.Reads.All(x => x.EndTime != 0 && x.EndTime < earliestCommandAliveStartTime) - && objectAccesses.Value.Writes.All(x => x.EndTime != 0 && x.EndTime < earliestCommandAliveStartTime)) - objectsAccesses.Remove(objectAccesses.Key); - } + // "Garbage collection" of accesses + var newEarliestCommandAliveStartTime = commandExecutionIntervals.Count > 0 ? commandExecutionIntervals.Min(x => x.Value.StartTime) : endTime; + if (newEarliestCommandAliveStartTime > earliestCommandAliveStartTime) + { + earliestCommandAliveStartTime = newEarliestCommandAliveStartTime; + + // We can remove objects whose all R/W accesses are "completed" (EndTime is set) + // and happened before all the current running commands started, since they won't affect us + foreach (var objectAccesses in objectsAccesses.ToList()) + { + if (objectAccesses.Value.Reads.All(x => x.EndTime != 0 && x.EndTime < earliestCommandAliveStartTime) + && objectAccesses.Value.Writes.All(x => x.EndTime != 0 && x.EndTime < earliestCommandAliveStartTime)) + objectsAccesses.Remove(objectAccesses.Key); } } } + } - class ObjectAccesses - { - public List> Reads { get; } = new List>(); - public List>> Writes { get; } = new List>>(); - } + class ObjectAccesses + { + public List> Reads { get; } = []; + public List>> Writes { get; } = []; } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/CommandResultEntry.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/CommandResultEntry.cs index 63dd797d05..73b22b9208 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/CommandResultEntry.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/CommandResultEntry.cs @@ -1,41 +1,37 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; + using Stride.Core.Diagnostics; using Stride.Core.Storage; -using System; -using System.Collections.Generic; -using System.Linq; using Stride.Core.Serialization.Contents; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +[ContentSerializer(typeof(DataContentSerializer))] +[DataContract,Serializable] +public class CommandResultEntry { - [ContentSerializer(typeof(DataContentSerializer))] - [DataContract,Serializable] - public class CommandResultEntry - { - public Dictionary InputDependencyVersions; - /// - /// Output object ids as saved in the object database. - /// - public Dictionary OutputObjects; + public Dictionary InputDependencyVersions; + /// + /// Output object ids as saved in the object database. + /// + public Dictionary OutputObjects; - /// - /// Log messages corresponding to the execution of the command. - /// - public List LogMessages; + /// + /// Log messages corresponding to the execution of the command. + /// + public List LogMessages; - /// - /// Tags added for a given URL. - /// - public List> TagSymbols; + /// + /// Tags added for a given URL. + /// + public List> TagSymbols; - public CommandResultEntry() - { - InputDependencyVersions = new Dictionary(); - OutputObjects = new Dictionary(); - LogMessages = new List(); - TagSymbols = new List>(); - } + public CommandResultEntry() + { + InputDependencyVersions = []; + OutputObjects = []; + LogMessages = []; + TagSymbols = []; } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/DynamicBuildStep.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/DynamicBuildStep.cs index a49edd1243..2ea8cae35d 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/DynamicBuildStep.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/DynamicBuildStep.cs @@ -1,128 +1,121 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +namespace Stride.Core.BuildEngine; -namespace Stride.Core.BuildEngine +public class DynamicBuildStep : BuildStep { - public class DynamicBuildStep : BuildStep - { - private readonly IBuildStepProvider buildStepProvider; + private readonly IBuildStepProvider buildStepProvider; - /// - /// The used to notify the dynamic build step that new work is requested. - /// - private readonly AutoResetEvent newWorkAvailable = new AutoResetEvent(false); + /// + /// The used to notify the dynamic build step that new work is requested. + /// + private readonly AutoResetEvent newWorkAvailable = new(false); - public DynamicBuildStep(IBuildStepProvider buildStepProvider, int maxParallelSteps) - { - this.buildStepProvider = buildStepProvider; - MaxParallelSteps = maxParallelSteps; - Priority = int.MinValue; // Highest priority - } + public DynamicBuildStep(IBuildStepProvider buildStepProvider, int maxParallelSteps) + { + this.buildStepProvider = buildStepProvider; + MaxParallelSteps = maxParallelSteps; + Priority = int.MinValue; // Highest priority + } - /// - /// Gets or sets the maximum number of steps that can run at the same time in parallel. - /// - public int MaxParallelSteps { get; set; } + /// + /// Gets or sets the maximum number of steps that can run at the same time in parallel. + /// + public int MaxParallelSteps { get; set; } - /// - /// Gets or sets the maximum number of steps slots that are kept specifically for high priority steps (negative) - /// - public int MaxHighPriorityParallelSteps { get; set; } + /// + /// Gets or sets the maximum number of steps slots that are kept specifically for high priority steps (negative) + /// + public int MaxHighPriorityParallelSteps { get; set; } - /// - public override string Title => ToString(); + /// + public override string Title => ToString(); - /// - /// Notify the dynamic build step new work is available. - /// - public void NotifyNewWorkAvailable() - { - newWorkAvailable.Set(); - } + /// + /// Notify the dynamic build step new work is available. + /// + public void NotifyNewWorkAvailable() + { + newWorkAvailable.Set(); + } + + public override async Task Execute(IExecuteContext executeContext, BuilderContext builderContext) + { + var buildStepsToWait = new List(); - public override async Task Execute(IExecuteContext executeContext, BuilderContext builderContext) + while (true) { - var buildStepsToWait = new List(); + // interrupt the build if cancellation is required. + if (executeContext.CancellationTokenSource.Token.IsCancellationRequested) + return ResultStatus.Cancelled; - while (true) + // Clean completed build steps + for (int index = buildStepsToWait.Count - 1; index >= 0; index--) { - // interrupt the build if cancellation is required. - if (executeContext.CancellationTokenSource.Token.IsCancellationRequested) - return ResultStatus.Cancelled; - - // Clean completed build steps - for (int index = buildStepsToWait.Count - 1; index >= 0; index--) - { - var buildStepToWait = buildStepsToWait[index]; - if (buildStepToWait.Processed) - buildStepsToWait.RemoveAt(index); - } - - // wait for a task to complete - if (buildStepsToWait.Count >= MaxParallelSteps) - await CompleteOneBuildStep(executeContext, buildStepsToWait); - - // Should we check for all tasks or only high priority tasks? (priority < 0) - bool checkOnlyForHighPriorityTasks = buildStepsToWait.Count >= MaxParallelSteps; - - // Transform item into build step - var buildStep = buildStepProvider.GetNextBuildStep(checkOnlyForHighPriorityTasks ? -1 : int.MaxValue); - - // No job => passively wait - if (buildStep == null) - { - newWorkAvailable.WaitOne(); - continue; - } - - // Safeguard if the provided build step is already processed - if (buildStep.Processed) - continue; - - // Schedule build step - executeContext.ScheduleBuildStep(buildStep); - buildStepsToWait.Add(buildStep); + var buildStepToWait = buildStepsToWait[index]; + if (buildStepToWait.Processed) + buildStepsToWait.RemoveAt(index); } - } - /// - public override string ToString() - { - return "DynamicBuildStep"; - } + // wait for a task to complete + if (buildStepsToWait.Count >= MaxParallelSteps) + await CompleteOneBuildStep(executeContext, buildStepsToWait); - private async Task CompleteOneBuildStep(IExecuteContext executeContext, List buildStepsToWait) - { - // Too many build steps, wait for one to finish - var waitHandles = buildStepsToWait.Select(x => ((IAsyncResult)x.ExecutedAsync()).AsyncWaitHandle); + // Should we check for all tasks or only high priority tasks? (priority < 0) + bool checkOnlyForHighPriorityTasks = buildStepsToWait.Count >= MaxParallelSteps; + + // Transform item into build step + var buildStep = buildStepProvider.GetNextBuildStep(checkOnlyForHighPriorityTasks ? -1 : int.MaxValue); - // Should we listen for new high priority tasks? - if (buildStepsToWait.Count >= MaxParallelSteps && buildStepsToWait.Count < MaxParallelSteps + MaxHighPriorityParallelSteps) + // No job => passively wait + if (buildStep == null) { - waitHandles = waitHandles.Concat(new[] { newWorkAvailable }); + newWorkAvailable.WaitOne(); + continue; } - var completedItem = WaitHandle.WaitAny(waitHandles.ToArray()); + // Safeguard if the provided build step is already processed + if (buildStep.Processed) + continue; - var completeBuildStep = completedItem < buildStepsToWait.Count ? buildStepsToWait[completedItem] : null; - if (completeBuildStep == null) - { - // Not an actual task completed, but we've got to check if there is a new high priority task so exit immediatly - return; - } + // Schedule build step + executeContext.ScheduleBuildStep(buildStep); + buildStepsToWait.Add(buildStep); + } + } - // wait for completion of all its spawned and dependent steps - // (probably instant most of the time, but it would be good to have a better ExecutedAsync to check that together as well) - await ListBuildStep.WaitCommands(new List { completeBuildStep }); + /// + public override string ToString() + { + return "DynamicBuildStep"; + } + + private async Task CompleteOneBuildStep(IExecuteContext executeContext, List buildStepsToWait) + { + // Too many build steps, wait for one to finish + var waitHandles = buildStepsToWait.Select(x => ((IAsyncResult)x.ExecutedAsync()).AsyncWaitHandle); - // Remove from list of build step to wait - buildStepsToWait.Remove(completeBuildStep); + // Should we listen for new high priority tasks? + if (buildStepsToWait.Count >= MaxParallelSteps && buildStepsToWait.Count < MaxParallelSteps + MaxHighPriorityParallelSteps) + { + waitHandles = waitHandles.Concat([newWorkAvailable]); } + + var completedItem = WaitHandle.WaitAny(waitHandles.ToArray()); + + var completeBuildStep = completedItem < buildStepsToWait.Count ? buildStepsToWait[completedItem] : null; + if (completeBuildStep == null) + { + // Not an actual task completed, but we've got to check if there is a new high priority task so exit immediatly + return; + } + + // wait for completion of all its spawned and dependent steps + // (probably instant most of the time, but it would be good to have a better ExecutedAsync to check that together as well) + await ListBuildStep.WaitCommands([completeBuildStep]); + + // Remove from list of build step to wait + buildStepsToWait.Remove(completeBuildStep); } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/DynamicBuilder.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/DynamicBuilder.cs index 6abedc2809..3b4ff8bec5 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/DynamicBuilder.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/DynamicBuilder.cs @@ -1,73 +1,70 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Threading; using Stride.Core.Diagnostics; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +/// +/// This class allow to run a given in a new thread. It will run a single +/// that can be fed with a given . +/// +public class DynamicBuilder : IDisposable { /// - /// This class allow to run a given in a new thread. It will run a single - /// that can be fed with a given . + /// The thread that will run an instance of to build provided steps. /// - public class DynamicBuilder : IDisposable - { - /// - /// The thread that will run an instance of to build provided steps. - /// - private readonly Thread builderThread; - private readonly Builder builder; - private readonly DynamicBuildStep dynamicBuildStep; + private readonly Thread builderThread; + private readonly Builder builder; + private readonly DynamicBuildStep dynamicBuildStep; - /// - /// Initializes a new instance of the class. - /// - /// The name of this instance. Used to name the created thread. - /// The builder to use. - /// The build step provider to use. - public DynamicBuilder(Builder builder, IBuildStepProvider buildStepProvider, string name = null) + /// + /// Initializes a new instance of the class. + /// + /// The name of this instance. Used to name the created thread. + /// The builder to use. + /// The build step provider to use. + public DynamicBuilder(Builder builder, IBuildStepProvider buildStepProvider, string? name = null) + { + this.builder = builder; + dynamicBuildStep = new DynamicBuildStep(buildStepProvider, builder.ThreadCount); + builderThread = new Thread(SafeAction.Wrap(BuilderThread)) { IsBackground = true }; + if (!string.IsNullOrEmpty(name)) { - this.builder = builder; - dynamicBuildStep = new DynamicBuildStep(buildStepProvider, builder.ThreadCount); - builderThread = new Thread(SafeAction.Wrap(BuilderThread)) { IsBackground = true }; - if (!string.IsNullOrEmpty(name)) - { - builderThread.Name = name; - } + builderThread.Name = name; } + } - /// - /// Starts the thread an run the builder. - /// - public void Start() - { - builderThread.Start(); - } + /// + /// Starts the thread an run the builder. + /// + public void Start() + { + builderThread.Start(); + } - /// - /// Cancels any build in progress and wait for the thread to exit. - /// - public void Dispose() - { - builder.CancelBuild(); - dynamicBuildStep.NotifyNewWorkAvailable(); - builderThread.Join(); - } + /// + /// Cancels any build in progress and wait for the thread to exit. + /// + public void Dispose() + { + builder.CancelBuild(); + dynamicBuildStep.NotifyNewWorkAvailable(); + builderThread.Join(); + } - /// - /// Notify the that a new build step is available. - /// - public void NotifyBuildStepAvailable() - { - dynamicBuildStep.NotifyNewWorkAvailable(); - } + /// + /// Notify the that a new build step is available. + /// + public void NotifyBuildStepAvailable() + { + dynamicBuildStep.NotifyNewWorkAvailable(); + } - private void BuilderThread() - { - builder.Reset(); - builder.Root.Add(dynamicBuildStep); - builder.Run(Builder.Mode.Build, true); - } + private void BuilderThread() + { + builder.Reset(); + builder.Root.Add(dynamicBuildStep); + builder.Run(Builder.Mode.Build, true); } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/FileVersionStorage.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/FileVersionStorage.cs index 0299d71456..29f09c1db6 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/FileVersionStorage.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/FileVersionStorage.cs @@ -1,126 +1,117 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; + using System.Text; -using Stride.Core; using Stride.Core.IO; using Stride.Core.Serialization; using Stride.Core.Serialization.Serializers; using Stride.Core.Storage; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +/// +/// Storage used for associated with an . +/// +[DataSerializerGlobal(typeof(KeyValuePairSerializer))] +public sealed class FileVersionStorage : DictionaryStore { /// - /// Storage used for associated with an . + /// Initializes a new instance of the class. + /// + /// The localStream. + public FileVersionStorage(Stream stream) : base(stream) + { + } + + /// + /// Compacts the specified storage path. /// - [DataSerializerGlobal(typeof(KeyValuePairSerializer))] - public sealed class FileVersionStorage : DictionaryStore + /// The storage path. + /// true if the storage path was successfully compacted, false otherwise. + public static bool Compact(string storagePath) { - /// - /// Initializes a new instance of the class. - /// - /// The localStream. - public FileVersionStorage(Stream stream) : base(stream) + FileStream fileStreamExclusive; + try { + fileStreamExclusive = new FileStream(storagePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None); } - - /// - /// Compacts the specified storage path. - /// - /// The storage path. - /// true if the storage path was successfully compacted, false otherwise. - public static bool Compact(string storagePath) + catch (Exception) { - FileStream fileStreamExclusive; - try - { - fileStreamExclusive = new FileStream(storagePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None); - } - catch (Exception) - { - return false; - } + return false; + } - try + try + { + using var localTracker = new FileVersionStorage(fileStreamExclusive) { UseTransaction = true, AutoLoadNewValues = false }; + localTracker.LoadNewValues(); + var latestVersion = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var keyValue in localTracker.GetValues()) { - using (var localTracker = new FileVersionStorage(fileStreamExclusive) { UseTransaction = true, AutoLoadNewValues = false }) + var filePath = keyValue.Key.Path; + if (!latestVersion.TryGetValue(filePath, out var previousKeyValue) || keyValue.Key.LastModifiedDate > previousKeyValue.Key.LastModifiedDate) { - localTracker.LoadNewValues(); - var latestVersion = new Dictionary>(StringComparer.OrdinalIgnoreCase); - foreach (var keyValue in localTracker.GetValues()) - { - var filePath = keyValue.Key.Path; - KeyValuePair previousKeyValue; - if (!latestVersion.TryGetValue(filePath, out previousKeyValue) || keyValue.Key.LastModifiedDate > previousKeyValue.Key.LastModifiedDate) - { - latestVersion[filePath] = keyValue; - } - } - localTracker.Reset(); - localTracker.AddValues(latestVersion.Values); - localTracker.Save(); + latestVersion[filePath] = keyValue; } } - catch (Exception) - { - return false; + localTracker.Reset(); + localTracker.AddValues(latestVersion.Values); + localTracker.Save(); + } + catch (Exception) + { + return false; - } - return true; } + return true; + } - protected override List> ReadEntries(System.IO.Stream localStream) + protected override List> ReadEntries(System.IO.Stream localStream) + { + // As the FileVersionStorage is not used at runtime but only at build time, it is not currently optimized + // TODO: performance of encoding/decoding could be improved using some manual (but much more laborious) code. + var reader = new StreamReader(localStream, Encoding.UTF8); + string? line; + var entries = new List>(); + while ((line = reader.ReadLine()) != null) { - // As the FileVersionStorage is not used at runtime but only at build time, it is not currently optimized - // TODO: performance of encoding/decoding could be improved using some manual (but much more laborious) code. - var reader = new StreamReader(localStream, Encoding.UTF8); - string line; - var entries = new List>(); - while ((line = reader.ReadLine()) != null) + var values = line.Split('\t'); + if (values.Length != 4) { - var values = line.Split('\t'); - if (values.Length != 4) - { - continue; - } - // Path: values[0] - var key = new FileVersionKey() { Path = values[0]}; - - long dateTime; - if (!long.TryParse(values[1], out dateTime)) - { - throw new InvalidOperationException("Unable to decode datetime [{0}] when reading file version index".ToFormat(values[1])); - } - key.LastModifiedDate = new DateTime(dateTime); + continue; + } + // Path: values[0] + var key = new FileVersionKey() { Path = values[0]}; - if (!long.TryParse(values[2], out key.FileSize)) - { - throw new InvalidOperationException("Unable to decode filesize [{0}] when reading file version index".ToFormat(values[2])); - } - var objectIdStr = values[3]; + if (!long.TryParse(values[1], out var dateTime)) + { + throw new InvalidOperationException("Unable to decode datetime [{0}] when reading file version index".ToFormat(values[1])); + } + key.LastModifiedDate = new DateTime(dateTime); - ObjectId objectId; - if (!ObjectId.TryParse(objectIdStr, out objectId)) - { - throw new InvalidOperationException("Unable to decode objectid [{0}] when reading file version index".ToFormat(objectIdStr)); - } + if (!long.TryParse(values[2], out key.FileSize)) + { + throw new InvalidOperationException("Unable to decode filesize [{0}] when reading file version index".ToFormat(values[2])); + } + var objectIdStr = values[3]; - var entry = new KeyValuePair(key, objectId); - entries.Add(entry); + if (!ObjectId.TryParse(objectIdStr, out var objectId)) + { + throw new InvalidOperationException("Unable to decode objectid [{0}] when reading file version index".ToFormat(objectIdStr)); } - return entries; + + var entry = new KeyValuePair(key, objectId); + entries.Add(entry); } + return entries; + } - protected override void WriteEntry(Stream localStream, KeyValuePair value) - { - ArgumentNullException.ThrowIfNull(localStream); + protected override void WriteEntry(Stream localStream, KeyValuePair value) + { + ArgumentNullException.ThrowIfNull(localStream); - var key = value.Key; - var line = $"{key.Path}\t{key.LastModifiedDate.Ticks}\t{key.FileSize}\t{value.Value}\n"; - var bytes = Encoding.UTF8.GetBytes(line); - localStream.Write(bytes, 0, bytes.Length); - } + var key = value.Key; + var line = $"{key.Path}\t{key.LastModifiedDate.Ticks}\t{key.FileSize}\t{value.Value}\n"; + var bytes = Encoding.UTF8.GetBytes(line); + localStream.Write(bytes, 0, bytes.Length); } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/FileVersionTracker.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/FileVersionTracker.cs index f39db7e194..411a470d5e 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/FileVersionTracker.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/FileVersionTracker.cs @@ -1,220 +1,206 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using Stride.Core; + using Stride.Core.Diagnostics; using Stride.Core.Storage; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +/// +/// A tracker of file date. +/// +public class FileVersionTracker : IDisposable { + private const string DefaultFileVersionTrackerFile = @"Stride\FileVersionTracker.cache"; + private readonly FileVersionStorage storage; + private readonly Dictionary locks; + private static readonly Logger log = GlobalLogger.GetLogger("FileVersionTracker"); + private static readonly object lockDefaultTracker = new(); + private static FileVersionTracker? defaultFileVersionTracker; + /// - /// A tracker of file date. + /// Initializes a new instance of the class. /// - public class FileVersionTracker : IDisposable + /// The stream. + public FileVersionTracker(Stream stream) { - private const string DefaultFileVersionTrackerFile = @"Stride\FileVersionTracker.cache"; - private readonly FileVersionStorage storage; - private readonly Dictionary locks; - private static readonly Logger log = GlobalLogger.GetLogger("FileVersionTracker"); - private static readonly object lockDefaultTracker = new object(); - private static FileVersionTracker defaultFileVersionTracker; - - /// - /// Initializes a new instance of the class. - /// - /// The stream. - public FileVersionTracker(Stream stream) - { - storage = new FileVersionStorage(stream); - locks = new Dictionary(); - } + storage = new FileVersionStorage(stream); + locks = []; + } - /// - /// Gets the default file version tracker for this machine. - /// - /// FileVersionTracker. - public static FileVersionTracker GetDefault() + /// + /// Gets the default file version tracker for this machine. + /// + /// FileVersionTracker. + public static FileVersionTracker GetDefault() + { + lock (lockDefaultTracker) { - lock (lockDefaultTracker) + if (defaultFileVersionTracker == null) { - if (defaultFileVersionTracker == null) + var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), DefaultFileVersionTrackerFile); + var directory = Path.GetDirectoryName(filePath); + if (directory != null && !Directory.Exists(directory)) { - var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), DefaultFileVersionTrackerFile); - var directory = Path.GetDirectoryName(filePath); - if (directory != null && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - // Loads the file version cache - defaultFileVersionTracker = Load(filePath); + Directory.CreateDirectory(directory); } + + // Loads the file version cache + defaultFileVersionTracker = Load(filePath); } - return defaultFileVersionTracker; } + return defaultFileVersionTracker; + } - /// - /// Loads previous versions stored from the specified file path. - /// - /// The file path. - /// FileVersionTracker. - public static FileVersionTracker Load(string filePath) + /// + /// Loads previous versions stored from the specified file path. + /// + /// The file path. + /// FileVersionTracker. + public static FileVersionTracker Load(string filePath) + { + // Try to compact it before using it + FileVersionStorage.Compact(filePath); + + bool isFirstPass = true; + while (true) { - // Try to compact it before using it - FileVersionStorage.Compact(filePath); + FileStream? fileStream = null; - bool isFirstPass = true; - while (true) + // Try to open the file, if we get an exception, this might be due only because someone is locking the file to + // save it while we are trying to open it + const int RetryOpenFileStream = 20; + var random = new Random(); + for (int i = 0; i < RetryOpenFileStream; i++) { - FileStream fileStream = null; - - // Try to open the file, if we get an exception, this might be due only because someone is locking the file to - // save it while we are trying to open it - const int RetryOpenFileStream = 20; - var random = new Random(); - for (int i = 0; i < RetryOpenFileStream; i++) - { - try - { - fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite); - break; - } - catch (Exception) - { - if ((i + 1) == RetryOpenFileStream) - throw; - - Thread.Sleep(50 + random.Next(100)); - } - } - - var tracker = new FileVersionTracker(fileStream); try { - tracker.storage.LoadNewValues(); - return tracker; + fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite); + break; } catch (Exception) { - // If an exception occurred, we are going to try to recover from it by reseting it. - // reset file length to 0 - fileStream.SetLength(0); - tracker.Dispose(); - if (!isFirstPass) - { + if ((i + 1) == RetryOpenFileStream) throw; - } + + Thread.Sleep(50 + random.Next(100)); } - isFirstPass = false; } - } - - public ObjectId ComputeFileHash(string filePath) - { - var inputVersionKey = new FileVersionKey(filePath); - storage.LoadNewValues(); - // Perform a lock per file as it can be expensive to compute - // them at the same time (for large file) - object versionLock; - lock (locks) + var tracker = new FileVersionTracker(fileStream); + try { - if (!locks.TryGetValue(inputVersionKey, out versionLock)) - { - versionLock = new object(); - locks.Add(inputVersionKey, versionLock); - } + tracker.storage.LoadNewValues(); + return tracker; } - - var hash = ObjectId.Empty; - lock (versionLock) + catch (Exception) { - if (!storage.TryGetValue(inputVersionKey, out hash)) + // If an exception occurred, we are going to try to recover from it by reseting it. + // reset file length to 0 + fileStream.SetLength(0); + tracker.Dispose(); + if (!isFirstPass) { - // TODO: we might want to allow retries, timeout, etc. since file processed here are files currently being edited by user - try - { - using (var fileStream = File.OpenRead(filePath)) - using (var stream = new DigestStream(Stream.Null)) - { - fileStream.CopyTo(stream); - hash = stream.CurrentHash; - } - } - catch (Exception ex) - { - log.Debug($"Cannot calculate hash for file [{filePath}]", ex); - } - storage[inputVersionKey] = hash; + throw; } } + isFirstPass = false; + } + } + + public ObjectId ComputeFileHash(string filePath) + { + var inputVersionKey = new FileVersionKey(filePath); + storage.LoadNewValues(); - return hash; + // Perform a lock per file as it can be expensive to compute + // them at the same time (for large file) + object? versionLock; + lock (locks) + { + if (!locks.TryGetValue(inputVersionKey, out versionLock)) + { + versionLock = new object(); + locks.Add(inputVersionKey, versionLock); + } } - public void Dispose() + var hash = ObjectId.Empty; + lock (versionLock) { - storage.Dispose(); + if (!storage.TryGetValue(inputVersionKey, out hash)) + { + // TODO: we might want to allow retries, timeout, etc. since file processed here are files currently being edited by user + try + { + using var fileStream = File.OpenRead(filePath); + using var stream = new DigestStream(Stream.Null); + fileStream.CopyTo(stream); + hash = stream.CurrentHash; + } + catch (Exception ex) + { + log.Debug($"Cannot calculate hash for file [{filePath}]", ex); + } + storage[inputVersionKey] = hash; + } } + + return hash; } - [DataContract] - public struct FileVersionKey : IEquatable + public void Dispose() { - public string Path; + storage.Dispose(); + } +} - public DateTime LastModifiedDate; +[DataContract] +public struct FileVersionKey : IEquatable +{ + public string Path; - public long FileSize; + public DateTime LastModifiedDate; - public FileVersionKey(string path) - { - if (path == null) throw new ArgumentNullException("path"); - Path = path; - LastModifiedDate = DateTime.MinValue; - FileSize = -1; + public long FileSize; - if (File.Exists(path)) - { - LastModifiedDate = File.GetLastWriteTime(path); - FileSize = new FileInfo(path).Length; - } - } + public FileVersionKey(string path) + { + ArgumentNullException.ThrowIfNull(path); + Path = path; + LastModifiedDate = DateTime.MinValue; + FileSize = -1; - public bool Equals(FileVersionKey other) + if (File.Exists(path)) { - return string.Equals(Path, other.Path) && LastModifiedDate.Equals(other.LastModifiedDate) && FileSize == other.FileSize; + LastModifiedDate = File.GetLastWriteTime(path); + FileSize = new FileInfo(path).Length; } + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is FileVersionKey && Equals((FileVersionKey)obj); - } + public readonly bool Equals(FileVersionKey other) + { + return string.Equals(Path, other.Path) && LastModifiedDate.Equals(other.LastModifiedDate) && FileSize == other.FileSize; + } - public override int GetHashCode() - { - unchecked - { - int hashCode = (Path != null ? Path.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ LastModifiedDate.GetHashCode(); - hashCode = (hashCode * 397) ^ FileSize.GetHashCode(); - return hashCode; - } - } + public override readonly bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is FileVersionKey key && Equals(key); + } - public static bool operator ==(FileVersionKey left, FileVersionKey right) - { - return left.Equals(right); - } + public override readonly int GetHashCode() + { + return HashCode.Combine(Path, LastModifiedDate, FileSize); + } - public static bool operator !=(FileVersionKey left, FileVersionKey right) - { - return !left.Equals(right); - } + public static bool operator ==(FileVersionKey left, FileVersionKey right) + { + return left.Equals(right); } + public static bool operator !=(FileVersionKey left, FileVersionKey right) + { + return !left.Equals(right); + } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/IBuildMonitorRemote.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/IBuildMonitorRemote.cs index 8a207238d1..96a5c2274a 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/IBuildMonitorRemote.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/IBuildMonitorRemote.cs @@ -1,49 +1,46 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public interface IBuildMonitorRemote { - public interface IBuildMonitorRemote - { - int Ping(); + int Ping(); - void StartBuild(Guid buildId, DateTime time); + void StartBuild(Guid buildId, DateTime time); - void SendBuildStepInfo(Guid buildId, long executionId, string description, DateTime startTime); + void SendBuildStepInfo(Guid buildId, long executionId, string description, DateTime startTime); - void SendCommandLog(Guid buildId, DateTime startTime, long microthreadId, List messages); + void SendCommandLog(Guid buildId, DateTime startTime, long microthreadId, List messages); - void SendMicrothreadEvents(Guid buildId, DateTime startTime, DateTime now, IEnumerable microthreadJobInfo); + void SendMicrothreadEvents(Guid buildId, DateTime startTime, DateTime now, IEnumerable microthreadJobInfo); - void SendBuildStepResult(Guid buildId, DateTime startTime, long microthreadId, ResultStatus status); + void SendBuildStepResult(Guid buildId, DateTime startTime, long microthreadId, ResultStatus status); - void EndBuild(Guid buildId, DateTime time); - } - public class MicrothreadNotification + void EndBuild(Guid buildId, DateTime time); +} +public class MicrothreadNotification +{ + public enum NotificationType + { + JobStarted, + JobEnded, + }; + + public int ThreadId; + public long MicrothreadId; + public long MicrothreadJobInfoId; + public long Time; + public NotificationType Type; + + public MicrothreadNotification() { } + + public MicrothreadNotification(int threadId, long microthreadId, long microthreadJobId, long time, NotificationType type) { - public enum NotificationType - { - JobStarted, - JobEnded, - }; - - public int ThreadId; - public long MicrothreadId; - public long MicrothreadJobInfoId; - public long Time; - public NotificationType Type; - - public MicrothreadNotification() { } - - public MicrothreadNotification(int threadId, long microthreadId, long microthreadJobId, long time, NotificationType type) - { - ThreadId = threadId; - MicrothreadId = microthreadId; - MicrothreadJobInfoId = microthreadJobId; - Time = time; - Type = type; - } + ThreadId = threadId; + MicrothreadId = microthreadId; + MicrothreadJobInfoId = microthreadJobId; + Time = time; + Type = type; } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/IBuildStepProvider.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/IBuildStepProvider.cs index 37c562f494..99f8b36c11 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/IBuildStepProvider.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/IBuildStepProvider.cs @@ -1,16 +1,16 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.BuildEngine + +namespace Stride.Core.BuildEngine; + +/// +/// This interface describes a class that is capable of providing build steps to a . +/// +public interface IBuildStepProvider { /// - /// This interface describes a class that is capable of providing build steps to a . + /// Gets the next build step to execute. /// - public interface IBuildStepProvider - { - /// - /// Gets the next build step to execute. - /// - /// The next build step to execute, or null if there is no build step to execute. - BuildStep GetNextBuildStep(int maxPriority); - } + /// The next build step to execute, or null if there is no build step to execute. + BuildStep GetNextBuildStep(int maxPriority); } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/ICommandContext.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/ICommandContext.cs index 7009e70f4e..5e55609e55 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/ICommandContext.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/ICommandContext.cs @@ -1,26 +1,25 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; + using Stride.Core.Storage; using Stride.Core.Diagnostics; using Stride.Core.Serialization.Contents; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public interface ICommandContext { - public interface ICommandContext - { - Command CurrentCommand { get; } + Command CurrentCommand { get; } - LoggerResult Logger { get; } + LoggerResult Logger { get; } - IEnumerable> GetOutputObjectsGroups(); + IEnumerable> GetOutputObjectsGroups(); - void RegisterInputDependency(ObjectUrl url); + void RegisterInputDependency(ObjectUrl url); - void RegisterOutput(ObjectUrl url, ObjectId hash); + void RegisterOutput(ObjectUrl url, ObjectId hash); - void RegisterCommandLog(IEnumerable logMessages); + void RegisterCommandLog(IEnumerable logMessages); - void AddTag(ObjectUrl url, string tag); - } + void AddTag(ObjectUrl url, string tag); } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/IExecuteContext.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/IExecuteContext.cs index fff2cf3a81..b3755e624a 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/IExecuteContext.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/IExecuteContext.cs @@ -1,32 +1,29 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Threading; using Stride.Core.Storage; using Stride.Core.Diagnostics; using Stride.Core.Serialization.Contents; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public interface IPrepareContext { - public interface IPrepareContext - { - Logger Logger { get; } - ObjectId ComputeInputHash(UrlType type, string filePath); - } + Logger Logger { get; } + ObjectId ComputeInputHash(UrlType type, string filePath); +} - public interface IExecuteContext : IPrepareContext - { - CancellationTokenSource CancellationTokenSource { get; } - ObjectDatabase ResultMap { get; } - Dictionary Variables { get; } +public interface IExecuteContext : IPrepareContext +{ + CancellationTokenSource CancellationTokenSource { get; } + ObjectDatabase ResultMap { get; } + Dictionary Variables { get; } - void ScheduleBuildStep(BuildStep step); + void ScheduleBuildStep(BuildStep step); - IEnumerable> GetOutputObjectsGroups(); + IEnumerable> GetOutputObjectsGroups(); - CommandBuildStep IsCommandCurrentlyRunning(ObjectId commandHash); - void NotifyCommandBuildStepStarted(CommandBuildStep commandBuildStep, ObjectId commandHash); - void NotifyCommandBuildStepFinished(CommandBuildStep commandBuildStep, ObjectId commandHash); - } + CommandBuildStep? IsCommandCurrentlyRunning(ObjectId commandHash); + void NotifyCommandBuildStepStarted(CommandBuildStep commandBuildStep, ObjectId commandHash); + void NotifyCommandBuildStepFinished(CommandBuildStep commandBuildStep, ObjectId commandHash); } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/IForwardSerializableLogRemote.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/IForwardSerializableLogRemote.cs index 610f9dbff3..6c052767b8 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/IForwardSerializableLogRemote.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/IForwardSerializableLogRemote.cs @@ -3,10 +3,9 @@ using Stride.Core.Diagnostics; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public interface IForwardSerializableLogRemote { - public interface IForwardSerializableLogRemote - { - void ForwardSerializableLog(SerializableLogMessage message); - } + void ForwardSerializableLog(SerializableLogMessage message); } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/IndexFileCommand.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/IndexFileCommand.cs index 60c46f8dca..1ba72c6836 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/IndexFileCommand.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/IndexFileCommand.cs @@ -1,44 +1,43 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +/// +/// A that reads and/or writes to the index file. +/// +public abstract class IndexFileCommand : Command { - /// - /// A that reads and/or writes to the index file. - /// - public abstract class IndexFileCommand : Command + private BuildTransaction buildTransaction; + + public override void PreCommand(ICommandContext commandContext) { - private BuildTransaction buildTransaction; + base.PreCommand(commandContext); - public override void PreCommand(ICommandContext commandContext) - { - base.PreCommand(commandContext); + buildTransaction = MicrothreadLocalDatabases.CreateTransaction(commandContext.GetOutputObjectsGroups()); + MicrothreadLocalDatabases.MountDatabase(buildTransaction); + } - buildTransaction = MicrothreadLocalDatabases.CreateTransaction(commandContext.GetOutputObjectsGroups()); - MicrothreadLocalDatabases.MountDatabase(buildTransaction); - } + public override void PostCommand(ICommandContext commandContext, ResultStatus status) + { + base.PostCommand(commandContext, status); - public override void PostCommand(ICommandContext commandContext, ResultStatus status) + if (status == ResultStatus.Successful) { - base.PostCommand(commandContext, status); - - if (status == ResultStatus.Successful) + // Save list of newly changed URLs in CommandResult.OutputObjects + foreach (var entry in buildTransaction.GetTransactionIdMap()) { - // Save list of newly changed URLs in CommandResult.OutputObjects - foreach (var entry in buildTransaction.GetTransactionIdMap()) - { - commandContext.RegisterOutput(entry.Key, entry.Value); - } - - // Note: In case of remote process, the remote process will save the index map. - // Alternative would be to not save it and just forward results to the master builder who would commit results locally. - // Not sure which is the best. - // - // Anyway, current approach should be OK for now since the index map is "process-safe" (as long as we load new values as necessary). - //contentIndexMap.Save(); + commandContext.RegisterOutput(entry.Key, entry.Value); } - MicrothreadLocalDatabases.UnmountDatabase(); + // Note: In case of remote process, the remote process will save the index map. + // Alternative would be to not save it and just forward results to the master builder who would commit results locally. + // Not sure which is the best. + // + // Anyway, current approach should be OK for now since the index map is "process-safe" (as long as we load new values as necessary). + //contentIndexMap.Save(); } + + MicrothreadLocalDatabases.UnmountDatabase(); } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/ListBuildStep.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/ListBuildStep.cs index c0cdf42ce6..cb4bb8d512 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/ListBuildStep.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/ListBuildStep.cs @@ -1,343 +1,332 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Serialization.Contents; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Stride.Core.Storage; -namespace Stride.Core.BuildEngine -{ - public class ListBuildStep : BuildStep - { - private readonly List steps = new List(); - private readonly List executedSteps = new List(); - private readonly Dictionary inputObjects = new Dictionary(); - private readonly Dictionary outputObjects = new Dictionary(); - private int mergeCounter; +namespace Stride.Core.BuildEngine; - /// - public override string Title => ToString(); +public class ListBuildStep : BuildStep +{ + private readonly List steps = []; + private readonly List executedSteps = []; + private readonly Dictionary inputObjects = []; + private readonly Dictionary outputObjects = []; + private int mergeCounter; - public IReadOnlyDictionary InputObjects => inputObjects; + /// + public override string Title => ToString(); - public IReadOnlyDictionary OutputObjects => outputObjects; + public IReadOnlyDictionary InputObjects => inputObjects; - /// - public override IEnumerable> OutputObjectIds => outputObjects.Select(x => new KeyValuePair(x.Key, x.Value.ObjectId)); + public IReadOnlyDictionary OutputObjects => outputObjects; - public IEnumerable Steps => steps; + /// + public override IEnumerable> OutputObjectIds => outputObjects.Select(x => new KeyValuePair(x.Key, x.Value.ObjectId)); - /// - public override string ToString() => $"Build step list ({Count} items)"; + public IEnumerable Steps => steps; - public override async Task Execute(IExecuteContext executeContext, BuilderContext builderContext) - { - var buildStepsToWait = new List(); + /// + public override string ToString() => $"Build step list ({Count} items)"; - // Process prerequisites build steps first - if (PrerequisiteSteps.Count > 0) - await CompleteCommands(executeContext, PrerequisiteSteps.ToList()); + public override async Task Execute(IExecuteContext executeContext, BuilderContext builderContext) + { + var buildStepsToWait = new List(); - foreach (var child in Steps) - { - executeContext.ScheduleBuildStep(child); - buildStepsToWait.Add(child); + // Process prerequisites build steps first + if (PrerequisiteSteps.Count > 0) + await CompleteCommands(executeContext, [.. PrerequisiteSteps]); - executedSteps.Add(child); - } - - await CompleteCommands(executeContext, buildStepsToWait); + foreach (var child in Steps) + { + executeContext.ScheduleBuildStep(child); + buildStepsToWait.Add(child); - return ComputeResultStatusFromExecutedSteps(); + executedSteps.Add(child); } - /// - /// Determine the result status of an execution of enumeration of build steps. - /// - /// The result status of the execution. - protected ResultStatus ComputeResultStatusFromExecutedSteps() - { - if (executedSteps.Count == 0) - return ResultStatus.Successful; - - // determine the result status of the list based on the children executed steps - // -> One or more children canceled => canceled - // -> One or more children failed (Prerequisite or Command) and none canceled => failed - // -> One or more children succeeded and none canceled nor failed => succeeded - // -> All the children were successful without triggering => not triggered was successful - var result = executedSteps[0].Status; - foreach (var executedStep in executedSteps) - { - if (executedStep.Status == ResultStatus.Cancelled) - { - result = ResultStatus.Cancelled; - break; - } - - if (executedStep.Failed) - result = ResultStatus.Failed; - else if (executedStep.Status == ResultStatus.Successful && result != ResultStatus.Failed) - result = ResultStatus.Successful; - } + await CompleteCommands(executeContext, buildStepsToWait); - return result; - } + return ComputeResultStatusFromExecutedSteps(); + } - /// - /// Wait for given build steps to finish, then processes their inputs and outputs. - /// - /// The execute context. - /// The build steps to wait. - /// - protected async Task CompleteCommands(IExecuteContext executeContext, List buildStepsToWait) + /// + /// Determine the result status of an execution of enumeration of build steps. + /// + /// The result status of the execution. + protected ResultStatus ComputeResultStatusFromExecutedSteps() + { + if (executedSteps.Count == 0) + return ResultStatus.Successful; + + // determine the result status of the list based on the children executed steps + // -> One or more children canceled => canceled + // -> One or more children failed (Prerequisite or Command) and none canceled => failed + // -> One or more children succeeded and none canceled nor failed => succeeded + // -> All the children were successful without triggering => not triggered was successful + var result = executedSteps[0].Status; + foreach (var executedStep in executedSteps) { - await WaitCommands(buildStepsToWait); - - // TODO: Merge results of sub lists - foreach (var buildStep in buildStepsToWait) + if (executedStep.Status == ResultStatus.Cancelled) { - var enumerableBuildStep = buildStep as ListBuildStep; - if (enumerableBuildStep != null) - { - // Merge results from sub list - - // Step1: Check inputs/outputs conflicts - foreach (var inputObject in enumerableBuildStep.inputObjects) - { - CheckInputObject(executeContext, inputObject.Key, inputObject.Value.Command); - } - - foreach (var outputObject in enumerableBuildStep.OutputObjects) - { - CheckOutputObject(executeContext, outputObject.Key, outputObject.Value.Command); - } - - // Step2: Add inputs/outputs - foreach (var inputObject in enumerableBuildStep.inputObjects) - { - AddInputObject(inputObject.Key, inputObject.Value.Command); - } - - foreach (var outputObject in enumerableBuildStep.OutputObjects) - { - var newOutputObject = AddOutputObject(executeContext, outputObject.Key, outputObject.Value.ObjectId, outputObject.Value.Command); - - // Merge tags - foreach (var tag in outputObject.Value.Tags) - { - newOutputObject.Tags.Add(tag); - } - } - } - - var commandBuildStep = buildStep as CommandBuildStep; - if (commandBuildStep != null) - { - // Merge results from spawned step - ProcessCommandBuildStepResult(executeContext, commandBuildStep); - } + result = ResultStatus.Cancelled; + break; } - buildStepsToWait.Clear(); - mergeCounter++; + if (executedStep.Failed) + result = ResultStatus.Failed; + else if (executedStep.Status == ResultStatus.Successful && result != ResultStatus.Failed) + result = ResultStatus.Successful; } - protected internal static async Task WaitCommands(List buildStepsToWait) - { - // Wait for steps to be finished - if (buildStepsToWait.Count > 0) - await Task.WhenAll(buildStepsToWait.Select(x => x.ExecutedAsync())); - } + return result; + } - /// - /// Processes the results from a . - /// - /// The execute context. - /// The build step. - private void ProcessCommandBuildStepResult(IExecuteContext executeContext, CommandBuildStep buildStep) + /// + /// Wait for given build steps to finish, then processes their inputs and outputs. + /// + /// The execute context. + /// The build steps to wait. + /// + protected async Task CompleteCommands(IExecuteContext executeContext, List buildStepsToWait) + { + await WaitCommands(buildStepsToWait); + + // TODO: Merge results of sub lists + foreach (var buildStep in buildStepsToWait) { - foreach (var resultInputObject in buildStep.Command.GetInputFiles()) + if (buildStep is ListBuildStep enumerableBuildStep) { - AddInputObject(resultInputObject, buildStep.Command); - } + // Merge results from sub list - if (buildStep.Result != null) - { // Step1: Check inputs/outputs conflicts - foreach (var resultInputObject in buildStep.Result.InputDependencyVersions) + foreach (var inputObject in enumerableBuildStep.inputObjects) { - CheckInputObject(executeContext, resultInputObject.Key, buildStep.Command); + CheckInputObject(executeContext, inputObject.Key, inputObject.Value.Command); } - foreach (var resultOutputObject in buildStep.Result.OutputObjects) + foreach (var outputObject in enumerableBuildStep.OutputObjects) { - CheckOutputObject(executeContext, resultOutputObject.Key, buildStep.Command); + CheckOutputObject(executeContext, outputObject.Key, outputObject.Value.Command); } // Step2: Add inputs/outputs - foreach (var resultInputObject in buildStep.Result.InputDependencyVersions) + foreach (var inputObject in enumerableBuildStep.inputObjects) { - AddInputObject(resultInputObject.Key, buildStep.Command); + AddInputObject(inputObject.Key, inputObject.Value.Command); } - foreach (var resultOutputObject in buildStep.Result.OutputObjects) + foreach (var outputObject in enumerableBuildStep.OutputObjects) { - AddOutputObject(executeContext, resultOutputObject.Key, resultOutputObject.Value, buildStep.Command); - } - } + var newOutputObject = AddOutputObject(executeContext, outputObject.Key, outputObject.Value.ObjectId, outputObject.Value.Command); - // Forward logs - buildStep.Logger.CopyTo(Logger); - - if (buildStep.Result != null) - { - // Resolve tags from TagSymbol - // TODO: Handle removed tags - foreach (var tag in buildStep.Result.TagSymbols) - { - var url = tag.Key; - - // TODO: Improve search complexity? - if (outputObjects.TryGetValue(url, out var outputObject)) + // Merge tags + foreach (var tag in outputObject.Value.Tags) { - outputObject.Tags.Add(tag.Value); + newOutputObject.Tags.Add(tag); } } } - } - /// - /// Adds the input object. Will try to detect input/output conflicts. - /// - /// The execute context. - /// The input object URL. - /// The command. - /// - private void CheckInputObject(IExecuteContext executeContext, ObjectUrl inputObjectUrl, Command command) - { - OutputObject outputObject; - if (outputObjects.TryGetValue(inputObjectUrl, out outputObject) - && outputObject.Command != command - && outputObject.Counter == mergeCounter) + if (buildStep is CommandBuildStep commandBuildStep) { - var error = $"Command {outputObject.Command} is writing {inputObjectUrl} while command {command} is reading it"; - executeContext.Logger.Error(error); - throw new InvalidOperationException(error); + // Merge results from spawned step + ProcessCommandBuildStepResult(executeContext, commandBuildStep); } } - private void AddInputObject(ObjectUrl inputObjectUrl, Command command) - { - OutputObject outputObject; - if (outputObjects.TryGetValue(inputObjectUrl, out outputObject) - && mergeCounter > outputObject.Counter) - { - // Object was outputed by ourself, so reading it as input should be ignored. - return; - } + buildStepsToWait.Clear(); + mergeCounter++; + } - inputObjects[inputObjectUrl] = new InputObject { Command = command, Counter = mergeCounter }; - } + protected internal static async Task WaitCommands(List buildStepsToWait) + { + // Wait for steps to be finished + if (buildStepsToWait.Count > 0) + await Task.WhenAll(buildStepsToWait.Select(x => x.ExecutedAsync())); + } - /// - /// Adds the output object. Will try to detect input/output conflicts, and output with different conflicts. - /// - /// The execute context. - /// The output object URL. - /// The command that produced the output object. - /// Two CommandBuildStep with same inputs did output different results. - private void CheckOutputObject(IExecuteContext executeContext, ObjectUrl outputObjectUrl, Command command) + /// + /// Processes the results from a . + /// + /// The execute context. + /// The build step. + private void ProcessCommandBuildStepResult(IExecuteContext executeContext, CommandBuildStep buildStep) + { + foreach (var resultInputObject in buildStep.Command.GetInputFiles()) { - InputObject inputObject; - if (inputObjects.TryGetValue(outputObjectUrl, out inputObject) - && inputObject.Command != command - && inputObject.Counter == mergeCounter) - { - var error = $"Command {command} is writing {outputObjectUrl} while command {inputObject.Command} is reading it"; - executeContext.Logger.Error(error); - throw new InvalidOperationException(error); - } + AddInputObject(resultInputObject, buildStep.Command); } - private OutputObject AddOutputObject(IExecuteContext executeContext, ObjectUrl outputObjectUrl, ObjectId outputObjectId, Command command) + if (buildStep.Result != null) { - OutputObject outputObject; + // Step1: Check inputs/outputs conflicts + foreach (var resultInputObject in buildStep.Result.InputDependencyVersions) + { + CheckInputObject(executeContext, resultInputObject.Key, buildStep.Command); + } - if (!outputObjects.TryGetValue(outputObjectUrl, out outputObject)) + foreach (var resultOutputObject in buildStep.Result.OutputObjects) { - // New item? - outputObject = new OutputObject(outputObjectUrl, outputObjectId); - outputObjects.Add(outputObjectUrl, outputObject); + CheckOutputObject(executeContext, resultOutputObject.Key, buildStep.Command); } - else + + // Step2: Add inputs/outputs + foreach (var resultInputObject in buildStep.Result.InputDependencyVersions) { - // ObjectId should be similar (if no Wait happened), otherwise two tasks spawned with same parameters did output different results - if (outputObject.ObjectId != outputObjectId && outputObject.Counter == mergeCounter) - { - var error = $"Commands {command} and {outputObject.Command} are both writing {outputObjectUrl} at the same time"; - executeContext.Logger.Error(error); - throw new InvalidOperationException(error); - } + AddInputObject(resultInputObject.Key, buildStep.Command); + } - // Update new ObjectId - outputObject.ObjectId = outputObjectId; + foreach (var resultOutputObject in buildStep.Result.OutputObjects) + { + AddOutputObject(executeContext, resultOutputObject.Key, resultOutputObject.Value, buildStep.Command); } + } + + // Forward logs + buildStep.Logger.CopyTo(Logger); - // Update Counter so that we know if a wait happened since this output object has been merged. - outputObject.Counter = mergeCounter; - outputObject.Command = command; + if (buildStep.Result != null) + { + // Resolve tags from TagSymbol + // TODO: Handle removed tags + foreach (var tag in buildStep.Result.TagSymbols) + { + var url = tag.Key; - return outputObject; + // TODO: Improve search complexity? + if (outputObjects.TryGetValue(url, out var outputObject)) + { + outputObject.Tags.Add(tag.Value); + } + } } + } - public struct InputObject + /// + /// Adds the input object. Will try to detect input/output conflicts. + /// + /// The execute context. + /// The input object URL. + /// The command. + /// + private void CheckInputObject(IExecuteContext executeContext, ObjectUrl inputObjectUrl, Command command) + { + if (outputObjects.TryGetValue(inputObjectUrl, out var outputObject) + && outputObject.Command != command + && outputObject.Counter == mergeCounter) { - public Command Command; - public int Counter; + var error = $"Command {outputObject.Command} is writing {inputObjectUrl} while command {command} is reading it"; + executeContext.Logger.Error(error); + throw new InvalidOperationException(error); } - - /// - public int Count => steps.Count; + } - /// - public IEnumerator GetEnumerator() + private void AddInputObject(ObjectUrl inputObjectUrl, Command command) + { + if (outputObjects.TryGetValue(inputObjectUrl, out var outputObject) + && mergeCounter > outputObject.Counter) { - return steps.GetEnumerator(); + // Object was outputed by ourself, so reading it as input should be ignored. + return; } - public CommandBuildStep Add(Command command) + inputObjects[inputObjectUrl] = new InputObject { Command = command, Counter = mergeCounter }; + } + + /// + /// Adds the output object. Will try to detect input/output conflicts, and output with different conflicts. + /// + /// The execute context. + /// The output object URL. + /// The command that produced the output object. + /// Two CommandBuildStep with same inputs did output different results. + private void CheckOutputObject(IExecuteContext executeContext, ObjectUrl outputObjectUrl, Command command) + { + if (inputObjects.TryGetValue(outputObjectUrl, out var inputObject) + && inputObject.Command != command + && inputObject.Counter == mergeCounter) { - var commandBuildStep = new CommandBuildStep(command); - Add(commandBuildStep); - return commandBuildStep; + var error = $"Command {command} is writing {outputObjectUrl} while command {inputObject.Command} is reading it"; + executeContext.Logger.Error(error); + throw new InvalidOperationException(error); } + } - public IEnumerable Add(IEnumerable commands) + private OutputObject AddOutputObject(IExecuteContext executeContext, ObjectUrl outputObjectUrl, ObjectId outputObjectId, Command command) + { + if (!outputObjects.TryGetValue(outputObjectUrl, out var outputObject)) + { + // New item? + outputObject = new OutputObject(outputObjectUrl, outputObjectId); + outputObjects.Add(outputObjectUrl, outputObject); + } + else { - var commandBuildSteps = commands.Select(x => new CommandBuildStep(x) ).ToArray(); - foreach (var commandBuildStep in commandBuildSteps) + // ObjectId should be similar (if no Wait happened), otherwise two tasks spawned with same parameters did output different results + if (outputObject.ObjectId != outputObjectId && outputObject.Counter == mergeCounter) { - Add(commandBuildStep); + var error = $"Commands {command} and {outputObject.Command} are both writing {outputObjectUrl} at the same time"; + executeContext.Logger.Error(error); + throw new InvalidOperationException(error); } - return commandBuildSteps; + + // Update new ObjectId + outputObject.ObjectId = outputObjectId; } - /// - public void Add(BuildStep buildStep) + // Update Counter so that we know if a wait happened since this output object has been merged. + outputObject.Counter = mergeCounter; + outputObject.Command = command; + + return outputObject; + } + + public struct InputObject + { + public Command Command; + public int Counter; + } + + /// + public int Count => steps.Count; + + /// + public IEnumerator GetEnumerator() + { + return steps.GetEnumerator(); + } + + public CommandBuildStep Add(Command command) + { + var commandBuildStep = new CommandBuildStep(command); + Add(commandBuildStep); + return commandBuildStep; + } + + public IEnumerable Add(IEnumerable commands) + { + var commandBuildSteps = commands.Select(x => new CommandBuildStep(x) ).ToArray(); + foreach (var commandBuildStep in commandBuildSteps) { - if (Status != ResultStatus.NotProcessed) - throw new InvalidOperationException("Unable to add a build step to an already processed ListBuildStep."); + Add(commandBuildStep); + } + return commandBuildSteps; + } - buildStep.Parent = this; - // Propagate priority if we have one - if (Priority.HasValue) - { - // Overwrite only if the new priority is smaller or if we didn't have a priority before - buildStep.Priority = buildStep.Priority.HasValue ? Math.Min(buildStep.Priority.Value, Priority.Value) : Priority.Value; - } - steps.Add(buildStep); + /// + public void Add(BuildStep buildStep) + { + if (Status != ResultStatus.NotProcessed) + throw new InvalidOperationException("Unable to add a build step to an already processed ListBuildStep."); + + buildStep.Parent = this; + // Propagate priority if we have one + if (Priority.HasValue) + { + // Overwrite only if the new priority is smaller or if we didn't have a priority before + buildStep.Priority = buildStep.Priority.HasValue ? Math.Min(buildStep.Priority.Value, Priority.Value) : Priority.Value; } + steps.Add(buildStep); } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/LocalCommandContext.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/LocalCommandContext.cs index b7553f824b..5f5fe5a9a3 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/LocalCommandContext.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/LocalCommandContext.cs @@ -1,38 +1,36 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Threading.Tasks; + using Stride.Core.Storage; using Stride.Core.Diagnostics; using Stride.Core.Serialization.Contents; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public class LocalCommandContext : CommandContextBase { - public class LocalCommandContext : CommandContextBase - { - private readonly IExecuteContext executeContext; + private readonly IExecuteContext executeContext; - private readonly LoggerResult logger; + private readonly LoggerResult logger; - public CommandBuildStep Step { get; protected set; } + public CommandBuildStep Step { get; protected set; } - public override LoggerResult Logger { get { return logger; } } + public override LoggerResult Logger { get { return logger; } } - public LocalCommandContext(IExecuteContext executeContext, CommandBuildStep step, BuilderContext builderContext) : base(step.Command, builderContext) - { - this.executeContext = executeContext; - logger = new ForwardingLoggerResult(executeContext.Logger); - Step = step; - } + public LocalCommandContext(IExecuteContext executeContext, CommandBuildStep step, BuilderContext builderContext) : base(step.Command, builderContext) + { + this.executeContext = executeContext; + logger = new ForwardingLoggerResult(executeContext.Logger); + Step = step; + } - public override IEnumerable> GetOutputObjectsGroups() - { - return Step.GetOutputObjectsGroups(); - } + public override IEnumerable> GetOutputObjectsGroups() + { + return Step.GetOutputObjectsGroups(); + } - public override ObjectId ComputeInputHash(UrlType type, string filePath) - { - return executeContext.ComputeInputHash(type, filePath); - } + public override ObjectId ComputeInputHash(UrlType type, string filePath) + { + return executeContext.ComputeInputHash(type, filePath); } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/MicrothreadLocalDatabases.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/MicrothreadLocalDatabases.cs index 2be2533ff0..8c892bea0e 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/MicrothreadLocalDatabases.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/MicrothreadLocalDatabases.cs @@ -1,119 +1,118 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; + using Stride.Core.IO; using Stride.Core.MicroThreading; using Stride.Core.Serialization.Contents; using Stride.Core.Storage; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +/// +/// A static class that allows to have a different on each . Objects can still be shared +/// between micro-threads by using the method. +/// +public static class MicrothreadLocalDatabases { - /// - /// A static class that allows to have a different on each . Objects can still be shared - /// between micro-threads by using the method. - /// - public static class MicrothreadLocalDatabases - { - private static readonly Dictionary SharedOutputObjects = new Dictionary(); - private static readonly MicroThreadLocal MicroThreadLocalDatabaseFileProvider; + private static readonly Dictionary SharedOutputObjects = []; + private static readonly MicroThreadLocal MicroThreadLocalDatabaseFileProvider; - static MicrothreadLocalDatabases() - { - MicroThreadLocalDatabaseFileProvider = new MicroThreadLocal(); + static MicrothreadLocalDatabases() + { + MicroThreadLocalDatabaseFileProvider = new MicroThreadLocal(); - ProviderService = new MicroThreadLocalProviderService(); - } + ProviderService = new MicroThreadLocalProviderService(); + } - public static IDatabaseFileProviderService ProviderService { get; } + public static IDatabaseFileProviderService ProviderService { get; } - /// - /// Gets a value indicating whether this instance has a valid database file provider. - /// - /// true if this instance has database file provider; otherwise, false. - public static bool HasValidDatabaseFileProvider => MicroThreadLocalDatabaseFileProvider.Value != null; + /// + /// Gets a value indicating whether this instance has a valid database file provider. + /// + /// true if this instance has database file provider; otherwise, false. + public static bool HasValidDatabaseFileProvider => MicroThreadLocalDatabaseFileProvider.Value != null; - /// - /// Gets the currently mounted microthread-local database provider. - /// - public static DatabaseFileProvider DatabaseFileProvider => MicroThreadLocalDatabaseFileProvider.Value; + /// + /// Gets the currently mounted microthread-local database provider. + /// + public static DatabaseFileProvider? DatabaseFileProvider => MicroThreadLocalDatabaseFileProvider.Value; - /// - /// Merges the given dictionary of build output objects into the shared group. Objects merged here will be integrated to every database. - /// - /// The dictionary containing the to merge into the shared group. - public static void AddToSharedGroup(IReadOnlyDictionary outputObjects) + /// + /// Merges the given dictionary of build output objects into the shared group. Objects merged here will be integrated to every database. + /// + /// The dictionary containing the to merge into the shared group. + public static void AddToSharedGroup(IReadOnlyDictionary outputObjects) + { + lock (SharedOutputObjects) { - lock (SharedOutputObjects) - { - foreach (var outputObject in outputObjects) - SharedOutputObjects[outputObject.Key] = outputObject.Value; - } + foreach (var outputObject in outputObjects) + SharedOutputObjects[outputObject.Key] = outputObject.Value; } + } - /// - /// Gets a containing only objects from the shared group. - /// The shared group is a group of objects registered via and shared amongst all databases. - /// - /// A that can provide objects from the common group. - public static DatabaseFileProvider GetSharedDatabase() - { - return CreateDatabase(CreateTransaction(null)); - } - - /// - /// Creates and mounts a database containing the given output object groups and the shared group in the microthread-local - /// . - /// - /// A collection of dictionaries representing a group of output object. - public static void MountDatabase(IEnumerable> outputObjectsGroups) - { - MountDatabase(CreateTransaction(outputObjectsGroups)); - } + /// + /// Gets a containing only objects from the shared group. + /// The shared group is a group of objects registered via and shared amongst all databases. + /// + /// A that can provide objects from the common group. + public static DatabaseFileProvider GetSharedDatabase() + { + return CreateDatabase(CreateTransaction(null)); + } + + /// + /// Creates and mounts a database containing the given output object groups and the shared group in the microthread-local + /// . + /// + /// A collection of dictionaries representing a group of output object. + public static void MountDatabase(IEnumerable> outputObjectsGroups) + { + MountDatabase(CreateTransaction(outputObjectsGroups)); + } - /// - /// Creates and mounts a database containing output objects from the shared group in the microthread-local . - /// - public static void MountCommonDatabase() - { - MicroThreadLocalDatabaseFileProvider.Value = CreateDatabase(CreateTransaction(null)); - } + /// + /// Creates and mounts a database containing output objects from the shared group in the microthread-local . + /// + public static void MountCommonDatabase() + { + MicroThreadLocalDatabaseFileProvider.Value = CreateDatabase(CreateTransaction(null)); + } - /// - /// Unmounts the currently mounted microthread-local database. - /// - public static void UnmountDatabase() - { - MicroThreadLocalDatabaseFileProvider.ClearValue(); - } + /// + /// Unmounts the currently mounted microthread-local database. + /// + public static void UnmountDatabase() + { + MicroThreadLocalDatabaseFileProvider.ClearValue(); + } - public static IEnumerable> GetOutputObjectsGroups(IEnumerable> transactionOutputObjectsGroups) + public static IEnumerable> GetOutputObjectsGroups(IEnumerable>? transactionOutputObjectsGroups) + { + if (transactionOutputObjectsGroups != null) { - if (transactionOutputObjectsGroups != null) - { - foreach (var outputObjects in transactionOutputObjectsGroups) - yield return outputObjects; - } - yield return SharedOutputObjects; + foreach (var outputObjects in transactionOutputObjectsGroups) + yield return outputObjects; } + yield return SharedOutputObjects; + } - internal static void MountDatabase(BuildTransaction transaction) - { - MicroThreadLocalDatabaseFileProvider.Value = CreateDatabase(transaction); - } + internal static void MountDatabase(BuildTransaction transaction) + { + MicroThreadLocalDatabaseFileProvider.Value = CreateDatabase(transaction); + } - internal static BuildTransaction CreateTransaction(IEnumerable> transactionOutputObjectsGroups) - { - return new BuildTransaction(Builder.ObjectDatabase.ContentIndexMap, GetOutputObjectsGroups(transactionOutputObjectsGroups)); - } + internal static BuildTransaction CreateTransaction(IEnumerable>? transactionOutputObjectsGroups) + { + return new BuildTransaction(Builder.ObjectDatabase.ContentIndexMap, GetOutputObjectsGroups(transactionOutputObjectsGroups)); + } - private static DatabaseFileProvider CreateDatabase(BuildTransaction transaction) - { - return new DatabaseFileProvider(new BuildTransaction.DatabaseContentIndexMap(transaction), Builder.ObjectDatabase); - } + private static DatabaseFileProvider CreateDatabase(BuildTransaction transaction) + { + return new DatabaseFileProvider(new BuildTransaction.DatabaseContentIndexMap(transaction), Builder.ObjectDatabase); + } - private class MicroThreadLocalProviderService : IDatabaseFileProviderService - { - public DatabaseFileProvider FileProvider => MicroThreadLocalDatabaseFileProvider.Value; - } + private class MicroThreadLocalProviderService : IDatabaseFileProviderService + { + public DatabaseFileProvider FileProvider => MicroThreadLocalDatabaseFileProvider.Value; } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/OutputObject.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/OutputObject.cs index 309971c113..8f8c56c991 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/OutputObject.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/OutputObject.cs @@ -1,47 +1,46 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; + using Stride.Core.Serialization.Contents; using Stride.Core.Storage; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +/// +/// A build output object, as exposed by . This object represents the hash of an output object and its associated url. +/// +public class OutputObject { /// - /// A build output object, as exposed by . This object represents the hash of an output object and its associated url. + /// The url of the object. /// - public class OutputObject - { - /// - /// The url of the object. - /// - public readonly ObjectUrl Url; - /// - /// The hash of the object. - /// - public ObjectId ObjectId; - /// - /// The tags associated to the object. - /// - public readonly HashSet Tags; - /// - /// An internal counter used to manage output object merging. - /// - protected internal int Counter; - /// - /// The command that generated this object. - /// - protected internal Command Command; + public readonly ObjectUrl Url; + /// + /// The hash of the object. + /// + public ObjectId ObjectId; + /// + /// The tags associated to the object. + /// + public readonly HashSet Tags; + /// + /// An internal counter used to manage output object merging. + /// + protected internal int Counter; + /// + /// The command that generated this object. + /// + protected internal Command Command; - /// - /// Initializes a new instance of the class. - /// - /// The url of the output object. - /// The hash of the output object. - public OutputObject(ObjectUrl url, ObjectId objectId) - { - Url = url; - ObjectId = objectId; - Tags = new HashSet(); - } + /// + /// Initializes a new instance of the class. + /// + /// The url of the output object. + /// The hash of the output object. + public OutputObject(ObjectUrl url, ObjectId objectId) + { + Url = url; + ObjectId = objectId; + Tags = []; } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/ResultStatus.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/ResultStatus.cs index 69ea221b50..45fe8aefbe 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/ResultStatus.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/ResultStatus.cs @@ -1,35 +1,35 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.BuildEngine + +namespace Stride.Core.BuildEngine; + +/// +/// Status of a command. +/// +public enum ResultStatus { /// - /// Status of a command. + /// The command has not finished yet /// - public enum ResultStatus - { - /// - /// The command has not finished yet - /// - NotProcessed, - /// - /// The command was successfully executed - /// - Successful, - /// - /// The command execution failed - /// - Failed, - /// - /// The command was started but cancelled, output is undeterminated - /// - Cancelled, - /// - /// A command may not be triggered if its input data haven't changed since the successful last execution - /// - NotTriggeredWasSuccessful, - /// - /// One of the prerequisite command failed and the command was not executed - /// - NotTriggeredPrerequisiteFailed, - } + NotProcessed, + /// + /// The command was successfully executed + /// + Successful, + /// + /// The command execution failed + /// + Failed, + /// + /// The command was started but cancelled, output is undeterminated + /// + Cancelled, + /// + /// A command may not be triggered if its input data haven't changed since the successful last execution + /// + NotTriggeredWasSuccessful, + /// + /// One of the prerequisite command failed and the command was not executed + /// + NotTriggeredPrerequisiteFailed, } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/SerializableTimestampLogMessage.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/SerializableTimestampLogMessage.cs index 602f711a1e..7555953947 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/SerializableTimestampLogMessage.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/SerializableTimestampLogMessage.cs @@ -1,36 +1,35 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; + using Stride.Core.Diagnostics; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +/// +/// A specialization of the class that contains a timestamp information. +/// +[DataContract] +public class SerializableTimestampLogMessage : SerializableLogMessage { /// - /// A specialization of the class that contains a timestamp information. + /// Initializes a new instance of the class with default values for its properties /// - [DataContract] - public class SerializableTimestampLogMessage : SerializableLogMessage + public SerializableTimestampLogMessage() { - /// - /// Initializes a new instance of the class with default values for its properties - /// - public SerializableTimestampLogMessage() - { - } - - /// - /// Initializes a new instance of the class from a instance. - /// - /// The instance to use to initialize properties. - public SerializableTimestampLogMessage(TimestampLocalLogger.Message message) - : base((LogMessage)message.LogMessage) - { - Timestamp = message.Timestamp; - } + } - /// - /// Gets or sets the timestamp of this message. - /// - public long Timestamp { get; set; } + /// + /// Initializes a new instance of the class from a instance. + /// + /// The instance to use to initialize properties. + public SerializableTimestampLogMessage(TimestampLocalLogger.Message message) + : base((LogMessage)message.LogMessage) + { + Timestamp = message.Timestamp; } + + /// + /// Gets or sets the timestamp of this message. + /// + public long Timestamp { get; set; } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/SingleFileImportCommand.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/SingleFileImportCommand.cs index 713931a25f..1ec2e26f7d 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/SingleFileImportCommand.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/SingleFileImportCommand.cs @@ -1,55 +1,53 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; using Stride.Core.IO; using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public abstract class SingleFileImportCommand : IndexFileCommand { - public abstract class SingleFileImportCommand : IndexFileCommand + /// + /// This is useful if the asset binary format has changed and we want to bump the version to force re-evaluation/compilation of the command + /// + protected int Version; + + protected SingleFileImportCommand() + { + } + + protected SingleFileImportCommand(UFile location, UFile sourcePath) { - /// - /// This is useful if the asset binary format has changed and we want to bump the version to force re-evaluation/compilation of the command - /// - protected int Version; - - protected SingleFileImportCommand() - { - } - - protected SingleFileImportCommand(UFile location, UFile sourcePath) - { - Location = location; - SourcePath = sourcePath; - } - - /// - /// Gets or sets the source path of the raw asset. - /// - /// The source path. - public UFile SourcePath { get; set; } - - /// - /// Gets or sets the destination location in the storage. - /// - /// The location. - public UFile Location { get; set; } - - public override IEnumerable GetInputFiles() - { - yield return new ObjectUrl(UrlType.File, SourcePath); - } - - protected override void ComputeParameterHash(BinarySerializationWriter writer) - { - base.ComputeParameterHash(writer); - - writer.Write(Version); - - writer.Write(SourcePath); - writer.Write(Location); - } + Location = location; + SourcePath = sourcePath; + } + + /// + /// Gets or sets the source path of the raw asset. + /// + /// The source path. + public UFile SourcePath { get; set; } + + /// + /// Gets or sets the destination location in the storage. + /// + /// The location. + public UFile Location { get; set; } + + public override IEnumerable GetInputFiles() + { + yield return new ObjectUrl(UrlType.File, SourcePath); + } + + protected override void ComputeParameterHash(BinarySerializationWriter writer) + { + base.ComputeParameterHash(writer); + + writer.Write(Version); + + writer.Write(SourcePath); + writer.Write(Location); } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/StepCounter.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/StepCounter.cs index 3fee92bc8d..a7cb0f9daa 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/StepCounter.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/StepCounter.cs @@ -1,44 +1,42 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public class StepCounter { - public class StepCounter - { - private readonly int[] stepResults; - public int Total { get; private set; } + private readonly int[] stepResults; + public int Total { get; private set; } - public StepCounter() - { - stepResults = new int[Enum.GetValues(typeof(ResultStatus)).Length]; - } + public StepCounter() + { + stepResults = new int[Enum.GetValues(typeof(ResultStatus)).Length]; + } - public void AddStepResult(ResultStatus result) + public void AddStepResult(ResultStatus result) + { + lock (stepResults) { - lock (stepResults) - { - ++Total; - ++stepResults[(int)result]; - } + ++Total; + ++stepResults[(int)result]; } + } - public int Get(ResultStatus result) + public int Get(ResultStatus result) + { + lock (stepResults) { - lock (stepResults) - { - return stepResults[(int)result]; - } + return stepResults[(int)result]; } + } - public void Clear() + public void Clear() + { + lock (stepResults) { - lock (stepResults) - { - Total = 0; - foreach (var value in Enum.GetValues(typeof(ResultStatus))) - stepResults[(int)value] = 0; - } + Total = 0; + foreach (var value in Enum.GetValues(typeof(ResultStatus))) + stepResults[(int)value] = 0; } } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/Stride.Core.BuildEngine.Common.csproj b/sources/buildengine/Stride.Core.BuildEngine.Common/Stride.Core.BuildEngine.Common.csproj index 375e7834d5..da344b2517 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/Stride.Core.BuildEngine.Common.csproj +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/Stride.Core.BuildEngine.Common.csproj @@ -6,6 +6,10 @@ 2.0 true $(StrideXplatEditorTargetFramework) + enable + latest + enable + Stride.Core.BuildEngine --auto-module-initializer --serialization diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/StrideServiceWireSerializer.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/StrideServiceWireSerializer.cs index f41b0ec257..10906fed30 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/StrideServiceWireSerializer.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/StrideServiceWireSerializer.cs @@ -1,111 +1,106 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.IO; -using System.Runtime.Serialization; + using System.Text; -using System.Xml; -//using Newtonsoft.Json; using System.Text.Json; using ServiceWire; using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +public class StrideServiceWireSerializer : ISerializer { - public class StrideServiceWireSerializer : ISerializer + public T? Deserialize(byte[] bytes) { - public T Deserialize(byte[] bytes) - { - if (null == bytes || bytes.Length == 0) return default(T); - var reader = new BinarySerializationReader(new MemoryStream(bytes)); - reader.Context.SerializerSelector = SerializerSelector.AssetWithReuse; - reader.Context.Set(ContentSerializerContext.SerializeAttachedReferenceProperty, ContentSerializerContext.AttachedReferenceSerialization.AsSerializableVersion); - T command = default(T); - reader.SerializeExtended(ref command, ArchiveMode.Deserialize, null); - return command; - } + if (null == bytes || bytes.Length == 0) return default; + var reader = new BinarySerializationReader(new MemoryStream(bytes)); + reader.Context.SerializerSelector = SerializerSelector.AssetWithReuse; + reader.Context.Set(ContentSerializerContext.SerializeAttachedReferenceProperty, ContentSerializerContext.AttachedReferenceSerialization.AsSerializableVersion); + T? command = default; + reader.SerializeExtended(ref command, ArchiveMode.Deserialize, null); + return command; + } - public object Deserialize(byte[] bytes, string typeConfigName) - { - if (null == typeConfigName) throw new ArgumentNullException(nameof(typeConfigName)); - var type = typeConfigName.ToType(); - if (null == typeConfigName || null == bytes || bytes.Length == 0) return type.GetDefault(); - var reader = new BinarySerializationReader(new MemoryStream(bytes)); - reader.Context.SerializerSelector = SerializerSelector.AssetWithReuse; - reader.Context.Set(ContentSerializerContext.SerializeAttachedReferenceProperty, ContentSerializerContext.AttachedReferenceSerialization.AsSerializableVersion); - object command = null; - reader.SerializeExtended(ref command, ArchiveMode.Deserialize, null); - return command; - } + public object? Deserialize(byte[] bytes, string typeConfigName) + { + ArgumentNullException.ThrowIfNull(typeConfigName); + var type = typeConfigName.ToType(); + if (null == typeConfigName || null == bytes || bytes.Length == 0) return type.GetDefault(); + var reader = new BinarySerializationReader(new MemoryStream(bytes)); + reader.Context.SerializerSelector = SerializerSelector.AssetWithReuse; + reader.Context.Set(ContentSerializerContext.SerializeAttachedReferenceProperty, ContentSerializerContext.AttachedReferenceSerialization.AsSerializableVersion); + object? command = null; + reader.SerializeExtended(ref command, ArchiveMode.Deserialize, null); + return command; + } - public byte[] Serialize(T obj) - { - if (null == obj) return null; - var memoryStream = new MemoryStream(); - var writer = new BinarySerializationWriter(memoryStream); - writer.Context.SerializerSelector = SerializerSelector.AssetWithReuse; - writer.Context.Set(ContentSerializerContext.SerializeAttachedReferenceProperty, ContentSerializerContext.AttachedReferenceSerialization.AsSerializableVersion); - writer.SerializeExtended(ref obj, ArchiveMode.Serialize); + public byte[]? Serialize(T obj) + { + if (null == obj) return null; + var memoryStream = new MemoryStream(); + var writer = new BinarySerializationWriter(memoryStream); + writer.Context.SerializerSelector = SerializerSelector.AssetWithReuse; + writer.Context.Set(ContentSerializerContext.SerializeAttachedReferenceProperty, ContentSerializerContext.AttachedReferenceSerialization.AsSerializableVersion); + writer.SerializeExtended(ref obj, ArchiveMode.Serialize); - return memoryStream.ToArray(); - } + return memoryStream.ToArray(); + } - public byte[] Serialize(object obj, string typeConfigName) - { - if (null == obj) return null; - var type = typeConfigName.ToType(); - var memoryStream = new MemoryStream(); - var writer = new BinarySerializationWriter(memoryStream); - writer.Context.SerializerSelector = SerializerSelector.AssetWithReuse; - writer.Context.Set(ContentSerializerContext.SerializeAttachedReferenceProperty, ContentSerializerContext.AttachedReferenceSerialization.AsSerializableVersion); - writer.SerializeExtended(ref obj, ArchiveMode.Serialize); + public byte[]? Serialize(object obj, string typeConfigName) + { + if (null == obj) return null; + var type = typeConfigName.ToType(); + var memoryStream = new MemoryStream(); + var writer = new BinarySerializationWriter(memoryStream); + writer.Context.SerializerSelector = SerializerSelector.AssetWithReuse; + writer.Context.Set(ContentSerializerContext.SerializeAttachedReferenceProperty, ContentSerializerContext.AttachedReferenceSerialization.AsSerializableVersion); + writer.SerializeExtended(ref obj, ArchiveMode.Serialize); - return memoryStream.ToArray(); - } + return memoryStream.ToArray(); } +} - public class NewtonsoftSerializer : ISerializer +public class NewtonsoftSerializer : ISerializer +{ + //private JsonSerializerSettings settings = new JsonSerializerSettings + //{ + // ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore + //}; + private readonly JsonSerializerOptions settings = new() { - //private JsonSerializerSettings settings = new JsonSerializerSettings - //{ - // ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore - //}; - private JsonSerializerOptions settings = new JsonSerializerOptions - { - ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles - }; + ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles + }; - public T Deserialize(byte[] bytes) - { - if (null == bytes || bytes.Length == 0) return default(T); - var json = Encoding.UTF8.GetString(bytes); + public T? Deserialize(byte[] bytes) + { + if (null == bytes || bytes.Length == 0) return default; + var json = Encoding.UTF8.GetString(bytes); - return JsonSerializer.Deserialize(json, settings); //return JsonConvert.DeserializeObject(json, settings); - } + return JsonSerializer.Deserialize(json, settings); //return JsonConvert.DeserializeObject(json, settings); + } - public object Deserialize(byte[] bytes, string typeConfigName) - { - if (null == typeConfigName) throw new ArgumentNullException(nameof(typeConfigName)); - var type = typeConfigName.ToType(); - if (null == typeConfigName || null == bytes || bytes.Length == 0) return type.GetDefault(); - var json = Encoding.UTF8.GetString(bytes); - return JsonSerializer.Deserialize(json,type, settings); //return JsonConvert.DeserializeObject(json, type, settings); - } + public object? Deserialize(byte[] bytes, string typeConfigName) + { + ArgumentNullException.ThrowIfNull(typeConfigName); + var type = typeConfigName.ToType(); + if (null == typeConfigName || null == bytes || bytes.Length == 0) return type.GetDefault(); + var json = Encoding.UTF8.GetString(bytes); + return JsonSerializer.Deserialize(json,type, settings); //return JsonConvert.DeserializeObject(json, type, settings); + } - public byte[] Serialize(T obj) - { - if (null == obj) return null; - var json = JsonSerializer.Serialize(obj, settings); //JsonConvert.SerializeObject(obj, settings); - return Encoding.UTF8.GetBytes(json); - } + public byte[]? Serialize(T obj) + { + if (null == obj) return null; + var json = JsonSerializer.Serialize(obj, settings); //JsonConvert.SerializeObject(obj, settings); + return Encoding.UTF8.GetBytes(json); + } - public byte[] Serialize(object obj, string typeConfigName) - { - if (null == obj) return null; - var type = typeConfigName.ToType(); - var json = JsonSerializer.Serialize(obj, type, settings); //JsonConvert.SerializeObject(obj, type, settings); - return Encoding.UTF8.GetBytes(json); - } + public byte[]? Serialize(object obj, string typeConfigName) + { + if (null == obj) return null; + var type = typeConfigName.ToType(); + var json = JsonSerializer.Serialize(obj, type, settings); //JsonConvert.SerializeObject(obj, type, settings); + return Encoding.UTF8.GetBytes(json); } } diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/TimeInterval.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/TimeInterval.cs index 5a2f1cf57a..51dc5d2c2a 100644 --- a/sources/buildengine/Stride.Core.BuildEngine.Common/TimeInterval.cs +++ b/sources/buildengine/Stride.Core.BuildEngine.Common/TimeInterval.cs @@ -1,62 +1,60 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.BuildEngine +namespace Stride.Core.BuildEngine; + +/// +/// An helper class used to store command timing +/// +public class TimeInterval { - /// - /// An helper class used to store command timing - /// - public class TimeInterval - { - public long StartTime { get; private set; } + public long StartTime { get; private set; } - public long EndTime { get { return endTimeVal; } private set { endTimeVal = value; } } - private long endTimeVal = IntervalNotEnded; + public long EndTime { get { return endTimeVal; } private set { endTimeVal = value; } } + private long endTimeVal = IntervalNotEnded; - public bool HasEnded { get { return endTimeVal != IntervalNotEnded; } } + public bool HasEnded { get { return endTimeVal != IntervalNotEnded; } } - private const long IntervalNotEnded = long.MaxValue; + private const long IntervalNotEnded = long.MaxValue; - public TimeInterval(long startTime) - { - StartTime = startTime; - } + public TimeInterval(long startTime) + { + StartTime = startTime; + } - public TimeInterval(long startTime, long endTime) - { - StartTime = startTime; - EndTime = endTime; - } + public TimeInterval(long startTime, long endTime) + { + StartTime = startTime; + EndTime = endTime; + } - public void End(long endTime) - { - if (endTimeVal != IntervalNotEnded) - throw new InvalidOperationException("TimeInterval has already ended"); + public void End(long endTime) + { + if (endTimeVal != IntervalNotEnded) + throw new InvalidOperationException("TimeInterval has already ended"); - EndTime = endTime; - } + EndTime = endTime; + } - public bool Overlap(long startTime, long endTime) - { - return (StartTime > startTime ? StartTime : startTime) < (EndTime < endTime ? EndTime : endTime); - } + public bool Overlap(long startTime, long endTime) + { + return (StartTime > startTime ? StartTime : startTime) < (EndTime < endTime ? EndTime : endTime); + } +} + +public class TimeInterval : TimeInterval +{ + public T Object { get; protected set; } + + public TimeInterval(T obj, long startTime) + : base(startTime) + { + Object = obj; } - public class TimeInterval : TimeInterval + public TimeInterval(T obj, long startTime, long endTime) + : base(startTime, endTime) { - public T Object { get; protected set; } - - public TimeInterval(T obj, long startTime) - : base(startTime) - { - Object = obj; - } - - public TimeInterval(T obj, long startTime, long endTime) - : base(startTime, endTime) - { - Object = obj; - } + Object = obj; } } diff --git a/sources/core/Stride.Core.Design/IO/UFile.cs b/sources/core/Stride.Core.Design/IO/UFile.cs index 713ac46a8c..4d0efd3d98 100644 --- a/sources/core/Stride.Core.Design/IO/UFile.cs +++ b/sources/core/Stride.Core.Design/IO/UFile.cs @@ -17,7 +17,7 @@ public sealed class UFile : UPath /// Initializes a new instance of the class. /// /// The file path. - public UFile(string filePath) + public UFile(string? filePath) : base(filePath, false) { } @@ -145,7 +145,7 @@ public static UFile Combine(UDirectory leftPath, UFile rightPath) } /// - /// Performs an implicit conversion from to . + /// Performs an implicit conversion from to . /// /// The full path. /// The result of the conversion. diff --git a/sources/core/Stride.Core.Design/PackageVersion.cs b/sources/core/Stride.Core.Design/PackageVersion.cs index d3bc779e7c..f5907ca049 100644 --- a/sources/core/Stride.Core.Design/PackageVersion.cs +++ b/sources/core/Stride.Core.Design/PackageVersion.cs @@ -309,7 +309,7 @@ public int CompareTo(PackageVersion? other) return version1.CompareTo(version2) > 0; } - public static bool operator >=(PackageVersion version1, PackageVersion version2) + public static bool operator >=(PackageVersion version1, PackageVersion? version2) { return version1 == version2 || version1 > version2; } diff --git a/sources/core/Stride.Core.Design/PackageVersionRange.cs b/sources/core/Stride.Core.Design/PackageVersionRange.cs index 6d03cf7b75..936ada32b0 100644 --- a/sources/core/Stride.Core.Design/PackageVersionRange.cs +++ b/sources/core/Stride.Core.Design/PackageVersionRange.cs @@ -47,7 +47,7 @@ public PackageVersionRange() /// Initializes a new instance of the class with only one possible version. /// /// The exact version. - public PackageVersionRange(PackageVersion version) : this(version, true, version, true) + public PackageVersionRange(PackageVersion? version) : this(version, true, version, true) { } @@ -70,7 +70,7 @@ public PackageVersionRange(PackageVersion minVersion, bool minVersionInclusive) /// if set to true the minimum version is inclusive /// The maximum version. /// if set to true the maximum version is inclusive - public PackageVersionRange(PackageVersion minVersion, bool minVersionInclusive, PackageVersion maxVersion, bool maxVersionInclusive) + public PackageVersionRange(PackageVersion? minVersion, bool minVersionInclusive, PackageVersion? maxVersion, bool maxVersionInclusive) { IsMinInclusive = minVersionInclusive; IsMaxInclusive = maxVersionInclusive; @@ -165,7 +165,7 @@ public static PackageVersionRange GetSafeRange(PackageVersion version) /// The version dependency as a string. /// The parsed result. /// true if successfuly parsed, false otherwise. - /// value + /// value public static bool TryParse(string value, [MaybeNullWhen(false)] out PackageVersionRange result) { #if NET6_0_OR_GREATER diff --git a/sources/core/Stride.Core.Design/Reflection/ShadowObject.cs b/sources/core/Stride.Core.Design/Reflection/ShadowObject.cs index 6b45ce58c5..dbaba1e48a 100644 --- a/sources/core/Stride.Core.Design/Reflection/ShadowObject.cs +++ b/sources/core/Stride.Core.Design/Reflection/ShadowObject.cs @@ -44,7 +44,7 @@ public static bool TryGet(object instance, [MaybeNullWhen(false)] out ShadowObje /// /// The live instance. /// The shadow instance or null if none - public static ShadowObject? Get(object instance) + public static ShadowObject? Get(object? instance) { if (!Enable || instance == null) return null; Shadows.TryGetValue(instance, out var shadow); diff --git a/sources/core/Stride.Core.Reflection/MemberPath.cs b/sources/core/Stride.Core.Reflection/MemberPath.cs index 06e0e9ce34..84d24fe0e5 100644 --- a/sources/core/Stride.Core.Reflection/MemberPath.cs +++ b/sources/core/Stride.Core.Reflection/MemberPath.cs @@ -53,7 +53,7 @@ private MemberPath(List items) /// /// /// true if the given matches with this instance; otherwise, false. - public bool Match(MemberPath other) + public bool Match(MemberPath? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; @@ -217,7 +217,7 @@ public void Pop() } } - public bool Apply(object rootObject, MemberPathAction actionType, object value) + public bool Apply(object rootObject, MemberPathAction actionType, object? value) { ArgumentNullException.ThrowIfNull(rootObject); if (rootObject.GetType().IsValueType) throw new ArgumentException("Value type for root objects are not supported", nameof(rootObject)); diff --git a/sources/core/Stride.Core.Reflection/TypeDescriptors/DictionaryDescriptor.cs b/sources/core/Stride.Core.Reflection/TypeDescriptors/DictionaryDescriptor.cs index 2e0d273aed..ddedd678d3 100644 --- a/sources/core/Stride.Core.Reflection/TypeDescriptors/DictionaryDescriptor.cs +++ b/sources/core/Stride.Core.Reflection/TypeDescriptors/DictionaryDescriptor.cs @@ -134,7 +134,7 @@ public void SetValue(object dictionary, object key, object? value) /// The key. /// The value. /// No Add() method found on dictionary [{0}].DoFormat(Type) - public void AddToDictionary(object dictionary, object key, object value) + public void AddToDictionary(object dictionary, object key, object? value) { ArgumentNullException.ThrowIfNull(dictionary); addMethod.Invoke(dictionary, key, value); diff --git a/sources/core/Stride.Core.Serialization/Serialization/AttachedReferenceManager.cs b/sources/core/Stride.Core.Serialization/Serialization/AttachedReferenceManager.cs index 1afd8a5199..71f46c5ac0 100644 --- a/sources/core/Stride.Core.Serialization/Serialization/AttachedReferenceManager.cs +++ b/sources/core/Stride.Core.Serialization/Serialization/AttachedReferenceManager.cs @@ -44,7 +44,7 @@ public static void SetUrl(object obj, string url) /// /// The object for which to get the attached reference. Can be null, in this case this method returns null. /// The attached to the given object if available, null otherwise. - public static AttachedReference? GetAttachedReference(object obj) + public static AttachedReference? GetAttachedReference(object? obj) { if (obj == null) return null; diff --git a/sources/core/Stride.Core/Collections/KeyedSortedList.cs b/sources/core/Stride.Core/Collections/KeyedSortedList.cs index cc01f8a0e8..14477c142f 100644 --- a/sources/core/Stride.Core/Collections/KeyedSortedList.cs +++ b/sources/core/Stride.Core/Collections/KeyedSortedList.cs @@ -32,7 +32,7 @@ protected KeyedSortedList(IComparer? comparer) /// /// The element from which to extract the key. /// The key for the specified item. - protected abstract TKey? GetKeyForItem(T? item); + protected abstract TKey GetKeyForItem(T item); /// /// Called every time an item should be added at a given index. @@ -208,7 +208,7 @@ public Enumerator GetEnumerator() return new Enumerator(items); } - public int BinarySearch(TKey? searchKey) + public int BinarySearch(TKey searchKey) { var values = items.Items; var start = 0; @@ -246,9 +246,15 @@ internal Comparer(KeyedSortedList list) this.list = list; } - public int Compare(T? x, T? y) + public readonly int Compare(T? x, T? y) { - return list.comparer.Compare(list.GetKeyForItem(x), list.GetKeyForItem(y)); + if (x is not null) + { + if (y is not null) return list.comparer.Compare(list.GetKeyForItem(x), list.GetKeyForItem(y)); + return 1; + } + if (y is not null) return -1; + return 0; } } diff --git a/sources/core/Stride.Core/Diagnostics/LogMessage.cs b/sources/core/Stride.Core/Diagnostics/LogMessage.cs index d0464415d8..41fec6f4c5 100644 --- a/sources/core/Stride.Core/Diagnostics/LogMessage.cs +++ b/sources/core/Stride.Core/Diagnostics/LogMessage.cs @@ -24,7 +24,7 @@ public LogMessage() /// The module. /// The type. /// The text. - public LogMessage(string module, LogMessageType type, string text) + public LogMessage(string? module, LogMessageType type, string text) { Module = module; Type = type; diff --git a/sources/editor/Stride.Core.Assets.Editor/Stride.Core.Assets.Editor.csproj b/sources/editor/Stride.Core.Assets.Editor/Stride.Core.Assets.Editor.csproj index 6c601a9761..989ce3c885 100644 --- a/sources/editor/Stride.Core.Assets.Editor/Stride.Core.Assets.Editor.csproj +++ b/sources/editor/Stride.Core.Assets.Editor/Stride.Core.Assets.Editor.csproj @@ -3,6 +3,8 @@ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} $(StrideEditorTargetFramework) + enable + latest true --auto-module-initializer --serialization true diff --git a/sources/editor/Stride.GameStudio.Tests/Stride.GameStudio.Tests.csproj b/sources/editor/Stride.GameStudio.Tests/Stride.GameStudio.Tests.csproj index ec9fd00f6d..b4dc2ecf84 100644 --- a/sources/editor/Stride.GameStudio.Tests/Stride.GameStudio.Tests.csproj +++ b/sources/editor/Stride.GameStudio.Tests/Stride.GameStudio.Tests.csproj @@ -3,6 +3,8 @@ $(StrideEditorTargetFramework) win-x64 + enable + latest WindowsTools diff --git a/sources/editor/Stride.GameStudio/Debugging/AssemblyRecompiler.cs b/sources/editor/Stride.GameStudio/Debugging/AssemblyRecompiler.cs index 263aed8348..7bc3aafaeb 100644 --- a/sources/editor/Stride.GameStudio/Debugging/AssemblyRecompiler.cs +++ b/sources/editor/Stride.GameStudio/Debugging/AssemblyRecompiler.cs @@ -173,7 +173,7 @@ public async Task Recompile(Project gameProject, LoggerResult logg var msbuildProject = await Task.Run(() => VSProjectHelper.LoadProject(gameProject.FilePath)); if (msbuildProject.GetPropertyValue("StrideAssemblyProcessor") == "true") { - var referenceBuild = await Task.Run(() => VSProjectHelper.CompileProjectAssemblyAsync(null, gameProject.FilePath, result, "ResolveReferences", flags: Microsoft.Build.Execution.BuildRequestDataFlags.ProvideProjectStateAfterBuild)); + var referenceBuild = await Task.Run(() => VSProjectHelper.CompileProjectAssemblyAsync(gameProject.FilePath, result, "ResolveReferences", flags: Microsoft.Build.Execution.BuildRequestDataFlags.ProvideProjectStateAfterBuild)); if (referenceBuild == null) { result.Error("Could not properly run ResolveAssemblyReferences"); diff --git a/sources/editor/Stride.GameStudio/ViewModels/DebuggingViewModel.cs b/sources/editor/Stride.GameStudio/ViewModels/DebuggingViewModel.cs index e795686f39..7f921c930c 100644 --- a/sources/editor/Stride.GameStudio/ViewModels/DebuggingViewModel.cs +++ b/sources/editor/Stride.GameStudio/ViewModels/DebuggingViewModel.cs @@ -472,7 +472,7 @@ private async Task BuildProjectCore(bool startProject) } // Build project - currentBuild = VSProjectHelper.CompileProjectAssemblyAsync(Session?.SolutionPath, projectViewModel.ProjectPath, logger, target, configuration, platformName, extraProperties, BuildRequestDataFlags.ProvideProjectStateAfterBuild); + currentBuild = VSProjectHelper.CompileProjectAssemblyAsync(projectViewModel.ProjectPath, logger, target, configuration, platformName, extraProperties, BuildRequestDataFlags.ProvideProjectStateAfterBuild); if (currentBuild == null) { logger.Error(string.Format(Tr._p("Message", "Unable to load and compile project {0}"), projectViewModel.ProjectPath)); @@ -639,7 +639,7 @@ private async Task BuildProject(UFile projectPath) ["StrideBuildEngineLogVerbose"] = "true", }; - currentBuild = VSProjectHelper.CompileProjectAssemblyAsync(Session?.SolutionPath, projectPath, logger, target, configuration, platformName, extraProperties, BuildRequestDataFlags.ProvideProjectStateAfterBuild); + currentBuild = VSProjectHelper.CompileProjectAssemblyAsync(projectPath, logger, target, configuration, platformName, extraProperties, BuildRequestDataFlags.ProvideProjectStateAfterBuild); if (currentBuild == null) { logger.Error(string.Format(Tr._p("Message", "Unable to load and compile project {0}"), projectPath)); diff --git a/sources/engine/Stride.Assets.Tests/Stride.Assets.Tests.csproj b/sources/engine/Stride.Assets.Tests/Stride.Assets.Tests.csproj index ddc4c9155c..ed41aecee2 100644 --- a/sources/engine/Stride.Assets.Tests/Stride.Assets.Tests.csproj +++ b/sources/engine/Stride.Assets.Tests/Stride.Assets.Tests.csproj @@ -2,6 +2,8 @@ $(StrideEditorTargetFramework) + enable + latest win-x64 false diff --git a/sources/engine/Stride.Debugger/Stride.Debugger.csproj b/sources/engine/Stride.Debugger/Stride.Debugger.csproj index 802078f33b..33e9b92638 100644 --- a/sources/engine/Stride.Debugger/Stride.Debugger.csproj +++ b/sources/engine/Stride.Debugger/Stride.Debugger.csproj @@ -9,7 +9,6 @@ $(StrideEditorTargetFramework) WindowsTools AnyCPU - true diff --git a/sources/launcher/Stride.Launcher/Stride.Launcher.csproj b/sources/launcher/Stride.Launcher/Stride.Launcher.csproj index a73286f0eb..f303090847 100644 --- a/sources/launcher/Stride.Launcher/Stride.Launcher.csproj +++ b/sources/launcher/Stride.Launcher/Stride.Launcher.csproj @@ -12,7 +12,6 @@ AnyCPU bin\Debug\ - false false TRACE;STRIDE_LAUNCHER @@ -22,7 +21,6 @@ true bin\Release\ TRACE;STRIDE_LAUNCHER - false Resources\Launcher.ico diff --git a/sources/presentation/Stride.Core.Quantum/GraphNodeLinker.cs b/sources/presentation/Stride.Core.Quantum/GraphNodeLinker.cs index 1e4cea9ca6..8f73364226 100644 --- a/sources/presentation/Stride.Core.Quantum/GraphNodeLinker.cs +++ b/sources/presentation/Stride.Core.Quantum/GraphNodeLinker.cs @@ -26,7 +26,7 @@ public GraphNodeLinkerVisitor(GraphNodeLinker linker) this.linker = linker; } - public void Reset(IGraphNode sourceNode, IGraphNode targetNode) + public void Reset(IGraphNode sourceNode, IGraphNode? targetNode) { VisitedLinks.Clear(); VisitedLinks.Add(sourceNode, targetNode); @@ -123,7 +123,7 @@ public GraphNodeLinker() /// /// The root node of the "source" object to link. /// The root node of the "target" object to link. - public void LinkGraph(IGraphNode sourceNode, IGraphNode targetNode) + public void LinkGraph(IGraphNode sourceNode, IGraphNode? targetNode) { visitor.Reset(sourceNode, targetNode); visitor.Visit(sourceNode); diff --git a/sources/presentation/Stride.Core.Quantum/IObjectNode.cs b/sources/presentation/Stride.Core.Quantum/IObjectNode.cs index 8cd1264441..7e72e9ff1a 100644 --- a/sources/presentation/Stride.Core.Quantum/IObjectNode.cs +++ b/sources/presentation/Stride.Core.Quantum/IObjectNode.cs @@ -51,7 +51,7 @@ public interface IObjectNode : IGraphNode, INotifyNodeItemChange /// /// The new value to set. /// The index where to update the value. - void Update(object newValue, NodeIndex index); + void Update(object? newValue, NodeIndex index); /// /// Adds a new item to this content, assuming the content is a collection. diff --git a/sources/presentation/Stride.Core.Quantum/ObjectNode.cs b/sources/presentation/Stride.Core.Quantum/ObjectNode.cs index 8b9b03c513..4bf2e5897d 100644 --- a/sources/presentation/Stride.Core.Quantum/ObjectNode.cs +++ b/sources/presentation/Stride.Core.Quantum/ObjectNode.cs @@ -78,7 +78,7 @@ public ObjectNode(INodeBuilder nodeBuilder, object value, Guid guid, ITypeDescri } /// - public void Update(object newValue, NodeIndex index) + public void Update(object? newValue, NodeIndex index) { Update(newValue, index, true); } @@ -204,7 +204,7 @@ protected void NotifyItemChanged(ItemChangeEventArgs args) FinalizeChange?.Invoke(this, args); } - private void Update(object newValue, NodeIndex index, bool sendNotification) + private void Update(object? newValue, NodeIndex index, bool sendNotification) { if (!index.TryGetValue(out var indexValue)) throw new ArgumentException("index cannot be empty.", nameof(index)); diff --git a/sources/tests/xunit.runner.stride/App.axaml.cs b/sources/tests/xunit.runner.stride/App.axaml.cs index 60d3718985..84662b30c2 100644 --- a/sources/tests/xunit.runner.stride/App.axaml.cs +++ b/sources/tests/xunit.runner.stride/App.axaml.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Threading; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Data.Core.Plugins; @@ -13,9 +11,9 @@ namespace xunit.runner.stride; public partial class App : Application -{ +{ internal readonly CancellationTokenSource cts = new(); - internal Action setInteractiveMode; + internal Action? setInteractiveMode; public override void Initialize() { diff --git a/sources/tests/xunit.runner.stride/StrideXunitRunner.cs b/sources/tests/xunit.runner.stride/StrideXunitRunner.cs index f982607aa7..3a111c2fa0 100644 --- a/sources/tests/xunit.runner.stride/StrideXunitRunner.cs +++ b/sources/tests/xunit.runner.stride/StrideXunitRunner.cs @@ -1,19 +1,18 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; namespace xunit.runner.stride; -public class StrideXunitRunner +public static class StrideXunitRunner { // Initialization code. Don't use any Avalonia, third-party APIs or any // SynchronizationContext-reliant code before AppMain is called: things aren't initialized // yet and stuff might break. - public static void Main(string[] args, Action setInteractiveMode = null) + public static void Main(string[] _, Action? setInteractiveMode = null) { var builder = BuildAvaloniaApp() .SetupWithLifetime(new ClassicDesktopStyleApplicationLifetime()); diff --git a/sources/tests/xunit.runner.stride/ViewModels/MainViewModel.cs b/sources/tests/xunit.runner.stride/ViewModels/MainViewModel.cs index 1b93b3bbef..c65e19374e 100644 --- a/sources/tests/xunit.runner.stride/ViewModels/MainViewModel.cs +++ b/sources/tests/xunit.runner.stride/ViewModels/MainViewModel.cs @@ -5,10 +5,5 @@ namespace xunit.runner.stride.ViewModels; public class MainViewModel : ViewModelBase { - public MainViewModel() - { - - } - public TestsViewModel Tests { get; } = new TestsViewModel(); } diff --git a/sources/tests/xunit.runner.stride/ViewModels/TestCaseViewModel.cs b/sources/tests/xunit.runner.stride/ViewModels/TestCaseViewModel.cs index 9ca872f353..1f57965322 100644 --- a/sources/tests/xunit.runner.stride/ViewModels/TestCaseViewModel.cs +++ b/sources/tests/xunit.runner.stride/ViewModels/TestCaseViewModel.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; using Xunit.Abstractions; namespace xunit.runner.stride.ViewModels; @@ -28,9 +27,9 @@ public override IEnumerable EnumerateTestCases() yield return this; } - public override TestCaseViewModel LocateTestCase(ITestCase testCase) + public override TestCaseViewModel? LocateTestCase(ITestCase testCase) { - return (testCase == this.TestCase) ? this : null; + return (testCase == TestCase) ? this : null; } public override string DisplayName => TestCase.DisplayName; diff --git a/sources/tests/xunit.runner.stride/ViewModels/TestGroupViewModel.cs b/sources/tests/xunit.runner.stride/ViewModels/TestGroupViewModel.cs index dfd18dd9ce..a04e6b53ac 100644 --- a/sources/tests/xunit.runner.stride/ViewModels/TestGroupViewModel.cs +++ b/sources/tests/xunit.runner.stride/ViewModels/TestGroupViewModel.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Linq; using Xunit.Abstractions; namespace xunit.runner.stride.ViewModels; @@ -27,12 +25,12 @@ public void RunTest() tests.RunTests(this); } - public override TestCaseViewModel LocateTestCase(ITestCase testCase) + public override TestCaseViewModel? LocateTestCase(ITestCase testCase) { foreach (var child in Children) { var result = child.LocateTestCase(testCase); - if (result != null) + if (result is not null) return result; } return null; diff --git a/sources/tests/xunit.runner.stride/ViewModels/TestNodeViewModel.cs b/sources/tests/xunit.runner.stride/ViewModels/TestNodeViewModel.cs index 072cbe91fa..04c6350d0b 100644 --- a/sources/tests/xunit.runner.stride/ViewModels/TestNodeViewModel.cs +++ b/sources/tests/xunit.runner.stride/ViewModels/TestNodeViewModel.cs @@ -1,38 +1,36 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; using Xunit.Abstractions; -namespace xunit.runner.stride.ViewModels +namespace xunit.runner.stride.ViewModels; + +public abstract class TestNodeViewModel : ViewModelBase { - public abstract class TestNodeViewModel : ViewModelBase + public abstract IEnumerable EnumerateTestCases(); + + public abstract TestCaseViewModel? LocateTestCase(ITestCase testCase); + + bool running; + public bool Running + { + get => running; + set => SetProperty(ref running, value); + } + + bool failed; + public bool Failed { - public abstract IEnumerable EnumerateTestCases(); - - public abstract TestCaseViewModel LocateTestCase(ITestCase testCase); - - bool running; - public bool Running - { - get => running; - set => SetProperty(ref running, value); - } - - bool failed; - public bool Failed - { - get => failed; - set => SetProperty(ref failed, value); - } - - bool succeeded; - public bool Succeeded - { - get => succeeded; - set => SetProperty(ref succeeded, value); - } - - public abstract string DisplayName { get; } + get => failed; + set => SetProperty(ref failed, value); } + + bool succeeded; + public bool Succeeded + { + get => succeeded; + set => SetProperty(ref succeeded, value); + } + + public abstract string DisplayName { get; } } diff --git a/sources/tests/xunit.runner.stride/ViewModels/TestsViewModel.cs b/sources/tests/xunit.runner.stride/ViewModels/TestsViewModel.cs index 98b8b02004..9a619bd7a8 100644 --- a/sources/tests/xunit.runner.stride/ViewModels/TestsViewModel.cs +++ b/sources/tests/xunit.runner.stride/ViewModels/TestsViewModel.cs @@ -1,12 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using Avalonia.Threading; using Xunit; @@ -18,7 +13,7 @@ public class TestsViewModel : ViewModelBase public TestsViewModel() { - var assemblyFileName = Assembly.GetEntryAssembly().Location; + var assemblyFileName = Assembly.GetEntryAssembly()!.Location; // TODO: currently we disable app domain otherwise GameTestBase.ForceInteractiveMode is not kept // we should find another way to transfer this parameter @@ -83,7 +78,7 @@ await Task.Run(() => testCaseViewModel.Succeeded = args.Message.TestsFailed == 0; testCaseViewModel.Running = false; // Update progress - TestCompletion = ((double)Interlocked.Increment(ref testCasesFinished) / (double)testCaseViewModels.Count) * 100.0; + TestCompletion = (double)Interlocked.Increment(ref testCasesFinished) / (double)testCaseViewModels.Count * 100.0; }); } }, @@ -91,10 +86,7 @@ await Task.Run(() => Controller.RunTests(testCaseViewModels.Select(x => x.Value.TestCase).ToArray(), sink, TestFrameworkOptions.ForExecution()); sink.Finished.WaitOne(); - Dispatcher.UIThread.Post(() => - { - RunningTests = false; - }); + Dispatcher.UIThread.Post(() => RunningTests = false); }); } @@ -124,5 +116,5 @@ public bool IsInteractiveMode } public List TestCases { get; } = []; - public Action SetInteractiveMode { get; set; } + public Action? SetInteractiveMode { get; set; } } diff --git a/sources/tests/xunit.runner.stride/ViewModels/ViewModelBase.cs b/sources/tests/xunit.runner.stride/ViewModels/ViewModelBase.cs index bc0499a618..466531a986 100644 --- a/sources/tests/xunit.runner.stride/ViewModels/ViewModelBase.cs +++ b/sources/tests/xunit.runner.stride/ViewModels/ViewModelBase.cs @@ -3,9 +3,6 @@ using CommunityToolkit.Mvvm.ComponentModel; -namespace xunit.runner.stride.ViewModels -{ - public class ViewModelBase : ObservableObject - { - } -} +namespace xunit.runner.stride.ViewModels; + +public class ViewModelBase : ObservableObject; diff --git a/sources/tests/xunit.runner.stride/ViewModels/XSink.cs b/sources/tests/xunit.runner.stride/ViewModels/XSink.cs index 094dc66244..c82ff52849 100644 --- a/sources/tests/xunit.runner.stride/ViewModels/XSink.cs +++ b/sources/tests/xunit.runner.stride/ViewModels/XSink.cs @@ -1,15 +1,12 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Threading; using Xunit; using Xunit.Abstractions; namespace xunit.runner.stride.ViewModels; -public class XSink : IExecutionSink +public sealed class XSink : IExecutionSink { volatile int errors; @@ -27,18 +24,18 @@ public bool OnMessageWithTypes(IMessageSinkMessage message, HashSet mess return message.Dispatch(messageTypes, HandleTestCaseFinished) && message.Dispatch(messageTypes, HandleTestCaseStarting) - && message.Dispatch(messageTypes, args => Interlocked.Increment(ref errors)) - && message.Dispatch(messageTypes, args => Interlocked.Increment(ref errors)) + && message.Dispatch(messageTypes, _ => Interlocked.Increment(ref errors)) + && message.Dispatch(messageTypes, _ => Interlocked.Increment(ref errors)) && message.Dispatch(messageTypes, HandleTestAssemblyFinished) - && message.Dispatch(messageTypes, args => Interlocked.Increment(ref errors)) - && message.Dispatch(messageTypes, args => Interlocked.Increment(ref errors)) - && message.Dispatch(messageTypes, args => Interlocked.Increment(ref errors)) - && message.Dispatch(messageTypes, args => Interlocked.Increment(ref errors)) - && message.Dispatch(messageTypes, args => Interlocked.Increment(ref errors)); + && message.Dispatch(messageTypes, _ => Interlocked.Increment(ref errors)) + && message.Dispatch(messageTypes, _ => Interlocked.Increment(ref errors)) + && message.Dispatch(messageTypes, _ => Interlocked.Increment(ref errors)) + && message.Dispatch(messageTypes, _ => Interlocked.Increment(ref errors)) + && message.Dispatch(messageTypes, _ => Interlocked.Increment(ref errors)); } - public MessageHandler HandleTestCaseFinished; - public MessageHandler HandleTestCaseStarting; + public MessageHandler? HandleTestCaseFinished; + public MessageHandler? HandleTestCaseStarting; void HandleTestAssemblyFinished(MessageHandlerArgs args) { diff --git a/sources/tests/xunit.runner.stride/xunit.runner.stride.csproj b/sources/tests/xunit.runner.stride/xunit.runner.stride.csproj index 632fca68cc..f08ee56f4c 100644 --- a/sources/tests/xunit.runner.stride/xunit.runner.stride.csproj +++ b/sources/tests/xunit.runner.stride/xunit.runner.stride.csproj @@ -1,6 +1,9 @@ net8.0 + enable + latest + enable false true app.manifest @@ -22,4 +25,4 @@ - \ No newline at end of file + diff --git a/sources/tools/Stride.ConnectionRouter/Stride.ConnectionRouter.csproj b/sources/tools/Stride.ConnectionRouter/Stride.ConnectionRouter.csproj index 72e1080cb0..34e4ce5242 100644 --- a/sources/tools/Stride.ConnectionRouter/Stride.ConnectionRouter.csproj +++ b/sources/tools/Stride.ConnectionRouter/Stride.ConnectionRouter.csproj @@ -12,7 +12,6 @@ true - false false diff --git a/sources/tools/Stride.EffectCompilerServer/Stride.EffectCompilerServer.csproj b/sources/tools/Stride.EffectCompilerServer/Stride.EffectCompilerServer.csproj index cdfd0f52f1..a06abae44f 100644 --- a/sources/tools/Stride.EffectCompilerServer/Stride.EffectCompilerServer.csproj +++ b/sources/tools/Stride.EffectCompilerServer/Stride.EffectCompilerServer.csproj @@ -8,9 +8,6 @@ WindowsTools true - - false - diff --git a/sources/tools/Stride.FixProjectReferences/Stride.FixProjectReferences.csproj b/sources/tools/Stride.FixProjectReferences/Stride.FixProjectReferences.csproj index 0ae8e5d41f..ba98ace033 100644 --- a/sources/tools/Stride.FixProjectReferences/Stride.FixProjectReferences.csproj +++ b/sources/tools/Stride.FixProjectReferences/Stride.FixProjectReferences.csproj @@ -8,12 +8,6 @@ WindowsTools false - - false - - - false - diff --git a/sources/tools/Stride.SamplesTestServer/Stride.SamplesTestServer.csproj b/sources/tools/Stride.SamplesTestServer/Stride.SamplesTestServer.csproj index bfa4fa310d..464f194d03 100644 --- a/sources/tools/Stride.SamplesTestServer/Stride.SamplesTestServer.csproj +++ b/sources/tools/Stride.SamplesTestServer/Stride.SamplesTestServer.csproj @@ -6,9 +6,6 @@ WindowsTools true - - false - Properties\SharedAssemblyInfo.cs