perf(templateengine): memoise CollisionDetector's composed-module walk per check (review 2026-10-21 04/P8b)
This commit is contained in:
@@ -258,4 +258,55 @@ public class CollisionDetectorTests
|
||||
var collisions = CollisionDetector.DetectCollisions(a, all);
|
||||
Assert.Empty(collisions);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Review 2026-10-21 04/P8(b): a module reached through several slots is walked once
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a module composed into several slots (a diamond) has its subtree
|
||||
/// walked once per collision check — every further slot reuses the memoised member
|
||||
/// list under its own prefix — while still producing every canonical name.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DetectCollisions_DiamondComposition_WalksSharedModuleOnce()
|
||||
{
|
||||
var leaf = new Template("Leaf") { Id = 3 };
|
||||
leaf.Attributes.Add(new TemplateAttribute("V") { Id = 30, TemplateId = 3, DataType = DataType.Float });
|
||||
|
||||
var mid = new Template("Mid") { Id = 2 };
|
||||
var midCompositions = new CountingCollection<TemplateComposition>
|
||||
{
|
||||
new TemplateComposition("x") { Id = 20, TemplateId = 2, ComposedTemplateId = 3 },
|
||||
new TemplateComposition("y") { Id = 21, TemplateId = 2, ComposedTemplateId = 3 },
|
||||
};
|
||||
mid.Compositions = midCompositions;
|
||||
|
||||
var root = new Template("Root") { Id = 1 };
|
||||
root.Compositions.Add(new TemplateComposition("a") { Id = 10, TemplateId = 1, ComposedTemplateId = 2 });
|
||||
root.Compositions.Add(new TemplateComposition("b") { Id = 11, TemplateId = 1, ComposedTemplateId = 2 });
|
||||
root.Compositions.Add(new TemplateComposition("c") { Id = 12, TemplateId = 1, ComposedTemplateId = 2 });
|
||||
// A genuine collision reachable only through the third slot of the shared module.
|
||||
root.Attributes.Add(new TemplateAttribute("c.y.V") { Id = 40, TemplateId = 1, DataType = DataType.Float });
|
||||
|
||||
midCompositions.Enumerations = 0;
|
||||
var collisions = CollisionDetector.DetectCollisions(root, new List<Template> { root, mid, leaf });
|
||||
|
||||
Assert.Equal(1, midCompositions.Enumerations); // pre-fix: 3 (once per slot)
|
||||
var collision = Assert.Single(collisions);
|
||||
Assert.Contains("'c.y.V'", collision);
|
||||
Assert.Contains("module 'c.y'", collision);
|
||||
}
|
||||
|
||||
/// <summary>An <see cref="ICollection{T}"/> that counts how often it is enumerated.</summary>
|
||||
private sealed class CountingCollection<T> : List<T>, ICollection<T>
|
||||
{
|
||||
public int Enumerations { get; set; }
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
Enumerations++;
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user