Make Block and Span cache Length.
- Part of caching length required the `Span`'s `ReplaceWith` method to propagate its changes to its parent so that it can propogate the change to invalidate all parent length caches. - Added Span and Block tests to validate the interaction of caching. #1927
This commit is contained in:
parent
e8af1141cb
commit
968e033e4b
|
|
@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
internal class Block : SyntaxTreeNode
|
internal class Block : SyntaxTreeNode
|
||||||
{
|
{
|
||||||
|
private int? _length;
|
||||||
|
|
||||||
public Block(BlockBuilder source)
|
public Block(BlockBuilder source)
|
||||||
: this(source.Type, source.Children, source.ChunkGenerator)
|
: this(source.Type, source.Children, source.ChunkGenerator)
|
||||||
{
|
{
|
||||||
|
|
@ -58,7 +60,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int Length => Children.Sum(child => child.Length);
|
public override int Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_length == null)
|
||||||
|
{
|
||||||
|
var length = 0;
|
||||||
|
for (var i = 0; i < Children.Count; i++)
|
||||||
|
{
|
||||||
|
length += Children[i].Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _length.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public virtual IEnumerable<Span> Flatten()
|
public virtual IEnumerable<Span> Flatten()
|
||||||
{
|
{
|
||||||
|
|
@ -228,6 +248,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return blockBuilder.Build();
|
return blockBuilder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void ChildChanged()
|
||||||
|
{
|
||||||
|
// A node in our graph has changed. We'll need to recompute our length the next time we're asked for it.
|
||||||
|
_length = null;
|
||||||
|
|
||||||
|
Parent?.ChildChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private class EquivalenceComparer : IEqualityComparer<SyntaxTreeNode>
|
private class EquivalenceComparer : IEqualityComparer<SyntaxTreeNode>
|
||||||
{
|
{
|
||||||
public static readonly EquivalenceComparer Default = new EquivalenceComparer();
|
public static readonly EquivalenceComparer Default = new EquivalenceComparer();
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
private static readonly int TypeHashCode = typeof(Span).GetHashCode();
|
private static readonly int TypeHashCode = typeof(Span).GetHashCode();
|
||||||
private string _content;
|
private string _content;
|
||||||
|
private int? _length;
|
||||||
private SourceLocation _start;
|
private SourceLocation _start;
|
||||||
|
|
||||||
public Span(SpanBuilder builder)
|
public Span(SpanBuilder builder)
|
||||||
|
|
@ -32,7 +33,31 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public override bool IsBlock => false;
|
public override bool IsBlock => false;
|
||||||
|
|
||||||
public override int Length => Content.Length;
|
public override int Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_length == null)
|
||||||
|
{
|
||||||
|
var length = 0;
|
||||||
|
if (_content == null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Symbols.Count; i++)
|
||||||
|
{
|
||||||
|
length += Symbols[i].Content.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
length = _content.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _length.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override SourceLocation Start => _start;
|
public override SourceLocation Start => _start;
|
||||||
|
|
||||||
|
|
@ -79,6 +104,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
ChunkGenerator = builder.ChunkGenerator ?? SpanChunkGenerator.Null;
|
ChunkGenerator = builder.ChunkGenerator ?? SpanChunkGenerator.Null;
|
||||||
_start = builder.Start;
|
_start = builder.Start;
|
||||||
_content = null;
|
_content = null;
|
||||||
|
_length = null;
|
||||||
|
|
||||||
|
Parent?.ChildChanged();
|
||||||
|
|
||||||
// Since we took references to the values in SpanBuilder, clear its references out
|
// Since we took references to the values in SpanBuilder, clear its references out
|
||||||
builder.Reset();
|
builder.Reset();
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,42 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
public class BlockTest
|
public class BlockTest
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ChildChanged_NotifiesParent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var spanBuilder = new SpanBuilder(SourceLocation.Zero);
|
||||||
|
spanBuilder.Accept(new HtmlSymbol("hello", HtmlSymbolType.Text));
|
||||||
|
var span = spanBuilder.Build();
|
||||||
|
var blockBuilder = new BlockBuilder()
|
||||||
|
{
|
||||||
|
Type = BlockKindInternal.Markup,
|
||||||
|
};
|
||||||
|
blockBuilder.Children.Add(span);
|
||||||
|
var childBlock = blockBuilder.Build();
|
||||||
|
blockBuilder = new BlockBuilder()
|
||||||
|
{
|
||||||
|
Type = BlockKindInternal.Markup,
|
||||||
|
};
|
||||||
|
blockBuilder.Children.Add(childBlock);
|
||||||
|
var parentBlock = blockBuilder.Build();
|
||||||
|
var originalBlockLength = parentBlock.Length;
|
||||||
|
spanBuilder = new SpanBuilder(SourceLocation.Zero);
|
||||||
|
spanBuilder.Accept(new HtmlSymbol("hi", HtmlSymbolType.Text));
|
||||||
|
span.ReplaceWith(spanBuilder);
|
||||||
|
|
||||||
|
// Wire up parents now so we can re-trigger ChildChanged to cause cache refresh.
|
||||||
|
span.Parent = childBlock;
|
||||||
|
childBlock.Parent = parentBlock;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
childBlock.ChildChanged();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(5, originalBlockLength);
|
||||||
|
Assert.Equal(2, parentBlock.Length);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Clone_ClonesBlock()
|
public void Clone_ClonesBlock()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -7,6 +7,53 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
{
|
{
|
||||||
public class SpanTest
|
public class SpanTest
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ReplaceWith_ResetsLength()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new SpanBuilder(SourceLocation.Zero);
|
||||||
|
builder.Accept(new HtmlSymbol("hello", HtmlSymbolType.Text));
|
||||||
|
var span = builder.Build();
|
||||||
|
var newBuilder = new SpanBuilder(SourceLocation.Zero);
|
||||||
|
newBuilder.Accept(new HtmlSymbol("hi", HtmlSymbolType.Text));
|
||||||
|
var originalLength = span.Length;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
span.ReplaceWith(newBuilder);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(5, originalLength);
|
||||||
|
Assert.Equal(2, span.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This is more of an integration-like test. However, it's valuable to determine
|
||||||
|
// that the Span's ReplaceWith code is properly propogating change notifications to parents.
|
||||||
|
[Fact]
|
||||||
|
public void ReplaceWith_NotifiesParentChildHasChanged()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var spanBuilder = new SpanBuilder(SourceLocation.Zero);
|
||||||
|
spanBuilder.Accept(new HtmlSymbol("hello", HtmlSymbolType.Text));
|
||||||
|
var span = spanBuilder.Build();
|
||||||
|
var blockBuilder = new BlockBuilder()
|
||||||
|
{
|
||||||
|
Type = BlockKindInternal.Markup,
|
||||||
|
};
|
||||||
|
blockBuilder.Children.Add(span);
|
||||||
|
var block = blockBuilder.Build();
|
||||||
|
span.Parent = block;
|
||||||
|
var originalBlockLength = block.Length;
|
||||||
|
var newSpanBuilder = new SpanBuilder(SourceLocation.Zero);
|
||||||
|
newSpanBuilder.Accept(new HtmlSymbol("hi", HtmlSymbolType.Text));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
span.ReplaceWith(newSpanBuilder);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(5, originalBlockLength);
|
||||||
|
Assert.Equal(2, block.Length);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Clone_ClonesSpan()
|
public void Clone_ClonesSpan()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue