@@ -237,80 +237,128 @@ protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
237237 return Task . CompletedTask ;
238238 }
239239
240- if ( usersSyncContext is not null && usersSyncContext != SynchronizationContext . Current )
240+ var renderEvent = new RenderEvent ( ) ;
241+
242+ for ( var i = 0 ; i < renderBatch . DisposedComponentIDs . Count ; i ++ )
241243 {
242- // The users' sync context, typically one established by
243- // xUnit or another testing framework is used to update any
244- // rendered fragments/dom trees and trigger WaitForX handlers.
245- // This ensures that changes to DOM observed inside a WaitForX handler
246- // will also be visible outside a WaitForX handler, since
247- // they will be running in the same sync context. The theory is that
248- // this should mitigate the issues where Blazor's dispatcher/thread is used
249- // to verify an assertion inside a WaitForX handler, and another thread is
250- // used again to access the DOM/repeat the assertion, where the change
251- // may not be visible yet (another theory about why that may happen is different
252- // CPU cache updates not happening immediately).
253- //
254- // There is no guarantee a caller/test framework has set a sync context.
255- usersSyncContext . Send ( static ( state ) =>
256- {
257- var ( renderBatch , renderer ) = ( ( RenderBatch , TestRenderer ) ) state ! ;
258- renderer . UpdateDisplay ( renderBatch ) ;
259- } , ( renderBatch , this ) ) ;
244+ var id = renderBatch . DisposedComponentIDs . Array [ i ] ;
245+ renderEvent . SetDisposed ( id ) ;
260246 }
261- else
247+
248+ for ( int i = 0 ; i < renderBatch . UpdatedComponents . Count ; i ++ )
262249 {
263- UpdateDisplay ( renderBatch ) ;
250+ ref var update = ref renderBatch . UpdatedComponents . Array [ i ] ;
251+ renderEvent . SetUpdated ( update . ComponentId , update . Edits . Count > 0 ) ;
264252 }
265253
266- return Task . CompletedTask ;
267- }
268-
269- private void UpdateDisplay ( in RenderBatch renderBatch )
270- {
271- RenderCount ++ ;
272- var renderEvent = new RenderEvent ( renderBatch , new RenderTreeFrameDictionary ( ) ) ;
273-
274- if ( disposed )
254+ foreach ( var ( key , rc ) in renderedComponents )
275255 {
276- logger . LogRenderCycleActiveAfterDispose ( ) ;
277- return ;
256+ LoadChangesIntoRenderEvent ( rc . ComponentId ) ;
278257 }
279258
280- // removes disposed components
281- for ( var i = 0 ; i < renderBatch . DisposedComponentIDs . Count ; i ++ )
282- {
283- var id = renderBatch . DisposedComponentIDs . Array [ i ] ;
259+ InvokeApplyRenderEvent ( ) ;
284260
285- // Add disposed components to the frames collection
286- // to avoid them being added into the dictionary later during a
287- // LoadRenderTreeFrames/GetOrLoadRenderTreeFrame call.
288- renderEvent . Frames . Add ( id , default ) ;
261+ return Task . CompletedTask ;
262+
263+ void LoadChangesIntoRenderEvent ( int componentId )
264+ {
265+ var status = renderEvent . GetStatus ( componentId ) ;
266+ if ( status . FramesLoaded || status . Disposed )
267+ {
268+ return ;
269+ }
289270
290- logger . LogComponentDisposed ( id ) ;
271+ var frames = GetCurrentRenderTreeFrames ( componentId ) ;
272+ renderEvent . AddFrames ( componentId , frames ) ;
291273
292- if ( renderedComponents . TryGetValue ( id , out var rc ) )
274+ for ( var i = 0 ; i < frames . Count ; i ++ )
293275 {
294- renderedComponents . Remove ( id ) ;
295- rc . OnRender ( renderEvent ) ;
276+ ref var frame = ref frames . Array [ i ] ;
277+ if ( frame . FrameType == RenderTreeFrameType . Component )
278+ {
279+ var childStatus = renderEvent . GetStatus ( frame . ComponentId ) ;
280+ if ( childStatus . Disposed )
281+ {
282+ logger . LogDisposedChildInRenderTreeFrame ( componentId , frame . ComponentId ) ;
283+ }
284+ else if ( ! renderEvent . GetStatus ( frame . ComponentId ) . FramesLoaded )
285+ {
286+ LoadChangesIntoRenderEvent ( frame . ComponentId ) ;
287+ }
288+
289+ if ( childStatus . Rendered || childStatus . Changed || childStatus . Disposed )
290+ {
291+ status . Rendered = status . Rendered || childStatus . Rendered ;
292+ status . Changed = status . Changed || childStatus . Changed || childStatus . Disposed ;
293+ }
294+ }
296295 }
297296 }
298297
299- // notify each rendered component about the render
300- foreach ( var ( key , rc ) in renderedComponents . ToArray ( ) )
298+ void InvokeApplyRenderEvent ( )
301299 {
302- LoadRenderTreeFrames ( rc . ComponentId , renderEvent . Frames ) ;
300+ if ( usersSyncContext is not null && usersSyncContext != SynchronizationContext . Current )
301+ {
302+ // The users' sync context, typically one established by
303+ // xUnit or another testing framework is used to update any
304+ // rendered fragments/dom trees and trigger WaitForX handlers.
305+ // This ensures that changes to DOM observed inside a WaitForX handler
306+ // will also be visible outside a WaitForX handler, since
307+ // they will be running in the same sync context. The theory is that
308+ // this should mitigate the issues where Blazor's dispatcher/thread is used
309+ // to verify an assertion inside a WaitForX handler, and another thread is
310+ // used again to access the DOM/repeat the assertion, where the change
311+ // may not be visible yet (another theory about why that may happen is different
312+ // CPU cache updates not happening immediately).
313+ //
314+ // There is no guarantee a caller/test framework has set a sync context.
315+ usersSyncContext . Send ( static ( state ) =>
316+ {
317+ var ( renderEvent , renderer ) = ( ( RenderEvent , TestRenderer ) ) state ! ;
318+ renderer . ApplyRenderEvent ( renderEvent ) ;
319+ } , ( renderEvent , this ) ) ;
320+ }
321+ else
322+ {
323+ ApplyRenderEvent ( renderEvent ) ;
324+ }
325+ }
326+ }
303327
304- rc . OnRender ( renderEvent ) ;
328+ private void ApplyRenderEvent ( RenderEvent renderEvent )
329+ {
330+ foreach ( var ( componentId , status ) in renderEvent . Statuses )
331+ {
332+ if ( status . UpdatesApplied || ! renderedComponents . TryGetValue ( componentId , out var rc ) )
333+ {
334+ continue ;
335+ }
305336
306- logger . LogComponentRendered ( rc . ComponentId ) ;
337+ if ( status . Disposed )
338+ {
339+ renderedComponents . Remove ( componentId ) ;
340+ rc . OnRender ( renderEvent ) ;
341+ renderEvent . SetUpdatedApplied ( componentId ) ;
342+ logger . LogComponentDisposed ( componentId ) ;
343+ continue ;
344+ }
307345
308- // RC can replace the instance of the component it is bound
309- // to while processing the update event.
310- if ( key != rc . ComponentId )
346+ if ( status . UpdateNeeded )
311347 {
312- renderedComponents . Remove ( key ) ;
313- renderedComponents . Add ( rc . ComponentId , rc ) ;
348+ rc . OnRender ( renderEvent ) ;
349+ renderEvent . SetUpdatedApplied ( componentId ) ;
350+
351+ // RC can replace the instance of the component it is bound
352+ // to while processing the update event, e.g. during the
353+ // initial render of a component.
354+ if ( componentId != rc . ComponentId )
355+ {
356+ renderedComponents . Remove ( componentId ) ;
357+ renderedComponents . Add ( rc . ComponentId , rc ) ;
358+ renderEvent . SetUpdatedApplied ( rc . ComponentId ) ;
359+ }
360+
361+ logger . LogComponentRendered ( rc . ComponentId ) ;
314362 }
315363 }
316364 }
@@ -451,7 +499,7 @@ private void LoadRenderTreeFrames(int componentId, RenderTreeFrameDictionary fra
451499 for ( var i = 0 ; i < frames . Count ; i ++ )
452500 {
453501 ref var frame = ref frames . Array [ i ] ;
454-
502+
455503 if ( frame . FrameType == RenderTreeFrameType . Component && ! framesCollection . Contains ( frame . ComponentId ) )
456504 {
457505 LoadRenderTreeFrames ( frame . ComponentId , framesCollection ) ;
0 commit comments