@@ -16,10 +16,14 @@ internal sealed class RenderedComponent<TComponent> : ComponentState, IRenderedC
16
16
17
17
[ SuppressMessage ( "Usage" , "CA2213:Disposable fields should be disposed" , Justification = "Owned by BunitServiceProvider, disposed by it." ) ]
18
18
private readonly BunitHtmlParser htmlParser ;
19
-
19
+ private int renderCount ;
20
20
private string markup = string . Empty ;
21
+ private int markupStartIndex ;
22
+ private int markupEndIndex ;
21
23
private INodeList ? latestRenderNodes ;
22
24
25
+ public bool IsDirty { get ; set ; }
26
+
23
27
/// <summary>
24
28
/// Gets the component under test.
25
29
/// </summary>
@@ -53,10 +57,22 @@ public string Markup
53
57
}
54
58
}
55
59
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
+
56
72
/// <summary>
57
73
/// Gets the total number times the fragment has been through its render life-cycle.
58
74
/// </summary>
59
- public int RenderCount { get ; private set ; }
75
+ public int RenderCount => renderCount ;
60
76
61
77
/// <summary>
62
78
/// Gets the AngleSharp <see cref="INodeList"/> based
@@ -77,6 +93,10 @@ public INodeList Nodes
77
93
/// </summary>
78
94
public IServiceProvider Services { get ; }
79
95
96
+ int IRenderedComponent . RenderCount { get => renderCount ; set { renderCount = value ; } }
97
+
98
+ public IRenderedComponent ? Root { get ; }
99
+
80
100
public RenderedComponent (
81
101
BunitRenderer renderer ,
82
102
int componentId ,
@@ -89,57 +109,76 @@ public RenderedComponent(
89
109
this . renderer = renderer ;
90
110
this . instance = ( TComponent ) instance ;
91
111
htmlParser = Services . GetRequiredService < BunitHtmlParser > ( ) ;
112
+ var parentRenderedComponent = parentComponentState as IRenderedComponent ;
113
+ Root = parentRenderedComponent ? . Root ?? parentRenderedComponent ;
92
114
}
93
115
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 ;
98
121
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
+ }
103
144
104
145
/// <summary>
105
146
/// Called by the owning <see cref="BunitRenderer"/> when it finishes a render.
106
147
/// </summary>
107
- public void UpdateState ( bool hasRendered , bool isMarkupGenerationRequired )
148
+ public void UpdateMarkup ( )
108
149
{
109
150
if ( IsDisposed )
110
151
return ;
111
152
112
- if ( hasRendered )
153
+ if ( Root is RenderedComponent < BunitRootComponent > root )
113
154
{
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
+ }
115
166
}
116
-
117
- if ( isMarkupGenerationRequired )
167
+ else
118
168
{
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 ;
120
177
OnMarkupUpdated ? . Invoke ( this , EventArgs . Empty ) ;
121
178
}
122
179
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 ) ;
143
182
}
144
183
145
184
/// <summary>
@@ -151,22 +190,5 @@ private void EnsureComponentNotDisposed()
151
190
if ( IsDisposed )
152
191
throw new ComponentDisposedException ( ComponentId ) ;
153
192
}
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
- }
172
193
}
194
+
0 commit comments