@@ -247,80 +247,128 @@ protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
247247 return Task . CompletedTask ;
248248 }
249249
250- if ( usersSyncContext is not null && usersSyncContext != SynchronizationContext . Current )
250+ var renderEvent = new RenderEvent ( ) ;
251+
252+ for ( var i = 0 ; i < renderBatch . DisposedComponentIDs . Count ; i ++ )
251253 {
252- // The users' sync context, typically one established by
253- // xUnit or another testing framework is used to update any
254- // rendered fragments/dom trees and trigger WaitForX handlers.
255- // This ensures that changes to DOM observed inside a WaitForX handler
256- // will also be visible outside a WaitForX handler, since
257- // they will be running in the same sync context. The theory is that
258- // this should mitigate the issues where Blazor's dispatcher/thread is used
259- // to verify an assertion inside a WaitForX handler, and another thread is
260- // used again to access the DOM/repeat the assertion, where the change
261- // may not be visible yet (another theory about why that may happen is different
262- // CPU cache updates not happening immediately).
263- //
264- // There is no guarantee a caller/test framework has set a sync context.
265- usersSyncContext . Send ( static ( state ) =>
266- {
267- var ( renderBatch , renderer ) = ( ( RenderBatch , TestRenderer ) ) state ! ;
268- renderer . UpdateDisplay ( renderBatch ) ;
269- } , ( renderBatch , this ) ) ;
254+ var id = renderBatch . DisposedComponentIDs . Array [ i ] ;
255+ renderEvent . SetDisposed ( id ) ;
270256 }
271- else
257+
258+ for ( int i = 0 ; i < renderBatch . UpdatedComponents . Count ; i ++ )
272259 {
273- UpdateDisplay ( renderBatch ) ;
260+ ref var update = ref renderBatch . UpdatedComponents . Array [ i ] ;
261+ renderEvent . SetUpdated ( update . ComponentId , update . Edits . Count > 0 ) ;
274262 }
275263
276- return Task . CompletedTask ;
277- }
278-
279- private void UpdateDisplay ( in RenderBatch renderBatch )
280- {
281- RenderCount ++ ;
282- var renderEvent = new RenderEvent ( renderBatch , new RenderTreeFrameDictionary ( ) ) ;
283-
284- if ( disposed )
264+ foreach ( var ( key , rc ) in renderedComponents )
285265 {
286- logger . LogRenderCycleActiveAfterDispose ( ) ;
287- return ;
266+ LoadChangesIntoRenderEvent ( rc . ComponentId ) ;
288267 }
289268
290- // removes disposed components
291- for ( var i = 0 ; i < renderBatch . DisposedComponentIDs . Count ; i ++ )
292- {
293- var id = renderBatch . DisposedComponentIDs . Array [ i ] ;
269+ InvokeApplyRenderEvent ( ) ;
294270
295- // Add disposed components to the frames collection
296- // to avoid them being added into the dictionary later during a
297- // LoadRenderTreeFrames/GetOrLoadRenderTreeFrame call.
298- renderEvent . Frames . Add ( id , default ) ;
271+ return Task . CompletedTask ;
272+
273+ void LoadChangesIntoRenderEvent ( int componentId )
274+ {
275+ var status = renderEvent . GetStatus ( componentId ) ;
276+ if ( status . FramesLoaded || status . Disposed )
277+ {
278+ return ;
279+ }
299280
300- logger . LogComponentDisposed ( id ) ;
281+ var frames = GetCurrentRenderTreeFrames ( componentId ) ;
282+ renderEvent . AddFrames ( componentId , frames ) ;
301283
302- if ( renderedComponents . TryGetValue ( id , out var rc ) )
284+ for ( var i = 0 ; i < frames . Count ; i ++ )
303285 {
304- renderedComponents . Remove ( id ) ;
305- rc . OnRender ( renderEvent ) ;
286+ ref var frame = ref frames . Array [ i ] ;
287+ if ( frame . FrameType == RenderTreeFrameType . Component )
288+ {
289+ var childStatus = renderEvent . GetStatus ( frame . ComponentId ) ;
290+ if ( childStatus . Disposed )
291+ {
292+ logger . LogDisposedChildInRenderTreeFrame ( componentId , frame . ComponentId ) ;
293+ }
294+ else if ( ! renderEvent . GetStatus ( frame . ComponentId ) . FramesLoaded )
295+ {
296+ LoadChangesIntoRenderEvent ( frame . ComponentId ) ;
297+ }
298+
299+ if ( childStatus . Rendered || childStatus . Changed || childStatus . Disposed )
300+ {
301+ status . Rendered = status . Rendered || childStatus . Rendered ;
302+ status . Changed = status . Changed || childStatus . Changed || childStatus . Disposed ;
303+ }
304+ }
306305 }
307306 }
308307
309- // notify each rendered component about the render
310- foreach ( var ( key , rc ) in renderedComponents . ToArray ( ) )
308+ void InvokeApplyRenderEvent ( )
311309 {
312- LoadRenderTreeFrames ( rc . ComponentId , renderEvent . Frames ) ;
310+ if ( usersSyncContext is not null && usersSyncContext != SynchronizationContext . Current )
311+ {
312+ // The users' sync context, typically one established by
313+ // xUnit or another testing framework is used to update any
314+ // rendered fragments/dom trees and trigger WaitForX handlers.
315+ // This ensures that changes to DOM observed inside a WaitForX handler
316+ // will also be visible outside a WaitForX handler, since
317+ // they will be running in the same sync context. The theory is that
318+ // this should mitigate the issues where Blazor's dispatcher/thread is used
319+ // to verify an assertion inside a WaitForX handler, and another thread is
320+ // used again to access the DOM/repeat the assertion, where the change
321+ // may not be visible yet (another theory about why that may happen is different
322+ // CPU cache updates not happening immediately).
323+ //
324+ // There is no guarantee a caller/test framework has set a sync context.
325+ usersSyncContext . Send ( static ( state ) =>
326+ {
327+ var ( renderEvent , renderer ) = ( ( RenderEvent , TestRenderer ) ) state ! ;
328+ renderer . ApplyRenderEvent ( renderEvent ) ;
329+ } , ( renderEvent , this ) ) ;
330+ }
331+ else
332+ {
333+ ApplyRenderEvent ( renderEvent ) ;
334+ }
335+ }
336+ }
313337
314- rc . OnRender ( renderEvent ) ;
338+ private void ApplyRenderEvent ( RenderEvent renderEvent )
339+ {
340+ foreach ( var ( componentId , status ) in renderEvent . Statuses )
341+ {
342+ if ( status . UpdatesApplied || ! renderedComponents . TryGetValue ( componentId , out var rc ) )
343+ {
344+ continue ;
345+ }
315346
316- logger . LogComponentRendered ( rc . ComponentId ) ;
347+ if ( status . Disposed )
348+ {
349+ renderedComponents . Remove ( componentId ) ;
350+ rc . OnRender ( renderEvent ) ;
351+ renderEvent . SetUpdatedApplied ( componentId ) ;
352+ logger . LogComponentDisposed ( componentId ) ;
353+ continue ;
354+ }
317355
318- // RC can replace the instance of the component it is bound
319- // to while processing the update event.
320- if ( key != rc . ComponentId )
356+ if ( status . UpdateNeeded )
321357 {
322- renderedComponents . Remove ( key ) ;
323- renderedComponents . Add ( rc . ComponentId , rc ) ;
358+ rc . OnRender ( renderEvent ) ;
359+ renderEvent . SetUpdatedApplied ( componentId ) ;
360+
361+ // RC can replace the instance of the component it is bound
362+ // to while processing the update event, e.g. during the
363+ // initial render of a component.
364+ if ( componentId != rc . ComponentId )
365+ {
366+ renderedComponents . Remove ( componentId ) ;
367+ renderedComponents . Add ( rc . ComponentId , rc ) ;
368+ renderEvent . SetUpdatedApplied ( rc . ComponentId ) ;
369+ }
370+
371+ logger . LogComponentRendered ( rc . ComponentId ) ;
324372 }
325373 }
326374 }
@@ -461,7 +509,7 @@ private void LoadRenderTreeFrames(int componentId, RenderTreeFrameDictionary fra
461509 for ( var i = 0 ; i < frames . Count ; i ++ )
462510 {
463511 ref var frame = ref frames . Array [ i ] ;
464-
512+
465513 if ( frame . FrameType == RenderTreeFrameType . Component && ! framesCollection . Contains ( frame . ComponentId ) )
466514 {
467515 LoadRenderTreeFrames ( frame . ComponentId , framesCollection ) ;
0 commit comments