33using System . Runtime . CompilerServices ;
44using System . Runtime . InteropServices ;
55using System . Runtime . InteropServices . JavaScript ;
6+ using System . Threading ;
67using Abies ;
78using Abies . DOM ;
89
@@ -164,6 +165,7 @@ public record Program<TApplication, TArguments, TModel> : Program
164165 private Node ? _dom ;
165166 // todo: clean up handlers when they are no longer needed
166167 private readonly ConcurrentDictionary < string , Message > _handlers = new ( ) ;
168+ private readonly SemaphoreSlim _dispatchLock = new ( 1 , 1 ) ;
167169
168170 public async Task Run ( TArguments arguments )
169171 {
@@ -210,8 +212,44 @@ private void RegisterHandlers(Node node)
210212 }
211213 }
212214
215+ private static Node PreserveIds ( Node ? oldNode , Node newNode )
216+ {
217+ if ( oldNode is Element oldElement && newNode is Element newElement && oldElement . Tag == newElement . Tag )
218+ {
219+ var attrs = new Abies . DOM . Attribute [ newElement . Attributes . Length ] ;
220+ for ( int i = 0 ; i < newElement . Attributes . Length ; i ++ )
221+ {
222+ var attr = newElement . Attributes [ i ] ;
223+ attrs [ i ] = attr . Name == "id" ? attr with { Value = oldElement . Id } : attr ;
224+ }
225+
226+ var children = new Node [ newElement . Children . Length ] ;
227+ for ( int i = 0 ; i < newElement . Children . Length ; i ++ )
228+ {
229+ var oldChild = i < oldElement . Children . Length ? oldElement . Children [ i ] : null ;
230+ children [ i ] = PreserveIds ( oldChild , newElement . Children [ i ] ) ;
231+ }
232+
233+ return new Element ( oldElement . Id , newElement . Tag , attrs , children ) ;
234+ }
235+ else if ( newNode is Element newElem )
236+ {
237+ var children = new Node [ newElem . Children . Length ] ;
238+ for ( int i = 0 ; i < newElem . Children . Length ; i ++ )
239+ {
240+ children [ i ] = PreserveIds ( null , newElem . Children [ i ] ) ;
241+ }
242+ return new Element ( newElem . Id , newElem . Tag , newElem . Attributes , children ) ;
243+ }
244+
245+ return newNode ;
246+ }
247+
213248 public async Task Dispatch ( Message message )
214249 {
250+ await _dispatchLock . WaitAsync ( ) ;
251+ try
252+ {
215253 if ( model is null )
216254 {
217255 await Interop . WriteToConsole ( "Model not initialized" ) ;
@@ -232,8 +270,10 @@ public async Task Dispatch(Message message)
232270 // Generate new virtual DOM
233271 var newDocument = TApplication . View ( newModel ) ;
234272
273+ var alignedBody = PreserveIds ( _dom , newDocument . Body ) ;
274+
235275 // Compute the patches
236- var patches = Operations . Diff ( _dom , newDocument . Body ) ;
276+ var patches = Operations . Diff ( _dom , alignedBody ) ;
237277
238278 // Apply patches and (de)register handlers
239279 foreach ( var patch in patches )
@@ -257,14 +297,21 @@ public async Task Dispatch(Message message)
257297 }
258298
259299 // Update the current virtual DOM
260- _dom = newDocument . Body ;
300+ _dom = alignedBody ;
261301 await Interop . SetTitle ( newDocument . Title ) ;
262302
263303 foreach ( var command in commands )
264304 {
265305 await HandleCommand ( command ) ;
266306 }
267- } private static async Task HandleCommand ( Command command )
307+ }
308+ finally
309+ {
310+ _dispatchLock . Release ( ) ;
311+ }
312+ }
313+
314+ private static async Task HandleCommand ( Command command )
268315 {
269316 switch ( command )
270317 {
0 commit comments