Further renderer refactoring
This commit is contained in:
parent
d8ae9fd478
commit
e37e22aa27
|
|
@ -14,7 +14,7 @@ namespace HostedInAspNet.Client
|
|||
{
|
||||
// Temporarily render this test component until there's a proper mechanism
|
||||
// for testing this.
|
||||
new BrowserRenderer().AddComponent("app", new MyComponent());
|
||||
new BrowserRenderer().AddComponent<MyComponent>("app");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ namespace StandaloneApp
|
|||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
new BrowserRenderer()
|
||||
.AddComponent("app", new Home());
|
||||
new BrowserRenderer().AddComponent<Home>("app");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,6 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
|||
{
|
||||
private readonly int _browserRendererId;
|
||||
|
||||
// Ensures the explicitly-added components aren't GCed, because the browser
|
||||
// will still send events referencing them by ID. We only need to store the
|
||||
// top-level components, because the associated ComponentState will reference
|
||||
// all the reachable descendant components of each.
|
||||
private IList<IComponent> _rootComponents = new List<IComponent>();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="BrowserRenderer"/>.
|
||||
/// </summary>
|
||||
|
|
@ -38,22 +32,33 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
|||
internal void RenderNewBatchInternal(int componentId)
|
||||
=> RenderNewBatch(componentId);
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a new root component to the renderer,
|
||||
/// causing it to be displayed in the specified DOM element.
|
||||
/// </summary>
|
||||
/// <typeparam name="TComponent">The type of the component.</typeparam>
|
||||
/// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param>
|
||||
public void AddComponent<TComponent>(string domElementSelector)
|
||||
where TComponent: IComponent
|
||||
{
|
||||
AddComponent(typeof(TComponent), domElementSelector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Associates the <see cref="IComponent"/> with the <see cref="BrowserRenderer"/>,
|
||||
/// causing it to be displayed in the specified DOM element.
|
||||
/// </summary>
|
||||
/// <param name="componentType">The type of the component.</param>
|
||||
/// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param>
|
||||
/// <param name="component">The <see cref="IComponent"/>.</param>
|
||||
public void AddComponent(string domElementSelector, IComponent component)
|
||||
public void AddComponent(Type componentType, string domElementSelector)
|
||||
{
|
||||
var component = InstantiateComponent(componentType);
|
||||
var componentId = AssignComponentId(component);
|
||||
RegisteredFunction.InvokeUnmarshalled<int, string, int, object>(
|
||||
"attachComponentToElement",
|
||||
_browserRendererId,
|
||||
domElementSelector,
|
||||
componentId);
|
||||
_rootComponents.Add(component);
|
||||
|
||||
RenderNewBatch(componentId);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,36 +10,6 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
{
|
||||
internal static class RenderTreeDiffBuilder
|
||||
{
|
||||
private struct DiffContext
|
||||
{
|
||||
// Exists only so that the various methods in this class can call each other without
|
||||
// constantly building up long lists of parameters. Is private to this class, so the
|
||||
// fact that it's a mutable struct is manageable.
|
||||
// Always pass by ref to avoid copying, and because the 'SiblingIndex' is mutable.
|
||||
|
||||
public readonly Renderer Renderer;
|
||||
public readonly RenderBatchBuilder BatchBuilder;
|
||||
public readonly RenderTreeFrame[] OldTree;
|
||||
public readonly RenderTreeFrame[] NewTree;
|
||||
public readonly ArrayBuilder<RenderTreeEdit> Edits;
|
||||
public readonly ArrayBuilder<RenderTreeFrame> ReferenceFrames;
|
||||
public int SiblingIndex;
|
||||
|
||||
public DiffContext(
|
||||
Renderer renderer,
|
||||
RenderBatchBuilder batchBuilder,
|
||||
RenderTreeFrame[] oldTree, RenderTreeFrame[] newTree)
|
||||
{
|
||||
Renderer = renderer;
|
||||
BatchBuilder = batchBuilder;
|
||||
OldTree = oldTree;
|
||||
NewTree = newTree;
|
||||
Edits = batchBuilder.EditsBuffer;
|
||||
ReferenceFrames = batchBuilder.ReferenceFramesBuffer;
|
||||
SiblingIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static RenderTreeDiff ComputeDiff(
|
||||
Renderer renderer,
|
||||
RenderBatchBuilder batchBuilder,
|
||||
|
|
@ -419,7 +389,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
{
|
||||
if (oldFrame.AttributeEventHandlerId > 0)
|
||||
{
|
||||
diffContext.BatchBuilder.AddDisposedEventHandlerId(oldFrame.AttributeEventHandlerId);
|
||||
diffContext.BatchBuilder.DisposedEventHandlerIds.Append(oldFrame.AttributeEventHandlerId);
|
||||
}
|
||||
InitializeNewAttributeFrame(ref diffContext, ref newFrame);
|
||||
var referenceFrameIndex = diffContext.ReferenceFrames.Append(newFrame);
|
||||
|
|
@ -490,7 +460,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
diffContext.Edits.Append(RenderTreeEdit.RemoveAttribute(diffContext.SiblingIndex, oldFrame.AttributeName));
|
||||
if (oldFrame.AttributeEventHandlerId > 0)
|
||||
{
|
||||
diffContext.BatchBuilder.AddDisposedEventHandlerId(oldFrame.AttributeEventHandlerId);
|
||||
diffContext.BatchBuilder.DisposedEventHandlerIds.Append(oldFrame.AttributeEventHandlerId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -568,7 +538,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
throw new InvalidOperationException($"Child component already exists during {nameof(InitializeNewComponentFrame)}");
|
||||
}
|
||||
|
||||
diffContext.Renderer.InstantiateChildComponent(ref frame);
|
||||
diffContext.Renderer.InstantiateChildComponentOnFrame(ref frame);
|
||||
var childComponentInstance = frame.Component;
|
||||
|
||||
// All descendants of a component are its properties
|
||||
|
|
@ -604,9 +574,42 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
}
|
||||
else if (frame.FrameType == RenderTreeFrameType.Attribute && frame.AttributeEventHandlerId > 0)
|
||||
{
|
||||
batchBuilder.AddDisposedEventHandlerId(frame.AttributeEventHandlerId);
|
||||
batchBuilder.DisposedEventHandlerIds.Append(frame.AttributeEventHandlerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exists only so that the various methods in this class can call each other without
|
||||
/// constantly building up long lists of parameters. Is private to this class, so the
|
||||
/// fact that it's a mutable struct is manageable.
|
||||
///
|
||||
/// Always pass by ref to avoid copying, and because the 'SiblingIndex' is mutable.
|
||||
/// </summary>
|
||||
private struct DiffContext
|
||||
{
|
||||
public readonly Renderer Renderer;
|
||||
public readonly RenderBatchBuilder BatchBuilder;
|
||||
public readonly RenderTreeFrame[] OldTree;
|
||||
public readonly RenderTreeFrame[] NewTree;
|
||||
public readonly ArrayBuilder<RenderTreeEdit> Edits;
|
||||
public readonly ArrayBuilder<RenderTreeFrame> ReferenceFrames;
|
||||
public int SiblingIndex;
|
||||
|
||||
public DiffContext(
|
||||
Renderer renderer,
|
||||
RenderBatchBuilder batchBuilder,
|
||||
RenderTreeFrame[] oldTree,
|
||||
RenderTreeFrame[] newTree)
|
||||
{
|
||||
Renderer = renderer;
|
||||
BatchBuilder = batchBuilder;
|
||||
OldTree = oldTree;
|
||||
NewTree = newTree;
|
||||
Edits = batchBuilder.EditsBuffer;
|
||||
ReferenceFrames = batchBuilder.ReferenceFramesBuffer;
|
||||
SiblingIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,11 +35,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
_renderTreeBuilderPrevious = new RenderTreeBuilder(renderer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regenerates the <see cref="RenderTree"/> and adds the changes to the
|
||||
/// <paramref name="batchBuilder"/>.
|
||||
/// </summary>
|
||||
public void Render(Renderer renderer, RenderBatchBuilder batchBuilder)
|
||||
public void RenderIntoBatch(RenderBatchBuilder batchBuilder)
|
||||
{
|
||||
if (_component is IHandlePropertiesChanged notifyableComponent)
|
||||
{
|
||||
|
|
@ -59,19 +55,9 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
_renderTreeBuilderPrevious.GetFrames(),
|
||||
_renderTreeBuilderCurrent.GetFrames());
|
||||
batchBuilder.UpdatedComponentDiffs.Append(diff);
|
||||
|
||||
// Process disposal queue now in case it causes further component renders to be enqueued
|
||||
while (batchBuilder.ComponentDisposalQueue.Count > 0)
|
||||
{
|
||||
var disposeComponentId = batchBuilder.ComponentDisposalQueue.Dequeue();
|
||||
renderer.DisposeInExistingBatch(batchBuilder, disposeComponentId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the component that it is being disposed.
|
||||
/// </summary>
|
||||
public void NotifyDisposed(RenderBatchBuilder batchBuilder)
|
||||
public void DisposeInBatch(RenderBatchBuilder batchBuilder)
|
||||
{
|
||||
// TODO: Handle components throwing during dispose. Shouldn't break the whole render batch.
|
||||
if (_component is IDisposable disposable)
|
||||
|
|
|
|||
|
|
@ -6,45 +6,41 @@ using Microsoft.AspNetCore.Blazor.RenderTree;
|
|||
|
||||
namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Collects the data produced by the rendering system during the course
|
||||
/// of rendering a single batch. This tracks both the final output data
|
||||
/// and the intermediate states (such as the queue of components still to
|
||||
/// be rendered).
|
||||
/// </summary>
|
||||
internal class RenderBatchBuilder
|
||||
{
|
||||
// Primary result data
|
||||
public ArrayBuilder<RenderTreeDiff> UpdatedComponentDiffs { get; } = new ArrayBuilder<RenderTreeDiff>();
|
||||
public ArrayBuilder<int> DisposedComponentIds { get; } = new ArrayBuilder<int>();
|
||||
public ArrayBuilder<int> DisposedEventHandlerIds { get; } = new ArrayBuilder<int>();
|
||||
|
||||
// Buffers referenced by UpdatedComponentDiffs
|
||||
public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>();
|
||||
public ArrayBuilder<RenderTreeFrame> ReferenceFramesBuffer { get; } = new ArrayBuilder<RenderTreeFrame>();
|
||||
|
||||
// State of render pipeline
|
||||
public Queue<int> ComponentRenderQueue { get; } = new Queue<int>();
|
||||
|
||||
public Queue<int> ComponentDisposalQueue { get; } = new Queue<int>();
|
||||
|
||||
public ArrayBuilder<RenderTreeDiff> UpdatedComponentDiffs { get; set; }
|
||||
= new ArrayBuilder<RenderTreeDiff>();
|
||||
|
||||
private readonly ArrayBuilder<int> _disposedComponentIds = new ArrayBuilder<int>();
|
||||
|
||||
private readonly ArrayBuilder<int> _disposedEventHandlerIds = new ArrayBuilder<int>();
|
||||
|
||||
public ArrayRange<int> GetDisposedEventHandlerIds()
|
||||
=> _disposedEventHandlerIds.ToRange();
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
EditsBuffer.Clear();
|
||||
ReferenceFramesBuffer.Clear();
|
||||
ComponentRenderQueue.Clear();
|
||||
UpdatedComponentDiffs.Clear();
|
||||
_disposedComponentIds.Clear();
|
||||
_disposedEventHandlerIds.Clear();
|
||||
DisposedComponentIds.Clear();
|
||||
DisposedEventHandlerIds.Clear();
|
||||
}
|
||||
|
||||
public RenderBatch ToBatch()
|
||||
=> new RenderBatch(
|
||||
UpdatedComponentDiffs.ToRange(),
|
||||
ReferenceFramesBuffer.ToRange(),
|
||||
_disposedComponentIds.ToRange());
|
||||
|
||||
public void AddDisposedComponentId(int componentId)
|
||||
=> _disposedComponentIds.Append(componentId);
|
||||
|
||||
public void AddDisposedEventHandlerId(int attributeEventHandlerId)
|
||||
=> _disposedEventHandlerIds.Append(attributeEventHandlerId);
|
||||
DisposedComponentIds.ToRange());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Blazor.Components;
|
|||
using Microsoft.AspNetCore.Blazor.RenderTree;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Rendering
|
||||
|
|
@ -16,15 +15,9 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
/// </summary>
|
||||
public abstract class Renderer
|
||||
{
|
||||
// Methods for tracking associations between component IDs, instances, and states,
|
||||
// without pinning any of them in memory here. The explictly GC rooted items are the
|
||||
// components explicitly added to the renderer (i.e., top-level components). In turn
|
||||
// these reference descendant components and associated ComponentState instances.
|
||||
private readonly WeakValueDictionary<int, ComponentState> _componentStateById
|
||||
= new WeakValueDictionary<int, ComponentState>();
|
||||
private readonly ConditionalWeakTable<IComponent, ComponentState> _componentStateByComponent
|
||||
= new ConditionalWeakTable<IComponent, ComponentState>();
|
||||
private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
|
||||
private readonly Dictionary<int, ComponentState> _componentStateById
|
||||
= new Dictionary<int, ComponentState>();
|
||||
|
||||
// Because rendering is currently synchronous and single-threaded, we can keep re-using the
|
||||
// same RenderBatchBuilder instance to avoid reallocating
|
||||
|
|
@ -35,35 +28,46 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
private readonly Dictionary<int, UIEventHandler> _eventHandlersById
|
||||
= new Dictionary<int, UIEventHandler>();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new component of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="componentType">The type of the component to instantiate.</param>
|
||||
/// <returns>The component instance.</returns>
|
||||
protected IComponent InstantiateComponent(Type componentType)
|
||||
{
|
||||
if (!typeof(IComponent).IsAssignableFrom(componentType))
|
||||
{
|
||||
throw new ArgumentException($"Must implement {nameof(IComponent)}", nameof(componentType));
|
||||
}
|
||||
|
||||
return (IComponent)Activator.CreateInstance(componentType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Associates the <see cref="IComponent"/> with the <see cref="Renderer"/>, assigning
|
||||
/// an identifier that is unique within the scope of the <see cref="Renderer"/>.
|
||||
/// </summary>
|
||||
/// <param name="component">The <see cref="IComponent"/>.</param>
|
||||
/// <returns>The assigned identifier for the <see cref="IComponent"/>.</returns>
|
||||
/// <param name="component">The component.</param>
|
||||
/// <returns>The component's assigned identifier.</returns>
|
||||
protected int AssignComponentId(IComponent component)
|
||||
{
|
||||
lock (_componentStateById)
|
||||
{
|
||||
var componentId = _nextComponentId++;
|
||||
var componentState = new ComponentState(this, componentId, component);
|
||||
_componentStateById.Add(componentId, componentState);
|
||||
_componentStateByComponent.Add(component, componentState); // Ensure the componentState lives for at least as long as the component
|
||||
return componentId;
|
||||
}
|
||||
var componentId = _nextComponentId++;
|
||||
var componentState = new ComponentState(this, componentId, component);
|
||||
_componentStateById.Add(componentId, componentState);
|
||||
return componentId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the visible UI.
|
||||
/// </summary>
|
||||
/// <param name="renderBatch">The changes to the UI since the previous call.</param>
|
||||
internal protected abstract void UpdateDisplay(RenderBatch renderBatch);
|
||||
protected abstract void UpdateDisplay(RenderBatch renderBatch);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the rendered state of the specified <see cref="IComponent"/>.
|
||||
/// </summary>
|
||||
/// <param name="componentId">The identifier of the <see cref="IComponent"/> to render.</param>
|
||||
protected internal void RenderNewBatch(int componentId)
|
||||
protected void RenderNewBatch(int componentId)
|
||||
{
|
||||
// It's very important that components' rendering logic has no side-effects, and in particular
|
||||
// components must *not* trigger Render from inside their render logic, otherwise you could
|
||||
|
|
@ -84,11 +88,11 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
"Render logic must not have side-effects such as manually triggering other rendering.");
|
||||
}
|
||||
|
||||
_sharedRenderBatchBuilder.ComponentRenderQueue.Enqueue(componentId);
|
||||
|
||||
try
|
||||
{
|
||||
RenderInExistingBatch(_sharedRenderBatchBuilder, componentId);
|
||||
|
||||
// Process
|
||||
// Process render queue until empty
|
||||
while (_sharedRenderBatchBuilder.ComponentRenderQueue.Count > 0)
|
||||
{
|
||||
var nextComponentIdToRender = _sharedRenderBatchBuilder.ComponentRenderQueue.Dequeue();
|
||||
|
|
@ -96,24 +100,15 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
}
|
||||
|
||||
UpdateDisplay(_sharedRenderBatchBuilder.ToBatch());
|
||||
RemoveEventHandlerIds(_sharedRenderBatchBuilder.GetDisposedEventHandlerIds());
|
||||
}
|
||||
finally
|
||||
{
|
||||
RemoveEventHandlerIds(_sharedRenderBatchBuilder.DisposedEventHandlerIds.ToRange());
|
||||
_sharedRenderBatchBuilder.Clear();
|
||||
Interlocked.Exchange(ref _renderBatchLock, 0);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RenderInExistingBatch(RenderBatchBuilder batchBuilder, int componentId)
|
||||
=> GetRequiredComponentState(componentId).Render(this, batchBuilder);
|
||||
|
||||
internal void DisposeInExistingBatch(RenderBatchBuilder batchBuilder, int componentId)
|
||||
{
|
||||
GetRequiredComponentState(componentId).NotifyDisposed(batchBuilder);
|
||||
batchBuilder.AddDisposedComponentId(componentId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the specified component that an event has occurred.
|
||||
/// </summary>
|
||||
|
|
@ -136,7 +131,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
}
|
||||
}
|
||||
|
||||
internal void InstantiateChildComponent(ref RenderTreeFrame frame)
|
||||
internal void InstantiateChildComponentOnFrame(ref RenderTreeFrame frame)
|
||||
{
|
||||
if (frame.FrameType != RenderTreeFrameType.Component)
|
||||
{
|
||||
|
|
@ -148,7 +143,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
throw new ArgumentException($"The frame already has a non-null component instance", nameof(frame));
|
||||
}
|
||||
|
||||
var newComponent = (IComponent)Activator.CreateInstance(frame.ComponentType);
|
||||
var newComponent = InstantiateComponent(frame.ComponentType);
|
||||
var newComponentId = AssignComponentId(newComponent);
|
||||
frame = frame.WithComponentInstance(newComponentId, newComponent);
|
||||
}
|
||||
|
|
@ -160,6 +155,25 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
frame = frame.WithAttributeEventHandlerId(id);
|
||||
}
|
||||
|
||||
private ComponentState GetRequiredComponentState(int componentId)
|
||||
=> _componentStateById.TryGetValue(componentId, out var componentState)
|
||||
? componentState
|
||||
: throw new ArgumentException($"The renderer does not have a component with ID {componentId}.");
|
||||
|
||||
private void RenderInExistingBatch(RenderBatchBuilder batchBuilder, int componentId)
|
||||
{
|
||||
GetRequiredComponentState(componentId).RenderIntoBatch(batchBuilder);
|
||||
|
||||
// Process disposal queue now in case it causes further component renders to be enqueued
|
||||
while (batchBuilder.ComponentDisposalQueue.Count > 0)
|
||||
{
|
||||
var disposeComponentId = batchBuilder.ComponentDisposalQueue.Dequeue();
|
||||
GetRequiredComponentState(disposeComponentId).DisposeInBatch(batchBuilder);
|
||||
_componentStateById.Remove(disposeComponentId);
|
||||
batchBuilder.DisposedComponentIds.Append(disposeComponentId);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveEventHandlerIds(ArrayRange<int> eventHandlerIds)
|
||||
{
|
||||
var array = eventHandlerIds.Array;
|
||||
|
|
@ -169,10 +183,5 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
|
|||
_eventHandlersById.Remove(array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private ComponentState GetRequiredComponentState(int componentId)
|
||||
=> _componentStateById.TryGetValue(componentId, out var componentState)
|
||||
? componentState
|
||||
: throw new ArgumentException($"The renderer does not have a component with ID {componentId}.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
|
||||
private class TestRenderer : Renderer
|
||||
{
|
||||
protected internal override void UpdateDisplay(RenderBatch renderBatch)
|
||||
protected override void UpdateDisplay(RenderBatch renderBatch)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -785,7 +785,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
|
||||
private class FakeRenderer : Renderer
|
||||
{
|
||||
internal protected override void UpdateDisplay(RenderBatch renderBatch)
|
||||
protected override void UpdateDisplay(RenderBatch renderBatch)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -275,78 +275,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
Assert.True(renderer2.Batches.Single().DiffsByComponentId.ContainsKey(renderer2ComponentId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComponentsAreNotPinnedInMemory()
|
||||
{
|
||||
// It's important that the Renderer base class does not itself pin in memory
|
||||
// any of the component instances that were attached to it (or by extension,
|
||||
// their descendants). This is because as the set of active components changes
|
||||
// over time, we need the GC to be able to release unused ones, and there isn't
|
||||
// any other mechanism for explicitly destroying components when they stop
|
||||
// being referenced.
|
||||
var renderer = new NoOpRenderer();
|
||||
|
||||
AssertCanBeCollected(() =>
|
||||
{
|
||||
var component = new TestComponent(null);
|
||||
renderer.AssignComponentId(component);
|
||||
return component;
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotRenderComponentsIfGCed()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new NoOpRenderer();
|
||||
|
||||
// Act
|
||||
var componentId = new Func<int>(() =>
|
||||
{
|
||||
var component = new TestComponent(builder =>
|
||||
throw new NotImplementedException("Should not be invoked"));
|
||||
|
||||
return renderer.AssignComponentId(component);
|
||||
})();
|
||||
|
||||
// Since there are no unit test references to 'component' here, the GC
|
||||
// should be able to collect it
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
// Assert
|
||||
Assert.ThrowsAny<Exception>(() =>
|
||||
{
|
||||
renderer.RenderNewBatch(componentId);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRenderComponentsIfNotGCed()
|
||||
{
|
||||
// Arrange
|
||||
var renderer = new NoOpRenderer();
|
||||
var didRender = false;
|
||||
|
||||
// Act
|
||||
var component = new TestComponent(builder =>
|
||||
{
|
||||
didRender = true;
|
||||
});
|
||||
var componentId = renderer.AssignComponentId(component);
|
||||
|
||||
// Unlike the preceding test, we still have a reference to the component
|
||||
// instance on the stack here, so the following should not cause it to
|
||||
// be collected. Then when we call RenderComponent, there should be no error.
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
renderer.RenderNewBatch(componentId);
|
||||
|
||||
// Assert
|
||||
Assert.True(didRender);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreservesChildComponentInstancesWithNoAttributes()
|
||||
{
|
||||
|
|
@ -721,7 +649,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public new void RenderNewBatch(int componentId)
|
||||
=> base.RenderNewBatch(componentId);
|
||||
|
||||
protected internal override void UpdateDisplay(RenderBatch renderBatch)
|
||||
protected override void UpdateDisplay(RenderBatch renderBatch)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -740,7 +668,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public new void DispatchEvent(int componentId, int eventHandlerId, UIEventArgs args)
|
||||
=> base.DispatchEvent(componentId, eventHandlerId, args);
|
||||
|
||||
protected internal override void UpdateDisplay(RenderBatch renderBatch)
|
||||
protected override void UpdateDisplay(RenderBatch renderBatch)
|
||||
{
|
||||
var capturedBatch = new CapturedBatch();
|
||||
Batches.Add(capturedBatch);
|
||||
|
|
@ -879,17 +807,6 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
}
|
||||
}
|
||||
|
||||
void AssertCanBeCollected(Func<object> targetFactory)
|
||||
{
|
||||
// We have to construct the WeakReference in a separate scope
|
||||
// otherwise its target won't be collected on this GC cycle
|
||||
var weakRef = new Func<WeakReference>(
|
||||
() => new WeakReference(targetFactory()))();
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
Assert.Null(weakRef.Target);
|
||||
}
|
||||
|
||||
(int, T) FirstWithIndex<T>(IEnumerable<T> items, Predicate<T> predicate)
|
||||
{
|
||||
var index = 0;
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ namespace BasicTestApp
|
|||
public static void MountTestComponent(string componentTypeName)
|
||||
{
|
||||
var componentType = Type.GetType(componentTypeName);
|
||||
var componentInstance = (IComponent)Activator.CreateInstance(componentType);
|
||||
new BrowserRenderer().AddComponent("app", componentInstance);
|
||||
new BrowserRenderer().AddComponent(componentType, "app");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue