com.amanotes.gdk 0.2.51 → 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.
package/CHANGELOG.md CHANGED
@@ -1,16 +1,15 @@
1
1
  # Changelog
2
2
 
3
- ## [0.2.51 - 2024-02-23]
4
- - New feature: GDKCIBuildCommand
5
- - [Dev] Change Google Mobile Ads SDK version for iOS, 10.9 -> 10.5.0
6
- - [Analytics] Add LogEventImmediately()
7
- - [Analytics] Move AddParam() to internal use
8
- - [Analytics] Replace event Queue by ConcurrentQueue
9
- - [Ads] Check null Ads._adapter
10
- - [Ads] Autostart AdEventTracking
11
- - [Analytics] Update AppsFlyer adapter version
12
- - [UserProfile] Change logic of getting ama_device_id
13
- - [Core] Init modules before Hooks
3
+ ## [0.2.52 - 2024-02-27]
4
+ - Change post processing order
5
+ - Remove GDKInstaller after installed
6
+ - Enable pipeline execution for branches with active merge requests
7
+ - [Fix] Non-fatal exceptions relate to Analytics and Ads
8
+ - Update Google Sheet Id default
9
+ - Optimize GC for Analytics
10
+
11
+ ## [0.2.51 - 2024-02-20]
12
+ - Change Google Mobile Ads SDK version , iosPod 10.5.0
14
13
 
15
14
  ## [0.2.50 - 2024-02-20]
16
15
  - New feature: Modify Android post processor
@@ -0,0 +1,312 @@
1
+ using UnityEditor;
2
+ using System.Linq;
3
+ using System;
4
+ using System.IO;
5
+
6
+ namespace Amanotes.Core
7
+ {
8
+ public static class GDKCIBuildCommand
9
+ {
10
+ private const string KEYSTORE_PASS = "KEYSTORE_PASS";
11
+ private const string KEY_ALIAS_PASS = "KEY_ALIAS_PASS";
12
+ private const string KEY_ALIAS_NAME = "KEY_ALIAS_NAME";
13
+ private const string KEYSTORE = "keystore.keystore";
14
+ private const string BUILD_OPTIONS_ENV_VAR = "BuildOptions";
15
+ private const string ANDROID_BUNDLE_VERSION_CODE = "VERSION_BUILD_VAR";
16
+ private const string ANDROID_APP_BUNDLE = "BUILD_APP_BUNDLE";
17
+ private const string SCRIPTING_BACKEND_ENV_VAR = "SCRIPTING_BACKEND";
18
+ private const string VERSION_NUMBER_VAR = "VERSION_NUMBER_VAR";
19
+ private const string VERSION_iOS = "VERSION_BUILD_VAR";
20
+
21
+ static string GetArgument(string name)
22
+ {
23
+ string[] args = Environment.GetCommandLineArgs();
24
+ for (int i = 0; i < args.Length; i++)
25
+ {
26
+ if (args[i].Contains(name))
27
+ {
28
+ return args[i + 1];
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+
34
+ static string[] GetEnabledScenes()
35
+ {
36
+ return (
37
+ from scene in EditorBuildSettings.scenes
38
+ where scene.enabled
39
+ where !string.IsNullOrEmpty(scene.path)
40
+ select scene.path
41
+ ).ToArray();
42
+ }
43
+
44
+ static BuildTarget GetBuildTarget()
45
+ {
46
+ string buildTargetName = GetArgument("customBuildTarget");
47
+ Console.WriteLine(":: Received customBuildTarget " + buildTargetName);
48
+
49
+ if (buildTargetName.ToLower() == "android")
50
+ {
51
+ #if !UNITY_5_6_OR_NEWER
52
+ // https://issuetracker.unity3d.com/issues/buildoptions-dot-acceptexternalmodificationstoplayer-causes-unityexception-unknown-project-type-0
53
+ // Fixed in Unity 5.6.0
54
+ // side effect to fix android build system:
55
+ EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Internal;
56
+ #endif
57
+ }
58
+
59
+ if (buildTargetName.TryConvertToEnum(out BuildTarget target))
60
+ return target;
61
+
62
+ Console.WriteLine($":: {nameof(buildTargetName)} \"{buildTargetName}\" not defined on enum {nameof(BuildTarget)}, using {nameof(BuildTarget.NoTarget)} enum to build");
63
+
64
+ return BuildTarget.NoTarget;
65
+ }
66
+
67
+ static string GetBuildPath()
68
+ {
69
+ string buildPath = GetArgument("customBuildPath");
70
+ Console.WriteLine(":: Received customBuildPath " + buildPath);
71
+ if (buildPath == "")
72
+ {
73
+ throw new Exception("customBuildPath argument is missing");
74
+ }
75
+ return buildPath;
76
+ }
77
+
78
+ static string GetBuildName()
79
+ {
80
+ string buildName = GetArgument("customBuildName");
81
+ Console.WriteLine(":: Received customBuildName " + buildName);
82
+ if (buildName == "")
83
+ {
84
+ throw new Exception("customBuildName argument is missing");
85
+ }
86
+ return buildName;
87
+ }
88
+
89
+ static string GetFixedBuildPath(BuildTarget buildTarget, string buildPath, string buildName)
90
+ {
91
+ if (buildTarget.ToString().ToLower().Contains("windows"))
92
+ {
93
+ buildName += ".exe";
94
+ }
95
+ else if (buildTarget == BuildTarget.Android)
96
+ {
97
+ #if UNITY_2018_3_OR_NEWER
98
+ buildName += EditorUserBuildSettings.buildAppBundle ? ".aab" : ".apk";
99
+ #else
100
+ buildName += ".apk";
101
+ #endif
102
+ }
103
+ return buildPath + buildName;
104
+ }
105
+
106
+ static BuildOptions GetBuildOptions()
107
+ {
108
+ if (TryGetEnv(BUILD_OPTIONS_ENV_VAR, out string envVar))
109
+ {
110
+ string[] allOptionVars = envVar.Split(',');
111
+ BuildOptions allOptions = BuildOptions.None;
112
+ BuildOptions option;
113
+ string optionVar;
114
+ int length = allOptionVars.Length;
115
+
116
+ Console.WriteLine($":: Detecting {BUILD_OPTIONS_ENV_VAR} env var with {length} elements ({envVar})");
117
+
118
+ for (int i = 0; i < length; i++)
119
+ {
120
+ optionVar = allOptionVars[i];
121
+
122
+ if (optionVar.TryConvertToEnum(out option))
123
+ {
124
+ allOptions |= option;
125
+ }
126
+ else
127
+ {
128
+ Console.WriteLine($":: Cannot convert {optionVar} to {nameof(BuildOptions)} enum, skipping it.");
129
+ }
130
+ }
131
+
132
+ return allOptions;
133
+ }
134
+
135
+ return BuildOptions.None;
136
+ }
137
+
138
+ // https://stackoverflow.com/questions/1082532/how-to-tryparse-for-enum-value
139
+ static bool TryConvertToEnum<TEnum>(this string strEnumValue, out TEnum value)
140
+ {
141
+ if (!Enum.IsDefined(typeof(TEnum), strEnumValue))
142
+ {
143
+ value = default;
144
+ return false;
145
+ }
146
+
147
+ value = (TEnum)Enum.Parse(typeof(TEnum), strEnumValue);
148
+ return true;
149
+ }
150
+
151
+ static bool TryGetEnv(string key, out string value)
152
+ {
153
+ value = Environment.GetEnvironmentVariable(key);
154
+ return !string.IsNullOrEmpty(value);
155
+ }
156
+
157
+ static void SetScriptingBackendFromEnv(BuildTarget platform)
158
+ {
159
+ var targetGroup = BuildPipeline.GetBuildTargetGroup(platform);
160
+ if (TryGetEnv(SCRIPTING_BACKEND_ENV_VAR, out string scriptingBackend))
161
+ {
162
+ if (scriptingBackend.TryConvertToEnum(out ScriptingImplementation backend))
163
+ {
164
+ Console.WriteLine($":: Setting ScriptingBackend to {backend}");
165
+ PlayerSettings.SetScriptingBackend(targetGroup, backend);
166
+ }
167
+ else
168
+ {
169
+ string possibleValues = string.Join(", ", Enum.GetValues(typeof(ScriptingImplementation)).Cast<ScriptingImplementation>());
170
+ throw new Exception($"Could not find '{scriptingBackend}' in ScriptingImplementation enum. Possible values are: {possibleValues}");
171
+ }
172
+ }
173
+ else
174
+ {
175
+ var defaultBackend = PlayerSettings.GetDefaultScriptingBackend(targetGroup);
176
+ Console.WriteLine($":: Using project's configured ScriptingBackend (should be {defaultBackend} for targetGroup {targetGroup}");
177
+ }
178
+ }
179
+
180
+ public static void PerformBuild()
181
+ {
182
+ var buildTarget = GetBuildTarget();
183
+
184
+ Console.WriteLine(":: Performing build");
185
+ if (TryGetEnv(VERSION_NUMBER_VAR, out var bundleVersionNumber))
186
+ {
187
+ if (buildTarget == BuildTarget.iOS)
188
+ {
189
+ bundleVersionNumber = GetIosVersion();
190
+ }
191
+ Console.WriteLine($":: Setting bundleVersionNumber to '{bundleVersionNumber}' (Length: {bundleVersionNumber.Length})");
192
+ PlayerSettings.bundleVersion = bundleVersionNumber;
193
+ }
194
+
195
+ if (buildTarget == BuildTarget.Android)
196
+ {
197
+ HandleAndroidAppBundle();
198
+ HandleAndroidBundleVersionCode();
199
+ HandleAndroidKeystore();
200
+ }
201
+
202
+ var buildPath = GetBuildPath();
203
+ var buildName = GetBuildName();
204
+ var buildOptions = GetBuildOptions();
205
+ var fixedBuildPath = GetFixedBuildPath(buildTarget, buildPath, buildName);
206
+
207
+ SetScriptingBackendFromEnv(buildTarget);
208
+
209
+ var buildReport = BuildPipeline.BuildPlayer(GetEnabledScenes(), fixedBuildPath, buildTarget, buildOptions);
210
+
211
+ if (buildReport.summary.result != UnityEditor.Build.Reporting.BuildResult.Succeeded)
212
+ throw new Exception($"Build ended with {buildReport.summary.result} status");
213
+
214
+ Console.WriteLine(":: Done with build");
215
+ }
216
+
217
+ private static void HandleAndroidAppBundle()
218
+ {
219
+ if (TryGetEnv(ANDROID_APP_BUNDLE, out string value))
220
+ {
221
+ #if UNITY_2018_3_OR_NEWER
222
+ if (bool.TryParse(value, out bool buildAppBundle))
223
+ {
224
+ EditorUserBuildSettings.buildAppBundle = buildAppBundle;
225
+ Console.WriteLine($":: {ANDROID_APP_BUNDLE} env var detected, set buildAppBundle to {value}.");
226
+ }
227
+ else
228
+ {
229
+ Console.WriteLine($":: {ANDROID_APP_BUNDLE} env var detected but the value \"{value}\" is not a boolean.");
230
+ }
231
+ #else
232
+ Console.WriteLine($":: {ANDROID_APP_BUNDLE} env var detected but does not work with lower Unity version than 2018.3");
233
+ #endif
234
+ }
235
+ }
236
+
237
+ private static void HandleAndroidBundleVersionCode()
238
+ {
239
+ if (TryGetEnv(ANDROID_BUNDLE_VERSION_CODE, out string value))
240
+ {
241
+ if (int.TryParse(value, out int version))
242
+ {
243
+ PlayerSettings.Android.bundleVersionCode = version;
244
+ Console.WriteLine($":: {ANDROID_BUNDLE_VERSION_CODE} env var detected, set the bundle version code to {value}.");
245
+ }
246
+ else
247
+ Console.WriteLine($":: {ANDROID_BUNDLE_VERSION_CODE} env var detected but the version value \"{value}\" is not an integer.");
248
+ }
249
+ }
250
+
251
+ private static string GetIosVersion()
252
+ {
253
+ if (TryGetEnv(VERSION_iOS, out string value))
254
+ {
255
+ if (int.TryParse(value, out int version))
256
+ {
257
+ Console.WriteLine($":: {VERSION_iOS} env var detected, set the version to {value}.");
258
+ return version.ToString();
259
+ }
260
+ else
261
+ Console.WriteLine($":: {VERSION_iOS} env var detected but the version value \"{value}\" is not an integer.");
262
+ }
263
+
264
+ throw new ArgumentNullException(nameof(value), $":: Error finding {VERSION_iOS} env var");
265
+ }
266
+
267
+ private static void HandleAndroidKeystore()
268
+ {
269
+ #if UNITY_2019_1_OR_NEWER
270
+ PlayerSettings.Android.useCustomKeystore = false;
271
+ #endif
272
+
273
+ if (!File.Exists(KEYSTORE))
274
+ {
275
+ Console.WriteLine($":: {KEYSTORE} not found, skipping setup, using Unity's default keystore");
276
+ return;
277
+ }
278
+
279
+ PlayerSettings.Android.keystoreName = KEYSTORE;
280
+
281
+ string keystorePass;
282
+ string keystoreAliasPass;
283
+
284
+ if (TryGetEnv(KEY_ALIAS_NAME, out string keyaliasName))
285
+ {
286
+ PlayerSettings.Android.keyaliasName = keyaliasName;
287
+ Console.WriteLine($":: using ${KEY_ALIAS_NAME} env var on PlayerSettings");
288
+ }
289
+ else
290
+ {
291
+ Console.WriteLine($":: ${KEY_ALIAS_NAME} env var not set, using Project's PlayerSettings");
292
+ }
293
+
294
+ if (!TryGetEnv(KEYSTORE_PASS, out keystorePass))
295
+ {
296
+ Console.WriteLine($":: ${KEYSTORE_PASS} env var not set, skipping setup, using Unity's default keystore");
297
+ return;
298
+ }
299
+
300
+ if (!TryGetEnv(KEY_ALIAS_PASS, out keystoreAliasPass))
301
+ {
302
+ Console.WriteLine($":: ${KEY_ALIAS_PASS} env var not set, skipping setup, using Unity's default keystore");
303
+ return;
304
+ }
305
+ #if UNITY_2019_1_OR_NEWER
306
+ PlayerSettings.Android.useCustomKeystore = true;
307
+ #endif
308
+ PlayerSettings.Android.keystorePass = keystorePass;
309
+ PlayerSettings.Android.keyaliasPass = keystoreAliasPass;
310
+ }
311
+ }
312
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: bfe10a860d56457f86c6bb3931b972a5
3
+ timeCreated: 1706775850
Binary file
Binary file
Binary file
@@ -1,5 +1,5 @@
1
1
  fileFormatVersion: 2
2
- guid: e331d1b8f5c1d49bc8ef6ea5239e4ca4
2
+ guid: ee4588fce1b7043429e48574edb72bde
3
3
  DefaultImporter:
4
4
  externalObjects: {}
5
5
  userData:
Binary file
Binary file
Binary file
@@ -22,34 +22,52 @@ namespace Amanotes.Core
22
22
 
23
23
  public class EventParams : IEventParamsBuilder
24
24
  {
25
- public readonly string eventName;
26
- 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>();
27
27
  public string funnelSignature;
28
28
  public bool accumulated;
29
29
  public bool withinSession;
30
30
  public bool doNotIncreaseCounter;
31
31
 
32
32
  public bool overrideConfig;
33
- public HashSet<string> ignoreAdapterIds = new HashSet<string>();
34
- public HashSet<string> allowAdapterIds = new HashSet<string>();
33
+ public HashSet<string> ignoreAdapterIds = null;
34
+ public HashSet<string> allowAdapterIds = null;
35
35
 
36
- public EventDetail EventDetail => localData.GetEventDetail(eventName, true);
36
+ private EventDetail _detail;
37
+ public EventDetail EventDetail
38
+ {
39
+ get
40
+ {
41
+ if (_detail != null) return _detail;
42
+ return _detail = localData.GetEventDetail(eventName, true);
43
+ }
44
+ }
37
45
 
38
- public EventParams(string eventName)
46
+ internal EventParams Init(string eventName)
39
47
  {
40
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;
41
59
  }
42
60
 
43
61
  public bool IsFunnelEvent => !string.IsNullOrEmpty(funnelSignature);
44
62
 
45
63
  public bool IsAdapterForbidden(string adapterId)
46
64
  {
47
- if (ignoreAdapterIds.Count > 0)
65
+ if (ignoreAdapterIds != null && ignoreAdapterIds.Count > 0)
48
66
  {
49
67
  return ignoreAdapterIds.Contains(adapterId);
50
68
  }
51
69
 
52
- if (allowAdapterIds.Count > 0)
70
+ if (allowAdapterIds != null && allowAdapterIds.Count > 0)
53
71
  {
54
72
  return !allowAdapterIds.Contains(adapterId);
55
73
  }
@@ -164,12 +182,13 @@ namespace Amanotes.Core
164
182
  }
165
183
 
166
184
  if (eventCountMap != null)
185
+ {
167
186
  foreach (KeyValuePair<string, int> e in eventCountMap)
168
187
  {
169
188
  EventDetail eventDetail = localData.GetEventDetail(e.Key);
170
189
  if (eventDetail == null)
171
190
  {
172
- localData.eventDetails.Add(new EventDetail
191
+ localData.AddEventDetail(new EventDetail
173
192
  {
174
193
  eventName = e.Key,
175
194
  totalCount = e.Value
@@ -188,6 +207,8 @@ namespace Amanotes.Core
188
207
  eventDetail.totalCount += e.Value;
189
208
  }
190
209
  }
210
+ }
211
+
191
212
 
192
213
  Profiler.EndSample();
193
214
  return flag;
@@ -252,8 +273,10 @@ namespace Amanotes.Core
252
273
  internal static Predicate<EventParams> logEventHook;
253
274
  internal static readonly ConcurrentQueue<EventParams> eventQueue = new ConcurrentQueue<EventParams>();
254
275
  internal static readonly List<AnalyticsAdapter> listAdapters = new List<AnalyticsAdapter>();
255
- internal static readonly AnalyticsData localData = new AnalyticsData().LoadJsonFromFile();
256
- 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);
257
280
 
258
281
  internal static readonly Dictionary<string, string> reservedPrefixes = new Dictionary<string, string>()
259
282
  {
@@ -315,7 +338,7 @@ namespace Amanotes.Core
315
338
  internal static EventParams PrepareEvent(string eventName)
316
339
  {
317
340
  PrepareEventCheck(eventName);
318
- var result = new EventParams(eventName);
341
+ var result = epPool.Get().Init(eventName);
319
342
  PostPrepareEventProcess(result);
320
343
  return result;
321
344
  }
@@ -324,7 +347,7 @@ namespace Amanotes.Core
324
347
  {
325
348
  if (parameters == null) return PrepareEvent(eventName);
326
349
  PrepareEventCheck(eventName);
327
- var result = new EventParams(eventName);
350
+ var result = epPool.Get().Init(eventName);
328
351
  result.AddParam(parameters);
329
352
  PostPrepareEventProcess(result);
330
353
  return result;
@@ -334,7 +357,7 @@ namespace Amanotes.Core
334
357
  {
335
358
  if (parameters == null) return PrepareEvent(eventName);
336
359
  PrepareEventCheck(eventName);
337
- var result = new EventParams(eventName);
360
+ var result = epPool.Get().Init(eventName);
338
361
  for (var i = 0; i < parameters.Length; i++)
339
362
  {
340
363
  result.AddParam(parameters[i].Item1, parameters[i].Item2);
@@ -348,18 +371,18 @@ namespace Amanotes.Core
348
371
  if (eventQueue.Count <= 0) return;
349
372
 
350
373
  Profiler.BeginSample("AmaGDK.Analytics.ProcessEventQueue()");
351
-
352
- sessionStat.savedEventStatsInLastFrame.Clear();
374
+
375
+ sessionStat?.Clear();
353
376
 
354
377
  while (eventQueue.TryDequeue(out var eventData))
355
378
  {
356
379
  var (eventNameToSend, inUseAdapterIDs) = LogEventActually(eventData);
357
- if (string.IsNullOrEmpty(eventNameToSend) || inUseAdapterIDs == null) continue;
358
- sessionStat.Add(eventNameToSend, eventData, inUseAdapterIDs);
380
+ sessionStat?.Add(eventNameToSend, eventData, inUseAdapterIDs);
381
+ epPool.Return(eventData);
359
382
  }
360
383
 
361
384
  localData.SaveIfDirty();
362
- sessionStat.Save();
385
+ sessionStat?.Save();
363
386
  Profiler.EndSample();
364
387
  }
365
388
 
@@ -384,7 +407,7 @@ namespace Amanotes.Core
384
407
 
385
408
  config.quality.CheckEventQuality(eventParams.eventName, eventParams.parameters);
386
409
 
387
- var inUseAdapterIDs = new List<string>();
410
+ var inUseAdapterIDs = config.enableEventStat ? new List<string>(listAdapters.Count) : null;
388
411
  foreach (AnalyticsAdapter m in listAdapters)
389
412
  {
390
413
  if (eventParams.overrideConfig)
@@ -400,7 +423,7 @@ namespace Amanotes.Core
400
423
  if (m.IsForbidden(eventParams.eventName)) continue;
401
424
  }
402
425
  m.LogEvent(eventNameToSend, eventParams.parameters);
403
- inUseAdapterIDs.Add(m.adapterId);
426
+ inUseAdapterIDs?.Add(m.adapterId);
404
427
  }
405
428
 
406
429
  return (eventNameToSend, inUseAdapterIDs);
@@ -409,9 +432,9 @@ namespace Amanotes.Core
409
432
  internal static void LogEventImmediately(EventParams eventParams)
410
433
  {
411
434
  var (eventNameToSend, inUseAdapterIDs) = LogEventActually(eventParams);
412
- if (string.IsNullOrEmpty(eventNameToSend) || inUseAdapterIDs == null) return;
413
- sessionStat.Add(eventNameToSend, eventParams, inUseAdapterIDs);
414
- sessionStat.Save();
435
+ sessionStat?.Add(eventNameToSend, eventParams, inUseAdapterIDs);
436
+ epPool.Return(eventParams);
437
+ sessionStat?.Save();
415
438
  localData.SaveIfDirty();
416
439
  }
417
440
 
@@ -452,12 +475,13 @@ namespace Amanotes.Core
452
475
  {
453
476
  Profiler.BeginSample("AmaGDK.Analytics.NormalizeEventName()");
454
477
  {
455
- StringBuilder sb = tmpWarningSB;
478
+ StringBuilder sb = sbPool.Get();
456
479
  sb.Clear();
457
480
  NormalizeKeyString(ref eventName, sb, dryRun);
458
481
  if (reservedEvents.Contains(eventName))
459
482
  sb.AppendLine($" - EventName <{eventName}> is reserved and should not be used");
460
- 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);
461
485
  }
462
486
  Profiler.EndSample();
463
487
  }
@@ -473,57 +497,103 @@ namespace Amanotes.Core
473
497
  return;
474
498
  }
475
499
 
476
- var sb = new StringBuilder();
500
+ var tempLog = Config.common.logLevel == LogLevel.None ? null : sbPool.Get();
501
+ tempLog?.Clear();
477
502
 
478
- string normalizedString = key.Trim()
479
- .Replace(' ', '_')
480
- .Replace('-', '_');
481
- if (key != normalizedString)
503
+ var tempKey = sbPool.Get();
504
+ tempKey.Clear();
505
+
506
+ var hasChanged = false;
507
+ var trimKey = key.Trim();
508
+
509
+ foreach (var c in trimKey)
482
510
  {
483
- 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
+ }
484
525
  }
485
526
 
486
- var r = new Regex("^[a-zA-Z0-9_]*$");
487
- if (!r.IsMatch(normalizedString))
527
+ if (hasChanged)
488
528
  {
489
- normalizedString = Regex.Replace(normalizedString, "[^a-zA-Z0-9_]", "");
490
- sb.Append(" - Name may only contain alphanumeric characters and underscores");
529
+ tempLog?.Append(" - Name may only contain alphanumeric characters and underscores");
491
530
  }
492
-
493
- if (!char.IsLetter(normalizedString[0]))
531
+
532
+ if (tempKey.Length > 0 && !IsAlphabet(tempKey[0]))
494
533
  {
495
- normalizedString = "E" + normalizedString;
496
- 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}>");
497
537
  }
498
-
538
+
499
539
  foreach (var prefix in reservedPrefixes)
500
540
  {
501
- if (!normalizedString.StartsWith(prefix.Key)) continue;
502
- normalizedString = prefix.Value + normalizedString.Remove(0, prefix.Key.Length);
503
- 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}>");
504
546
  }
505
547
 
506
- if (normalizedString.Length > 40)
548
+ if (trimKey.Length > 40)
507
549
  {
508
- sb.AppendLine($" - Name is too long ({key.Length} > 40): <{key}> ");
509
- normalizedString = normalizedString.Substring(0, 40);
550
+ hasChanged = true;
551
+ tempLog?.AppendLine($" - Name is too long ({key.Length} > 40): <{key}> ");
552
+ tempKey.Length = 40;
510
553
  }
511
554
 
512
555
  if (dryRun)
513
556
  {
514
- warningLog.Append(sb);
557
+ warningLog.Append(tempLog);
515
558
  } else {
516
- key = normalizedString;
559
+ if (hasChanged) key = tempKey.ToString();
517
560
  }
561
+
562
+ if (tempLog != null) sbPool.Return(tempLog);
563
+ sbPool.Return(tempKey);
518
564
  }
519
565
  Profiler.EndSample();
520
566
  }
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
+ }
521
591
 
522
592
  internal static void PrepareEventCheck(string eventName)
523
593
  {
524
594
  NormalizeEventName(ref eventName, true);
525
595
 
526
- if (localData.GetEventDetail(eventName) == null && localData.eventDetails.Count >= 500)
596
+ if (localData.GetEventDetail(eventName) == null && localData.totalEventDetailCount >= 500)
527
597
  {
528
598
  LogWarning($" - You have reported 500 types of events. New event <{eventName}> will be ignored.");
529
599
  }
@@ -625,23 +695,63 @@ namespace Amanotes.Core
625
695
  internal class AnalyticsData : IJsonFile
626
696
  {
627
697
  public List<string> funnelSignatures = new List<string>();
628
- public List<EventDetail> eventDetails = new List<EventDetail>();
698
+
629
699
  public MigrationLog migrationLog = new MigrationLog();
630
700
  [NonSerialized] internal bool _dirty;
631
-
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
+
632
740
  internal EventDetail GetEventDetail(string eventName, bool autoNew = false)
633
741
  {
634
742
  if (string.IsNullOrEmpty(eventName)) return null;
635
743
 
636
- EventDetail result = eventDetails.Find(e => e.eventName == eventName);
637
- if (result != null) return result;
744
+ if (cachedEventDetails.TryGetValue(eventName, out var result))
745
+ {
746
+ return result;
747
+ }
638
748
  if (autoNew == false) return null;
639
749
 
640
750
  result = new EventDetail
641
751
  {
642
752
  eventName = eventName
643
753
  };
644
- eventDetails.Add(result);
754
+ AddEventDetail(result);
645
755
  _dirty = true;
646
756
  return result;
647
757
  }
@@ -706,14 +816,13 @@ namespace Amanotes.Core
706
816
 
707
817
  internal void Add(string eventNameToSend, EventParams eventParams, List<string> adapterIDs)
708
818
  {
819
+ if (string.IsNullOrEmpty(eventNameToSend) || adapterIDs == null) return;
709
820
  var eventStat = new EventStat(eventNameToSend, eventParams, adapterIDs);
710
821
  lastFrameEventStats.Enqueue(eventStat);
711
822
  }
712
823
 
713
824
  internal void Save()
714
825
  {
715
- if (!Config.analytics.enableEventStat || lastFrameEventStats.Count == 0) return;
716
-
717
826
  contents.Clear();
718
827
  if (isFirstSave)
719
828
  {
@@ -728,9 +837,19 @@ namespace Amanotes.Core
728
837
 
729
838
  GDKFileUtils.SaveAppend(fileName, contents.ToString());
730
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
+ }
731
852
  }
732
-
733
-
734
853
  }
735
854
  }
736
855
 
@@ -781,6 +900,8 @@ namespace Amanotes.Core
781
900
  }
782
901
 
783
902
  eventData.overrideConfig = true;
903
+ eventData.ignoreAdapterIds ??= new HashSet<string>();
904
+
784
905
  foreach (string mId in analyticIds)
785
906
  {
786
907
  eventData.ignoreAdapterIds.Add(mId);
@@ -805,6 +926,8 @@ namespace Amanotes.Core
805
926
  }
806
927
 
807
928
  eventData.overrideConfig = true;
929
+ eventData.allowAdapterIds ??= new HashSet<string>();
930
+
808
931
  foreach (string mId in analyticIds)
809
932
  {
810
933
  eventData.allowAdapterIds.Add(mId);
@@ -879,11 +1002,12 @@ namespace Amanotes.Core
879
1002
  {
880
1003
  string eventName = (ap as EventParams)?.eventName;
881
1004
 
882
- StringBuilder sb = tmpWarningSB;
1005
+ StringBuilder sb = sbPool.Get();
883
1006
  sb.Clear();
884
1007
  NormalizeKeyString(ref key, sb, AmaGDK.Config.analytics.normalizeEventName);
885
1008
 
886
1009
  if (sb.Length > 0) LogWarningOnce($"Warning for event <{eventName}>, param <{key}>:\n{sb}", $"NormalizeParamKey <{eventName}>");
1010
+ sbPool.Return(sb);
887
1011
 
888
1012
  if (string.IsNullOrWhiteSpace(key))
889
1013
  {
package/Runtime/AmaGDK.cs CHANGED
@@ -17,7 +17,7 @@ namespace Amanotes.Core
17
17
  {
18
18
  public partial class AmaGDK : MonoBehaviour
19
19
  {
20
- public const string VERSION = "0.2.51";
20
+ public const string VERSION = "0.2.52";
21
21
 
22
22
  internal static AmaGDK _instance;
23
23
  internal static Status _status = Status.None;
@@ -93,6 +93,27 @@ namespace Amanotes.Core.Internal
93
93
 
94
94
  public static class Forbidden
95
95
  {
96
+ private static bool presetShowAnalyticsLog;
97
+ private static bool presetEnableEventStat;
98
+ private static LogLevel presetLogLevel = LogLevel.Warning;
99
+
100
+ public static void PresetConfig()
101
+ {
102
+ presetLogLevel = Config.common.logLevel;
103
+ Config.common.logLevel = LogLevel.Warning;
104
+ presetShowAnalyticsLog = Config.analytics.showAnalyticsLog;
105
+ Config.analytics.showAnalyticsLog = true;
106
+ presetEnableEventStat = Config.analytics.enableEventStat;
107
+ Config.analytics.enableEventStat = true;
108
+ }
109
+
110
+ public static void ReloadConfig()
111
+ {
112
+ Config.analytics.showAnalyticsLog = presetShowAnalyticsLog;
113
+ Config.analytics.enableEventStat = presetEnableEventStat;
114
+ Config.common.logLevel = presetLogLevel;
115
+ }
116
+
96
117
  public static void DestroyAndReset()
97
118
  {
98
119
  if (_instance != null)
@@ -139,9 +160,12 @@ namespace Amanotes.Core.Internal
139
160
  public static void ClearAnalyticsData()
140
161
  {
141
162
  AnalyticsData analyticsData = localData;
163
+ analyticsData._dirty = true;
142
164
  analyticsData.funnelSignatures.Clear();
143
165
  analyticsData.eventDetails.Clear();
166
+ analyticsData.cachedEventDetails.Clear();
144
167
  analyticsData.migrationLog = new MigrationLog();
168
+ analyticsData.SaveIfDirty();
145
169
  }
146
170
  }
147
171
 
@@ -1,5 +1,6 @@
1
1
  using System;
2
2
  using System.Collections;
3
+ using System.Collections.Concurrent;
3
4
  using System.Collections.Generic;
4
5
  using System.IO;
5
6
  using System.Reflection;
@@ -391,7 +392,6 @@ namespace Amanotes.Core.Internal
391
392
 
392
393
  internal interface IJsonFile { }
393
394
 
394
-
395
395
  internal static class JsonFileExtension
396
396
  {
397
397
  public static T LoadJsonFromFile<T>(this T instance) where T : IJsonFile, new()
@@ -569,4 +569,30 @@ namespace Amanotes.Core.Internal
569
569
  // public T[] items;
570
570
  //}
571
571
  }
572
+
573
+ internal class Pool<T> where T : new()
574
+ {
575
+ private readonly ConcurrentQueue<T> _pool;
576
+
577
+ public Pool(int initialCapacity)
578
+ {
579
+ _pool = new ConcurrentQueue<T>();
580
+ for (int i = 0; i < initialCapacity; i++)
581
+ {
582
+ _pool.Enqueue(new T());
583
+ }
584
+ }
585
+
586
+ public T Get()
587
+ {
588
+ return _pool.TryDequeue(out var obj) ? obj : new T();
589
+ }
590
+
591
+ public void Return(T obj)
592
+ {
593
+ _pool.Enqueue(obj);
594
+ }
595
+
596
+ public int Count => _pool.Count;
597
+ }
572
598
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.amanotes.gdk",
3
- "version": "0.2.51",
3
+ "version": "0.2.52",
4
4
  "displayName": "AmaGDK",
5
5
  "description": "Amanotes Game Development Kit",
6
6
  "unity": "2019.4",