com.amanotes.gdk 0.2.50 → 0.2.52

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.
@@ -1,4 +1,5 @@
1
1
  using System;
2
+ using System.Collections.Concurrent;
2
3
  using System.Collections.Generic;
3
4
  using System.Text;
4
5
  using System.Text.RegularExpressions;
@@ -21,37 +22,52 @@ namespace Amanotes.Core
21
22
 
22
23
  public class EventParams : IEventParamsBuilder
23
24
  {
24
- public readonly string eventName;
25
- public Dictionary<string, object> parameters = new Dictionary<string, object>();
25
+ public string eventName { get; private set; }
26
+ public readonly Dictionary<string, object> parameters = new Dictionary<string, object>();
26
27
  public string funnelSignature;
27
28
  public bool accumulated;
28
29
  public bool withinSession;
29
30
  public bool doNotIncreaseCounter;
30
31
 
31
32
  public bool overrideConfig;
32
- public HashSet<string> ignoreAdapterIds = new HashSet<string>();
33
- public HashSet<string> allowAdapterIds = new HashSet<string>();
33
+ public HashSet<string> ignoreAdapterIds = null;
34
+ public HashSet<string> allowAdapterIds = null;
34
35
 
35
- public EventDetail EventDetail => localData.GetEventDetail(eventName, true);
36
-
37
- public EventParams(string eventName)
36
+ private EventDetail _detail;
37
+ public EventDetail EventDetail
38
38
  {
39
- this.eventName = eventName;
39
+ get
40
+ {
41
+ if (_detail != null) return _detail;
42
+ return _detail = localData.GetEventDetail(eventName, true);
43
+ }
40
44
  }
41
45
 
42
- public bool IsFunnelEvent
46
+ internal EventParams Init(string eventName)
43
47
  {
44
- get { return !string.IsNullOrEmpty(funnelSignature); }
48
+ this.eventName = eventName;
49
+ parameters.Clear();
50
+ funnelSignature = "";
51
+ accumulated = false;
52
+ withinSession = false;
53
+ doNotIncreaseCounter = false;
54
+ overrideConfig = false;
55
+ ignoreAdapterIds = null;
56
+ allowAdapterIds = null;
57
+ _detail = null;
58
+ return this;
45
59
  }
46
60
 
61
+ public bool IsFunnelEvent => !string.IsNullOrEmpty(funnelSignature);
62
+
47
63
  public bool IsAdapterForbidden(string adapterId)
48
64
  {
49
- if (ignoreAdapterIds.Count > 0)
65
+ if (ignoreAdapterIds != null && ignoreAdapterIds.Count > 0)
50
66
  {
51
67
  return ignoreAdapterIds.Contains(adapterId);
52
68
  }
53
69
 
54
- if (allowAdapterIds.Count > 0)
70
+ if (allowAdapterIds != null && allowAdapterIds.Count > 0)
55
71
  {
56
72
  return !allowAdapterIds.Contains(adapterId);
57
73
  }
@@ -71,31 +87,22 @@ namespace Amanotes.Core
71
87
 
72
88
  public static IEventParamsBuilder LogEvent(string eventName)
73
89
  {
74
- NormalizeEventName(ref eventName, true);
75
- if (localData.GetEventDetail(eventName) == null && localData.eventDetails.Count >= 500)
76
- {
77
- LogWarning($" - You have reported 500 types of events. New event <{eventName}> will be ignored.");
78
- return null;
79
- }
80
- var result = new EventParams(eventName);
90
+ var result = PrepareEvent(eventName);
81
91
  eventQueue.Enqueue(result);
82
92
  return result;
83
93
  }
84
94
 
85
95
  public static IEventParamsBuilder LogEvent(string eventName, Dictionary<string, object> parameters)
86
96
  {
87
- if (parameters == null) return LogEvent(eventName);
88
- return LogEvent(eventName).AddParam(parameters);
97
+ var result = PrepareEvent(eventName, parameters);
98
+ eventQueue.Enqueue(result);
99
+ return result;
89
100
  }
90
101
 
91
102
  public static IEventParamsBuilder LogEvent(string eventName, params (string, object)[] parameters)
92
103
  {
93
- IEventParamsBuilder result = LogEvent(eventName);
94
- if (parameters == null) return result;
95
- for (var i = 0; i < parameters.Length; i++)
96
- {
97
- result.AddParam(parameters[i].Item1, parameters[i].Item2);
98
- }
104
+ var result = PrepareEvent(eventName, parameters);
105
+ eventQueue.Enqueue(result);
99
106
  return result;
100
107
  }
101
108
 
@@ -113,6 +120,24 @@ namespace Amanotes.Core
113
120
  {
114
121
  return LogEvent(eventName, parameters).SetAsFunnel(signature);
115
122
  }
123
+
124
+ public static void LogEventImmediately(string eventName)
125
+ {
126
+ var eventParams = PrepareEvent(eventName);
127
+ LogEventImmediately(eventParams);
128
+ }
129
+
130
+ public static void LogEventImmediately(string eventName, Dictionary<string, object> parameters)
131
+ {
132
+ var eventParams = PrepareEvent(eventName, parameters);
133
+ LogEventImmediately(eventParams);
134
+ }
135
+
136
+ public static void LogEventImmediately(string eventName, params (string, object)[] parameters)
137
+ {
138
+ var eventParams = PrepareEvent(eventName, parameters);
139
+ LogEventImmediately(eventParams);
140
+ }
116
141
 
117
142
  public static int GetAccumulatedCount(string eventName, bool withinSession = false)
118
143
  {
@@ -128,12 +153,18 @@ namespace Amanotes.Core
128
153
 
129
154
  public static void SetUserId(string userId)
130
155
  {
131
- actionQueue.Enqueue(() => InternalSetUserId(userId));
156
+ foreach (AnalyticsAdapter s in listAdapters)
157
+ {
158
+ s.SetUserId(userId);
159
+ }
132
160
  }
133
161
 
134
162
  public static void SetUserProperty(string key, string value)
135
163
  {
136
- actionQueue.Enqueue(() => InternalSetUserProperty(key, value));
164
+ foreach (AnalyticsAdapter s in listAdapters)
165
+ {
166
+ s.SetUserProperty(key, value);
167
+ }
137
168
  }
138
169
 
139
170
  private static IntegrityFlag MergeData(List<string> funnelEvents, Dictionary<string, int> eventCountMap, bool sumEventCount)
@@ -151,12 +182,13 @@ namespace Amanotes.Core
151
182
  }
152
183
 
153
184
  if (eventCountMap != null)
185
+ {
154
186
  foreach (KeyValuePair<string, int> e in eventCountMap)
155
187
  {
156
188
  EventDetail eventDetail = localData.GetEventDetail(e.Key);
157
189
  if (eventDetail == null)
158
190
  {
159
- localData.eventDetails.Add(new EventDetail
191
+ localData.AddEventDetail(new EventDetail
160
192
  {
161
193
  eventName = e.Key,
162
194
  totalCount = e.Value
@@ -175,6 +207,8 @@ namespace Amanotes.Core
175
207
  eventDetail.totalCount += e.Value;
176
208
  }
177
209
  }
210
+ }
211
+
178
212
 
179
213
  Profiler.EndSample();
180
214
  return flag;
@@ -237,11 +271,12 @@ namespace Amanotes.Core
237
271
 
238
272
  internal static readonly Dictionary<string, EventConfig> sendAtCount = new Dictionary<string, EventConfig>();
239
273
  internal static Predicate<EventParams> logEventHook;
240
- internal static readonly Queue<EventParams> eventQueue = new Queue<EventParams>();
241
- internal static readonly Queue<Action> actionQueue = new Queue<Action>();
274
+ internal static readonly ConcurrentQueue<EventParams> eventQueue = new ConcurrentQueue<EventParams>();
242
275
  internal static readonly List<AnalyticsAdapter> listAdapters = new List<AnalyticsAdapter>();
243
- internal static readonly AnalyticsData localData = new AnalyticsData().LoadJsonFromFile();
244
- internal static readonly SessionStat sessionStat = new SessionStat();
276
+ internal static readonly AnalyticsData localData = new AnalyticsData().LoadJsonFromFile().BuildCache();
277
+ internal static readonly SessionStat sessionStat = _config.analytics.enableEventStat ? new SessionStat() : null;
278
+ internal static readonly Pool<StringBuilder> sbPool = new Pool<StringBuilder>(3);
279
+ internal static readonly Pool<EventParams> epPool = new Pool<EventParams>(1);
245
280
 
246
281
  internal static readonly Dictionary<string, string> reservedPrefixes = new Dictionary<string, string>()
247
282
  {
@@ -298,68 +333,109 @@ namespace Amanotes.Core
298
333
  // TODO: Refactor Resolve Early Calls
299
334
  onFrameUpdate -= ProcessEventQueue;
300
335
  onFrameUpdate += ProcessEventQueue;
301
-
302
- onFrameUpdate -= ProcessActionQueue;
303
- onFrameUpdate += ProcessActionQueue;
304
336
  }
305
-
337
+
338
+ internal static EventParams PrepareEvent(string eventName)
339
+ {
340
+ PrepareEventCheck(eventName);
341
+ var result = epPool.Get().Init(eventName);
342
+ PostPrepareEventProcess(result);
343
+ return result;
344
+ }
345
+
346
+ internal static EventParams PrepareEvent(string eventName, Dictionary<string, object> parameters)
347
+ {
348
+ if (parameters == null) return PrepareEvent(eventName);
349
+ PrepareEventCheck(eventName);
350
+ var result = epPool.Get().Init(eventName);
351
+ result.AddParam(parameters);
352
+ PostPrepareEventProcess(result);
353
+ return result;
354
+ }
355
+
356
+ internal static EventParams PrepareEvent(string eventName, params (string, object)[] parameters)
357
+ {
358
+ if (parameters == null) return PrepareEvent(eventName);
359
+ PrepareEventCheck(eventName);
360
+ var result = epPool.Get().Init(eventName);
361
+ for (var i = 0; i < parameters.Length; i++)
362
+ {
363
+ result.AddParam(parameters[i].Item1, parameters[i].Item2);
364
+ }
365
+ PostPrepareEventProcess(result);
366
+ return result;
367
+ }
368
+
306
369
  internal static void ProcessEventQueue()
307
370
  {
308
371
  if (eventQueue.Count <= 0) return;
309
372
 
310
373
  Profiler.BeginSample("AmaGDK.Analytics.ProcessEventQueue()");
311
- AnalyticsConfig config = Config.analytics;
312
- bool showAnalyticsLog = config.showAnalyticsLog;
313
- sessionStat.ClearLastFrameEventInfo();
314
374
 
315
- while (eventQueue.Count > 0)
375
+ sessionStat?.Clear();
376
+
377
+ while (eventQueue.TryDequeue(out var eventData))
316
378
  {
317
- var eventData = eventQueue.Dequeue();
318
- if (!ShouldSendEvent(eventData)) continue;
319
-
320
- var (countInSession, totalCount) = localData.IncreaseCounter(eventData);
321
- if (eventData.accumulated || eventData.parameters.ContainsKey("accumulated_count"))
322
- {
323
- UpdateAccumulatedCount(eventData, countInSession, totalCount);
324
- }
379
+ var (eventNameToSend, inUseAdapterIDs) = LogEventActually(eventData);
380
+ sessionStat?.Add(eventNameToSend, eventData, inUseAdapterIDs);
381
+ epPool.Return(eventData);
382
+ }
325
383
 
326
- if (!eventData.parameters.ContainsKey("ama_device_id"))
327
- eventData.AddParam("ama_device_id", User.AmaDeviceId);
384
+ localData.SaveIfDirty();
385
+ sessionStat?.Save();
386
+ Profiler.EndSample();
387
+ }
388
+
389
+ internal static (string eventNameToSend, List<string> inUseAdapterIDs) LogEventActually(EventParams eventParams)
390
+ {
391
+ if (eventParams == null) return ("", null);
392
+ if (!ShouldSendEvent(eventParams)) return ("", null);
393
+ var (countInSession, totalCount) = localData.IncreaseCounter(eventParams);
394
+ if (eventParams.accumulated || eventParams.parameters.ContainsKey("accumulated_count"))
395
+ {
396
+ UpdateAccumulatedCount(eventParams, countInSession, totalCount);
397
+ }
398
+
399
+ string eventNameToSend = GetEventNameToSend(eventParams.eventName, countInSession, totalCount);
400
+ AnalyticsConfig config = Config.analytics;
401
+ if (config.normalizeEventName) NormalizeEventName(ref eventNameToSend);
402
+
403
+ if (config.showAnalyticsLog)
404
+ {
405
+ Debug.Log($"AmaGDK [Analytics] Event <{eventNameToSend}> (in session: {countInSession}, total: {totalCount})\n{JsonUtils.DictionaryToJson(eventParams.parameters, true)}");
406
+ }
328
407
 
329
- string eventNameToSend = GetEventNameToSend(eventData.eventName, countInSession, totalCount);
330
- if (config.normalizeEventName) NormalizeEventName(ref eventNameToSend);
408
+ config.quality.CheckEventQuality(eventParams.eventName, eventParams.parameters);
331
409
 
332
- if (showAnalyticsLog)
410
+ var inUseAdapterIDs = config.enableEventStat ? new List<string>(listAdapters.Count) : null;
411
+ foreach (AnalyticsAdapter m in listAdapters)
412
+ {
413
+ if (eventParams.overrideConfig)
333
414
  {
334
- Debug.Log($"AmaGDK [Analytics] Event <{eventNameToSend}> (in session: {countInSession}, total: {totalCount})\n{JsonUtils.DictionaryToJson(eventData.parameters, true)}");
415
+ // IMPORTANT NOTE:
416
+ //
417
+ // Once set overrideConfig, we should skip the adapter config completely!!!
418
+ //
419
+ if (eventParams.IsAdapterForbidden(m.adapterId)) continue;
335
420
  }
336
-
337
- config.quality.CheckEventQuality(eventData.eventName, eventData.parameters);
338
-
339
- var inUseAdapterIDs = new List<string>();
340
- foreach (AnalyticsAdapter m in listAdapters)
421
+ else // Do not remove else
341
422
  {
342
- if (eventData.overrideConfig)
343
- {
344
- // IMPORTANT NOTE:
345
- //
346
- // Once set overrideConfig, we should skip the adapter config completely!!!
347
- //
348
- if (eventData.IsAdapterForbidden(m.adapterId)) continue;
349
- }
350
- else // Do not remove else
351
- {
352
- if (m.IsForbidden(eventData.eventName)) continue;
353
- }
354
- m.LogEvent(eventNameToSend, eventData.parameters);
355
- inUseAdapterIDs.Add(m.adapterId);
423
+ if (m.IsForbidden(eventParams.eventName)) continue;
356
424
  }
357
- sessionStat.Add(eventNameToSend, eventData, inUseAdapterIDs);
425
+ m.LogEvent(eventNameToSend, eventParams.parameters);
426
+ inUseAdapterIDs?.Add(m.adapterId);
358
427
  }
359
428
 
429
+ return (eventNameToSend, inUseAdapterIDs);
430
+ }
431
+
432
+ internal static void LogEventImmediately(EventParams eventParams)
433
+ {
434
+ var (eventNameToSend, inUseAdapterIDs) = LogEventActually(eventParams);
435
+ sessionStat?.Add(eventNameToSend, eventParams, inUseAdapterIDs);
436
+ epPool.Return(eventParams);
437
+ sessionStat?.Save();
360
438
  localData.SaveIfDirty();
361
- sessionStat.Save();
362
- Profiler.EndSample();
363
439
  }
364
440
 
365
441
  internal static void UpdateAccumulatedCount(EventParams eventData, int countInSession, int totalCount)
@@ -399,19 +475,20 @@ namespace Amanotes.Core
399
475
  {
400
476
  Profiler.BeginSample("AmaGDK.Analytics.NormalizeEventName()");
401
477
  {
402
- StringBuilder sb = tmpWarningSB;
478
+ StringBuilder sb = sbPool.Get();
403
479
  sb.Clear();
404
480
  NormalizeKeyString(ref eventName, sb, dryRun);
405
481
  if (reservedEvents.Contains(eventName))
406
482
  sb.AppendLine($" - EventName <{eventName}> is reserved and should not be used");
407
- if (sb.Length > 0 && dryRun) LogWarningOnce($"Warning for event name <{eventName}>:\n{sb}", $"NormalizeEventName<{eventName}>");
483
+ if (sb.Length > 0 && dryRun) LogWarningOnce($"Warning for event name <{eventName}>:\n{sb}", $"NormalizeEventName<{eventName}>");
484
+ sbPool.Return(sb);
408
485
  }
409
486
  Profiler.EndSample();
410
487
  }
411
488
 
412
489
  internal static void NormalizeKeyString(ref string key, StringBuilder warningLog, bool dryRun)
413
490
  {
414
- Profiler.BeginSample("AmaGDK.Analytics.NormalizeEventName()");
491
+ Profiler.BeginSample("AmaGDK.Analytics.NormalizeKeyString()");
415
492
  {
416
493
  if (string.IsNullOrWhiteSpace(key))
417
494
  {
@@ -420,52 +497,114 @@ namespace Amanotes.Core
420
497
  return;
421
498
  }
422
499
 
423
- var sb = new StringBuilder();
500
+ var tempLog = Config.common.logLevel == LogLevel.None ? null : sbPool.Get();
501
+ tempLog?.Clear();
502
+
503
+ var tempKey = sbPool.Get();
504
+ tempKey.Clear();
505
+
506
+ var hasChanged = false;
507
+ var trimKey = key.Trim();
424
508
 
425
- string normalizedString = key.Trim()
426
- .Replace(' ', '_')
427
- .Replace('-', '_');
428
- if (key != normalizedString)
509
+ foreach (var c in trimKey)
429
510
  {
430
- sb.Append($" - Malformed name (should not contains <space> or <dash>): <{key}>");
511
+ var isLetter = IsAlphabet(c);
512
+ var isNumber = char.IsDigit(c);
513
+
514
+ if (c == '_' || isLetter || isNumber)
515
+ {
516
+ tempKey.Append(c);
517
+ continue;
518
+ }
519
+
520
+ hasChanged = true;
521
+ if (c == ' ' || c == '-')
522
+ {
523
+ tempKey.Append('_');
524
+ }
431
525
  }
432
526
 
433
- var r = new Regex("^[a-zA-Z0-9_]*$");
434
- if (!r.IsMatch(normalizedString))
527
+ if (hasChanged)
435
528
  {
436
- normalizedString = Regex.Replace(normalizedString, "[^a-zA-Z0-9_]", "");
437
- sb.Append(" - Name may only contain alphanumeric characters and underscores");
529
+ tempLog?.Append(" - Name may only contain alphanumeric characters and underscores");
438
530
  }
439
-
440
- if (!char.IsLetter(normalizedString[0]))
531
+
532
+ if (tempKey.Length > 0 && !IsAlphabet(tempKey[0]))
441
533
  {
442
- normalizedString = "E" + normalizedString;
443
- sb.Append($" - Name must start with a letter: <{key}> ");
534
+ hasChanged = true;
535
+ tempKey.Insert(0, 'E');
536
+ tempLog?.Append($" - Name must start with a letter: <{key}>");
444
537
  }
445
-
538
+
446
539
  foreach (var prefix in reservedPrefixes)
447
540
  {
448
- if (!normalizedString.StartsWith(prefix.Key)) continue;
449
- normalizedString = prefix.Value + normalizedString.Remove(0, prefix.Key.Length);
450
- sb.AppendLine($" - <{key}> starts with reserved prefix <{prefix}>");
541
+ if (tempKey.Length < prefix.Key.Length || !StringStartsWith(tempKey, prefix.Key)) continue;
542
+ hasChanged = true;
543
+ tempKey.Remove(0, prefix.Key.Length);
544
+ tempKey.Insert(0, prefix.Value);
545
+ tempLog?.AppendLine($" - <{key}> starts with reserved prefix <{prefix}>");
451
546
  }
452
547
 
453
- if (normalizedString.Length > 40)
548
+ if (trimKey.Length > 40)
454
549
  {
455
- sb.AppendLine($" - Name is too long ({key.Length} > 40): <{key}> ");
456
- normalizedString = normalizedString.Substring(0, 40);
550
+ hasChanged = true;
551
+ tempLog?.AppendLine($" - Name is too long ({key.Length} > 40): <{key}> ");
552
+ tempKey.Length = 40;
457
553
  }
458
554
 
459
555
  if (dryRun)
460
556
  {
461
- warningLog.Append(sb);
557
+ warningLog.Append(tempLog);
462
558
  } else {
463
- key = normalizedString;
559
+ if (hasChanged) key = tempKey.ToString();
464
560
  }
561
+
562
+ if (tempLog != null) sbPool.Return(tempLog);
563
+ sbPool.Return(tempKey);
465
564
  }
466
565
  Profiler.EndSample();
467
566
  }
468
567
 
568
+ private static bool StringStartsWith(StringBuilder stringBuilder, string prefix)
569
+ {
570
+ if (stringBuilder == null || prefix == null)
571
+ return false;
572
+
573
+ if (stringBuilder.Length < prefix.Length)
574
+ return false;
575
+
576
+ for (int i = 0; i < prefix.Length; i++)
577
+ {
578
+ if (stringBuilder[i] != prefix[i])
579
+ return false;
580
+ }
581
+
582
+ return true;
583
+ }
584
+
585
+ private static bool IsAlphabet(char c)
586
+ {
587
+ if (c >= 'A' && c <= 'Z') return true;
588
+ if (c >= 'a' && c <= 'z') return true;
589
+ return false;
590
+ }
591
+
592
+ internal static void PrepareEventCheck(string eventName)
593
+ {
594
+ NormalizeEventName(ref eventName, true);
595
+
596
+ if (localData.GetEventDetail(eventName) == null && localData.totalEventDetailCount >= 500)
597
+ {
598
+ LogWarning($" - You have reported 500 types of events. New event <{eventName}> will be ignored.");
599
+ }
600
+ }
601
+
602
+ internal static void PostPrepareEventProcess(EventParams eventData)
603
+ {
604
+ if (!eventData.parameters.ContainsKey("ama_device_id"))
605
+ eventData.AddParam("ama_device_id", User.AmaDeviceId);
606
+ }
607
+
469
608
  internal static bool ShouldSendEvent(EventParams eventData)
470
609
  {
471
610
  //Funnel event
@@ -489,33 +628,6 @@ namespace Amanotes.Core
489
628
 
490
629
  return true;
491
630
  }
492
-
493
-
494
- internal static void ProcessActionQueue()
495
- {
496
- if (actionQueue.Count <= 0) return;
497
- while (actionQueue.Count > 0)
498
- {
499
- var action = actionQueue.Dequeue();
500
- action.Invoke();
501
- }
502
- }
503
-
504
- internal static void InternalSetUserId(string userId)
505
- {
506
- foreach (AnalyticsAdapter s in listAdapters)
507
- {
508
- s.SetUserId(userId);
509
- }
510
- }
511
-
512
- internal static void InternalSetUserProperty(string key, string value)
513
- {
514
- foreach (AnalyticsAdapter s in listAdapters)
515
- {
516
- s.SetUserProperty(key, value);
517
- }
518
- }
519
631
  }
520
632
 
521
633
  //PERSISTENT DATA
@@ -583,23 +695,63 @@ namespace Amanotes.Core
583
695
  internal class AnalyticsData : IJsonFile
584
696
  {
585
697
  public List<string> funnelSignatures = new List<string>();
586
- public List<EventDetail> eventDetails = new List<EventDetail>();
698
+
587
699
  public MigrationLog migrationLog = new MigrationLog();
588
700
  [NonSerialized] internal bool _dirty;
589
-
701
+
702
+ [SerializeField] internal List<EventDetail> eventDetails = new List<EventDetail>();
703
+ internal readonly Dictionary<string, EventDetail> cachedEventDetails = new Dictionary<string, EventDetail>();
704
+
705
+ public int totalEventDetailCount => eventDetails.Count;
706
+ public bool AddEventDetail(EventDetail ed)
707
+ {
708
+ if (cachedEventDetails.ContainsKey(ed.eventName))
709
+ {
710
+ Debug.LogWarning($"EventName <{ed.eventName}> existed!");
711
+ return false;
712
+ }
713
+
714
+ eventDetails.Add(ed);
715
+ cachedEventDetails.Add(ed.eventName, ed);
716
+ return true;
717
+ }
718
+
719
+ internal AnalyticsData BuildCache()
720
+ {
721
+ if (cachedEventDetails.Count > 0)
722
+ {
723
+ LogWarning("BuildCache should be called once!");
724
+ cachedEventDetails.Clear();
725
+ }
726
+
727
+ for (var i = 0; i < eventDetails.Count; i++)
728
+ {
729
+ var item = eventDetails[i];
730
+ if (cachedEventDetails.ContainsKey(item.eventName))
731
+ {
732
+ LogWarning($"Duplicated eventName <{item.eventName}> entry found in AnalyticData!");
733
+ continue;
734
+ }
735
+ cachedEventDetails.Add(item.eventName, item);
736
+ }
737
+ return this;
738
+ }
739
+
590
740
  internal EventDetail GetEventDetail(string eventName, bool autoNew = false)
591
741
  {
592
742
  if (string.IsNullOrEmpty(eventName)) return null;
593
743
 
594
- EventDetail result = eventDetails.Find(e => e.eventName == eventName);
595
- if (result != null) return result;
744
+ if (cachedEventDetails.TryGetValue(eventName, out var result))
745
+ {
746
+ return result;
747
+ }
596
748
  if (autoNew == false) return null;
597
749
 
598
750
  result = new EventDetail
599
751
  {
600
752
  eventName = eventName
601
753
  };
602
- eventDetails.Add(result);
754
+ AddEventDetail(result);
603
755
  _dirty = true;
604
756
  return result;
605
757
  }
@@ -645,7 +797,7 @@ namespace Amanotes.Core
645
797
  migrationLog.count++;
646
798
  Log($"Analytics data was migrated {migrationLog.count} times");
647
799
 
648
- if (migrationLog.count == 2) LogEvent("migration_error").AddParam(migrationLog.ToDictionary());
800
+ if (migrationLog.count == 2) LogEvent("migration_error", migrationLog.ToDictionary());
649
801
  }
650
802
 
651
803
  _dirty = true;
@@ -659,36 +811,44 @@ namespace Amanotes.Core
659
811
  private readonly string fileName = nameof(SessionStat) + DateTime.Now.ToString("_yyMMdd_HHmmss") + ".tsv";
660
812
  private StringBuilder contents = new StringBuilder();
661
813
  private bool isFirstSave = true;
662
- public List<EventStat> lastFrameEventStats = new List<EventStat>();
814
+ private readonly ConcurrentQueue<EventStat> lastFrameEventStats = new ConcurrentQueue<EventStat>();
815
+ internal readonly ConcurrentQueue<EventStat> savedEventStatsInLastFrame = new ConcurrentQueue<EventStat>();
663
816
 
664
817
  internal void Add(string eventNameToSend, EventParams eventParams, List<string> adapterIDs)
665
818
  {
819
+ if (string.IsNullOrEmpty(eventNameToSend) || adapterIDs == null) return;
666
820
  var eventStat = new EventStat(eventNameToSend, eventParams, adapterIDs);
667
- lastFrameEventStats.Add(eventStat);
668
- }
669
-
670
- internal void ClearLastFrameEventInfo()
671
- {
672
- lastFrameEventStats.Clear();
821
+ lastFrameEventStats.Enqueue(eventStat);
673
822
  }
674
823
 
675
824
  internal void Save()
676
825
  {
677
- if (!Config.analytics.enableEventStat || lastFrameEventStats.Count == 0) return;
678
-
679
826
  contents.Clear();
680
827
  if (isFirstSave)
681
828
  {
682
829
  contents = contents.AppendLine(TSV_HEADER_ROW);
683
830
  isFirstSave = false;
684
831
  }
685
- foreach (EventStat stat in lastFrameEventStats)
832
+ while (lastFrameEventStats.TryDequeue(out var stat))
686
833
  {
687
834
  contents.AppendLine(stat.ToTsv());
835
+ savedEventStatsInLastFrame.Enqueue(stat);
688
836
  }
689
837
 
690
838
  GDKFileUtils.SaveAppend(fileName, contents.ToString());
691
839
  }
840
+
841
+ internal void Clear()
842
+ {
843
+ //Clear() is supported from .Net Standard 2.1
844
+ #if UNITY_2021_3_OR_NEWER
845
+ sessionStat?.savedEventStatsInLastFrame.Clear();
846
+ #else
847
+ while (savedEventStatsInLastFrame.TryDequeue(out var stat))
848
+ {
849
+ }
850
+ #endif
851
+ }
692
852
  }
693
853
  }
694
854
  }
@@ -740,6 +900,8 @@ namespace Amanotes.Core
740
900
  }
741
901
 
742
902
  eventData.overrideConfig = true;
903
+ eventData.ignoreAdapterIds ??= new HashSet<string>();
904
+
743
905
  foreach (string mId in analyticIds)
744
906
  {
745
907
  eventData.ignoreAdapterIds.Add(mId);
@@ -764,6 +926,8 @@ namespace Amanotes.Core
764
926
  }
765
927
 
766
928
  eventData.overrideConfig = true;
929
+ eventData.allowAdapterIds ??= new HashSet<string>();
930
+
767
931
  foreach (string mId in analyticIds)
768
932
  {
769
933
  eventData.allowAdapterIds.Add(mId);
@@ -771,7 +935,7 @@ namespace Amanotes.Core
771
935
  return ap;
772
936
  }
773
937
 
774
- public static IEventParamsBuilder AddParam(this IEventParamsBuilder ap, string key, object value)
938
+ internal static IEventParamsBuilder AddParam(this IEventParamsBuilder ap, string key, object value)
775
939
  {
776
940
  if (ap == null) return null;
777
941
  if (!IsValidKeyValue(ap, ref key, ref value)) return ap;
@@ -795,7 +959,7 @@ namespace Amanotes.Core
795
959
  return ap;
796
960
  }
797
961
 
798
- public static IEventParamsBuilder AddParam(this IEventParamsBuilder ap, Dictionary<string, object> dictParams)
962
+ internal static IEventParamsBuilder AddParam(this IEventParamsBuilder ap, Dictionary<string, object> dictParams)
799
963
  {
800
964
  if (ap == null) return null;
801
965
  if (dictParams == null)
@@ -814,7 +978,7 @@ namespace Amanotes.Core
814
978
  return ap;
815
979
  }
816
980
 
817
- public static IEventParamsBuilder AddParam(this IEventParamsBuilder ap, params (string key, object value)[] parameters)
981
+ internal static IEventParamsBuilder AddParam(this IEventParamsBuilder ap, params (string key, object value)[] parameters)
818
982
  {
819
983
  if (ap == null) return null;
820
984
  if (parameters.Length == 0)
@@ -838,11 +1002,12 @@ namespace Amanotes.Core
838
1002
  {
839
1003
  string eventName = (ap as EventParams)?.eventName;
840
1004
 
841
- StringBuilder sb = tmpWarningSB;
1005
+ StringBuilder sb = sbPool.Get();
842
1006
  sb.Clear();
843
1007
  NormalizeKeyString(ref key, sb, AmaGDK.Config.analytics.normalizeEventName);
844
1008
 
845
1009
  if (sb.Length > 0) LogWarningOnce($"Warning for event <{eventName}>, param <{key}>:\n{sb}", $"NormalizeParamKey <{eventName}>");
1010
+ sbPool.Return(sb);
846
1011
 
847
1012
  if (string.IsNullOrWhiteSpace(key))
848
1013
  {