public void TestMultiplePrefabsMixedInheritance() { // The purpose of this test is to check that modifying a prefab base is correctly propagated through all // derived prefabs. We use the following scenario: // a1: base asset) // a2: inherit from a1 by composition with 2 instances (baseParts: a1, 2 instances) // a3: direct inheritance from a2 (base: a2) // This scenario doesn't happen in practice, as we have restricted only to inheritance by composition for prefabs // but we verify that the code is actually working for this scenario var package = new Package(); var assetItems = package.Assets; // Before Adding Package // a1: a2: (baseParts: a1, 2 instances) a3: (base: a2) // | ea | ea1 (base: ea) | ea1' (base: ea1) // | eb | eb1 (base: eb) | eb1' (base: eb1) // | ea2 (base: ea) | ea2' (base: ea2) // | eb2 (base: eb) | eb2' (base: eb2) // After adding the package to the session // We add one entity to the base a1 // a1: a2: (baseParts: a1, 2 instances) a3: (base: a2) // | ea | ea1 (base: ea) | ea1' (base: ea1) // | eb | eb1 (base: eb) | eb1' (base: eb1) // | ec | ec1 (base: ec) | ec1' (base: ec1) // | ea2 (base: ea) | ea2' (base: ea2) // | eb2 (base: eb) | eb2' (base: eb2) // | ec2 (base: ec) | ec2' (base: ec2) var a1 = new PrefabAsset(); var ea = new Entity("ea"); var eb = new Entity("eb"); a1.Hierarchy.Parts.Add(new EntityDesign(ea)); a1.Hierarchy.Parts.Add(new EntityDesign(eb)); a1.Hierarchy.RootPartIds.Add(ea.Id); a1.Hierarchy.RootPartIds.Add(eb.Id); assetItems.Add(new AssetItem("a1", a1)); var a2 = new PrefabAsset(); var aPartInstance1 = a1.CreatePrefabInstance(a2, "a1"); var aPartInstance2 = a1.CreatePrefabInstance(a2, "a1"); a2.Hierarchy.Parts.AddRange(aPartInstance1.Parts); a2.Hierarchy.Parts.AddRange(aPartInstance2.Parts); a2.Hierarchy.RootPartIds.AddRange(aPartInstance1.RootPartIds); a2.Hierarchy.RootPartIds.AddRange(aPartInstance2.RootPartIds); assetItems.Add(new AssetItem("a2", a2)); // Modify a1 to add entity ec var ec = new Entity("ec"); a1.Hierarchy.Parts.Add(new EntityDesign(ec)); a1.Hierarchy.RootPartIds.Add(ec.Id); var a3 = (PrefabAsset)a2.CreateDerivedAsset("a2"); assetItems.Add(new AssetItem("a3", a3)); // Create a session with this project using (var session = new PackageSession()) { var logger = new LoggerResult(); session.AddExistingPackage(package, logger); Assert.False(logger.HasErrors); Assert.AreEqual(6, a2.Hierarchy.RootPartIds.Count); Assert.True(a2.Hierarchy.Parts.All(it => it.Base != null)); Assert.AreEqual(6, a3.Hierarchy.RootPartIds.Count); Assert.True(a3.Hierarchy.Parts.All(it => it.Base != null && a2.Hierarchy.Parts.ContainsKey(it.Base.BasePartId))); } }
public void TestMultiplePrefabsInheritanceAndChildren() { // The purpose of this test is to check that modifying a prefab base is correctly propagated through all // derived prefabs. We use the following scenario: // // a1: base asset // a2: inherit from a1 by composition with 2 instances (baseParts: a1 => 2 instances) // a3: inherit from a1 by composition with 1 instances (baseParts: a1 => 1 instances) // a4: inherit from a2 and a3 by composition with 1 instances for each (baseParts: a1 => 1 instance, a2 => 1 instance) // // Unlike TestMultiplePrefabsMixedInheritance, we use only inheritance by composition for this scenario to match current use cases var package = new Package(); var assetItems = package.Assets; // First we create assets with the following configuration: // a1: a2: (baseParts: a1, 2 instances) a3: (baseParts: a1) a4: (baseParts: a2 x 1, a3 x 1) // | er | er1 (base: er) | er1' (base: er) | eRoot // | ea | ea1 (base: ea) | ea1' (base: ea) | er1* (base: er) // | eb | eb1 (base: eb) | eb1' (base: eb) | ea1* (base: ea) // | ec | ec1 (base: ec) | ec1' (base: ec) | eb1* (base: eb) // | er2 (base: er) | ec1* (base: ec) // | ea2 (base: ea) | er2* (base: er) // | eb2 (base: eb) | ea2* (base: ea) // | ec2 (base: ec) | eb2* (base: eb) // | ec2* (base: ec) // | er1'* (base: er) // | ea1'* (base: ea) // | eb1'* (base: eb) // | ec1'* (base: ec) var a1 = new PrefabAsset(); var er = new Entity("er"); var ea = new Entity("ea"); var eb = new Entity("eb"); var ec = new Entity("ec"); a1.Hierarchy.Parts.Add(new EntityDesign(er)); a1.Hierarchy.Parts.Add(new EntityDesign(ea)); a1.Hierarchy.Parts.Add(new EntityDesign(eb)); a1.Hierarchy.Parts.Add(new EntityDesign(ec)); a1.Hierarchy.RootPartIds.Add(er.Id); er.AddChild(ea); er.AddChild(eb); er.AddChild(ec); assetItems.Add(new AssetItem("a1", a1)); var member = (IMemberDescriptor)TypeDescriptorFactory.Default.Find(typeof(Entity))[nameof(Entity.Name)]; var a2 = new PrefabAsset(); var a2PartInstance1 = a1.CreatePrefabInstance(a2, "a1"); foreach (var entity in a2PartInstance1.Parts) { entity.Entity.Name += "1"; } var a2PartInstance2 = a1.CreatePrefabInstance(a2, "a1"); foreach (var entity in a2PartInstance2.Parts) { entity.Entity.Name += "2"; } a2.Hierarchy.Parts.AddRange(a2PartInstance1.Parts); a2.Hierarchy.Parts.AddRange(a2PartInstance2.Parts); a2.Hierarchy.RootPartIds.AddRange(a2PartInstance1.RootPartIds); a2.Hierarchy.RootPartIds.AddRange(a2PartInstance2.RootPartIds); Assert.AreEqual(8, a2.Hierarchy.Parts.Count); Assert.AreEqual(2, a2.Hierarchy.RootPartIds.Count); assetItems.Add(new AssetItem("a2", a2)); var a3 = new PrefabAsset(); var a3PartInstance1 = a1.CreatePrefabInstance(a3, "a1"); foreach (var entity in a3PartInstance1.Parts) { entity.Entity.Name += "1'"; } a3.Hierarchy.Parts.AddRange(a3PartInstance1.Parts); a3.Hierarchy.RootPartIds.AddRange(a3PartInstance1.RootPartIds); Assert.AreEqual(4, a3.Hierarchy.Parts.Count); Assert.AreEqual(1, a3.Hierarchy.RootPartIds.Count); assetItems.Add(new AssetItem("a3", a3)); var a4 = new PrefabAsset(); var eRoot = new Entity("eRoot"); var a2PartInstance3 = a2.CreatePrefabInstance(a4, "a2"); foreach (var entity in a2PartInstance3.Parts) { entity.Entity.Name += "*"; } foreach (var entity in a2PartInstance3.Parts.Where(t => a2PartInstance3.RootPartIds.Contains(t.Entity.Id))) { eRoot.AddChild(entity.Entity); } var a3PartInstance2 = a3.CreatePrefabInstance(a4, "a3"); foreach (var entity in a3PartInstance2.Parts) { entity.Entity.Name += "*"; } a4.Hierarchy.Parts.Add(new EntityDesign(eRoot)); a4.Hierarchy.Parts.AddRange(a2PartInstance3.Parts); a4.Hierarchy.Parts.AddRange(a3PartInstance2.Parts); a4.Hierarchy.RootPartIds.Add(eRoot.Id); a4.Hierarchy.RootPartIds.AddRange(a3PartInstance2.RootPartIds); Assert.AreEqual(13, a4.Hierarchy.Parts.Count); Assert.AreEqual(2, a4.Hierarchy.RootPartIds.Count); assetItems.Add(new AssetItem("a4", a4)); Assert.True(a1.DumpTo(Console.Out, "a1 BEFORE PrefabMergeAsset")); Assert.True(a2.DumpTo(Console.Out, "a2 BEFORE PrefabMergeAsset")); Assert.True(a3.DumpTo(Console.Out, "a3 BEFORE PrefabMergeAsset")); Assert.True(a4.DumpTo(Console.Out, "a4 BEFORE PrefabMergeAsset")); // Then we simulate a concurrent change to a1 by someone that didn't have a2/a3/a4 // - Add one component to a1, linking to an existing entity ea // - Add a root entity to a1 with a link to an existing entity eb // // a1: a2: (baseParts: a1, 2 instances) a3: (baseParts: a1) a4: (baseParts: a2 x 1, a3 x 1) // | er | er1 (base: er) | er1' (base: er) | eRoot // | ea | ea1 (base: ea) | ea1' (base: ea) | er1* (base: er) // | eb | eb1 (base: eb) | eb1' (base: eb) | ea1* (base: ea) // | ec + link ea | ec1 + link ea1 (base: ec) | ec1' + link ea1' (base: ec) | eb1* (base: eb) // | ex | er2 (base: er) | ex(1') (base: ex) | ec1* + link ea1* (base: ec) // | ey + link eb | ea2 (base: ea) | ey(1') + link eb1' | er2* (base: er) // | eb2 (base: eb) | ea2* (base: ea) // | ec2 + link ea2 (base: ec) | eb2* (base: eb) // | ex(1) | ec2* + link ea2* (base: ec) // | ey(1) + link eb1 | er1'* (base: er) // | ex(2) | ea1'* (base: ea) // | ey(2) + link eb2 | eb1'* (base: eb) // | ec1'* + link ea1'* (base: ec) // | ex(1*) // | ey(1*) + link eb1* // | ex(2*) // | ey(2*) + link eb2* // | ex(1') (base: ex) // | ey(1') + link eb1'* ec.Components.Add(new TestEntityComponent() { EntityLink = ea }); var ex = new Entity("ex"); var ey = new Entity("ey"); ey.Components.Add(new TestEntityComponent() { EntityLink = eb }); ex.AddChild(ey); a1.Hierarchy.Parts.Add(new EntityDesign(ex)); a1.Hierarchy.Parts.Add(new EntityDesign(ey)); a1.Hierarchy.RootPartIds.Add(ex.Id); Assert.AreEqual(6, a1.Hierarchy.Parts.Count); Assert.AreEqual(2, a1.Hierarchy.RootPartIds.Count); // Simulates the loading of this package using (var session = new PackageSession()) { var logger = new LoggerResult(); session.AddExistingPackage(package, logger); Assert.False(logger.HasErrors); Assert.True(a1.DumpTo(Console.Out, "a1 AFTER PrefabMergeAsset")); // ------------------------------------------------ // Check for a2 // ------------------------------------------------ // a2: (baseParts: a1, 2 instances) // | er1 (base: er) // | ea1 (base: ea) // | eb1 (base: eb) // | ec1 + link ea1 (base: ec) // | er2 (base: er) // | ea2 (base: ea) // | eb2 (base: eb) // | ec2 + link ea2 (base: ec) // | ex(1) // | ey(1) + link eb1 // | ex(2) // | ey(2) + link eb2 { Assert.True(a2.DumpTo(Console.Out, "a2 AFTER PrefabMergeAsset")); Assert.AreEqual(4, a2.Hierarchy.RootPartIds.Count); Assert.True(a2.Hierarchy.Parts.All(it => it.Base != null)); // Check that we have all expected entities Assert.AreEqual(12, a2.Hierarchy.Parts.Count); var eb1 = a2.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == "eb1")?.Entity; var eb2 = a2.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == "eb2")?.Entity; Assert.NotNull(eb1); Assert.NotNull(eb2); // Check that we have ex and ey var exList = a2.Hierarchy.Parts.Where(it => it.Entity.Name == ex.Name).ToList(); Assert.AreEqual(2, exList.Count); // Check that both [ex] have both 1 element [ey] and the links to eb1/eb2 are correct { var expecting = new List<Entity>() { eb1, eb2 }; for (int i = 0; i < exList.Count; i++) { var ex1 = exList[i].Entity; Assert.AreEqual(1, ex1.Transform.Children.Count); var ey1 = ex1.Transform.Children[0].Entity; Assert.AreEqual(ey.Name, ey1.Name); Assert.NotNull(ey1.Get<TestEntityComponent>()); var entityLink = ey1.Get<TestEntityComponent>().EntityLink; Assert.True(expecting.Contains(entityLink)); expecting.Remove(entityLink); } } // Check link from ec1 to ea1 { var ec1 = a2.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == "ec1")?.Entity; Assert.NotNull(ec1); var ea1 = a2.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == "ea1")?.Entity; Assert.NotNull(ea1); Assert.NotNull(ec1.Get<TestEntityComponent>()); Assert.AreEqual(ea1, ec1.Get<TestEntityComponent>().EntityLink); } // Check link from ec2 to ea2 { var ec2 = a2.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == "ec2")?.Entity; Assert.NotNull(ec2); var ea2 = a2.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == "ea2")?.Entity; Assert.NotNull(ea2); Assert.NotNull(ec2.Get<TestEntityComponent>()); Assert.AreEqual(ea2, ec2.Get<TestEntityComponent>().EntityLink); } } // ------------------------------------------------ // Check for a3 // ------------------------------------------------ // a3: (baseParts: a1) // | er1' (base: er) // | ea1' (base: ea) // | eb1' (base: eb) // | ec1' + link ea1' (base: ec) // | ex1' (base: ex) // | ey1' + link eb1' { Assert.True(a3.DumpTo(Console.Out, "a3 AFTER PrefabMergeAsset")); Assert.AreEqual(2, a3.Hierarchy.RootPartIds.Count); Assert.True(a3.Hierarchy.Parts.All(it => it.Base != null)); // Check that we have all expected entities Assert.AreEqual(6, a3.Hierarchy.Parts.Count); var eb1 = a3.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == "eb1'")?.Entity; Assert.NotNull(eb1); // Check that we have ex and ey var exList = a3.Hierarchy.Parts.Where(it => it.Entity.Name == ex.Name).ToList(); Assert.AreEqual(1, exList.Count); // Check that [ex] have 1 element [ey] and the link to eb1 is correct { var ex1 = exList[0].Entity; Assert.AreEqual(1, ex1.Transform.Children.Count); var ey1 = ex1.Transform.Children[0].Entity; Assert.AreEqual(ey.Name, ey1.Name); Assert.NotNull(ey1.Get<TestEntityComponent>()); Assert.AreEqual(eb1, ey1.Get<TestEntityComponent>().EntityLink); } { var ec1 = a3.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == "ec1'")?.Entity; Assert.NotNull(ec1); var ea1 = a3.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == "ea1'")?.Entity; Assert.NotNull(ea1); Assert.NotNull(ec1.Get<TestEntityComponent>()); Assert.AreEqual(ea1, ec1.Get<TestEntityComponent>().EntityLink); } } // ------------------------------------------------ // Check for a4 // ------------------------------------------------ // a4: (baseParts: a2 x 1, a3 x 1) // | eNewRoot // | er1* (base: er) // | ea1* (base: ea) // | eb1* (base: eb) // | ec1* + link ea1* (base: ec) // | er2* (base: er) // | ea2* (base: ea) // | eb2* (base: eb) // | ec2* + link ea2* (base: ec) // | er1'* (base: er) // | ea1'* (base: ea) // | eb1'* (base: eb) // | ec1'* + link ea1'* (base: ec) // | ex(1*) // | ey(1*) + link eb1* // | ex(2*) // | ey(2*) + link eb2* // | ex(1') (base: ex) // | ey(1') + link eb1'* { Assert.True(a4.DumpTo(Console.Out, "a4 AFTER PrefabMergeAsset")); Assert.AreEqual(5, a4.Hierarchy.RootPartIds.Count); Assert.True(a4.Hierarchy.Parts.Where(it => it.Entity.Name != "eRoot").All(it => it.Entity.Name != "eRoot" && it.Base != null)); // Check that we have all expected entities Assert.AreEqual(19, a4.Hierarchy.Parts.Count); var eb1 = a4.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == "eb1*")?.Entity; var eb1_2 = a4.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == "eb1'*")?.Entity; var eb2 = a4.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == "eb2*")?.Entity; Assert.NotNull(eb1); Assert.NotNull(eb1_2); Assert.NotNull(eb2); // Check that we have ex and ey var exList = a4.Hierarchy.Parts.Where(it => it.Entity.Name == ex.Name).ToList(); Assert.AreEqual(3, exList.Count); // Check that both [ex] have both 1 element [ey] and the links to eb1/eb2 are correct { var expecting = new List<Entity>() { eb1, eb1_2, eb2 }; for (int i = 0; i < exList.Count; i++) { var ex1 = exList[i].Entity; Assert.AreEqual(1, ex1.Transform.Children.Count); var ey1 = ex1.Transform.Children[0].Entity; Assert.AreEqual(ey.Name, ey1.Name); Assert.NotNull(ey1.Get<TestEntityComponent>()); var entityLink = ey1.Get<TestEntityComponent>().EntityLink; Assert.True(expecting.Contains(entityLink)); expecting.Remove(entityLink); } } // Check all [er] entities Action<string> checkErX = (erName) => { var er1 = a4.Hierarchy.Parts.FirstOrDefault(it => it.Entity.Name == erName)?.Entity; Assert.NotNull(er1); Assert.AreEqual(3, er1.Transform.Children.Count); var ec1 = er1.Transform.Children.FirstOrDefault(it => it.Entity.Name.StartsWith("ec"))?.Entity; Assert.NotNull(ec1); var ea1 = er1.Transform.Children.FirstOrDefault(it => it.Entity.Name.StartsWith("ea"))?.Entity; Assert.NotNull(ea1); Assert.NotNull(ec1.Get<TestEntityComponent>()); Assert.AreEqual(ea1, ec1.Get<TestEntityComponent>().EntityLink); }; checkErX("er1*"); checkErX("er2*"); checkErX("er1'*"); } } }
public void TestPackageAssetTemplatingAnalysis() { var package = new Package(); var assetItems = package.Assets; // Before Adding Package // a1: a2: (baseParts: a1, 2 instances) a3: (base: a2) // | ea | ea1 (base: ea) | ea1' (base: ea1) // | eb | eb1 (base: eb) | eb1' (base: eb1) // | ea2 (base: ea) | ea2' (base: ea2) // | eb2 (base: eb) | eb2' (base: eb2) // After adding the package to the session // We add one entity to the base a1 // a1: a2: (baseParts: a1, 2 instances) a3: (base: a2) // | ea | ea1 (base: ea) | ea1' (base: ea1) // | eb | eb1 (base: eb) | eb1' (base: eb1) // | ec | ec1 (base: ec) | ec1' (base: ec1) // | ea2 (base: ea) | ea2' (base: ea2) // | eb2 (base: eb) | eb2' (base: eb2) // | ec2 (base: ec) | ec2' (base: ec2) var a1 = new EntityGroupAsset(); var ea = new Entity("ea"); var eb = new Entity("eb"); a1.Hierarchy.Entities.Add(ea); a1.Hierarchy.Entities.Add(eb); a1.Hierarchy.RootEntities.Add(ea.Id); a1.Hierarchy.RootEntities.Add(eb.Id); assetItems.Add(new AssetItem("a1", a1)); var a2 = new EntityGroupAsset(); var aPartInstance1 = (EntityGroupAsset)a1.CreateChildAsset("a1"); var aPartInstance2 = (EntityGroupAsset)a1.CreateChildAsset("a1"); a2.AddPart(aPartInstance1); a2.AddPart(aPartInstance2); assetItems.Add(new AssetItem("a2", a2)); // Modify a1 to add entity ec var ec = new Entity("ec"); a1.Hierarchy.Entities.Add(ec); a1.Hierarchy.RootEntities.Add(ec.Id); var a3 = (EntityGroupAsset)a2.CreateChildAsset("a2"); assetItems.Add(new AssetItem("a3", a3)); // Create a session with this project using (var session = new PackageSession()) { var logger = new LoggerResult(); session.AddExistingPackage(package, logger); Assert.False(logger.HasErrors); Assert.AreEqual(6, a2.Hierarchy.RootEntities.Count); Assert.True(a2.Hierarchy.Entities.All(it => it.Design.BaseId.HasValue && it.Design.BasePartInstanceId.HasValue)); Assert.AreEqual(6, a3.Hierarchy.RootEntities.Count); Assert.True(a3.Hierarchy.Entities.All(it => !it.Design.BasePartInstanceId.HasValue)); Assert.True(a3.Hierarchy.Entities.All(it => it.Design.BaseId.HasValue && a2.Hierarchy.Entities.ContainsKey(it.Design.BaseId.Value))); } }