1
- using Microsoft . Extensions . Logging ;
1
+ using System . Diagnostics ;
2
2
using System . Reflection ;
3
3
using System . Runtime . CompilerServices ;
4
4
using System . Runtime . ExceptionServices ;
5
+ using Microsoft . Extensions . Logging ;
5
6
6
7
namespace Bunit . Rendering ;
7
8
@@ -20,7 +21,8 @@ public sealed class BunitRenderer : Renderer
20
21
private static extern void CallSetDirectParameters ( ComponentState componentState , ParameterView parameters ) ;
21
22
22
23
private readonly object renderTreeUpdateLock = new ( ) ;
23
- private readonly Dictionary < int , IRenderedComponent > renderedComponents = new ( ) ;
24
+
25
+ private readonly HashSet < int > returnedRenderedComponentIds = new ( ) ;
24
26
private readonly List < BunitRootComponent > rootComponents = new ( ) ;
25
27
private readonly ILogger < BunitRenderer > logger ;
26
28
private bool disposed ;
@@ -182,7 +184,6 @@ public IReadOnlyList<IRenderedComponent<TComponent>> FindComponents<TComponent>(
182
184
public Task DisposeComponents ( )
183
185
{
184
186
ObjectDisposedException . ThrowIf ( disposed , this ) ;
185
-
186
187
Task ? returnTask ;
187
188
188
189
lock ( renderTreeUpdateLock )
@@ -206,6 +207,19 @@ public Task DisposeComponents()
206
207
207
208
return returnTask ;
208
209
}
210
+ /// <inheritdoc/>
211
+ protected override ComponentState CreateComponentState ( int componentId , IComponent component , ComponentState ? parentComponentState )
212
+ {
213
+ ArgumentNullException . ThrowIfNull ( component ) ;
214
+
215
+ var TComponent = component . GetType ( ) ;
216
+ var renderedComponentType = typeof ( RenderedComponent < > ) . MakeGenericType ( TComponent ) ;
217
+ var renderedComponent = Activator . CreateInstance ( renderedComponentType , this , componentId , component , services , parentComponentState ) ;
218
+
219
+ Debug . Assert ( renderedComponent is not null , "RenderedComponent should not be null" ) ;
220
+
221
+ return ( ComponentState ) renderedComponent ;
222
+ }
209
223
210
224
/// <inheritdoc/>
211
225
protected override IComponent ResolveComponentForRenderMode ( Type componentType , int ? parentComponentId ,
@@ -307,118 +321,74 @@ protected override void ProcessPendingRender()
307
321
/// <inheritdoc/>
308
322
protected override Task UpdateDisplayAsync ( in RenderBatch renderBatch )
309
323
{
310
- var renderEvent = new RenderEvent ( ) ;
311
- PrepareRenderEvent ( renderBatch ) ;
312
- ApplyRenderEvent ( renderEvent ) ;
313
-
314
- return Task . CompletedTask ;
324
+ for ( var i = 0 ; i < renderBatch . DisposedComponentIDs . Count ; i ++ )
325
+ {
326
+ var id = renderBatch . DisposedComponentIDs . Array [ i ] ;
327
+ returnedRenderedComponentIds . Remove ( id ) ;
328
+ }
315
329
316
- void PrepareRenderEvent ( in RenderBatch renderBatch )
330
+ for ( var i = 0 ; i < renderBatch . UpdatedComponents . Count ; i ++ )
317
331
{
318
- for ( var i = 0 ; i < renderBatch . DisposedComponentIDs . Count ; i ++ )
319
- {
320
- var id = renderBatch . DisposedComponentIDs . Array [ i ] ;
321
- renderEvent . SetDisposed ( id ) ;
322
- }
332
+ var diff = renderBatch . UpdatedComponents . Array [ i ] ;
333
+ var componentState = GetComponentState ( diff . ComponentId ) ;
334
+ var renderedComponent = ( IRenderedComponent ) componentState ;
323
335
324
- for ( int i = 0 ; i < renderBatch . UpdatedComponents . Count ; i ++ )
336
+ if ( returnedRenderedComponentIds . Contains ( diff . ComponentId ) )
325
337
{
326
- ref var update = ref renderBatch . UpdatedComponents . Array [ i ] ;
327
- renderEvent . SetUpdated ( update . ComponentId , update . Edits . Count > 0 ) ;
338
+ renderedComponent . UpdateState ( hasRendered : true , isMarkupGenerationRequired : diff . Edits . Count > 0 ) ;
328
339
}
329
-
330
- foreach ( var ( _, rc ) in renderedComponents )
340
+ else
331
341
{
332
- LoadChangesIntoRenderEvent ( rc . ComponentId ) ;
342
+ renderedComponent . UpdateState ( hasRendered : true , false ) ;
333
343
}
344
+
345
+ UpdateParents ( diff . Edits . Count > 0 , componentState , in renderBatch ) ;
334
346
}
335
347
336
- void LoadChangesIntoRenderEvent ( int componentId )
348
+ return Task . CompletedTask ;
349
+
350
+ void UpdateParents ( bool hasChanges , ComponentState componentState , in RenderBatch renderBatch )
337
351
{
338
- var status = renderEvent . GetOrCreateStatus ( componentId ) ;
339
- if ( status . FramesLoaded || status . Disposed )
352
+ var parent = componentState . ParentComponentState ;
353
+ if ( parent is null )
354
+ {
340
355
return ;
356
+ }
341
357
342
- var frames = GetCurrentRenderTreeFrames ( componentId ) ;
343
- renderEvent . AddFrames ( componentId , frames ) ;
344
-
345
- for ( var i = 0 ; i < frames . Count ; i ++ )
358
+ if ( ! IsParentComponentAlreadyUpdated ( parent . ComponentId , in renderBatch ) )
346
359
{
347
- ref var frame = ref frames . Array [ i ] ;
348
- if ( frame . FrameType == RenderTreeFrameType . Component )
360
+ if ( returnedRenderedComponentIds . Contains ( parent . ComponentId ) )
349
361
{
350
- // If a child component of the current components has been
351
- // disposed, there is no reason to load the disposed components
352
- // render tree frames. This can also cause a stack overflow if
353
- // the current component was previously a child of the disposed
354
- // component (is that possible?)
355
- var childStatus = renderEvent . GetOrCreateStatus ( frame . ComponentId ) ;
356
- if ( childStatus . Disposed )
357
- {
358
- logger . LogDisposedChildInRenderTreeFrame ( componentId , frame . ComponentId ) ;
359
- }
360
- // The assumption is that a component cannot be in multiple places at
361
- // once. However, in case this is not a correct assumption, this
362
- // ensures that a child components frames are only loaded once.
363
- else if ( ! renderEvent . GetOrCreateStatus ( frame . ComponentId ) . FramesLoaded )
364
- {
365
- LoadChangesIntoRenderEvent ( frame . ComponentId ) ;
366
- }
367
-
368
- if ( childStatus . Rendered || childStatus . Changed || childStatus . Disposed )
369
- {
370
- status . Rendered = status . Rendered || childStatus . Rendered ;
371
-
372
- // The current component should also be marked as changed if the child component is
373
- // either changed or disposed, as there is a good chance that the child component
374
- // contained markup which is no longer visible.
375
- status . Changed = status . Changed || childStatus . Changed || childStatus . Disposed ;
376
- }
362
+ ( ( IRenderedComponent ) parent ) . UpdateState ( hasRendered : true , isMarkupGenerationRequired : hasChanges ) ;
377
363
}
364
+ else
365
+ {
366
+ ( ( IRenderedComponent ) parent ) . UpdateState ( hasRendered : true , false ) ;
367
+ }
368
+
369
+ UpdateParents ( hasChanges , parent , in renderBatch ) ;
378
370
}
379
371
}
380
- }
381
-
382
- private void ApplyRenderEvent ( RenderEvent renderEvent )
383
- {
384
- RenderCount ++ ;
385
372
386
- foreach ( var ( componentId , status ) in renderEvent . Statuses )
373
+ static bool IsParentComponentAlreadyUpdated ( int componentId , in RenderBatch renderBatch )
387
374
{
388
- if ( status . UpdatesApplied || ! renderedComponents . TryGetValue ( componentId , out var rc ) )
375
+ for ( var i = 0 ; i < renderBatch . UpdatedComponents . Count ; i ++ )
389
376
{
390
- continue ;
391
- }
392
-
393
- if ( status . Disposed )
394
- {
395
- renderedComponents . Remove ( componentId ) ;
396
- rc . OnRender ( renderEvent ) ;
397
- renderEvent . SetUpdatedApplied ( componentId ) ;
398
- logger . LogComponentDisposed ( componentId ) ;
399
- continue ;
400
- }
401
-
402
- if ( status . UpdateNeeded )
403
- {
404
- rc . OnRender ( renderEvent ) ;
405
- renderEvent . SetUpdatedApplied ( componentId ) ;
406
-
407
- // RC can replace the instance of the component it is bound
408
- // to while processing the update event, e.g. during the
409
- // initial render of a component.
410
- if ( componentId != rc . ComponentId )
377
+ var diff = renderBatch . UpdatedComponents . Array [ i ] ;
378
+ if ( diff . ComponentId == componentId )
411
379
{
412
- renderedComponents . Remove ( componentId ) ;
413
- renderedComponents . Add ( rc . ComponentId , rc ) ;
414
- renderEvent . SetUpdatedApplied ( rc . ComponentId ) ;
380
+ return diff . Edits . Count > 0 ;
415
381
}
416
-
417
- logger . LogComponentRendered ( rc . ComponentId ) ;
418
382
}
383
+
384
+ return false ;
419
385
}
420
386
}
421
387
388
+ /// <inheritdoc/>
389
+ internal new ArrayRange < RenderTreeFrame > GetCurrentRenderTreeFrames ( int componentId )
390
+ => base . GetCurrentRenderTreeFrames ( componentId ) ;
391
+
422
392
/// <inheritdoc/>
423
393
protected override void Dispose ( bool disposing )
424
394
{
@@ -434,12 +404,7 @@ protected override void Dispose(bool disposing)
434
404
435
405
if ( disposing )
436
406
{
437
- foreach ( var rc in renderedComponents . Values )
438
- {
439
- rc . Dispose ( ) ;
440
- }
441
-
442
- renderedComponents . Clear ( ) ;
407
+ returnedRenderedComponentIds . Clear ( ) ;
443
408
disposalTasks . Clear ( ) ;
444
409
unhandledExceptionTsc . TrySetCanceled ( ) ;
445
410
}
@@ -448,7 +413,7 @@ protected override void Dispose(bool disposing)
448
413
}
449
414
}
450
415
451
- private RenderedComponent < BunitRootComponent > Render ( RenderFragment renderFragment )
416
+ private IRenderedComponent < BunitRootComponent > Render ( RenderFragment renderFragment )
452
417
{
453
418
ObjectDisposedException . ThrowIf ( disposed , this ) ;
454
419
@@ -458,25 +423,26 @@ private RenderedComponent<BunitRootComponent> Render(RenderFragment renderFragme
458
423
459
424
var root = new BunitRootComponent ( renderFragment ) ;
460
425
var rootComponentId = AssignRootComponentId ( root ) ;
461
- var result = new RenderedComponent < BunitRootComponent > ( rootComponentId , root , services ) ;
462
- renderedComponents . Add ( rootComponentId , result ) ;
426
+ returnedRenderedComponentIds . Add ( rootComponentId ) ;
463
427
rootComponents . Add ( root ) ;
464
428
root . Render ( ) ;
465
- return result ;
429
+ return rootComponentId ;
466
430
} ) ;
467
431
468
- RenderedComponent < BunitRootComponent > result ;
432
+ int componentId = - 1 ;
469
433
470
434
if ( ! renderTask . IsCompleted )
471
435
{
472
436
logger . LogAsyncInitialRender ( ) ;
473
- result = renderTask . GetAwaiter ( ) . GetResult ( ) ;
437
+ componentId = renderTask . GetAwaiter ( ) . GetResult ( ) ;
474
438
}
475
439
else
476
440
{
477
- result = renderTask . Result ;
441
+ componentId = renderTask . Result ;
478
442
}
479
443
444
+ var result = GetRenderedComponent < BunitRootComponent > ( componentId ) ;
445
+
480
446
logger . LogInitialRenderCompleted ( result . ComponentId ) ;
481
447
482
448
AssertNoUnhandledExceptions ( ) ;
@@ -488,35 +454,38 @@ private List<IRenderedComponent<TComponent>> FindComponents<TComponent>(IRendere
488
454
where TComponent : IComponent
489
455
{
490
456
ArgumentNullException . ThrowIfNull ( parentComponent ) ;
491
-
492
457
ObjectDisposedException . ThrowIf ( disposed , this ) ;
493
458
494
- var result = new List < IRenderedComponent < TComponent > > ( ) ;
495
- var framesCollection = new RenderTreeFrameDictionary ( ) ;
459
+ var result = resultLimit == int . MaxValue
460
+ ? new List < IRenderedComponent < TComponent > > ( )
461
+ : new List < IRenderedComponent < TComponent > > ( resultLimit ) ;
496
462
497
463
// Blocks the renderer from changing the render tree
498
464
// while this method searches through it.
499
465
lock ( renderTreeUpdateLock )
500
466
{
501
467
ObjectDisposedException . ThrowIf ( disposed , this ) ;
502
-
503
468
FindComponentsInRenderTree ( parentComponent . ComponentId ) ;
469
+ foreach ( var rc in result )
470
+ {
471
+ ( ( IRenderedComponent ) rc ) . UpdateState ( hasRendered : false , isMarkupGenerationRequired : true ) ;
472
+ }
504
473
}
505
474
506
475
return result ;
507
476
508
477
void FindComponentsInRenderTree ( int componentId )
509
478
{
510
- var frames = GetOrLoadRenderTreeFrame ( framesCollection , componentId ) ;
479
+ var frames = GetCurrentRenderTreeFrames ( componentId ) ;
511
480
512
481
for ( var i = 0 ; i < frames . Count ; i ++ )
513
482
{
514
483
ref var frame = ref frames . Array [ i ] ;
515
484
if ( frame . FrameType == RenderTreeFrameType . Component )
516
485
{
517
- if ( frame . Component is TComponent component )
486
+ if ( frame . Component is TComponent )
518
487
{
519
- result . Add ( GetOrCreateRenderedComponent ( framesCollection , frame . ComponentId , component ) ) ;
488
+ result . Add ( GetRenderedComponent < TComponent > ( frame . ComponentId ) ) ;
520
489
521
490
if ( result . Count == resultLimit )
522
491
return ;
@@ -531,19 +500,12 @@ void FindComponentsInRenderTree(int componentId)
531
500
}
532
501
}
533
502
534
- private IRenderedComponent < TComponent > GetOrCreateRenderedComponent < TComponent > ( RenderTreeFrameDictionary framesCollection , int componentId , TComponent component )
503
+ private IRenderedComponent < TComponent > GetRenderedComponent < TComponent > ( int componentId )
535
504
where TComponent : IComponent
536
505
{
537
- if ( renderedComponents . TryGetValue ( componentId , out var renderedComponent ) )
538
- {
539
- return ( IRenderedComponent < TComponent > ) renderedComponent ;
540
- }
541
-
542
- LoadRenderTreeFrames ( componentId , framesCollection ) ;
543
- var result = new RenderedComponent < TComponent > ( componentId , component , framesCollection , services ) ;
544
- renderedComponents . Add ( result . ComponentId , result ) ;
545
-
546
- return result ;
506
+ var result = GetComponentState ( componentId ) ;
507
+ returnedRenderedComponentIds . Add ( result . ComponentId ) ;
508
+ return ( IRenderedComponent < TComponent > ) result ;
547
509
}
548
510
549
511
/// <summary>
0 commit comments