@@ -16,10 +16,14 @@ internal sealed class RenderedComponent<TComponent> : ComponentState, IRenderedC
1616
1717	[ SuppressMessage ( "Usage" ,  "CA2213:Disposable fields should be disposed" ,  Justification  =  "Owned by BunitServiceProvider, disposed by it." ) ] 
1818	private  readonly  BunitHtmlParser  htmlParser ; 
19- 
19+ 	 private   int   renderCount ; 
2020	private  string  markup  =  string . Empty ; 
21+ 	private  int  markupStartIndex ; 
22+ 	private  int  markupEndIndex ; 
2123	private  INodeList ?  latestRenderNodes ; 
2224
25+ 	public  bool  IsDirty  {  get ;  set ;  } 
26+ 
2327	/// <summary> 
2428	/// Gets the component under test. 
2529	/// </summary> 
@@ -53,10 +57,22 @@ public string Markup
5357		} 
5458	} 
5559
60+ 	/// <summary> 
61+ 	/// Adds or removes an event handler that will be triggered after 
62+ 	/// each render of this <see cref="RenderedComponent{T}"/>. 
63+ 	/// </summary> 
64+ 	public  event  EventHandler ?  OnAfterRender ; 
65+ 
66+ 	/// <summary> 
67+ 	/// An event that is raised after the markup of the 
68+ 	/// <see cref="RenderedComponent{T}"/> is updated. 
69+ 	/// </summary> 
70+ 	public  event  EventHandler ?  OnMarkupUpdated ; 
71+ 
5672	/// <summary> 
5773	/// Gets the total number times the fragment has been through its render life-cycle. 
5874	/// </summary> 
59- 	public  int  RenderCount  {   get ;   private   set ;   } 
75+ 	public  int  RenderCount  =>   renderCount ; 
6076
6177	/// <summary> 
6278	/// Gets the AngleSharp <see cref="INodeList"/> based 
@@ -77,6 +93,10 @@ public INodeList Nodes
7793	/// </summary> 
7894	public  IServiceProvider  Services  {  get ;  } 
7995
96+ 	int  IRenderedComponent . RenderCount  {  get  =>  renderCount ;  set  {  renderCount  =  value ;  }  } 
97+ 
98+ 	public  IRenderedComponent ?  Root  {  get ;  } 
99+ 
80100	public  RenderedComponent ( 
81101		BunitRenderer  renderer , 
82102		int  componentId , 
@@ -89,57 +109,76 @@ public RenderedComponent(
89109		this . renderer  =  renderer ; 
90110		this . instance  =  ( TComponent ) instance ; 
91111		htmlParser  =  Services . GetRequiredService < BunitHtmlParser > ( ) ; 
112+ 		var  parentRenderedComponent  =  parentComponentState  as  IRenderedComponent ; 
113+ 		Root  =  parentRenderedComponent ? . Root  ??  parentRenderedComponent ; 
92114	} 
93115
94- 	/// <summary> 
95- 	/// Adds or removes an event handler that will be triggered after each render of this <see cref="RenderedComponent{T}"/>. 
96- 	/// </summary> 
97- 	public  event  EventHandler ?  OnAfterRender ; 
116+ 	/// <inheritdoc/> 
117+ 	public  void  Dispose ( ) 
118+ 	{ 
119+ 		if  ( IsDisposed ) 
120+ 			return ; 
98121
99- 	/// <summary> 
100- 	/// An event that is raised after the markup of the <see cref="RenderedComponent{T}"/> is updated. 
101- 	/// </summary> 
102- 	public  event  EventHandler ?  OnMarkupUpdated ; 
122+ 		if  ( Root  is  not null ) 
123+ 			Root . IsDirty  =  true ; 
124+ 
125+ 		IsDisposed  =  true ; 
126+ 		markup  =  string . Empty ; 
127+ 		OnAfterRender  =  null ; 
128+ 		OnMarkupUpdated  =  null ; 
129+ 	} 
130+ 
131+ 	/// <inheritdoc/> 
132+ 	public  override  ValueTask  DisposeAsync ( ) 
133+ 	{ 
134+ 		Dispose ( ) ; 
135+ 		return  base . DisposeAsync ( ) ; 
136+ 	} 
137+ 
138+ 	public  void  SetMarkupIndices ( int  start ,  int  end ) 
139+ 	{ 
140+ 		markupStartIndex  =  start ; 
141+ 		markupEndIndex  =  end ; 
142+ 		IsDirty  =  true ; 
143+ 	} 
103144
104145	/// <summary> 
105146	/// Called by the owning <see cref="BunitRenderer"/> when it finishes a render. 
106147	/// </summary> 
107- 	public  void  UpdateState ( bool   hasRendered ,   bool   isMarkupGenerationRequired ) 
148+ 	public  void  UpdateMarkup ( ) 
108149	{ 
109150		if  ( IsDisposed ) 
110151			return ; 
111152
112- 		if  ( hasRendered ) 
153+ 		if  ( Root   is   RenderedComponent < BunitRootComponent >   root ) 
113154		{ 
114- 			RenderCount ++ ; 
155+ 			var  newMarkup  =  root . markup [ markupStartIndex ..markupEndIndex ] ; 
156+ 			if  ( markup  !=  newMarkup ) 
157+ 			{ 
158+ 				Volatile . Write ( ref  markup ,  newMarkup ) ; 
159+ 				latestRenderNodes  =  null ; 
160+ 				OnMarkupUpdated ? . Invoke ( this ,  EventArgs . Empty ) ; 
161+ 			} 
162+ 			else 
163+ 			{ 
164+ 				// no change 
165+ 			} 
115166		} 
116- 
117- 		if  ( isMarkupGenerationRequired ) 
167+ 		else 
118168		{ 
119- 			UpdateMarkup ( ) ; 
169+ 			var  newMarkup  =  Htmlizer . GetHtml ( ComponentId ,  renderer ) ; 
170+ 
171+ 			// Volatile write is necessary to ensure the updated markup 
172+ 			// is available across CPU cores. Without it, the pointer to the 
173+ 			// markup string can be stored in a CPUs register and not 
174+ 			// get updated when another CPU changes the string. 
175+ 			Volatile . Write ( ref  markup ,  newMarkup ) ; 
176+ 			latestRenderNodes  =  null ; 
120177			OnMarkupUpdated ? . Invoke ( this ,  EventArgs . Empty ) ; 
121178		} 
122179
123- 		// The order here is important, since consumers of the events 
124- 		// expect that markup has indeed changed when OnAfterRender is invoked 
125- 		// (assuming there are markup changes) 
126- 		if  ( hasRendered ) 
127- 			OnAfterRender ? . Invoke ( this ,  EventArgs . Empty ) ; 
128- 	} 
129- 
130- 	/// <summary> 
131- 	/// Updates the markup of the rendered fragment. 
132- 	/// </summary> 
133- 	private  void  UpdateMarkup ( ) 
134- 	{ 
135- 		latestRenderNodes  =  null ; 
136- 		var  newMarkup  =  Htmlizer . GetHtml ( ComponentId ,  renderer ) ; 
137- 
138- 		// Volatile write is necessary to ensure the updated markup 
139- 		// is available across CPU cores. Without it, the pointer to the 
140- 		// markup string can be stored in a CPUs register and not 
141- 		// get updated when another CPU changes the string. 
142- 		Volatile . Write ( ref  markup ,  newMarkup ) ; 
180+ 		IsDirty  =  false ; 
181+ 		OnAfterRender ? . Invoke ( this ,  EventArgs . Empty ) ; 
143182	} 
144183
145184	/// <summary> 
@@ -151,22 +190,5 @@ private void EnsureComponentNotDisposed()
151190		if  ( IsDisposed ) 
152191			throw  new  ComponentDisposedException ( ComponentId ) ; 
153192	} 
154- 
155- 	/// <inheritdoc/> 
156- 	public  void  Dispose ( ) 
157- 	{ 
158- 		if  ( IsDisposed ) 
159- 			return ; 
160- 
161- 		IsDisposed  =  true ; 
162- 		markup  =  string . Empty ; 
163- 		OnAfterRender  =  null ; 
164- 		OnMarkupUpdated  =  null ; 
165- 	} 
166- 
167- 	public  override  ValueTask  DisposeAsync ( ) 
168- 	{ 
169- 		Dispose ( ) ; 
170- 		return  base . DisposeAsync ( ) ; 
171- 	} 
172193} 
194+ 
0 commit comments