@@ -333,6 +333,7 @@ private string GetPluginDirectoryPath()
333333 const string pluginDir = "Plugins" ;
334334 return Path . Combine ( Application . dataPath , pluginDir ) ;
335335 }
336+
336337 /// <summary>
337338 /// Generate path to Crashpad handler binary
338339 /// </summary>
@@ -367,12 +368,24 @@ internal static void CleanScopedAttributes()
367368 {
368369 return ;
369370 }
370- var attributes = JsonUtility . FromJson < ScopedAttributesContainer > ( attributesJson ) ;
371- foreach ( var attributeKey in attributes . Keys )
371+
372+ try
373+ {
374+ var attributes = JsonUtility . FromJson < ScopedAttributesContainer > ( attributesJson ) ;
375+ foreach ( var attributeKey in attributes . Keys )
376+ {
377+ PlayerPrefs . DeleteKey ( string . Format ( ScopedAttributesPattern , attributeKey ) ) ;
378+ }
379+ }
380+ catch ( Exception e )
381+ {
382+ Debug . LogWarning ( $ "Backtrace Failed to parse scoped attributes for cleanup: { e . Message } ") ;
383+ }
384+ finally
372385 {
373- PlayerPrefs . DeleteKey ( string . Format ( ScopedAttributesPattern , attributeKey ) ) ;
386+ PlayerPrefs . DeleteKey ( ScopedAttributeListKey ) ;
387+ PlayerPrefs . Save ( ) ;
374388 }
375- PlayerPrefs . DeleteKey ( ScopedAttributeListKey ) ;
376389 }
377390
378391 internal static IDictionary < string , string > GetScopedAttributes ( )
@@ -382,8 +395,25 @@ internal static IDictionary<string, string> GetScopedAttributes()
382395 {
383396 return new Dictionary < string , string > ( ) ;
384397 }
385- var result = new Dictionary < string , string > ( ) ;
386- var attributes = JsonUtility . FromJson < ScopedAttributesContainer > ( attributesJson ) ;
398+
399+ var result = new Dictionary < string , string > ( StringComparer . Ordinal ) ;
400+ ScopedAttributesContainer attributes = null ;
401+
402+ try
403+ {
404+ attributes = JsonUtility . FromJson < ScopedAttributesContainer > ( attributesJson ) ;
405+ }
406+ catch ( Exception e )
407+ {
408+ Debug . LogWarning ( $ "Backtrace Failed to parse scoped attributes at read: { e . Message } ") ;
409+ return result ;
410+ }
411+
412+ if ( attributes ? . Keys == null )
413+ {
414+ return result ;
415+ }
416+
387417 foreach ( var attributeKey in attributes . Keys )
388418 {
389419 var value = PlayerPrefs . GetString ( string . Format ( ScopedAttributesPattern , attributeKey ) , string . Empty ) ;
@@ -411,7 +441,7 @@ internal static IDictionary<string, string> GetScopedAttributes()
411441 }
412442
413443 /// <summary>
414- /// Adds attributes to scoped registry and to native clietn
444+ /// Adds attributes to scoped registry and to native client
415445 /// </summary>
416446 /// <param name="key">attribute key</param>
417447 /// <param name="value">attribute value</param>
@@ -424,23 +454,100 @@ private void AddAttributes(string key, string value)
424454 AddScopedAttribute ( key , value ) ;
425455 }
426456
457+ /// <summary>Load a deduped set of scoped attribute keys from PlayerPrefs.</summary>
458+ private HashSet < string > LoadScopedKeys ( )
459+ {
460+ var keys = new HashSet < string > ( StringComparer . Ordinal ) ;
461+ var attributesJson = PlayerPrefs . GetString ( ScopedAttributeListKey ) ;
462+ if ( HasScopedAttributesEmpty ( attributesJson ) )
463+ {
464+ try
465+ {
466+ var container = JsonUtility . FromJson < ScopedAttributesContainer > ( attributesJson ) ;
467+ foreach ( var k in container . Keys )
468+ {
469+ if ( ! string . IsNullOrEmpty ( k ) )
470+ {
471+ keys . Add ( k ) ;
472+ }
473+ }
474+ }
475+ catch ( Exception e )
476+ {
477+ Debug . LogWarning ( $ "Backtrace Failed to parse scoped attributes list. { e . Message } ") ;
478+ keys . Clear ( ) ;
479+ }
480+ }
481+ return keys ;
482+ }
483+
484+ /// <summary>Persist a deduped list of keys back to PlayerPrefs.</summary>
485+ private void SaveScopedKeys ( HashSet < string > keys )
486+ {
487+ var container = new ScopedAttributesContainer
488+ {
489+ Keys = keys . ToList ( )
490+ } ;
491+ PlayerPrefs . SetString ( ScopedAttributeListKey , JsonUtility . ToJson ( container ) ) ;
492+ PlayerPrefs . Save ( ) ;
493+ }
494+
495+ /// <summary>Return the currently stored "value" for a scoped key or "null" if missing.</summary>
496+ private string GetScopedValue ( string key )
497+ {
498+ var prefsKey = string . Format ( ScopedAttributesPattern , key ) ;
499+ return PlayerPrefs . HasKey ( prefsKey ) ? PlayerPrefs . GetString ( prefsKey ) : null ;
500+ }
501+
502+ /// <summary>Set the stored value for a scoped key.</summary>
503+ private void SetScopedValue ( string key , string value )
504+ {
505+ PlayerPrefs . SetString ( string . Format ( ScopedAttributesPattern , key ) , value ?? string . Empty ) ;
506+ }
507+
427508 /// <summary>
428509 /// Adds dictionary of attributes to player prefs for windows crashes captured by unity crash handler
429510 /// </summary>
430- /// <param name="atributes ">Attributes</param>
511+ /// <param name="attributes ">Attributes</param>
431512 internal void AddScopedAttributes ( IDictionary < string , string > attributes )
432513 {
433514 if ( ! _configuration . SendUnhandledGameCrashesOnGameStartup )
434515 {
435516 return ;
436517 }
437- var attributesContainer = new ScopedAttributesContainer ( ) ;
438- foreach ( var attribute in attributes )
518+ if ( attributes == null || attributes . Count == 0 )
439519 {
440- attributesContainer . Keys . Add ( attribute . Key ) ;
441- PlayerPrefs . SetString ( string . Format ( ScopedAttributesPattern , attribute . Key ) , attribute . Value ) ;
520+ return ;
521+ }
522+
523+ var keys = LoadScopedKeys ( ) ;
524+ bool listChanged = false ;
525+
526+ foreach ( var kv in attributes )
527+ {
528+ var key = kv . Key ;
529+ var value = kv . Value ?? string . Empty ;
530+
531+ // skip when unchanged
532+ var current = GetScopedValue ( key ) ;
533+ if ( current == null || ! string . Equals ( current , value , StringComparison . Ordinal ) )
534+ {
535+ SetScopedValue ( key , value ) ;
536+ }
537+
538+ // deduplication
539+ if ( keys . Add ( key ) )
540+ {
541+ listChanged = true ;
542+ }
442543 }
443- PlayerPrefs . SetString ( ScopedAttributeListKey , JsonUtility . ToJson ( attributesContainer ) ) ;
544+
545+ if ( listChanged )
546+ {
547+ SaveScopedKeys ( keys ) ;
548+ }
549+
550+ PlayerPrefs . Save ( ) ;
444551 }
445552
446553 /// <summary>
@@ -454,14 +561,29 @@ private void AddScopedAttribute(string key, string value)
454561 {
455562 return ;
456563 }
457- var attributesJson = PlayerPrefs . GetString ( ScopedAttributeListKey ) ;
458- var attributes = HasScopedAttributesEmpty ( attributesJson )
459- ? JsonUtility . FromJson < ScopedAttributesContainer > ( attributesJson )
460- : new ScopedAttributesContainer ( ) ;
564+ if ( string . IsNullOrEmpty ( key ) )
565+ {
566+ return ;
567+ }
568+
569+ var normalized = value ?? string . Empty ;
570+
571+ // skip when unchanged
572+ var current = GetScopedValue ( key ) ;
573+ if ( current != null && string . Equals ( current , normalized , StringComparison . Ordinal ) )
574+ {
575+ return ;
576+ }
577+
578+ SetScopedValue ( key , normalized ) ;
579+
580+ var keys = LoadScopedKeys ( ) ;
581+ if ( keys . Add ( key ) )
582+ {
583+ SaveScopedKeys ( keys ) ;
584+ }
461585
462- attributes . Keys . Add ( key ) ;
463- PlayerPrefs . SetString ( ScopedAttributeListKey , JsonUtility . ToJson ( attributes ) ) ;
464- PlayerPrefs . SetString ( string . Format ( ScopedAttributesPattern , key ) , value ) ;
586+ PlayerPrefs . Save ( ) ;
465587 }
466588
467589 private static bool HasScopedAttributesEmpty ( string attributesJson )
0 commit comments