com.amanotes.gdk 0.2.85 → 0.2.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/Editor/Extra/GDKCIBuildCommand.cs +19 -2
  3. package/Editor/Extra/GDKLocalBuild.cs +25 -1
  4. package/Editor/Extra/SDKVersionTracking.cs +9 -1
  5. package/Editor/Utils/AmaGDKEditor.ExtractVersion.Max.cs +120 -0
  6. package/Editor/Utils/AmaGDKEditor.ExtractVersion.Max.cs.meta +3 -0
  7. package/Editor/Utils/AmaGDKEditor.ExtractVersion.cs +8 -10
  8. package/Extra/AmaGDKInstaller.unitypackage +0 -0
  9. package/Extra/AutoEventQC.unitypackage +0 -0
  10. package/Extra/CheckDiskSpace.unitypackage +0 -0
  11. package/Extra/Consent.unitypackage +0 -0
  12. package/Extra/CrashlyticHook.unitypackage +0 -0
  13. package/Extra/ForceUpdate.unitypackage +0 -0
  14. package/Extra/LegacyGDKUpdateHelper.unitypackage +0 -0
  15. package/Extra/PostProcessor.unitypackage +0 -0
  16. package/Packages/AmaGDKConfig.unitypackage +0 -0
  17. package/Packages/AmaGDKExample.unitypackage +0 -0
  18. package/Packages/AmaGDKTest.unitypackage +0 -0
  19. package/Packages/AppsFlyerAdapter.PurchaseConnector.unitypackage +0 -0
  20. package/Packages/AppsFlyerAdapter.unitypackage +0 -0
  21. package/Packages/FirebaseAnalyticsAdapter.unitypackage +0 -0
  22. package/Packages/FirebaseRemoteConfigAdapter.unitypackage +0 -0
  23. package/Packages/IronSourceAdapter.AdQuality.unitypackage +0 -0
  24. package/Packages/IronSourceAdapter.unitypackage +0 -0
  25. package/Packages/MaxAdNetworkAdapter.unitypackage +0 -0
  26. package/Packages/RevenueCatAdapter.unitypackage +0 -0
  27. package/Packages/SqliteAnalyticsAdapter.unitypackage +0 -0
  28. package/Runtime/Ad/AdLogic.cs +230 -148
  29. package/Runtime/AmaGDK.Adapters.cs +2 -0
  30. package/Runtime/AmaGDK.Analytics.cs +140 -78
  31. package/Runtime/AmaGDK.RemoteConfig.cs +50 -40
  32. package/Runtime/AmaGDK.Singleton.cs +2 -0
  33. package/Runtime/AmaGDK.cs +3 -1
  34. package/Runtime/AnalyticQualityAsset.cs +69 -38
  35. package/Runtime/AudioToolkit/Plugins/AudioDeviceDetector.cs +31 -17
  36. package/Runtime/Core/GDKDispatcher.cs +4 -2
  37. package/Runtime/Core/GDKSemVer.cs +23 -26
  38. package/Runtime/Fps/AmaFPSVisualizer.cs +48 -26
  39. package/Runtime/Internal/AmaGDK.Utils.cs +90 -46
  40. package/Runtime/Internal/ForceQuitMonitor.cs +43 -31
  41. package/Runtime/Klavar/Attributes/MinMaxAttribute.cs +12 -3
  42. package/Runtime/UI/ScrollView/GDKScrollView.cs +83 -48
  43. package/Runtime/Utils/GDKFileUtils.cs +3 -8
  44. package/Runtime/Utils/GDKUtils.cs +20 -8
  45. package/package.json +1 -1
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
3
3
  using System.Collections.Generic;
4
4
  using System.Text;
5
5
  using Amanotes.Core.Internal;
6
+ using System.Linq;
6
7
  using UnityEngine;
7
8
  using UnityEngine.Profiling;
8
9
  using static Amanotes.Core.AmaGDK.Analytics;
@@ -90,7 +91,7 @@ namespace Amanotes.Core
90
91
  return !allowAdapterIds.Contains(adapterId);
91
92
  }
92
93
 
93
- Debug.LogWarning("[AmaGDK] Should never be here: overrideConfig == true but no id in listAdapterIds!");
94
+ LogWarning("[AmaGDK] Should never be here: overrideConfig == true but no id in listAdapterIds!");
94
95
  return false;
95
96
  }
96
97
 
@@ -106,8 +107,14 @@ namespace Amanotes.Core
106
107
 
107
108
  public static IEventParamsBuilder LogEvent(string eventName)
108
109
  {
110
+ if (listAdapters.Count == 0)
111
+ {
112
+ InitModule();
113
+ }
114
+
109
115
  var result = PrepareEvent(eventName);
110
116
  eventQueue.Enqueue(result);
117
+ Log($"Queued Event <{result.eventName}>");
111
118
  return result;
112
119
  }
113
120
 
@@ -364,6 +371,7 @@ namespace Amanotes.Core
364
371
  {
365
372
  sessionStat = new SessionStat();
366
373
  }
374
+
367
375
  listAdapters.Clear();
368
376
  listAdapters.AddRange(Adapter2.GetAllAdapter<AnalyticsAdapter>());
369
377
  }
@@ -448,35 +456,53 @@ namespace Amanotes.Core
448
456
  string eventNameToSend = GetEventNameToSend(eventParams.eventName, countInSession, totalCount);
449
457
  AnalyticsConfig config = Config.analytics;
450
458
  if (config.normalizeEventName) NormalizeEventName(ref eventNameToSend);
451
-
452
- if (config.showAnalyticsLog)
453
- {
454
- ForceLog($"[Analytics] Event <{eventNameToSend}> (in session: {countInSession}, total: {totalCount})\n{JsonUtils.DictionaryToJson(eventParams.parameters, true)}");
455
- }
456
-
457
459
  config.quality.CheckEventQuality(eventParams.eventName, eventParams.parameters);
458
460
 
459
- var inUseAdapterIDs = config.enableEventStat ? new List<string>(listAdapters.Count) : null;
461
+ List<string> inUseAdapterIDs = config.enableEventStat ? new List<string>(listAdapters.Count) : null;
462
+ bool effective = ProcessAdapters(eventParams, eventNameToSend, inUseAdapterIDs);
463
+ HandleEventResult(effective, config, eventNameToSend, countInSession, totalCount, eventParams, inUseAdapterIDs);
464
+ }
465
+
466
+ private static bool ProcessAdapters(EventParams eventParams, string eventNameToSend, List<string> inUseAdapterIDs)
467
+ {
468
+ var effective = false;
460
469
  foreach (AnalyticsAdapter m in listAdapters)
461
470
  {
462
- if (eventParams.overrideConfig)
463
- {
464
- // IMPORTANT NOTE:
465
- //
466
- // Once set overrideConfig, we should skip the adapter config completely!!!
467
- //
468
- if (eventParams.IsAdapterForbidden(m.adapterId)) continue;
469
- }
470
- else // Do not remove else
471
- {
472
- if (m.IsForbidden(eventParams.eventName)) continue;
473
- }
471
+ if (ShouldSkipAdapter(eventParams, m)) continue;
472
+
473
+ effective = true;
474
474
  m.LogEvent(eventNameToSend, eventParams.parameters);
475
475
  inUseAdapterIDs?.Add(m.adapterId);
476
476
  }
477
-
478
- GDKUtils.SafeInvoke(OnEventSent, eventNameToSend, eventParams.parameters);
479
- sessionStat?.OnEventSent(eventNameToSend, eventParams.parameters, inUseAdapterIDs);
477
+ return effective;
478
+ }
479
+
480
+ private static bool ShouldSkipAdapter(EventParams eventParams, AnalyticsAdapter adapter)
481
+ {
482
+ // IMPORTANT NOTE:
483
+ //
484
+ // Once set overrideConfig, we should skip the adapter config completely!!!
485
+ //
486
+ return eventParams.overrideConfig
487
+ ? eventParams.IsAdapterForbidden(adapter.adapterId)
488
+ : adapter.IsForbidden(eventParams.eventName);
489
+ }
490
+
491
+ private static void HandleEventResult(bool effective, AnalyticsConfig config, string eventNameToSend, int countInSession, int totalCount, EventParams eventParams, List<string> inUseAdapterIDs)
492
+ {
493
+ if (effective)
494
+ {
495
+ if (config.showAnalyticsLog)
496
+ {
497
+ ForceLog($"[Analytics] Event <{eventNameToSend}> (in session: {countInSession}, total: {totalCount})\n{JsonUtils.DictionaryToJson(eventParams.parameters, true)}");
498
+ }
499
+ GDKUtils.SafeInvoke(OnEventSent, eventNameToSend, eventParams.parameters);
500
+ sessionStat?.OnEventSent(eventNameToSend, eventParams.parameters, inUseAdapterIDs);
501
+ }
502
+ else
503
+ {
504
+ LogError($"Event <{eventParams}> has been skipped (no matching Adapter)\n{JsonUtility.ToJson(eventParams)}");
505
+ }
480
506
  }
481
507
 
482
508
  internal static void LogEventImmediately(EventParams eventParams)
@@ -551,63 +577,89 @@ namespace Amanotes.Core
551
577
  var hasChanged = false;
552
578
  var trimKey = key.Trim();
553
579
 
554
- foreach (var c in trimKey)
555
- {
556
- var isLetter = IsAlphabet(c);
557
- var isNumber = char.IsDigit(c);
558
-
559
- if (c == '_' || isLetter || isNumber)
560
- {
561
- tempKey.Append(c);
562
- continue;
563
- }
564
-
565
- hasChanged = true;
566
- if (c == ' ' || c == '-')
567
- {
568
- tempKey.Append('_');
569
- }
570
- }
571
-
572
- if (hasChanged && willLog)
573
- {
574
- tempLog.Append(" - Name may only contain alphanumeric characters and underscores");
575
- }
580
+ hasChanged = ProcessCharacters(trimKey, tempKey, tempLog, willLog, hasChanged);
581
+ hasChanged = EnsureValidStart(tempKey, tempLog, willLog, hasChanged, key);
582
+ hasChanged = HandleReservedPrefixes(tempKey, tempLog, willLog, hasChanged, key);
583
+ hasChanged = ValidateLength(trimKey, tempKey, tempLog, willLog, hasChanged, key);
576
584
 
577
- if (tempKey.Length > 0 && !IsAlphabet(tempKey[0]))
578
- {
579
- hasChanged = true;
580
- tempKey.Insert(0, 'E');
581
- if (willLog) tempLog.Append($" - Name must start with a letter: <{key}>");
582
- }
585
+ ApplyChanges(dryRun, warningLog, tempLog, hasChanged, ref key, tempKey);
583
586
 
584
- foreach (var prefix in reservedPrefixes)
585
- {
586
- if (tempKey.Length < prefix.Key.Length || !StringStartsWith(tempKey, prefix.Key)) continue;
587
- hasChanged = true;
588
- tempKey.Remove(0, prefix.Key.Length);
589
- tempKey.Insert(0, prefix.Value);
590
- if (willLog) tempLog.AppendLine($" - <{key}> starts with reserved prefix <{prefix}>");
591
- }
587
+ GDKPool.Return(tempLog);
588
+ GDKPool.Return(tempKey);
589
+ }
590
+ Profiler.EndSample();
591
+ }
592
592
 
593
- if (trimKey.Length > 40)
593
+ private static bool ProcessCharacters(string trimKey, StringBuilder tempKey, StringBuilder tempLog, bool willLog, bool hasChanged)
594
+ {
595
+ foreach (var c in trimKey)
596
+ {
597
+ var isLetter = IsAlphabet(c);
598
+ var isNumber = char.IsDigit(c);
599
+
600
+ if (c == '_' || isLetter || isNumber)
594
601
  {
595
- hasChanged = true;
596
- if (willLog) tempLog.AppendLine($" - Name is too long ({key.Length} > 40): <{key}> ");
597
- tempKey.Length = 40;
602
+ tempKey.Append(c);
603
+ continue;
598
604
  }
599
605
 
600
- if (dryRun)
606
+ hasChanged = true;
607
+ if (c == ' ' || c == '-')
601
608
  {
602
- warningLog.Append(tempLog);
603
- } else {
604
- if (hasChanged) key = tempKey.ToString();
609
+ tempKey.Append('_');
605
610
  }
606
-
607
- GDKPool.Return(tempLog);
608
- GDKPool.Return(tempKey);
609
611
  }
610
- Profiler.EndSample();
612
+
613
+ if (hasChanged && willLog)
614
+ {
615
+ tempLog.Append(" - Name may only contain alphanumeric characters and underscores");
616
+ }
617
+ return hasChanged;
618
+ }
619
+
620
+ private static bool EnsureValidStart(StringBuilder tempKey, StringBuilder tempLog, bool willLog, bool hasChanged, string key)
621
+ {
622
+ if (tempKey.Length > 0 && !IsAlphabet(tempKey[0]))
623
+ {
624
+ hasChanged = true;
625
+ tempKey.Insert(0, 'E');
626
+ if (willLog) tempLog.Append($" - Name must start with a letter: <{key}>");
627
+ }
628
+ return hasChanged;
629
+ }
630
+
631
+ private static bool HandleReservedPrefixes(StringBuilder tempKey, StringBuilder tempLog, bool willLog, bool hasChanged, string key)
632
+ {
633
+ foreach (var prefix in reservedPrefixes)
634
+ {
635
+ if (tempKey.Length < prefix.Key.Length || !StringStartsWith(tempKey, prefix.Key)) continue;
636
+ hasChanged = true;
637
+ tempKey.Remove(0, prefix.Key.Length);
638
+ tempKey.Insert(0, prefix.Value);
639
+ if (willLog) tempLog.AppendLine($" - <{key}> starts with reserved prefix <{prefix}>");
640
+ }
641
+ return hasChanged;
642
+ }
643
+
644
+ private static bool ValidateLength(string trimKey, StringBuilder tempKey, StringBuilder tempLog, bool willLog, bool hasChanged, string key)
645
+ {
646
+ if (trimKey.Length > 40)
647
+ {
648
+ hasChanged = true;
649
+ if (willLog) tempLog.AppendLine($" - Name is too long ({key.Length} > 40): <{key}> ");
650
+ tempKey.Length = 40;
651
+ }
652
+ return hasChanged;
653
+ }
654
+
655
+ private static void ApplyChanges(bool dryRun, StringBuilder warningLog, StringBuilder tempLog, bool hasChanged, ref string key, StringBuilder tempKey)
656
+ {
657
+ if (dryRun)
658
+ {
659
+ warningLog.Append(tempLog);
660
+ } else {
661
+ if (hasChanged) key = tempKey.ToString();
662
+ }
611
663
  }
612
664
 
613
665
  private static bool StringStartsWith(StringBuilder stringBuilder, string prefix)
@@ -777,7 +829,7 @@ namespace Amanotes.Core
777
829
  {
778
830
  if (cachedEventDetails.ContainsKey(ed.eventName))
779
831
  {
780
- Debug.LogWarning($"EventName <{ed.eventName}> existed!");
832
+ LogWarning($"EventName <{ed.eventName}> existed!");
781
833
  return false;
782
834
  }
783
835
 
@@ -1039,18 +1091,28 @@ namespace Amanotes.Core
1039
1091
  LogWarning("OnlyTo() can only be set once!");
1040
1092
  return ap;
1041
1093
  }
1042
-
1043
- eventData.overrideConfig = true;
1044
- eventData.allowAdapterIds ??= GDKPool.GetHashSet();
1045
1094
 
1095
+ var h = GDKPool.GetHashSet();
1046
1096
  foreach (string mId in analyticIds)
1047
1097
  {
1048
- if (!listAdapters.Exists(a => a.adapterId == mId))
1098
+ if (listAdapters.Count > 0 && !listAdapters.Exists(a => a.adapterId == mId))
1049
1099
  {
1050
- LogError($"Cannot send event <{eventData.eventName}> to <{mId}>. Analytics adapter for <{mId}> is not available.");
1100
+ LogError($"Cannot send event <{eventData.eventName}> to <{mId}>. Analytics adapter for <{mId}> is not available.\n" +
1101
+ $"listAdapters ({listAdapters.Count}): [{string.Join(",", listAdapters.Select(item=>item.adapterId).ToArray())}]");
1051
1102
  continue;
1052
1103
  }
1053
- eventData.allowAdapterIds.Add(mId);
1104
+ h.Add(mId);
1105
+ }
1106
+
1107
+ eventData.overrideConfig = true;
1108
+ if (h.Count > 0)
1109
+ {
1110
+ eventData.allowAdapterIds ??= h;
1111
+ }
1112
+ else
1113
+ {
1114
+ // No valid adapters found, create empty set to ensure event is skipped
1115
+ eventData.allowAdapterIds ??= GDKPool.GetHashSet();
1054
1116
  }
1055
1117
 
1056
1118
  return ap;
@@ -259,43 +259,9 @@ namespace Amanotes.Core
259
259
  try
260
260
  {
261
261
  DictConfig.Clear();
262
- using (StreamReader sr = new StreamReader(GDKFileUtils.GetPath(fileName)))
263
- {
264
- //Check new app update
265
- string firstLine = sr.ReadLine();
266
- if (firstLine == null)
267
- {
268
- NextState(false);
269
- return;
270
- }
271
- if (ShouldClearCache(firstLine))
272
- {
273
- GDKFileUtils.Delete(fileName);
274
- string oldVersion = firstLine.StartsWith(VERSION_PREFIX) ? firstLine : "<none>";
275
- Log($"[RemoteConfig] Cached remote config cleared due to embed version changed:\n{oldVersion} -> {EmbedVersion}");
276
- NextState(false);
277
- return;
278
- }
279
-
280
- //Read key-value
281
- string line;
282
- int lineNo = 1;
283
- while ((line = sr.ReadLine()) != null)
284
- {
285
- lineNo++;
286
- var idx = line.IndexOf('\t');
287
- if (idx == -1)
288
- {
289
- LogWarning($"[RemoteConfig] Cannot get (key, value) at line {lineNo} of cache. Content:\n<{line}>");
290
- continue;
291
- }
292
- string key = line.Substring(0, idx);
293
- string value = line.Substring(idx + 1);
294
- DictConfig.Add(key, Decode(value));
295
- }
296
- }
297
- Source = RemoteConfigSource.LocalCache;
298
- Log("[RemoteConfig] Load from cache");
262
+ bool success = ReadCacheFile();
263
+ Profiler.EndSample();
264
+ NextState(success);
299
265
  }
300
266
  catch (Exception ex)
301
267
  {
@@ -303,11 +269,55 @@ namespace Amanotes.Core
303
269
  _cacheExisted = false;
304
270
  Profiler.EndSample();
305
271
  NextState(false);
306
- return;
307
272
  }
273
+ }
308
274
 
309
- Profiler.EndSample();
310
- NextState(true);
275
+ static bool ReadCacheFile()
276
+ {
277
+ using (StreamReader sr = new StreamReader(GDKFileUtils.GetPath(fileName)))
278
+ {
279
+ string firstLine = sr.ReadLine();
280
+ if (firstLine == null) return false;
281
+
282
+ if (!ValidateCacheVersion(firstLine)) return false;
283
+
284
+ ReadConfigKeyValues(sr);
285
+ }
286
+
287
+ Source = RemoteConfigSource.LocalCache;
288
+ Log("[RemoteConfig] Load from cache");
289
+ return true;
290
+ }
291
+
292
+ static bool ValidateCacheVersion(string firstLine)
293
+ {
294
+ if (ShouldClearCache(firstLine))
295
+ {
296
+ GDKFileUtils.Delete(fileName);
297
+ string oldVersion = firstLine.StartsWith(VERSION_PREFIX) ? firstLine : "<none>";
298
+ Log($"[RemoteConfig] Cached remote config cleared due to embed version changed:\n{oldVersion} -> {EmbedVersion}");
299
+ return false;
300
+ }
301
+ return true;
302
+ }
303
+
304
+ static void ReadConfigKeyValues(StreamReader sr)
305
+ {
306
+ string line;
307
+ int lineNo = 1;
308
+ while ((line = sr.ReadLine()) != null)
309
+ {
310
+ lineNo++;
311
+ var idx = line.IndexOf('\t');
312
+ if (idx == -1)
313
+ {
314
+ LogWarning($"[RemoteConfig] Cannot get (key, value) at line {lineNo} of cache. Content:\n<{line}>");
315
+ continue;
316
+ }
317
+ string key = line.Substring(0, idx);
318
+ string value = line.Substring(idx + 1);
319
+ DictConfig.Add(key, Decode(value));
320
+ }
311
321
  }
312
322
 
313
323
  static void LoadEmbedConfig()
@@ -12,6 +12,8 @@ namespace Amanotes.Core
12
12
 
13
13
  private void Awake()
14
14
  {
15
+ Log("Awake()");
16
+
15
17
  if (_instance != null)
16
18
  {
17
19
  LogWarning(MULTIPLE_INSTANCE);
package/Runtime/AmaGDK.cs CHANGED
@@ -27,7 +27,7 @@ namespace Amanotes.Core
27
27
  {
28
28
  public partial class AmaGDK : MonoBehaviour
29
29
  {
30
- public const string VERSION = "0.2.85";
30
+ public const string VERSION = "0.2.87";
31
31
 
32
32
  internal static Status _status = Status.None;
33
33
  internal static ConfigAsset _config = null;
@@ -200,6 +200,8 @@ namespace Amanotes.Core
200
200
 
201
201
  IEnumerator InitRoutine()
202
202
  {
203
+ Log("InitRoutine!");
204
+
203
205
  _allowInit = _allowInit || autoInit;
204
206
 
205
207
  if (Config == null)
@@ -54,31 +54,27 @@ namespace Amanotes.Core
54
54
  }
55
55
  }
56
56
 
57
+ private static readonly HashSet<Type> NumericTypes = new HashSet<Type>
58
+ {
59
+ typeof(int), typeof(long), typeof(short), typeof(byte),
60
+ typeof(double), typeof(float), typeof(decimal),
61
+ typeof(uint), typeof(ulong), typeof(ushort), typeof(sbyte)
62
+ };
63
+
57
64
  private bool IsNumericType(Type type)
58
65
  {
59
- return type == typeof(int) ||
60
- type == typeof(long) ||
61
- type == typeof(short) ||
62
- type == typeof(byte) ||
63
- type == typeof(double) ||
64
- type == typeof(float) ||
65
- type == typeof(decimal) ||
66
- type == typeof(uint) ||
67
- type == typeof(ulong) ||
68
- type == typeof(ushort) ||
69
- type == typeof(sbyte);
66
+ return NumericTypes.Contains(type);
70
67
  }
71
68
 
69
+ private static readonly HashSet<Type> IntegerTypes = new HashSet<Type>
70
+ {
71
+ typeof(sbyte), typeof(byte), typeof(short), typeof(ushort),
72
+ typeof(int), typeof(uint), typeof(long), typeof(ulong)
73
+ };
74
+
72
75
  private bool IsIntegerType(Type type)
73
76
  {
74
- return type == typeof(sbyte) ||
75
- type == typeof(byte) ||
76
- type == typeof(short) ||
77
- type == typeof(ushort) ||
78
- type == typeof(int) ||
79
- type == typeof(uint) ||
80
- type == typeof(long) ||
81
- type == typeof(ulong);
77
+ return IntegerTypes.Contains(type);
82
78
  }
83
79
 
84
80
  internal void ValidateParams(string eventName, Dictionary<string, object> eventParams)
@@ -89,6 +85,19 @@ namespace Amanotes.Core
89
85
  var isValid = true;
90
86
  HashSet<string> checkedParameters = GDKPool.GetHashSet();
91
87
 
88
+ isValid &= ValidateRequiredParams(eventParams, logBuilder, checkedParameters);
89
+ isValid &= ValidateExtraParams(eventParams, logBuilder, checkedParameters);
90
+
91
+ if (!isValid) LogWarningOnce(logBuilder.ToString());
92
+
93
+ GDKPool.Return(logBuilder);
94
+ GDKPool.Return(checkedParameters);
95
+ }
96
+
97
+ private bool ValidateRequiredParams(Dictionary<string, object> eventParams, StringBuilder logBuilder, HashSet<string> checkedParameters)
98
+ {
99
+ bool isValid = true;
100
+
92
101
  foreach (var kvp in DicParam)
93
102
  {
94
103
  if (!eventParams.TryGetValue(kvp.Key, out object paramValue))
@@ -109,7 +118,14 @@ namespace Amanotes.Core
109
118
 
110
119
  checkedParameters.Add(kvp.Key);
111
120
  }
121
+
122
+ return isValid;
123
+ }
112
124
 
125
+ private bool ValidateExtraParams(Dictionary<string, object> eventParams, StringBuilder logBuilder, HashSet<string> checkedParameters)
126
+ {
127
+ bool isValid = true;
128
+
113
129
  foreach (var parameter in eventParams)
114
130
  {
115
131
  if (checkedParameters.Contains(parameter.Key)) continue;
@@ -118,10 +134,7 @@ namespace Amanotes.Core
118
134
  logBuilder.AppendLine($"[+] {parameter.Key}");
119
135
  }
120
136
 
121
- if (!isValid) LogWarningOnce(logBuilder.ToString());
122
-
123
- GDKPool.Return(logBuilder);
124
- GDKPool.Return(checkedParameters);
137
+ return isValid;
125
138
  }
126
139
 
127
140
  private bool ValidateParamType(object paramValue, ParamType paramType)
@@ -188,26 +201,44 @@ namespace Amanotes.Core
188
201
 
189
202
  foreach (EventQuality eventQuality in lstEvent)
190
203
  {
191
- if (string.IsNullOrWhiteSpace(eventQuality.eventName)) Debug.LogWarning("Event name cannot be empty or whitespace.");
204
+ ValidateEventName(eventQuality.eventName, eventNames);
205
+ ValidateEventParameters(eventQuality);
206
+ }
207
+
208
+ GDKPool.Return(eventNames);
209
+ EditorUtility.SetDirty(this);
210
+ }
192
211
 
193
- if (!eventNames.Add(eventQuality.eventName)) Debug.LogWarning($"Duplicate event name found: {eventQuality.eventName}. Event names must be unique.");
212
+ private void ValidateEventName(string eventName, HashSet<string> eventNames)
213
+ {
214
+ if (string.IsNullOrWhiteSpace(eventName))
215
+ Debug.LogWarning("Event name cannot be empty or whitespace.");
194
216
 
195
- HashSet<string> paramNames = GDKPool.GetHashSet();
196
-
197
- eventQuality.lstParam = eventQuality.lstParam.OrderBy(param => param.paramName).ToList();
198
-
199
- foreach (var param in eventQuality.lstParam)
200
- {
201
- if (string.IsNullOrWhiteSpace(param.paramName)) Debug.LogWarning("Event parameter name cannot be empty or whitespace.");
217
+ if (!eventNames.Add(eventName))
218
+ Debug.LogWarning($"Duplicate event name found: {eventName}. Event names must be unique.");
219
+ }
202
220
 
203
- if (!paramNames.Add(param.paramName)) Debug.LogWarning($"Duplicate parameter name found in event {eventQuality.eventName}: {param.paramName}. Parameter names must be unique within an event.");
204
- }
205
-
206
- GDKPool.Return(paramNames);
221
+ private void ValidateEventParameters(EventQuality eventQuality)
222
+ {
223
+ HashSet<string> paramNames = GDKPool.GetHashSet();
224
+
225
+ eventQuality.lstParam = eventQuality.lstParam.OrderBy(param => param.paramName).ToList();
226
+
227
+ foreach (var param in eventQuality.lstParam)
228
+ {
229
+ ValidateParameterName(param.paramName, eventQuality.eventName, paramNames);
207
230
  }
208
231
 
209
- GDKPool.Return(eventNames);
210
- EditorUtility.SetDirty(this);
232
+ GDKPool.Return(paramNames);
233
+ }
234
+
235
+ private void ValidateParameterName(string paramName, string eventName, HashSet<string> paramNames)
236
+ {
237
+ if (string.IsNullOrWhiteSpace(paramName))
238
+ Debug.LogWarning("Event parameter name cannot be empty or whitespace.");
239
+
240
+ if (!paramNames.Add(paramName))
241
+ Debug.LogWarning($"Duplicate parameter name found in event {eventName}: {paramName}. Parameter names must be unique within an event.");
211
242
  }
212
243
  #endif
213
244
 
@@ -162,10 +162,7 @@ namespace Amanotes.Core
162
162
 
163
163
  private static string RemoveSurrogatePairs(string str, string replacementCharacter = "?")
164
164
  {
165
- if (str == null)
166
- {
167
- return null;
168
- }
165
+ if (str == null) return null;
169
166
 
170
167
  StringBuilder sb = null;
171
168
  for (var i = 0; i < str.Length; i++)
@@ -174,19 +171,8 @@ namespace Amanotes.Core
174
171
 
175
172
  if (char.IsSurrogate(ch))
176
173
  {
177
- if (sb == null)
178
- {
179
- sb = GDKPool.GetStringBuilder();
180
- sb.Append(str, 0, i);
181
- }
182
-
183
- sb.Append(replacementCharacter);
184
-
185
- // If there is a high+low surrogate, skip the low surrogate
186
- if (i + 1 < str.Length && char.IsHighSurrogate(ch) && char.IsLowSurrogate(str[i + 1]))
187
- {
188
- i++;
189
- }
174
+ sb = HandleSurrogateCharacter(str, i, sb, replacementCharacter, out int skipCount);
175
+ i += skipCount;
190
176
  }
191
177
  else
192
178
  {
@@ -196,6 +182,34 @@ namespace Amanotes.Core
196
182
 
197
183
  return sb == null ? str : GDKPool.ReturnString(sb);
198
184
  }
185
+
186
+ private static StringBuilder HandleSurrogateCharacter(string str, int index, StringBuilder sb, string replacementCharacter, out int skipCount)
187
+ {
188
+ skipCount = 0;
189
+
190
+ if (sb == null)
191
+ {
192
+ sb = GDKPool.GetStringBuilder();
193
+ sb.Append(str, 0, index);
194
+ }
195
+
196
+ sb.Append(replacementCharacter);
197
+
198
+ // If there is a high+low surrogate, skip the low surrogate
199
+ if (ShouldSkipLowSurrogate(str, index))
200
+ {
201
+ skipCount = 1;
202
+ }
203
+
204
+ return sb;
205
+ }
206
+
207
+ private static bool ShouldSkipLowSurrogate(string str, int index)
208
+ {
209
+ return index + 1 < str.Length &&
210
+ char.IsHighSurrogate(str[index]) &&
211
+ char.IsLowSurrogate(str[index + 1]);
212
+ }
199
213
  }
200
214
 
201
215
  internal enum AudioDeviceType
@@ -44,9 +44,11 @@ namespace Amanotes.Core.Internal
44
44
  return false;
45
45
  }
46
46
 
47
- if (args.Length != nParams)
47
+ // Fix: https://amanotes.sentry.io/issues/6366288384/events/d45dd8fb736f43d0a8e2aec0df0f173d/
48
+ int argsLength = args != null ? args.Length : 0;
49
+ if (argsLength != nParams)
48
50
  {
49
- GDKUtils.ThrowExceptionInEditor($"Dispatch <{eventName}> with incorrect number of params, expecting {nParams}, got {args.Length}!",
51
+ GDKUtils.ThrowExceptionInEditor($"Dispatch <{eventName}> with incorrect number of params, expecting {nParams}, got {argsLength}!",
50
52
  (m) => new TargetParameterCountException(m));
51
53
  return false;
52
54
  }