Skip to content

Commit 82008f2

Browse files
committed
Undo renderer changes
1 parent 50435cb commit 82008f2

File tree

6 files changed

+101
-39
lines changed

6 files changed

+101
-39
lines changed

src/Components/Components/src/Rendering/Renderer.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ public abstract class Renderer : IDisposable
1818
{
1919
private readonly ComponentFactory _componentFactory;
2020
private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>();
21+
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
2122
private readonly Dictionary<int, EventCallback> _eventBindings = new Dictionary<int, EventCallback>();
22-
private IDispatcher _dispatcher { get; }
23+
private readonly IDispatcher _dispatcher;
2324

2425
private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
2526
private bool _isBatchInProgress;
@@ -68,8 +69,6 @@ public Renderer(IServiceProvider serviceProvider, IDispatcher dispatcher) : this
6869
_dispatcher = dispatcher;
6970
}
7071

71-
internal RenderBatchBuilder RenderBatchBuilder { get; } = new RenderBatchBuilder();
72-
7372
/// <summary>
7473
/// Creates an <see cref="IDispatcher"/> that can be used with one or more <see cref="Renderer"/>.
7574
/// </summary>
@@ -388,7 +387,7 @@ protected internal virtual void AddToRenderQueue(int componentId, RenderFragment
388387
return;
389388
}
390389

391-
RenderBatchBuilder.ComponentRenderQueue.Enqueue(
390+
_batchBuilder.ComponentRenderQueue.Enqueue(
392391
new RenderQueueEntry(componentState, renderFragment));
393392

394393
if (!_isBatchInProgress)
@@ -420,23 +419,24 @@ private ComponentState GetRequiredComponentState(int componentId)
420419

421420
private ComponentState GetOptionalComponentState(int componentId)
422421
=> _componentStateById.TryGetValue(componentId, out var componentState)
423-
? componentState
422+
? componentState
424423
: null;
425424

426425
private void ProcessRenderQueue()
427426
{
428-
var updateDisplayTask = Task.CompletedTask;
429427
_isBatchInProgress = true;
428+
var updateDisplayTask = Task.CompletedTask;
429+
430430
try
431431
{
432432
// Process render queue until empty
433-
while (RenderBatchBuilder.ComponentRenderQueue.Count > 0)
433+
while (_batchBuilder.ComponentRenderQueue.Count > 0)
434434
{
435-
var nextToRender = RenderBatchBuilder.ComponentRenderQueue.Dequeue();
435+
var nextToRender = _batchBuilder.ComponentRenderQueue.Dequeue();
436436
RenderInExistingBatch(nextToRender);
437437
}
438438

439-
var batch = RenderBatchBuilder.ToBatch();
439+
var batch = _batchBuilder.ToBatch();
440440
updateDisplayTask = UpdateDisplayAsync(batch);
441441

442442
// Fire off the execution of OnAfterRenderAsync, but don't wait for it
@@ -445,8 +445,8 @@ private void ProcessRenderQueue()
445445
}
446446
finally
447447
{
448-
RemoveEventHandlerIds(RenderBatchBuilder.DisposedEventHandlerIds.ToRange(), updateDisplayTask);
449-
RenderBatchBuilder.Clear();
448+
RemoveEventHandlerIds(_batchBuilder.DisposedEventHandlerIds.ToRange(), updateDisplayTask);
449+
_batchBuilder.Clear();
450450
_isBatchInProgress = false;
451451
}
452452
}
@@ -495,15 +495,15 @@ private Task InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updatedCompon
495495
private void RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
496496
{
497497
renderQueueEntry.ComponentState
498-
.RenderIntoBatch(RenderBatchBuilder, renderQueueEntry.RenderFragment);
498+
.RenderIntoBatch(_batchBuilder, renderQueueEntry.RenderFragment);
499499

500500
// Process disposal queue now in case it causes further component renders to be enqueued
501-
while (RenderBatchBuilder.ComponentDisposalQueue.Count > 0)
501+
while (_batchBuilder.ComponentDisposalQueue.Count > 0)
502502
{
503-
var disposeComponentId = RenderBatchBuilder.ComponentDisposalQueue.Dequeue();
504-
GetRequiredComponentState(disposeComponentId).DisposeInBatch(RenderBatchBuilder);
503+
var disposeComponentId = _batchBuilder.ComponentDisposalQueue.Dequeue();
504+
GetRequiredComponentState(disposeComponentId).DisposeInBatch(_batchBuilder);
505505
_componentStateById.Remove(disposeComponentId);
506-
RenderBatchBuilder.DisposedComponentIds.Append(disposeComponentId);
506+
_batchBuilder.DisposedComponentIds.Append(disposeComponentId);
507507
}
508508
}
509509

src/Components/Server/src/Circuits/CircuitRegistry.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,6 @@ public virtual async Task<CircuitHost> ConnectAsync(string circuitId, IClientPro
174174

175175
await circuitHandlerTask;
176176

177-
// Dispatch any buffered renders we accumulated during a disconnect.
178-
await circuitHost.Renderer.ProcessBufferedRenderBatches();
179-
180177
return circuitHost;
181178
}
182179

src/Components/Server/src/Circuits/RemoteRenderer.cs

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ internal class RemoteRenderer : HtmlRenderer
2424

2525
private readonly int _id;
2626
private readonly IJSRuntime _jsRuntime;
27+
private readonly CircuitClientProxy _client;
2728
private readonly RendererRegistry _rendererRegistry;
2829
private readonly ConcurrentDictionary<long, AutoCancelTaskCompletionSource<object>> _pendingRenders
2930
= new ConcurrentDictionary<long, AutoCancelTaskCompletionSource<object>>();
@@ -50,15 +51,13 @@ public RemoteRenderer(
5051
{
5152
_rendererRegistry = rendererRegistry;
5253
_jsRuntime = jsRuntime;
53-
Client = client;
54+
_client = client;
5455

5556
_id = _rendererRegistry.Add(this);
5657
_logger = logger;
5758
}
5859

59-
internal ConcurrentQueue<RenderBatch> OfflineRenderBatches = new ConcurrentQueue<RenderBatch>();
60-
61-
internal CircuitClientProxy Client { get; }
60+
internal ConcurrentQueue<byte[]> OfflineRenderBatches = new ConcurrentQueue<byte[]>();
6261

6362
/// <summary>
6463
/// Associates the <see cref="IComponent"/> with the <see cref="RemoteRenderer"/>,
@@ -107,37 +106,37 @@ protected override void Dispose(bool disposing)
107106
/// <inheritdoc />
108107
protected override Task UpdateDisplayAsync(in RenderBatch batch)
109108
{
110-
if (!Client.Connected)
109+
// Note that we have to capture the data as a byte[] synchronously here, because
110+
// SignalR's SendAsync can wait an arbitrary duration before serializing the params.
111+
// The RenderBatch buffer will get reused by subsequent renders, so we need to
112+
// snapshot its contents now.
113+
// TODO: Consider using some kind of array pool instead of allocating a new
114+
// buffer on every render.
115+
var batchBytes = MessagePackSerializer.Serialize(batch, RenderBatchFormatterResolver.Instance);
116+
117+
if (!_client.Connected)
111118
{
112119
// Buffer the rendered batches while the client is disconnected. We'll send it down once the client reconnects.
113-
OfflineRenderBatches.Enqueue(batch);
120+
OfflineRenderBatches.Enqueue(batchBytes);
114121
return Task.CompletedTask;
115122
}
116123

117-
Log.BeginUpdateDisplayAsync(_logger, Client.ConnectionId);
118-
return WriteBatchBytes(batch);
124+
Log.BeginUpdateDisplayAsync(_logger, _client.ConnectionId);
125+
return WriteBatchBytes(batchBytes);
119126
}
120127

121128
public async Task ProcessBufferedRenderBatches()
122129
{
123130
// The server may discover that the client disconnected while we're attempting to write empty rendered batches.
124131
// Discontinue writing in this event.
125-
while (Client.Connected && OfflineRenderBatches.TryDequeue(out var renderBatch))
132+
while (_client.Connected && OfflineRenderBatches.TryDequeue(out var renderBatch))
126133
{
127134
await WriteBatchBytes(renderBatch);
128135
}
129136
}
130137

131-
private Task WriteBatchBytes(in RenderBatch renderBatch)
138+
private Task WriteBatchBytes(byte[] batchBytes)
132139
{
133-
// Note that we have to capture the data as a byte[] synchronously here, because
134-
// SignalR's SendAsync can wait an arbitrary duration before serializing the params.
135-
// The RenderBatch buffer will get reused by subsequent renders, so we need to
136-
// snapshot its contents now.
137-
// TODO: Consider using some kind of array pool instead of allocating a new
138-
// buffer on every render.
139-
var batchBytes = MessagePackSerializer.Serialize(renderBatch, RenderBatchFormatterResolver.Instance);
140-
141140
var renderId = Interlocked.Increment(ref _nextRenderId);
142141

143142
var pendingRenderInfo = new AutoCancelTaskCompletionSource<object>(TimeoutMilliseconds);
@@ -148,7 +147,7 @@ private Task WriteBatchBytes(in RenderBatch renderBatch)
148147
// the whole render with that exception
149148
try
150149
{
151-
Client.SendAsync("JS.RenderBatch", _id, renderId, batchBytes).ContinueWith(sendTask =>
150+
_client.SendAsync("JS.RenderBatch", _id, renderId, batchBytes).ContinueWith(sendTask =>
152151
{
153152
if (sendTask.IsFaulted)
154153
{

src/Components/Server/src/ComponentHub.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ public async Task<bool> ConnectCircuit(string circuitId)
9595
if (circuitHost != null)
9696
{
9797
CircuitHost = circuitHost;
98+
99+
// Dispatch any buffered renders we accumulated during a disconnect.
100+
// Note that while the rendering is async, we cannot await it here. The Task returned by ProcessBufferedRenderBatches relies on
101+
// OnRenderCompleted to be invoked to complete, and SignalR does not allow concurrent hub method invocations.
102+
_ = circuitHost.Renderer.ProcessBufferedRenderBatches();
98103
return true;
99104
}
100105

src/Components/Server/test/Circuits/RemoteRendererTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public async Task ProcessBufferedRenderBatches_WritesRenders()
8686
component.TriggerRender();
8787

8888
// Act
89-
renderer.Client.Transfer(client.Object, "new-connection");
89+
circuitClient.Transfer(client.Object, "new-connection");
9090
var task = renderer.ProcessBufferedRenderBatches();
9191
foreach (var id in renderIds)
9292
{
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
@page "/fetchdata"
2+
@page "/fetchdata/{StartDate:datetime}"
3+
@inject WeatherForecastService ForecastService
4+
5+
<h1>Weather forecast</h1>
6+
7+
<p>This component demonstrates fetching data from the server.</p>
8+
9+
@if (forecasts == null)
10+
{
11+
<p><em>Loading...</em></p>
12+
}
13+
else
14+
{
15+
<table class='table'>
16+
<thead>
17+
<tr>
18+
<th>Date</th>
19+
<th>Temp. (C)</th>
20+
<th>Temp. (F)</th>
21+
<th>Summary</th>
22+
</tr>
23+
</thead>
24+
<tbody>
25+
@foreach (var forecast in forecasts)
26+
{
27+
<tr>
28+
<td>@forecast.DateFormatted</td>
29+
<td>@forecast.TemperatureC</td>
30+
<td>@forecast.TemperatureF</td>
31+
<td>@forecast.Summary</td>
32+
</tr>
33+
}
34+
</tbody>
35+
</table>
36+
<p>
37+
<a href="fetchdata/@StartDate.AddDays(-5).ToString("yyyy-MM-dd")" class="btn btn-secondary float-left">
38+
Previous
39+
</a>
40+
<a href="fetchdata/@StartDate.AddDays(5).ToString("yyyy-MM-dd")" class="btn btn-secondary float-right">
41+
Next
42+
</a>
43+
</p>
44+
}
45+
46+
@functions {
47+
[Parameter] DateTime StartDate { get; set; }
48+
49+
WeatherForecast[] forecasts;
50+
51+
public override Task SetParametersAsync(ParameterCollection parameters)
52+
{
53+
StartDate = DateTime.Now;
54+
return base.SetParametersAsync(parameters);
55+
}
56+
57+
protected override async Task OnParametersSetAsync()
58+
{
59+
forecasts = await ForecastService.GetForecastAsync(StartDate);
60+
}
61+
}

0 commit comments

Comments
 (0)