1- using Microsoft . Extensions . Logging ;
1+ using System . Diagnostics ;
22using System . Reflection ;
33using System . Runtime . CompilerServices ;
44using System . Runtime . ExceptionServices ;
5+ using Microsoft . Extensions . Logging ;
56
67namespace Bunit . Rendering ;
78
@@ -20,7 +21,8 @@ public sealed class BunitRenderer : Renderer
2021 private static extern void CallSetDirectParameters ( ComponentState componentState , ParameterView parameters ) ;
2122
2223 private readonly object renderTreeUpdateLock = new ( ) ;
23- private readonly Dictionary < int , IRenderedComponent > renderedComponents = new ( ) ;
24+
25+ private readonly HashSet < int > returnedRenderedComponentIds = new ( ) ;
2426 private readonly List < BunitRootComponent > rootComponents = new ( ) ;
2527 private readonly ILogger < BunitRenderer > logger ;
2628 private bool disposed ;
@@ -182,7 +184,6 @@ public IReadOnlyList<IRenderedComponent<TComponent>> FindComponents<TComponent>(
182184 public Task DisposeComponents ( )
183185 {
184186 ObjectDisposedException . ThrowIf ( disposed , this ) ;
185-
186187 Task ? returnTask ;
187188
188189 lock ( renderTreeUpdateLock )
@@ -206,6 +207,19 @@ public Task DisposeComponents()
206207
207208 return returnTask ;
208209 }
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+ }
209223
210224 /// <inheritdoc/>
211225 protected override IComponent ResolveComponentForRenderMode ( Type componentType , int ? parentComponentId ,
@@ -307,118 +321,74 @@ protected override void ProcessPendingRender()
307321 /// <inheritdoc/>
308322 protected override Task UpdateDisplayAsync ( in RenderBatch renderBatch )
309323 {
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+ }
315329
316- void PrepareRenderEvent ( in RenderBatch renderBatch )
330+ for ( var i = 0 ; i < renderBatch . UpdatedComponents . Count ; i ++ )
317331 {
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 ;
323335
324- for ( int i = 0 ; i < renderBatch . UpdatedComponents . Count ; i ++ )
336+ if ( returnedRenderedComponentIds . Contains ( diff . ComponentId ) )
325337 {
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 ) ;
328339 }
329-
330- foreach ( var ( _, rc ) in renderedComponents )
340+ else
331341 {
332- LoadChangesIntoRenderEvent ( rc . ComponentId ) ;
342+ renderedComponent . UpdateState ( hasRendered : true , false ) ;
333343 }
344+
345+ UpdateParents ( diff . Edits . Count > 0 , componentState , in renderBatch ) ;
334346 }
335347
336- void LoadChangesIntoRenderEvent ( int componentId )
348+ return Task . CompletedTask ;
349+
350+ void UpdateParents ( bool hasChanges , ComponentState componentState , in RenderBatch renderBatch )
337351 {
338- var status = renderEvent . GetOrCreateStatus ( componentId ) ;
339- if ( status . FramesLoaded || status . Disposed )
352+ var parent = componentState . ParentComponentState ;
353+ if ( parent is null )
354+ {
340355 return ;
356+ }
341357
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 ) )
346359 {
347- ref var frame = ref frames . Array [ i ] ;
348- if ( frame . FrameType == RenderTreeFrameType . Component )
360+ if ( returnedRenderedComponentIds . Contains ( parent . ComponentId ) )
349361 {
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 ) ;
377363 }
364+ else
365+ {
366+ ( ( IRenderedComponent ) parent ) . UpdateState ( hasRendered : true , false ) ;
367+ }
368+
369+ UpdateParents ( hasChanges , parent , in renderBatch ) ;
378370 }
379371 }
380- }
381-
382- private void ApplyRenderEvent ( RenderEvent renderEvent )
383- {
384- RenderCount ++ ;
385372
386- foreach ( var ( componentId , status ) in renderEvent . Statuses )
373+ static bool IsParentComponentAlreadyUpdated ( int componentId , in RenderBatch renderBatch )
387374 {
388- if ( status . UpdatesApplied || ! renderedComponents . TryGetValue ( componentId , out var rc ) )
375+ for ( var i = 0 ; i < renderBatch . UpdatedComponents . Count ; i ++ )
389376 {
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 )
411379 {
412- renderedComponents . Remove ( componentId ) ;
413- renderedComponents . Add ( rc . ComponentId , rc ) ;
414- renderEvent . SetUpdatedApplied ( rc . ComponentId ) ;
380+ return diff . Edits . Count > 0 ;
415381 }
416-
417- logger . LogComponentRendered ( rc . ComponentId ) ;
418382 }
383+
384+ return false ;
419385 }
420386 }
421387
388+ /// <inheritdoc/>
389+ internal new ArrayRange < RenderTreeFrame > GetCurrentRenderTreeFrames ( int componentId )
390+ => base . GetCurrentRenderTreeFrames ( componentId ) ;
391+
422392 /// <inheritdoc/>
423393 protected override void Dispose ( bool disposing )
424394 {
@@ -434,12 +404,7 @@ protected override void Dispose(bool disposing)
434404
435405 if ( disposing )
436406 {
437- foreach ( var rc in renderedComponents . Values )
438- {
439- rc . Dispose ( ) ;
440- }
441-
442- renderedComponents . Clear ( ) ;
407+ returnedRenderedComponentIds . Clear ( ) ;
443408 disposalTasks . Clear ( ) ;
444409 unhandledExceptionTsc . TrySetCanceled ( ) ;
445410 }
@@ -448,7 +413,7 @@ protected override void Dispose(bool disposing)
448413 }
449414 }
450415
451- private RenderedComponent < BunitRootComponent > Render ( RenderFragment renderFragment )
416+ private IRenderedComponent < BunitRootComponent > Render ( RenderFragment renderFragment )
452417 {
453418 ObjectDisposedException . ThrowIf ( disposed , this ) ;
454419
@@ -458,25 +423,26 @@ private RenderedComponent<BunitRootComponent> Render(RenderFragment renderFragme
458423
459424 var root = new BunitRootComponent ( renderFragment ) ;
460425 var rootComponentId = AssignRootComponentId ( root ) ;
461- var result = new RenderedComponent < BunitRootComponent > ( rootComponentId , root , services ) ;
462- renderedComponents . Add ( rootComponentId , result ) ;
426+ returnedRenderedComponentIds . Add ( rootComponentId ) ;
463427 rootComponents . Add ( root ) ;
464428 root . Render ( ) ;
465- return result ;
429+ return rootComponentId ;
466430 } ) ;
467431
468- RenderedComponent < BunitRootComponent > result ;
432+ int componentId = - 1 ;
469433
470434 if ( ! renderTask . IsCompleted )
471435 {
472436 logger . LogAsyncInitialRender ( ) ;
473- result = renderTask . GetAwaiter ( ) . GetResult ( ) ;
437+ componentId = renderTask . GetAwaiter ( ) . GetResult ( ) ;
474438 }
475439 else
476440 {
477- result = renderTask . Result ;
441+ componentId = renderTask . Result ;
478442 }
479443
444+ var result = GetRenderedComponent < BunitRootComponent > ( componentId ) ;
445+
480446 logger . LogInitialRenderCompleted ( result . ComponentId ) ;
481447
482448 AssertNoUnhandledExceptions ( ) ;
@@ -488,35 +454,38 @@ private List<IRenderedComponent<TComponent>> FindComponents<TComponent>(IRendere
488454 where TComponent : IComponent
489455 {
490456 ArgumentNullException . ThrowIfNull ( parentComponent ) ;
491-
492457 ObjectDisposedException . ThrowIf ( disposed , this ) ;
493458
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 ) ;
496462
497463 // Blocks the renderer from changing the render tree
498464 // while this method searches through it.
499465 lock ( renderTreeUpdateLock )
500466 {
501467 ObjectDisposedException . ThrowIf ( disposed , this ) ;
502-
503468 FindComponentsInRenderTree ( parentComponent . ComponentId ) ;
469+ foreach ( var rc in result )
470+ {
471+ ( ( IRenderedComponent ) rc ) . UpdateState ( hasRendered : false , isMarkupGenerationRequired : true ) ;
472+ }
504473 }
505474
506475 return result ;
507476
508477 void FindComponentsInRenderTree ( int componentId )
509478 {
510- var frames = GetOrLoadRenderTreeFrame ( framesCollection , componentId ) ;
479+ var frames = GetCurrentRenderTreeFrames ( componentId ) ;
511480
512481 for ( var i = 0 ; i < frames . Count ; i ++ )
513482 {
514483 ref var frame = ref frames . Array [ i ] ;
515484 if ( frame . FrameType == RenderTreeFrameType . Component )
516485 {
517- if ( frame . Component is TComponent component )
486+ if ( frame . Component is TComponent )
518487 {
519- result . Add ( GetOrCreateRenderedComponent ( framesCollection , frame . ComponentId , component ) ) ;
488+ result . Add ( GetRenderedComponent < TComponent > ( frame . ComponentId ) ) ;
520489
521490 if ( result . Count == resultLimit )
522491 return ;
@@ -531,19 +500,12 @@ void FindComponentsInRenderTree(int componentId)
531500 }
532501 }
533502
534- private IRenderedComponent < TComponent > GetOrCreateRenderedComponent < TComponent > ( RenderTreeFrameDictionary framesCollection , int componentId , TComponent component )
503+ private IRenderedComponent < TComponent > GetRenderedComponent < TComponent > ( int componentId )
535504 where TComponent : IComponent
536505 {
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 ;
547509 }
548510
549511 /// <summary>
0 commit comments