@@ -32,6 +32,9 @@ namespace Intersect.Server.Database.PlayerData
3232 [ ApiVisibility ( ApiVisibility . Restricted | ApiVisibility . Private ) ]
3333 public partial class User
3434 {
35+ private long _lastSave ;
36+ private readonly object _lastSaveLock = new ( ) ;
37+
3538 private static readonly ConcurrentDictionary < Guid , User > OnlineUsers = new ( ) ;
3639
3740 [ JsonIgnore ] [ NotMapped ] private readonly object mSavingLock = new ( ) ;
@@ -225,20 +228,19 @@ public void DeleteCharacter(Player deleteCharacter)
225228 {
226229 lock ( mSavingLock )
227230 {
228- using ( var context = DbInterface . CreatePlayerContext ( false ) )
229- {
230- context . Users . Update ( this ) ;
231+ using var context = DbInterface . CreatePlayerContext ( false ) ;
231232
232- context . ChangeTracker . DetectChanges ( ) ;
233+ context . Users . Update ( this ) ;
233234
234- context . StopTrackingUsersExcept ( this ) ;
235+ Players . Remove ( deleteCharacter ) ;
235236
236- context . Entry ( deleteCharacter ) . State = EntityState . Deleted ;
237+ context . ChangeTracker . DetectChanges ( ) ;
237238
238- Players . Remove ( deleteCharacter ) ;
239+ context . StopTrackingUsersExcept ( this ) ;
239240
240- context . SaveChanges ( ) ;
241- }
241+ context . Entry ( deleteCharacter ) . State = EntityState . Deleted ;
242+
243+ context . SaveChanges ( ) ;
242244 }
243245 }
244246 catch ( Exception ex )
@@ -294,16 +296,35 @@ public async Task SaveAsync(
294296 CancellationToken cancellationToken = default
295297 ) => Save ( playerContext , force ) ;
296298
297- public void Save ( bool force ) => Save ( force : force , create : false ) ;
299+ public void SaveWithDebounce ( long debounceMs = 5000 )
300+ {
301+ lock ( _lastSaveLock )
302+ {
303+ if ( _lastSave < debounceMs + Timing . Global . MillisecondsUtc )
304+ {
305+ Log . Debug ( "Skipping save due to debounce" ) ;
306+ return ;
307+ }
308+ }
309+
310+ Save ( ) ;
311+ }
298312
299- public void Save ( bool force = false , bool create = false ) => Save ( default , force , create ) ;
313+ public UserSaveResult Save ( bool force ) => Save ( force : force , create : false ) ;
314+
315+ public UserSaveResult Save ( bool force = false , bool create = false ) => Save ( default , force , create ) ;
300316
301317#if DIAGNOSTIC
302318 private int _saveCounter = 0 ;
303319#endif
304320
305- private void Save ( PlayerContext ? playerContext , bool force = false , bool create = false )
321+ private UserSaveResult Save ( PlayerContext ? playerContext , bool force = false , bool create = false )
306322 {
323+ lock ( _lastSaveLock )
324+ {
325+ _lastSave = Timing . Global . MillisecondsUtc ;
326+ }
327+
307328#if DIAGNOSTIC
308329 var currentExecutionId = _saveCounter ++ ;
309330#endif
@@ -326,7 +347,10 @@ private void Save(PlayerContext? playerContext, bool force = false, bool create
326347
327348 if ( ! lockTaken )
328349 {
329- return ;
350+ #if DIAGNOSTIC
351+ Log . Debug ( $ "Failed to take lock { Environment . StackTrace } ") ;
352+ #endif
353+ return UserSaveResult . SkippedCouldNotTakeLock ;
330354 }
331355
332356#if DIAGNOSTIC
@@ -345,6 +369,7 @@ private void Save(PlayerContext? playerContext, bool force = false, bool create
345369 }
346370 else
347371 {
372+ // playerContext.Attach(this);
348373 playerContext . Users . Update ( this ) ;
349374 }
350375
@@ -367,6 +392,8 @@ private void Save(PlayerContext? playerContext, bool force = false, bool create
367392#if DIAGNOSTIC
368393 Log . Debug ( $ "DBOP-B Save({ playerContext } , { force } , { create } ) #{ currentExecutionId } { Name } ({ Id } )") ;
369394#endif
395+
396+ return UserSaveResult . Completed ;
370397 }
371398 catch ( DbUpdateConcurrencyException ex )
372399 {
@@ -428,6 +455,8 @@ private void Save(PlayerContext? playerContext, bool force = false, bool create
428455 Monitor . Exit ( mSavingLock ) ;
429456 }
430457 }
458+
459+ return UserSaveResult . Failed ;
431460 }
432461
433462 [ return : NotNullIfNotNull ( nameof ( user ) ) ]
0 commit comments