com.amanotes.gdk 0.2.69 → 0.2.70-2
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 +5 -0
- package/Editor/AmaGDKEditor.cs +2 -1
- package/Editor/Extra/GDKCIBuildCommand.cs +2 -16
- package/Editor/Utils/AmaGDKEditor.ExtractVersion.cs +6 -0
- package/Extra/AmaGDKInstaller.unitypackage +0 -0
- package/Extra/CheckDiskSpace.unitypackage +0 -0
- package/Extra/Consent.unitypackage +0 -0
- package/Extra/ForceUpdate.unitypackage +0 -0
- package/Extra/LegacyGDKUpdateHelper.unitypackage +0 -0
- package/Extra/PostProcessor.unitypackage +0 -0
- package/Packages/AmaGDKConfig.unitypackage +0 -0
- package/Packages/AmaGDKExample.unitypackage +0 -0
- package/Packages/AmaGDKTest.unitypackage +0 -0
- package/Packages/AppsFlyerAdapter.PurchaseConnector.unitypackage +0 -0
- package/Packages/AppsFlyerAdapter.unitypackage +0 -0
- package/Packages/FirebaseAnalyticsAdapter.unitypackage +0 -0
- package/Packages/FirebaseRemoteConfigAdapter.unitypackage +0 -0
- package/Packages/IronSourceAdapter.unitypackage +0 -0
- package/Packages/MaxAdNetworkAdapter.unitypackage +0 -0
- package/Packages/RevenueCatAdapter.unitypackage +0 -0
- package/Packages/SqliteAnalyticsAdapter.unitypackage +0 -0
- package/Runtime/Ad/AdLogic.cs +56 -49
- package/Runtime/Ad/AdShowContext.cs +1 -1
- package/Runtime/AmaGDK.Analytics.cs +45 -38
- package/Runtime/AmaGDK.Device.cs +4 -4
- package/Runtime/AmaGDK.RemoteConfig.cs +3 -1
- package/Runtime/AmaGDK.cs +4 -2
- package/Runtime/AnalyticQualityAsset.cs +16 -9
- package/Runtime/AudioToolkit/Plugins/AudioDeviceDetector.cs +13 -12
- package/Runtime/Core/GDKPool.cs +83 -17
- package/Runtime/Internal/AmaGDK.Internal.cs +2 -5
- package/Runtime/Internal/AmaGDK.Utils.cs +32 -81
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.70 - 2024-08-02]
|
|
4
|
+
- [Fix] onAdResult call multiple times
|
|
5
|
+
- [Dev] Show SQLite analytics in GDK Inspector
|
|
6
|
+
- [Dev] Reduce GC by reuse StringBuilders
|
|
7
|
+
|
|
3
8
|
## [0.2.69 - 2024-07-26]
|
|
4
9
|
- [Fix] disable Max adapter
|
|
5
10
|
- [Dev] Update OnAdResult callback
|
package/Editor/AmaGDKEditor.cs
CHANGED
|
@@ -315,13 +315,6 @@ namespace Amanotes.Core
|
|
|
315
315
|
|
|
316
316
|
public class GDKPreProcessBuild: IPreprocessBuildWithReport
|
|
317
317
|
{
|
|
318
|
-
#if UNITY_2022_1_OR_NEWER
|
|
319
|
-
private static readonly string GRADLE_VERSION = "7.6.1";
|
|
320
|
-
#else
|
|
321
|
-
private static readonly string GRADLE_VERSION = "6.7.1";
|
|
322
|
-
#endif
|
|
323
|
-
private static readonly string GRADLE_ROOT = "/usr/local/gradle";
|
|
324
|
-
|
|
325
318
|
public int callbackOrder
|
|
326
319
|
{
|
|
327
320
|
get { return 0; }
|
|
@@ -333,19 +326,12 @@ namespace Amanotes.Core
|
|
|
333
326
|
// Change Gradle path when running in headless mode (CI Machine)
|
|
334
327
|
if (Application.isBatchMode)
|
|
335
328
|
{
|
|
336
|
-
EditorPrefs.SetBool("GradleUseEmbedded", false);
|
|
337
|
-
|
|
338
329
|
if (GDKCIBuildCommand.TryGetEnv("GRADLE_PATH", out string envGradlePath))
|
|
339
|
-
{
|
|
330
|
+
{
|
|
331
|
+
EditorPrefs.SetBool("GradleUseEmbedded", false);
|
|
340
332
|
EditorPrefs.SetString("GradlePath", envGradlePath);
|
|
341
333
|
Debug.Log("Update gradle path to: " + envGradlePath);
|
|
342
334
|
}
|
|
343
|
-
else
|
|
344
|
-
{
|
|
345
|
-
string gradlePath = GRADLE_ROOT + "/gradle-" + GRADLE_VERSION;
|
|
346
|
-
EditorPrefs.SetString("GradlePath", gradlePath);
|
|
347
|
-
Debug.Log("Update gradle path to: " + gradlePath);
|
|
348
|
-
}
|
|
349
335
|
}
|
|
350
336
|
}
|
|
351
337
|
|
|
@@ -185,6 +185,7 @@ namespace Amanotes.Editor
|
|
|
185
185
|
case IRONSOURCE: return IronSource();
|
|
186
186
|
case MAX: return Max();
|
|
187
187
|
case REVENUECAT: return RevenueCat();
|
|
188
|
+
case SQLITE_ANALYTICS: return SqliteAnalytics();
|
|
188
189
|
default:
|
|
189
190
|
Debug.LogWarning($"Unsupported sdkID <{sdkId}>");
|
|
190
191
|
break;
|
|
@@ -329,6 +330,11 @@ namespace Amanotes.Editor
|
|
|
329
330
|
return version?.Trim();
|
|
330
331
|
}
|
|
331
332
|
|
|
333
|
+
internal static string SqliteAnalytics()
|
|
334
|
+
{
|
|
335
|
+
return "1.0.0";
|
|
336
|
+
}
|
|
337
|
+
|
|
332
338
|
internal static string RevenueCat()
|
|
333
339
|
{
|
|
334
340
|
string[] files = Directory.GetFiles("Assets", "PurchasesWrapper.java", SearchOption.AllDirectories);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/Runtime/Ad/AdLogic.cs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
using System;
|
|
1
2
|
using System.Collections;
|
|
2
3
|
using UnityEngine;
|
|
3
4
|
using static Amanotes.Core.Internal.Logging;
|
|
@@ -131,14 +132,9 @@ namespace Amanotes.Core.Internal
|
|
|
131
132
|
private Coroutine _showAdRoutine;
|
|
132
133
|
protected bool _isRequesting;
|
|
133
134
|
protected bool _isInterstitial => adType == AdType.Interstitial;
|
|
134
|
-
private bool isFocus;
|
|
135
135
|
|
|
136
136
|
public void StartShowAd()
|
|
137
137
|
{
|
|
138
|
-
isFocus = false;
|
|
139
|
-
unityCallbacks.OnApplicationFocus -= OnApplicationFocus;
|
|
140
|
-
unityCallbacks.OnApplicationFocus += OnApplicationFocus;
|
|
141
|
-
|
|
142
138
|
if (_showState != ShowAdsState.None)
|
|
143
139
|
{
|
|
144
140
|
LogWarning("[Ad] StartShowAd() --> Invalid _showState: " + _showState);
|
|
@@ -160,14 +156,6 @@ namespace Amanotes.Core.Internal
|
|
|
160
156
|
_showAdRoutine = _instance.StartCoroutine(ShowAdRoutine());
|
|
161
157
|
}
|
|
162
158
|
|
|
163
|
-
private void OnApplicationFocus(bool focus)
|
|
164
|
-
{
|
|
165
|
-
if (!focus) return;
|
|
166
|
-
|
|
167
|
-
unityCallbacks.OnApplicationFocus -= OnApplicationFocus;
|
|
168
|
-
isFocus = true;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
159
|
public void StopShowAd()
|
|
172
160
|
{
|
|
173
161
|
Log($"[Ad] {adType} StopShowAd");
|
|
@@ -235,14 +223,61 @@ namespace Amanotes.Core.Internal
|
|
|
235
223
|
_showState = ShowAdsState.Showing;
|
|
236
224
|
OnAdShowReadyStatus(true, AdShowReadyStatus.Ready);
|
|
237
225
|
TriggerBeforeAdShow();
|
|
226
|
+
#if !UNITY_EDITOR && UNITY_ANDROID
|
|
227
|
+
GDKUtils.StartCoroutine(HandleAdActivityKilledSilently());
|
|
228
|
+
#endif
|
|
238
229
|
ShowAd();
|
|
239
230
|
|
|
240
231
|
yield return WaitForMediationCallback();
|
|
241
232
|
|
|
242
|
-
// wait 1 frame in case hasReward callback after onClose
|
|
243
|
-
if (!context.hasReward && adType == AdType.VideoReward && !context.hasFailedOrCancelled) yield return null;
|
|
244
233
|
ShowAdRoutineCompleted(context.isSuccess);
|
|
245
234
|
}
|
|
235
|
+
|
|
236
|
+
private IEnumerator WaitForMediationCallback()
|
|
237
|
+
{
|
|
238
|
+
while (context != null && !context.potentiallyCompleted)
|
|
239
|
+
{
|
|
240
|
+
yield return null;
|
|
241
|
+
}
|
|
242
|
+
// wait 1 frame in case hasReward callback after onClose
|
|
243
|
+
if (context != null && !context.hasReward && adType == AdType.VideoReward && !context.hasFailedOrCancelled) yield return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private IEnumerator HandleAdActivityKilledSilently()
|
|
247
|
+
{
|
|
248
|
+
bool hasOutOfFocus = false;
|
|
249
|
+
bool hasFocusAgain = false;
|
|
250
|
+
Action<bool> onApplicationFocusCallback = focus =>
|
|
251
|
+
{
|
|
252
|
+
if (focus)
|
|
253
|
+
{
|
|
254
|
+
hasFocusAgain = true;
|
|
255
|
+
}
|
|
256
|
+
else
|
|
257
|
+
{
|
|
258
|
+
hasOutOfFocus = true;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
unityCallbacks.OnApplicationFocus += onApplicationFocusCallback;
|
|
262
|
+
int counter = 0;
|
|
263
|
+
while (context != null && !context.potentiallyCompleted)
|
|
264
|
+
{
|
|
265
|
+
if (hasFocusAgain && hasOutOfFocus)
|
|
266
|
+
{
|
|
267
|
+
// Start counting frames only after closed the ad
|
|
268
|
+
counter++;
|
|
269
|
+
}
|
|
270
|
+
if (counter > MAX_FRAMES_WAIT_FOR_CALLBACK)
|
|
271
|
+
{
|
|
272
|
+
// Timeout reached, force close the ad
|
|
273
|
+
LogError("Force close the ad");
|
|
274
|
+
StopShowAd();
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
yield return null;
|
|
278
|
+
}
|
|
279
|
+
unityCallbacks.OnApplicationFocus -= onApplicationFocusCallback;
|
|
280
|
+
}
|
|
246
281
|
|
|
247
282
|
private void EndShowAdRoutineWithFailure(AdShowReadyStatus status)
|
|
248
283
|
{
|
|
@@ -279,37 +314,6 @@ namespace Amanotes.Core.Internal
|
|
|
279
314
|
}
|
|
280
315
|
}
|
|
281
316
|
|
|
282
|
-
private IEnumerator WaitForMediationCallback()
|
|
283
|
-
{
|
|
284
|
-
#if !UNITY_EDITOR
|
|
285
|
-
while (!isFocus) yield return null;
|
|
286
|
-
#endif
|
|
287
|
-
|
|
288
|
-
int wait4CallbackCounter = 0;
|
|
289
|
-
while (!context.potentiallyCompleted && wait4CallbackCounter <= MAX_FRAMES_WAIT_FOR_CALLBACK)
|
|
290
|
-
{
|
|
291
|
-
wait4CallbackCounter++;
|
|
292
|
-
yield return null;
|
|
293
|
-
}
|
|
294
|
-
Log($"[Ad] Waiting for potentially completed callback: {wait4CallbackCounter} frames");
|
|
295
|
-
|
|
296
|
-
if (!context.hasClosed) yield return WaitForAdClosedCallback();
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
private IEnumerator WaitForAdClosedCallback()
|
|
300
|
-
{
|
|
301
|
-
int counter = 0;
|
|
302
|
-
while (!context.hasClosed && counter <= MAX_FRAMES_WAIT_FOR_CALLBACK)
|
|
303
|
-
{
|
|
304
|
-
counter++;
|
|
305
|
-
yield return null;
|
|
306
|
-
}
|
|
307
|
-
Log($"[Ad] Waiting for ad closed callback: {counter} frames");
|
|
308
|
-
if (context.hasClosed) yield break;
|
|
309
|
-
LogWarning("[Ad] Force close ad (mediation missing OnAdClose callback)");
|
|
310
|
-
OnAdClosed();
|
|
311
|
-
}
|
|
312
|
-
|
|
313
317
|
private void ShowAdRoutineCompleted(bool isSuccess)
|
|
314
318
|
{
|
|
315
319
|
if (_loadState == LoadAdsState.Ready)
|
|
@@ -320,9 +324,7 @@ namespace Amanotes.Core.Internal
|
|
|
320
324
|
|
|
321
325
|
_showState = isSuccess ? ShowAdsState.ShowSuccess : ShowAdsState.ShowFail;
|
|
322
326
|
|
|
323
|
-
|
|
324
|
-
context.showFinishAt = Time.realtimeSinceStartup;
|
|
325
|
-
context.onAdResult?.Invoke(isSuccess);
|
|
327
|
+
|
|
326
328
|
|
|
327
329
|
var localData = Ads.localData;
|
|
328
330
|
var stat = _isInterstitial ? localData.interstitial : localData.reward;
|
|
@@ -341,8 +343,13 @@ namespace Amanotes.Core.Internal
|
|
|
341
343
|
TriggerOnAfterAdShow();
|
|
342
344
|
|
|
343
345
|
Log($"[Ad] {adType} ShowAdRoutineCompleted: {isSuccess}\n{JsonUtility.ToJson(Ads.context)}");
|
|
346
|
+
|
|
347
|
+
// Extra callback
|
|
348
|
+
context.showFinishAt = Time.realtimeSinceStartup;
|
|
349
|
+
var resultCallback = context.onAdResult;
|
|
344
350
|
Ads.context = null;
|
|
345
351
|
_showState = ShowAdsState.None;
|
|
352
|
+
resultCallback?.Invoke(isSuccess);
|
|
346
353
|
}
|
|
347
354
|
|
|
348
355
|
private void TriggerBeforeAdShow()
|
|
@@ -35,7 +35,7 @@ namespace Amanotes.Core.Internal
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
public bool hasFailedOrCancelled => (adCallback & (AdCallback.Fail | AdCallback.Cancel)) != 0;
|
|
38
|
-
public bool potentiallyCompleted => (adCallback & (AdCallback.Close | AdCallback.Fail | AdCallback.
|
|
38
|
+
public bool potentiallyCompleted => (adCallback & (AdCallback.Close | AdCallback.Fail | AdCallback.Cancel)) != 0;
|
|
39
39
|
public bool hasReward => (adCallback & AdCallback.Reward) != 0;
|
|
40
40
|
public bool hasClosed => (adCallback & AdCallback.Close) != 0;
|
|
41
41
|
public bool hasClicked => (adCallback & AdCallback.Click) != 0;
|
|
@@ -68,8 +68,9 @@ namespace Amanotes.Core
|
|
|
68
68
|
doNotIncreaseCounter = false;
|
|
69
69
|
isLoggedImmediately = false;
|
|
70
70
|
overrideConfig = false;
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
|
|
72
|
+
GDKPool.ReturnAndNullize(ref ignoreAdapterIds);
|
|
73
|
+
GDKPool.ReturnAndNullize(ref allowAdapterIds);
|
|
73
74
|
_detail = null;
|
|
74
75
|
}
|
|
75
76
|
|
|
@@ -307,9 +308,8 @@ namespace Amanotes.Core
|
|
|
307
308
|
internal static readonly List<AnalyticsAdapter> listAdapters = new List<AnalyticsAdapter>();
|
|
308
309
|
internal static readonly AnalyticsData localData = new AnalyticsData().LoadJsonFromFile().BuildCache();
|
|
309
310
|
internal static SessionStat sessionStat;
|
|
310
|
-
internal static readonly GDKPool<
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
internal static readonly GDKPool.Pool<EventParams> epPool = new GDKPool.Pool<EventParams>(1);
|
|
312
|
+
|
|
313
313
|
internal static readonly Dictionary<string, string> reservedPrefixes = new Dictionary<string, string>()
|
|
314
314
|
{
|
|
315
315
|
{ "firebase_", "frb_" },
|
|
@@ -515,32 +515,31 @@ namespace Amanotes.Core
|
|
|
515
515
|
{
|
|
516
516
|
Profiler.BeginSample("AmaGDK.Analytics.NormalizeEventName()");
|
|
517
517
|
{
|
|
518
|
-
var
|
|
519
|
-
|
|
520
|
-
NormalizeKeyString(ref eventName, sb, dryRun);
|
|
518
|
+
var warningLog = GDKPool.GetStringBuilder(8);
|
|
519
|
+
NormalizeKeyString(ref eventName, warningLog, dryRun);
|
|
521
520
|
if (reservedEvents.Contains(eventName))
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
521
|
+
{
|
|
522
|
+
warningLog.AppendLine($" - EventName <{eventName}> is reserved and should not be used");
|
|
523
|
+
}
|
|
524
|
+
if (warningLog.Length > 0 && dryRun) LogWarningOnce($"NormalizeEventName <{eventName}> warnings:\n{warningLog}\n", $"NormalizeEventName<{eventName}>");
|
|
525
|
+
GDKPool.Return(warningLog);
|
|
525
526
|
}
|
|
526
527
|
Profiler.EndSample();
|
|
527
528
|
}
|
|
528
529
|
|
|
529
530
|
internal static void NormalizeKeyString(ref string key, StringBuilder warningLog, bool dryRun)
|
|
530
531
|
{
|
|
532
|
+
if (string.IsNullOrWhiteSpace(key))
|
|
533
|
+
{
|
|
534
|
+
LogWarning($" - Name should not be empty");
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
531
538
|
Profiler.BeginSample("AmaGDK.Analytics.NormalizeKeyString()");
|
|
532
539
|
{
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
Profiler.EndSample();
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
var tempLog = Config.common.logLevel == LogLevel.None ? null : sbPool.Get();
|
|
541
|
-
tempLog?.Clear();
|
|
542
|
-
var tempKey = sbPool.Get();
|
|
543
|
-
tempKey.Clear();
|
|
540
|
+
var willLog = Config.common.logLevel != LogLevel.None;
|
|
541
|
+
var tempLog = willLog ? GDKPool.GetStringBuilder() : null;
|
|
542
|
+
var tempKey = GDKPool.GetStringBuilder();
|
|
544
543
|
|
|
545
544
|
var hasChanged = false;
|
|
546
545
|
var trimKey = key.Trim();
|
|
@@ -563,16 +562,16 @@ namespace Amanotes.Core
|
|
|
563
562
|
}
|
|
564
563
|
}
|
|
565
564
|
|
|
566
|
-
if (hasChanged)
|
|
565
|
+
if (hasChanged && willLog)
|
|
567
566
|
{
|
|
568
|
-
tempLog
|
|
567
|
+
tempLog.Append(" - Name may only contain alphanumeric characters and underscores");
|
|
569
568
|
}
|
|
570
569
|
|
|
571
570
|
if (tempKey.Length > 0 && !IsAlphabet(tempKey[0]))
|
|
572
571
|
{
|
|
573
572
|
hasChanged = true;
|
|
574
573
|
tempKey.Insert(0, 'E');
|
|
575
|
-
tempLog
|
|
574
|
+
if (willLog) tempLog.Append($" - Name must start with a letter: <{key}>");
|
|
576
575
|
}
|
|
577
576
|
|
|
578
577
|
foreach (var prefix in reservedPrefixes)
|
|
@@ -581,13 +580,13 @@ namespace Amanotes.Core
|
|
|
581
580
|
hasChanged = true;
|
|
582
581
|
tempKey.Remove(0, prefix.Key.Length);
|
|
583
582
|
tempKey.Insert(0, prefix.Value);
|
|
584
|
-
tempLog
|
|
583
|
+
if (willLog) tempLog.AppendLine($" - <{key}> starts with reserved prefix <{prefix}>");
|
|
585
584
|
}
|
|
586
585
|
|
|
587
586
|
if (trimKey.Length > 40)
|
|
588
587
|
{
|
|
589
588
|
hasChanged = true;
|
|
590
|
-
tempLog
|
|
589
|
+
if (willLog) tempLog.AppendLine($" - Name is too long ({key.Length} > 40): <{key}> ");
|
|
591
590
|
tempKey.Length = 40;
|
|
592
591
|
}
|
|
593
592
|
|
|
@@ -598,8 +597,8 @@ namespace Amanotes.Core
|
|
|
598
597
|
if (hasChanged) key = tempKey.ToString();
|
|
599
598
|
}
|
|
600
599
|
|
|
601
|
-
|
|
602
|
-
|
|
600
|
+
GDKPool.Return(tempLog);
|
|
601
|
+
GDKPool.Return(tempKey);
|
|
603
602
|
}
|
|
604
603
|
Profiler.EndSample();
|
|
605
604
|
}
|
|
@@ -702,9 +701,17 @@ namespace Amanotes.Core
|
|
|
702
701
|
adapters = adapterIDs;
|
|
703
702
|
}
|
|
704
703
|
|
|
705
|
-
public
|
|
704
|
+
public void AppendAsTSV(StringBuilder sb)
|
|
706
705
|
{
|
|
707
|
-
|
|
706
|
+
sb.Append(eventNameToSend);
|
|
707
|
+
sb.Append("\t");
|
|
708
|
+
for (var i = 0; i < adapters.Count; i++)
|
|
709
|
+
{
|
|
710
|
+
sb.Append(adapters[i]);
|
|
711
|
+
if (i < adapters.Count - 1) sb.Append(",");
|
|
712
|
+
}
|
|
713
|
+
sb.Append("\t");
|
|
714
|
+
sb.Append(parameters);
|
|
708
715
|
}
|
|
709
716
|
}
|
|
710
717
|
|
|
@@ -856,8 +863,8 @@ namespace Amanotes.Core
|
|
|
856
863
|
{
|
|
857
864
|
private const string TSV_HEADER_ROW = "EventName\tAdapters\tParameters";
|
|
858
865
|
private readonly string fileName = nameof(SessionStat) + DateTime.Now.ToString("_yyMMdd_HHmmss") + ".tsv";
|
|
859
|
-
private StringBuilder contents = new StringBuilder();
|
|
860
866
|
private bool isFirstSave = true;
|
|
867
|
+
private StringBuilder contents = new StringBuilder(8192);
|
|
861
868
|
private readonly ConcurrentQueue<EventStat> lastFrameEventStats = new ConcurrentQueue<EventStat>();
|
|
862
869
|
internal readonly ConcurrentQueue<EventStat> savedEventStatsInLastFrame = new ConcurrentQueue<EventStat>();
|
|
863
870
|
|
|
@@ -878,10 +885,10 @@ namespace Amanotes.Core
|
|
|
878
885
|
}
|
|
879
886
|
while (lastFrameEventStats.TryDequeue(out var stat))
|
|
880
887
|
{
|
|
881
|
-
|
|
888
|
+
stat.AppendAsTSV(contents);
|
|
882
889
|
savedEventStatsInLastFrame.Enqueue(stat);
|
|
883
890
|
}
|
|
884
|
-
|
|
891
|
+
|
|
885
892
|
GDKFileUtils.SaveAppend(fileName, contents.ToString());
|
|
886
893
|
}
|
|
887
894
|
|
|
@@ -972,7 +979,7 @@ namespace Amanotes.Core
|
|
|
972
979
|
}
|
|
973
980
|
|
|
974
981
|
eventData.overrideConfig = true;
|
|
975
|
-
eventData.ignoreAdapterIds ??=
|
|
982
|
+
eventData.ignoreAdapterIds ??= GDKPool.GetHashSet();
|
|
976
983
|
|
|
977
984
|
foreach (string mId in analyticIds)
|
|
978
985
|
{
|
|
@@ -998,7 +1005,7 @@ namespace Amanotes.Core
|
|
|
998
1005
|
}
|
|
999
1006
|
|
|
1000
1007
|
eventData.overrideConfig = true;
|
|
1001
|
-
eventData.allowAdapterIds ??=
|
|
1008
|
+
eventData.allowAdapterIds ??= GDKPool.GetHashSet();
|
|
1002
1009
|
|
|
1003
1010
|
foreach (string mId in analyticIds)
|
|
1004
1011
|
{
|
|
@@ -1080,12 +1087,12 @@ namespace Amanotes.Core
|
|
|
1080
1087
|
{
|
|
1081
1088
|
string eventName = (ap as EventParams)?.eventName;
|
|
1082
1089
|
|
|
1083
|
-
var sb =
|
|
1090
|
+
var sb = GDKPool.GetStringBuilder();
|
|
1084
1091
|
sb.Clear();
|
|
1085
1092
|
NormalizeKeyString(ref key, sb, AmaGDK.Config.analytics.normalizeEventName);
|
|
1086
1093
|
|
|
1087
1094
|
if (sb.Length > 0) LogWarningOnce($"Warning for event <{eventName}>, param <{key}>:\n{sb}", $"NormalizeParamKey <{eventName}>");
|
|
1088
|
-
|
|
1095
|
+
GDKPool.Return(sb);
|
|
1089
1096
|
|
|
1090
1097
|
if (string.IsNullOrWhiteSpace(key))
|
|
1091
1098
|
{
|
package/Runtime/AmaGDK.Device.cs
CHANGED
|
@@ -67,16 +67,16 @@ namespace Amanotes.Core.Internal
|
|
|
67
67
|
|
|
68
68
|
if (AmaGDK.Config.common.logLevel == LogLevel.Verbose)
|
|
69
69
|
{
|
|
70
|
-
var stringBuilder =
|
|
70
|
+
var stringBuilder = GDKPool.GetStringBuilder();
|
|
71
71
|
stringBuilder.Append("Device info:");
|
|
72
72
|
stringBuilder.AppendLine("DeviceSpec: " + result);
|
|
73
73
|
stringBuilder.AppendLine("systemMemorySize: " + SystemInfo.systemMemorySize);
|
|
74
74
|
stringBuilder.AppendLine("processorCount: " + SystemInfo.processorCount);
|
|
75
75
|
stringBuilder.AppendLine("processorFrequency: " + SystemInfo.processorFrequency);
|
|
76
|
-
stringBuilder.AppendLine("graphicsMemorySize: " + SystemInfo.graphicsMemorySize);
|
|
77
|
-
|
|
78
|
-
Logging.Log(stringBuilder.ToString());
|
|
76
|
+
stringBuilder.AppendLine("graphicsMemorySize: " + SystemInfo.graphicsMemorySize);
|
|
77
|
+
Logging.Log(GDKPool.ReturnString(stringBuilder));
|
|
79
78
|
}
|
|
79
|
+
|
|
80
80
|
return result;
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -214,7 +214,7 @@ namespace Amanotes.Core
|
|
|
214
214
|
Profiler.BeginSample("AmaGDK.RemoteConfig.SaveCache()");
|
|
215
215
|
|
|
216
216
|
_fetchState = FetchState.SaveCache;
|
|
217
|
-
StringBuilder sb =
|
|
217
|
+
StringBuilder sb = GDKPool.GetStringBuilder(8);
|
|
218
218
|
|
|
219
219
|
//Add embedded remote config version to force load from embedded config after app update
|
|
220
220
|
sb.AppendLine($"{VERSION_PREFIX}{EmbedVersion}");
|
|
@@ -226,6 +226,8 @@ namespace Amanotes.Core
|
|
|
226
226
|
|
|
227
227
|
string filePath = GDKFileUtils.GetPath(fileName);
|
|
228
228
|
GDKFileUtils.Save(filePath, sb.ToString());
|
|
229
|
+
GDKPool.Return(sb);
|
|
230
|
+
|
|
229
231
|
Log($"[RemoteConfig] Saved remote config to {filePath}");
|
|
230
232
|
Profiler.EndSample();
|
|
231
233
|
NextState();
|
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.
|
|
20
|
+
public const string VERSION = "0.2.70-2";
|
|
21
21
|
|
|
22
22
|
internal static Status _status = Status.None;
|
|
23
23
|
private static ConfigAsset _config = null;
|
|
@@ -176,7 +176,7 @@ namespace Amanotes.Core
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
var startTime = Time.realtimeSinceStartup;
|
|
179
|
-
var sb =
|
|
179
|
+
var sb = GDKPool.GetStringBuilder();
|
|
180
180
|
|
|
181
181
|
InitModules();
|
|
182
182
|
|
|
@@ -211,6 +211,7 @@ namespace Amanotes.Core
|
|
|
211
211
|
|
|
212
212
|
sb.Append($"\nMapping UserId: {User.UserId}\n");
|
|
213
213
|
Debug.Log($"AmaGDK v{VERSION} | {READY} in {initDuration:#0.00}s\n\n{sb}");
|
|
214
|
+
GDKPool.Return(sb);
|
|
214
215
|
}
|
|
215
216
|
|
|
216
217
|
private void ConfigGeoLocation()
|
|
@@ -354,6 +355,7 @@ namespace Amanotes.Core
|
|
|
354
355
|
public const string IRONSOURCE = "IronSource";
|
|
355
356
|
public const string MAX = "Max";
|
|
356
357
|
public const string REVENUECAT = "RevenueCat";
|
|
358
|
+
public const string SQLITE_ANALYTICS = "SqliteAnalytics";
|
|
357
359
|
}
|
|
358
360
|
}
|
|
359
361
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
using Amanotes.Core.Internal;
|
|
1
2
|
using System;
|
|
2
3
|
using System.Collections.Generic;
|
|
3
4
|
using System.Linq;
|
|
@@ -82,14 +83,15 @@ namespace Amanotes.Core
|
|
|
82
83
|
|
|
83
84
|
internal void ValidateParams(string eventName, Dictionary<string, object> eventParams)
|
|
84
85
|
{
|
|
85
|
-
|
|
86
|
+
var logBuilder = GDKPool.GetStringBuilder();
|
|
87
|
+
|
|
86
88
|
logBuilder.AppendLine($"Analytics quality violation found for event <{eventName}>");
|
|
87
|
-
|
|
88
|
-
HashSet<string> checkedParameters =
|
|
89
|
+
var isValid = true;
|
|
90
|
+
HashSet<string> checkedParameters = GDKPool.GetHashSet();
|
|
89
91
|
|
|
90
92
|
foreach (var kvp in DicParam)
|
|
91
93
|
{
|
|
92
|
-
if (!eventParams.
|
|
94
|
+
if (!eventParams.TryGetValue(kvp.Key, out object paramValue))
|
|
93
95
|
{
|
|
94
96
|
if (!kvp.Value.isOptional)
|
|
95
97
|
{
|
|
@@ -99,8 +101,6 @@ namespace Amanotes.Core
|
|
|
99
101
|
continue;
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
object paramValue = eventParams[kvp.Key];
|
|
103
|
-
|
|
104
104
|
if (!ValidateParamType(paramValue, kvp.Value.type))
|
|
105
105
|
{
|
|
106
106
|
isValid = false;
|
|
@@ -117,8 +117,11 @@ namespace Amanotes.Core
|
|
|
117
117
|
isValid = false;
|
|
118
118
|
logBuilder.AppendLine($"[+] {parameter.Key}");
|
|
119
119
|
}
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
if (!isValid) LogWarningOnce(logBuilder.ToString());
|
|
122
|
+
|
|
123
|
+
GDKPool.Return(logBuilder);
|
|
124
|
+
GDKPool.Return(checkedParameters);
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
private bool ValidateParamType(object paramValue, ParamType paramType)
|
|
@@ -179,7 +182,7 @@ namespace Amanotes.Core
|
|
|
179
182
|
[ContextMenu("Validate Unique EventNames")]
|
|
180
183
|
private void ValidateUniqueEventNames()
|
|
181
184
|
{
|
|
182
|
-
HashSet<string> eventNames =
|
|
185
|
+
HashSet<string> eventNames = GDKPool.GetHashSet();
|
|
183
186
|
|
|
184
187
|
lstEvent = lstEvent.OrderBy(evt => evt.eventName).ToList();
|
|
185
188
|
|
|
@@ -189,7 +192,7 @@ namespace Amanotes.Core
|
|
|
189
192
|
|
|
190
193
|
if (!eventNames.Add(eventQuality.eventName)) Debug.LogWarning($"Duplicate event name found: {eventQuality.eventName}. Event names must be unique.");
|
|
191
194
|
|
|
192
|
-
HashSet<string> paramNames =
|
|
195
|
+
HashSet<string> paramNames = GDKPool.GetHashSet();
|
|
193
196
|
|
|
194
197
|
eventQuality.lstParam = eventQuality.lstParam.OrderBy(param => param.paramName).ToList();
|
|
195
198
|
|
|
@@ -199,7 +202,11 @@ namespace Amanotes.Core
|
|
|
199
202
|
|
|
200
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.");
|
|
201
204
|
}
|
|
205
|
+
|
|
206
|
+
GDKPool.Return(paramNames);
|
|
202
207
|
}
|
|
208
|
+
|
|
209
|
+
GDKPool.Return(eventNames);
|
|
203
210
|
EditorUtility.SetDirty(this);
|
|
204
211
|
}
|
|
205
212
|
#endif
|
|
@@ -86,15 +86,13 @@ namespace Amanotes.Core
|
|
|
86
86
|
|
|
87
87
|
private void OnAudioDeviceAdded(string param)
|
|
88
88
|
{
|
|
89
|
-
if (string.IsNullOrEmpty(param))
|
|
90
|
-
return;
|
|
89
|
+
if (string.IsNullOrEmpty(param)) return;
|
|
91
90
|
onChanged?.Invoke(param);
|
|
92
91
|
}
|
|
93
92
|
|
|
94
93
|
private void OnAudioDeviceRemoved(string param)
|
|
95
94
|
{
|
|
96
|
-
if (string.IsNullOrEmpty(param))
|
|
97
|
-
return;
|
|
95
|
+
if (string.IsNullOrEmpty(param)) return;
|
|
98
96
|
onChanged?.Invoke(param);
|
|
99
97
|
}
|
|
100
98
|
|
|
@@ -170,15 +168,18 @@ namespace Amanotes.Core
|
|
|
170
168
|
}
|
|
171
169
|
|
|
172
170
|
StringBuilder sb = null;
|
|
173
|
-
|
|
174
|
-
for (int i = 0; i < str.Length; i++)
|
|
171
|
+
for (var i = 0; i < str.Length; i++)
|
|
175
172
|
{
|
|
176
173
|
char ch = str[i];
|
|
177
174
|
|
|
178
175
|
if (char.IsSurrogate(ch))
|
|
179
176
|
{
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
if (sb == null)
|
|
178
|
+
{
|
|
179
|
+
sb = GDKPool.GetStringBuilder();
|
|
180
|
+
sb.Append(str, 0, i);
|
|
181
|
+
}
|
|
182
|
+
|
|
182
183
|
sb.Append(replacementCharacter);
|
|
183
184
|
|
|
184
185
|
// If there is a high+low surrogate, skip the low surrogate
|
|
@@ -187,13 +188,13 @@ namespace Amanotes.Core
|
|
|
187
188
|
i++;
|
|
188
189
|
}
|
|
189
190
|
}
|
|
190
|
-
else
|
|
191
|
+
else
|
|
191
192
|
{
|
|
192
|
-
sb
|
|
193
|
+
sb?.Append(ch);
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
|
-
|
|
196
|
-
return sb == null ? str :
|
|
196
|
+
|
|
197
|
+
return sb == null ? str : GDKPool.ReturnString(sb);
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
package/Runtime/Core/GDKPool.cs
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
using System;
|
|
1
2
|
using System.Collections.Concurrent;
|
|
3
|
+
using System.Collections.Generic;
|
|
4
|
+
using System.Text;
|
|
2
5
|
|
|
3
6
|
namespace Amanotes.Core.Internal
|
|
4
7
|
{
|
|
@@ -6,32 +9,95 @@ namespace Amanotes.Core.Internal
|
|
|
6
9
|
{
|
|
7
10
|
void Reset();
|
|
8
11
|
}
|
|
9
|
-
|
|
10
|
-
public class GDKPool
|
|
12
|
+
|
|
13
|
+
public static class GDKPool
|
|
11
14
|
{
|
|
12
|
-
|
|
15
|
+
internal class Pool<T> where T : new()
|
|
16
|
+
{
|
|
17
|
+
private readonly ConcurrentQueue<T> _pool;
|
|
18
|
+
public Pool(int initialCapacity = 4)
|
|
19
|
+
{
|
|
20
|
+
_pool = new ConcurrentQueue<T>();
|
|
21
|
+
for (var i = 0; i < initialCapacity; i++)
|
|
22
|
+
{
|
|
23
|
+
_pool.Enqueue(new T());
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public Pool(int initialCapacity, Func<T> newT)
|
|
28
|
+
{
|
|
29
|
+
_pool = new ConcurrentQueue<T>();
|
|
30
|
+
for (var i = 0; i < initialCapacity; i++)
|
|
31
|
+
{
|
|
32
|
+
_pool.Enqueue(newT());
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public T Get()
|
|
37
|
+
{
|
|
38
|
+
if (!_pool.TryDequeue(out var obj)) return new T();
|
|
39
|
+
if (obj is IPoolItem poolItem) poolItem.Reset();
|
|
40
|
+
return obj;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public void Return(T obj)
|
|
44
|
+
{
|
|
45
|
+
_pool.Enqueue(obj);
|
|
46
|
+
}
|
|
13
47
|
|
|
14
|
-
|
|
48
|
+
public int Count => _pool.Count;
|
|
49
|
+
}
|
|
50
|
+
private static readonly Pool<StringBuilder> sbPool1KB = new Pool<StringBuilder>(8, () => new StringBuilder(1024));
|
|
51
|
+
private static readonly Pool<StringBuilder> sbPool8KB = new Pool<StringBuilder>(8, () => new StringBuilder(8*1024));
|
|
52
|
+
private static readonly Pool<HashSet<string>> hashsetPool = new Pool<HashSet<string>>(4);
|
|
53
|
+
|
|
54
|
+
public static StringBuilder GetStringBuilder(int capacityInKb = 1)
|
|
55
|
+
{
|
|
56
|
+
StringBuilder result = capacityInKb <= 1 ? sbPool1KB.Get() : sbPool8KB.Get();
|
|
57
|
+
result.Clear();
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public static HashSet<string> GetHashSet()
|
|
62
|
+
{
|
|
63
|
+
HashSet<string> result = hashsetPool.Get();
|
|
64
|
+
result.Clear();
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public static string ReturnString(StringBuilder sb)
|
|
15
69
|
{
|
|
16
|
-
|
|
17
|
-
|
|
70
|
+
if (sb == null) return null;
|
|
71
|
+
var result = sb.ToString();
|
|
72
|
+
Return(sb);
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public static void Return(StringBuilder sb)
|
|
77
|
+
{
|
|
78
|
+
if (sb == null) return;
|
|
79
|
+
int capacity = sb.Capacity;
|
|
80
|
+
|
|
81
|
+
if (capacity == 1024)
|
|
18
82
|
{
|
|
19
|
-
|
|
83
|
+
sbPool1KB.Return(sb);
|
|
84
|
+
return;
|
|
20
85
|
}
|
|
86
|
+
|
|
87
|
+
sbPool8KB.Return(sb);
|
|
21
88
|
}
|
|
22
|
-
|
|
23
|
-
public
|
|
89
|
+
|
|
90
|
+
public static void Return(HashSet<string> hashset)
|
|
24
91
|
{
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
return obj;
|
|
92
|
+
if (hashset == null) return;
|
|
93
|
+
hashsetPool.Return(hashset);
|
|
28
94
|
}
|
|
29
|
-
|
|
30
|
-
public void
|
|
95
|
+
|
|
96
|
+
public static void ReturnAndNullize(ref HashSet<string> hashset)
|
|
31
97
|
{
|
|
32
|
-
|
|
98
|
+
if (hashset == null) return;
|
|
99
|
+
hashsetPool.Return(hashset);
|
|
100
|
+
hashset = null;
|
|
33
101
|
}
|
|
34
|
-
|
|
35
|
-
public int Count => _pool.Count;
|
|
36
102
|
}
|
|
37
103
|
}
|
|
@@ -68,16 +68,13 @@ namespace Amanotes.Core.Internal
|
|
|
68
68
|
{
|
|
69
69
|
public static Action<string> logNonfatalErrorHook = null;
|
|
70
70
|
private static readonly HashSet<string> nonFatalDict = new HashSet<string>();
|
|
71
|
-
|
|
72
|
-
public static readonly StringBuilder tmpWarningSB = new StringBuilder();
|
|
73
71
|
public static readonly HashSet<string> logWarningDict = new HashSet<string>();
|
|
74
72
|
|
|
75
73
|
public static void LogWarningOnce(string message, string key = null)
|
|
76
74
|
{
|
|
77
75
|
if (Config.common.logLevel < LogLevel.Warning) return;
|
|
78
76
|
if (string.IsNullOrEmpty(key)) key = message;
|
|
79
|
-
if (logWarningDict.
|
|
80
|
-
logWarningDict.Add(key);
|
|
77
|
+
if (!logWarningDict.Add(key)) return;
|
|
81
78
|
Debug.LogWarning($"AmaGDK {message}");
|
|
82
79
|
}
|
|
83
80
|
public static void LogWarning(string message)
|
|
@@ -133,7 +130,7 @@ namespace Amanotes.Core.Internal
|
|
|
133
130
|
}
|
|
134
131
|
|
|
135
132
|
AmaGDK[] result = Resources.FindObjectsOfTypeAll<AmaGDK>();
|
|
136
|
-
for (
|
|
133
|
+
for (var i = 0; i < result.Length; i++)
|
|
137
134
|
{
|
|
138
135
|
if (result[i] == null) continue;
|
|
139
136
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
using System;
|
|
2
2
|
using System.Collections;
|
|
3
|
-
using System.Collections.Concurrent;
|
|
4
3
|
using System.Collections.Generic;
|
|
5
4
|
using System.IO;
|
|
6
5
|
using System.Linq;
|
|
@@ -15,8 +14,6 @@ namespace Amanotes.Core.Internal
|
|
|
15
14
|
{
|
|
16
15
|
public static partial class GDKUtils
|
|
17
16
|
{
|
|
18
|
-
|
|
19
|
-
|
|
20
17
|
public static bool IsFromTemplate(string formattedString, string template)
|
|
21
18
|
{
|
|
22
19
|
// Split the original template into segments based on the positions of placeholders
|
|
@@ -271,7 +268,7 @@ namespace Amanotes.Core.Internal
|
|
|
271
268
|
|
|
272
269
|
public static void ClearCacheData()
|
|
273
270
|
{
|
|
274
|
-
var sb =
|
|
271
|
+
var sb = GDKPool.GetStringBuilder();
|
|
275
272
|
sb.AppendLine("[AmaGDK] All cached data & stats cleared");
|
|
276
273
|
|
|
277
274
|
if (!Directory.Exists(basePath))
|
|
@@ -408,16 +405,16 @@ namespace Amanotes.Core.Internal
|
|
|
408
405
|
return DictionaryToJson(new Dictionary<string, object>
|
|
409
406
|
{ { key, value } });
|
|
410
407
|
}
|
|
411
|
-
|
|
408
|
+
|
|
412
409
|
public static string DictionaryToJson(IDictionary inputDict, bool format = false, bool checkJsonInString = true)
|
|
413
410
|
{
|
|
414
411
|
if (inputDict == null) return "{}";
|
|
415
412
|
|
|
416
413
|
Profiler.BeginSample("AmaGDK.JsonUtils.DictionaryToJson()");
|
|
417
|
-
var jsonSb =
|
|
414
|
+
var jsonSb = GDKPool.GetStringBuilder(8);
|
|
418
415
|
jsonSb.Append("{");
|
|
419
416
|
bool isFirstElement = true;
|
|
420
|
-
|
|
417
|
+
|
|
421
418
|
foreach (DictionaryEntry entry in inputDict)
|
|
422
419
|
{
|
|
423
420
|
if (entry.Key == null || string.IsNullOrEmpty(entry.Key.ToString())) continue;
|
|
@@ -432,9 +429,9 @@ namespace Amanotes.Core.Internal
|
|
|
432
429
|
jsonSb.AppendFormat("{0}\"{1}\":", format ? "\n" : "", key);
|
|
433
430
|
AppendJsonValue(ref jsonSb, key, value, format, checkJsonInString);
|
|
434
431
|
}
|
|
435
|
-
|
|
432
|
+
|
|
436
433
|
jsonSb.AppendFormat("{0}{1}", format ? "\n" : "", "}");
|
|
437
|
-
|
|
434
|
+
var result = GDKPool.ReturnString(jsonSb);
|
|
438
435
|
Profiler.EndSample();
|
|
439
436
|
|
|
440
437
|
return result;
|
|
@@ -470,7 +467,7 @@ namespace Amanotes.Core.Internal
|
|
|
470
467
|
|
|
471
468
|
if (value is string)
|
|
472
469
|
{
|
|
473
|
-
|
|
470
|
+
var s = value.ToString();
|
|
474
471
|
if (checkJsonInString)
|
|
475
472
|
{
|
|
476
473
|
if (s.StartsWith("{") && s.EndsWith("}"))
|
|
@@ -484,7 +481,9 @@ namespace Amanotes.Core.Internal
|
|
|
484
481
|
return;
|
|
485
482
|
}
|
|
486
483
|
}
|
|
487
|
-
jsonSb.Append($"\"
|
|
484
|
+
jsonSb.Append($"\"");
|
|
485
|
+
AppendEscapeJsonString(jsonSb, s);
|
|
486
|
+
jsonSb.Append($"\"");
|
|
488
487
|
return;
|
|
489
488
|
}
|
|
490
489
|
|
|
@@ -521,81 +520,33 @@ namespace Amanotes.Core.Internal
|
|
|
521
520
|
/// </summary>
|
|
522
521
|
/// <param name="input">The input string to be escaped.</param>
|
|
523
522
|
/// <returns>The escaped string.</returns>
|
|
524
|
-
|
|
523
|
+
private static readonly Dictionary<char, string> escapeSequences = new Dictionary<char, string>
|
|
524
|
+
{
|
|
525
|
+
{ '\"', "\\\"" },
|
|
526
|
+
{ '\\', @"\\" },
|
|
527
|
+
{ '\b', "\\b" },
|
|
528
|
+
{ '\f', "\\f" },
|
|
529
|
+
{ '\n', "\\n" },
|
|
530
|
+
{ '\r', "\\r" },
|
|
531
|
+
{ '\t', "\\t" }
|
|
532
|
+
};
|
|
533
|
+
public static void AppendEscapeJsonString(StringBuilder sb, string input)
|
|
525
534
|
{
|
|
526
|
-
var sb = new StringBuilder();
|
|
527
535
|
foreach (char c in input)
|
|
528
536
|
{
|
|
529
|
-
|
|
537
|
+
if (escapeSequences.TryGetValue(c, out var escapedValue))
|
|
538
|
+
{
|
|
539
|
+
sb.Append(escapedValue);
|
|
540
|
+
}
|
|
541
|
+
else if (c < 0x20 || char.IsSurrogate(c))
|
|
530
542
|
{
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
case '\r': sb.Append("\\r"); break;
|
|
537
|
-
case '\t': sb.Append("\\t"); break;
|
|
538
|
-
default:
|
|
539
|
-
if (c < 0x20)
|
|
540
|
-
{
|
|
541
|
-
// Encode as \uXXXX for control characters
|
|
542
|
-
sb.Append("\\u" + ((int)c).ToString("X4"));
|
|
543
|
-
}
|
|
544
|
-
else if (char.IsSurrogate(c))
|
|
545
|
-
{
|
|
546
|
-
// Handle surrogate pairs
|
|
547
|
-
if (char.IsHighSurrogate(c))
|
|
548
|
-
{
|
|
549
|
-
sb.Append("\\u" + ((int)c).ToString("X4"));
|
|
550
|
-
}
|
|
551
|
-
else if (char.IsLowSurrogate(c))
|
|
552
|
-
{
|
|
553
|
-
sb.Append("\\u" + ((int)c).ToString("X4"));
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
else
|
|
557
|
-
{
|
|
558
|
-
// Keep characters as is if they are in the UTF-8 range
|
|
559
|
-
sb.Append(c);
|
|
560
|
-
}
|
|
561
|
-
break;
|
|
543
|
+
sb.AppendFormat("\\u{0:X4}", (int)c);
|
|
544
|
+
}
|
|
545
|
+
else
|
|
546
|
+
{
|
|
547
|
+
sb.Append(c);
|
|
562
548
|
}
|
|
563
549
|
}
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
//public static T[] FromJsonToArray<T>(string json)
|
|
569
|
-
//{
|
|
570
|
-
// if (!json.StartsWith("{\"items\":"))
|
|
571
|
-
// {
|
|
572
|
-
// json = $"{{\"items\":{json}}}";
|
|
573
|
-
// }
|
|
574
|
-
// Wrapper<T> wrapper = JsonUtility.FromJson<Wrapper<T>>(json);
|
|
575
|
-
// return wrapper.items;
|
|
576
|
-
//}
|
|
577
|
-
|
|
578
|
-
//public static T FromJsonToObject<T>(string json)
|
|
579
|
-
//{
|
|
580
|
-
// return JsonUtility.FromJson<T>(json);
|
|
581
|
-
//}
|
|
582
|
-
|
|
583
|
-
//public static string ToJson<T>(T[] array)
|
|
584
|
-
//{
|
|
585
|
-
// Wrapper<T> wrapper = new Wrapper<T>();
|
|
586
|
-
// wrapper.items = array;
|
|
587
|
-
// return JsonUtility.ToJson(wrapper);
|
|
588
|
-
//}
|
|
589
|
-
|
|
590
|
-
//public static string ToJson<T>(T obj)
|
|
591
|
-
//{
|
|
592
|
-
// return JsonUtility.ToJson(obj);
|
|
593
|
-
//}
|
|
594
|
-
|
|
595
|
-
//[Serializable]
|
|
596
|
-
//private class Wrapper<T>
|
|
597
|
-
//{
|
|
598
|
-
// public T[] items;
|
|
599
|
-
//}
|
|
550
|
+
}
|
|
600
551
|
}
|
|
601
552
|
}
|