com.amanotes.gdk 0.2.81-alpha.13 → 0.2.81-alpha.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/Extra/ABLab.unitypackage +0 -0
  2. package/Extra/ABLab.unitypackage.meta +7 -0
  3. package/Extra/AmaGDKInstaller.unitypackage +0 -0
  4. package/Extra/AutoEventQC.unitypackage +0 -0
  5. package/Extra/CheckDiskSpace.unitypackage +0 -0
  6. package/Extra/Consent.unitypackage +0 -0
  7. package/Extra/CrashlyticHook.unitypackage +0 -0
  8. package/Extra/ForceUpdate.unitypackage +0 -0
  9. package/Extra/LegacyGDKUpdateHelper.unitypackage +0 -0
  10. package/Extra/PostProcessor.unitypackage +0 -0
  11. package/Packages/AmaGDKConfig.unitypackage +0 -0
  12. package/Packages/AmaGDKExample.unitypackage +0 -0
  13. package/Packages/AmaGDKTest.unitypackage +0 -0
  14. package/Packages/AppsFlyerAdapter.PurchaseConnector.unitypackage +0 -0
  15. package/Packages/AppsFlyerAdapter.unitypackage +0 -0
  16. package/Packages/FirebaseAnalyticsAdapter.unitypackage +0 -0
  17. package/Packages/FirebaseRemoteConfigAdapter.unitypackage +0 -0
  18. package/Packages/IronSourceAdapter.AdQuality.unitypackage +0 -0
  19. package/Packages/IronSourceAdapter.unitypackage +0 -0
  20. package/Packages/MaxAdNetworkAdapter.unitypackage +0 -0
  21. package/Packages/RevenueCatAdapter.unitypackage +0 -0
  22. package/Packages/SqliteAnalyticsAdapter.unitypackage +0 -0
  23. package/Runtime/Ad/AmaGDK.Ads.cs +9 -6
  24. package/Runtime/AmaGDK.Analytics.cs +22 -12
  25. package/Runtime/AmaGDK.IAP.cs +6 -5
  26. package/Runtime/AmaGDK.Mono.cs +19 -1
  27. package/Runtime/AmaGDK.RemoteConfig.cs +4 -2
  28. package/Runtime/AmaGDK.Singleton.cs +2 -1
  29. package/Runtime/AmaGDK.UserProfile.cs +2 -2
  30. package/Runtime/AmaGDK.asmdef +3 -1
  31. package/Runtime/AmaGDK.cs +52 -48
  32. package/Runtime/Consent/AmaGDK.Consent.cs +7 -6
  33. package/Runtime/Core/GDKDebug.cs +15 -1
  34. package/Runtime/Internal/AmaGDK.Utils.cs +1 -1
  35. package/Runtime/Internal/ForceQuitMonitor.cs +1 -1
  36. package/Runtime/Utils/GDKFileUtils.cs +240 -104
  37. package/package.json +30 -18
Binary file
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: 092a3e310bfa5423db4dc58ea256a62a
3
+ DefaultImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -3,7 +3,7 @@ using System.Collections;
3
3
  using System.Collections.Generic;
4
4
  using UnityEngine;
5
5
  using Amanotes.Core.Internal;
6
-
6
+ using Cysharp.Threading.Tasks;
7
7
  using static Amanotes.Core.GDKDebug;
8
8
  using static Amanotes.Core.AmaGDK;
9
9
 
@@ -93,7 +93,7 @@ namespace Amanotes.Core
93
93
 
94
94
  internal void Save()
95
95
  {
96
- this.SaveJsonToFile();
96
+ this.SaveJsonToFile(false);
97
97
  }
98
98
  }
99
99
 
@@ -108,8 +108,7 @@ namespace Amanotes.Core
108
108
  public static bool allowAdRequest = true;
109
109
 
110
110
  // internal use
111
- private static readonly Lazy<AdsData> _localData = new Lazy<AdsData>(() => new AdsData().LoadJsonFromFile());
112
- internal static AdsData localData => _localData.Value;
111
+ internal static AdsData localData;
113
112
 
114
113
  internal protected static AdShowContext context;
115
114
  internal protected static AdAdapter _adapter;
@@ -154,14 +153,18 @@ namespace Amanotes.Core
154
153
 
155
154
  public partial class Ads // Public
156
155
  {
157
- internal static bool InitModule()
156
+ internal static async UniTask InitModule()
158
157
  {
158
+ await UniTask.SwitchToThreadPool();
159
159
  if (Adapter2.GetAllAdapter<AdAdapter>().Count > 1)
160
160
  {
161
161
  GDKUtils.ThrowExceptionIfEditor("Multiple AdAdapters is not supported!");
162
162
  }
163
163
  _adapter = Adapter2.GetAdapter<AdAdapter>();
164
- return _adapter != null;
164
+ if(_adapter != null)
165
+ {
166
+ localData = new AdsData().LoadJsonFromFile();
167
+ }
165
168
  }
166
169
 
167
170
  internal static void StartModule()
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
3
3
  using System.Collections.Generic;
4
4
  using System.Text;
5
5
  using Amanotes.Core.Internal;
6
+ using Cysharp.Threading.Tasks;
6
7
  using UnityEngine;
7
8
  using UnityEngine.Profiling;
8
9
  using static Amanotes.Core.AmaGDK.Analytics;
@@ -130,12 +131,11 @@ namespace Amanotes.Core
130
131
  if (Config.analytics.autoFlushEventDetail)
131
132
  {
132
133
  LogWarning("[Analytics] This function FlushEventDetails() is not supposed to be called manually!");
133
- return;
134
134
  }
135
135
 
136
136
  if (!localData._dirty) return;
137
137
  localData._dirty = false;
138
- localData.SaveJsonToFile();
138
+ localData.SaveJsonToFile(false);
139
139
  }
140
140
 
141
141
  public static IEventParamsBuilder LogFunnelEvent(string eventName, string signature = "")
@@ -312,10 +312,7 @@ namespace Amanotes.Core
312
312
  internal static readonly List<AnalyticsAdapter> listAdapters = new List<AnalyticsAdapter>();
313
313
  internal static SessionStat sessionStat;
314
314
  internal static readonly GDKPool.Pool<EventParams> epPool = new GDKPool.Pool<EventParams>(1);
315
-
316
- private static readonly Lazy<AnalyticsData> _localData = new Lazy<AnalyticsData>(
317
- () => new AnalyticsData().LoadJsonFromFile().BuildCache());
318
- internal static AnalyticsData localData => _localData.Value;
315
+ internal static AnalyticsData localData;
319
316
 
320
317
  internal static readonly Dictionary<string, string> reservedPrefixes = new Dictionary<string, string>()
321
318
  {
@@ -359,14 +356,19 @@ namespace Amanotes.Core
359
356
  "user_engagement"
360
357
  };
361
358
 
362
- internal static void InitModule()
359
+ internal static async UniTask InitModule()
363
360
  {
361
+ await UniTask.SwitchToThreadPool();
364
362
  if (Config.analytics.enableEventStat)
365
363
  {
366
364
  sessionStat = new SessionStat();
367
365
  }
368
366
  listAdapters.Clear();
369
367
  listAdapters.AddRange(Adapter2.GetAllAdapter<AnalyticsAdapter>());
368
+ if (listAdapters.Count > 0)
369
+ {
370
+ localData = new AnalyticsData().LoadJsonFromFile().BuildCache();
371
+ }
370
372
  }
371
373
 
372
374
  internal static void StartModule()
@@ -648,7 +650,12 @@ namespace Amanotes.Core
648
650
  internal static void PostPrepareEventProcess(EventParams eventData)
649
651
  {
650
652
  if (!eventData.parameters.ContainsKey("ama_device_id"))
651
- eventData.AddParam("ama_device_id", User.AmaDeviceId);
653
+ {
654
+ if (!string.IsNullOrWhiteSpace(User.AmaDeviceId))
655
+ {
656
+ eventData.AddParam("ama_device_id", User.AmaDeviceId);
657
+ }
658
+ }
652
659
  }
653
660
 
654
661
  internal static bool ShouldSendEvent(EventParams eventData)
@@ -715,10 +722,13 @@ namespace Amanotes.Core
715
722
  {
716
723
  sb.Append(eventNameToSend);
717
724
  sb.Append("\t");
718
- for (var i = 0; i < adapters.Count; i++)
725
+ if (adapters != null && adapters.Count > 0)
719
726
  {
720
- sb.Append(adapters[i]);
721
- if (i < adapters.Count - 1) sb.Append(",");
727
+ for (var i = 0; i < adapters.Count; i++)
728
+ {
729
+ sb.Append(adapters[i]);
730
+ if (i < adapters.Count - 1) sb.Append(",");
731
+ }
722
732
  }
723
733
  sb.Append("\t");
724
734
  sb.AppendLine(eventParamsInJSON);
@@ -843,7 +853,7 @@ namespace Amanotes.Core
843
853
  if (!Config.analytics.autoFlushEventDetail) return;
844
854
  if (!_dirty) return;
845
855
  _dirty = false;
846
- this.SaveJsonToFile();
856
+ this.SaveJsonToFile(false);
847
857
  }
848
858
 
849
859
  internal void UpdateMigrationLog()
@@ -4,7 +4,7 @@ using System.Collections.Generic;
4
4
  using System.Globalization;
5
5
  using UnityEngine;
6
6
  using Amanotes.Core.Internal;
7
-
7
+ using Cysharp.Threading.Tasks;
8
8
  using static Amanotes.Core.AmaGDK.IAP;
9
9
  using static Amanotes.Core.GDKDebug;
10
10
 
@@ -176,12 +176,13 @@ namespace Amanotes.Core
176
176
 
177
177
  private static bool IsModuleReady => _syncTaskTracker.HasCompleted && _getProductsTaskTracker.HasCompleted;
178
178
  private static Action _onModuleReady;
179
- internal static bool InitModule()
179
+ internal static async UniTask InitModule()
180
180
  {
181
+ await UniTask.SwitchToThreadPool();
181
182
  _adapter = Adapter2.GetAdapter<IAPAdapter>();
182
- if (_adapter == null) return false;
183
+ if (_adapter == null)
184
+ return;
183
185
  localData = new IAPData().LoadJsonFromFile();
184
- return true;
185
186
  }
186
187
 
187
188
  internal static void StartModule()
@@ -281,7 +282,7 @@ namespace Amanotes.Core
281
282
  subscriptionExpiredDate = _subscriptionExpiredDate?.ToString(DATE_FORMAT);
282
283
  _unsubscribedDate = Adapter.GetUnsubscribedDate();
283
284
  unsubscribedDate = _unsubscribedDate?.ToString(DATE_FORMAT);
284
- this.SaveJsonToFile();
285
+ this.SaveJsonToFile(false);
285
286
  }
286
287
  }
287
288
  }
@@ -3,6 +3,7 @@ using System;
3
3
  using System.Collections;
4
4
  using System.Collections.Generic;
5
5
  using System.Threading;
6
+ using Cysharp.Threading.Tasks;
6
7
  using UnityEngine;
7
8
 
8
9
  namespace Amanotes.Core
@@ -78,7 +79,18 @@ namespace Amanotes.Core.Internal
78
79
  {
79
80
  StartCoroutine(DelayCallRoutine(seconds, action));
80
81
  }
81
-
82
+
83
+ public static async UniTask TimeoutTask(float timeoutInSecs, Func<bool> completeCheckFunc)
84
+ {
85
+ float time = Time.realtimeSinceStartup;
86
+ while (completeCheckFunc() == false)
87
+ {
88
+ await UniTask.Yield();
89
+ if (Time.realtimeSinceStartup - time > timeoutInSecs)
90
+ return;
91
+ }
92
+ }
93
+
82
94
  public static IEnumerator Timeout(float timeoutInSecs, Func<bool> completeCheckFunc)
83
95
  {
84
96
  float time = Time.realtimeSinceStartup;
@@ -88,6 +100,12 @@ namespace Amanotes.Core.Internal
88
100
  if (Time.realtimeSinceStartup - time > timeoutInSecs) yield break;
89
101
  }
90
102
  }
103
+
104
+ private static async UniTask DelayCallRoutineTask(float seconds, Action callback)
105
+ {
106
+ await UniTask.Delay(TimeSpan.FromSeconds(seconds));
107
+ callback();
108
+ }
91
109
 
92
110
  private static IEnumerator DelayCallRoutine(float seconds, Action callback)
93
111
  {
@@ -5,6 +5,7 @@ using System.IO;
5
5
  using System.Text;
6
6
  using UnityEngine;
7
7
  using Amanotes.Core.Internal;
8
+ using Cysharp.Threading.Tasks;
8
9
  using UnityEngine.Profiling;
9
10
  using UnityEngine.Serialization;
10
11
  using static Amanotes.Core.GDKDebug;
@@ -173,8 +174,9 @@ namespace Amanotes.Core
173
174
  actions[result ? 1 : 0]();
174
175
  }
175
176
 
176
- internal static void InitModule()
177
+ internal static async UniTask InitModule()
177
178
  {
179
+ await UniTask.SwitchToThreadPool();
178
180
  adapter = Adapter2.GetAdapter<RemoteConfigAdapter>();
179
181
  }
180
182
 
@@ -235,7 +237,7 @@ namespace Amanotes.Core
235
237
  }
236
238
 
237
239
  string filePath = GDKFileUtils.GetPath(fileName);
238
- GDKFileUtils.Save(filePath, sb.ToString());
240
+ GDKFileUtils.Save(filePath, sb.ToString(), false);
239
241
  GDKPool.Return(sb);
240
242
 
241
243
  Log($"[RemoteConfig] Saved remote config to {filePath}");
@@ -1,6 +1,7 @@
1
1
 
2
2
  using Amanotes.Core.Internal;
3
3
  using System.Threading;
4
+ using Cysharp.Threading.Tasks;
4
5
  using static Amanotes.Core.GDKDebug;
5
6
  using static Amanotes.Core.Internal.Messsage;
6
7
 
@@ -23,7 +24,7 @@ namespace Amanotes.Core
23
24
  mainThreadId = Thread.CurrentThread.ManagedThreadId;
24
25
  DontDestroyOnLoad(this);
25
26
 
26
- GDKUtils.StartCoroutine(InitRoutine());
27
+ InitRoutine().Forget();
27
28
  }
28
29
  }
29
30
  }
@@ -33,14 +33,14 @@ namespace Amanotes.Core
33
33
  private bool _dirty = false;
34
34
  private void Save()
35
35
  {
36
- this.SaveJsonToFile();
36
+ this.SaveJsonToFile(false);
37
37
  }
38
38
 
39
39
  private void SaveIfDirty()
40
40
  {
41
41
  if (!_dirty) return;
42
42
  _dirty = false;
43
- this.SaveJsonToFile();
43
+ this.SaveJsonToFile(false);
44
44
  }
45
45
  }
46
46
 
@@ -1,7 +1,9 @@
1
1
  {
2
2
  "name": "AmaGDK",
3
3
  "rootNamespace": "",
4
- "references": [],
4
+ "references": [
5
+ "UniTask"
6
+ ],
5
7
  "includePlatforms": [],
6
8
  "excludePlatforms": [],
7
9
  "allowUnsafeCode": true,
package/Runtime/AmaGDK.cs CHANGED
@@ -15,6 +15,7 @@ using Amanotes.Core.Internal;
15
15
  using static Amanotes.Core.GDKDebug;
16
16
  using static Amanotes.Core.Internal.Messsage;
17
17
  using System.Threading.Tasks;
18
+ using Cysharp.Threading.Tasks;
18
19
 
19
20
 
20
21
  #if UNITY_EDITOR
@@ -27,7 +28,7 @@ namespace Amanotes.Core
27
28
  {
28
29
  public partial class AmaGDK : MonoBehaviour
29
30
  {
30
- public const string VERSION = "v0.2.81-alpha.13";
31
+ public const string VERSION = "v0.2.81-alpha.17";
31
32
 
32
33
  internal static Status _status = Status.None;
33
34
  internal static ConfigAsset _config = null;
@@ -81,8 +82,8 @@ namespace Amanotes.Core
81
82
  return _config;
82
83
  }
83
84
  }
84
-
85
- IEnumerator InitHook()
85
+
86
+ async UniTask InitHook()
86
87
  {
87
88
  // dispatcher.Dispatch(Event.GDK_HOOK_START);
88
89
  if (_onHookStart!= null) GDKUtils.SafeInvokeAndClear(ref _onHookStart);
@@ -91,24 +92,23 @@ namespace Amanotes.Core
91
92
  {
92
93
  Log($"[Hook] No hook registered!");
93
94
  if (_onHookEnd!= null) GDKUtils.SafeInvokeAndClear(ref _onHookEnd);
94
- yield break;
95
+ return;
95
96
  }
96
-
97
+
97
98
  //TODO: Sort Hook by orderId
98
99
  var hookCompleted = false;
99
-
100
+
100
101
  float stTime = Time.realtimeSinceStartup;
101
102
  Hook.TriggerHooks(() =>
102
103
  {
103
104
  hookCompleted = true;
104
105
  });
105
- yield return new WaitUntil(() => hookCompleted);
106
+ await UniTask.WaitUntil(() => hookCompleted);
106
107
  float elapsed = Time.realtimeSinceStartup - stTime;
107
108
  Log($"[Hook] {nHooks} hooks complete after {elapsed} secs");
108
109
  if (_onHookEnd!= null) GDKUtils.SafeInvokeAndClear(ref _onHookEnd);
109
110
  }
110
-
111
- IEnumerator WaitForManualInit()
111
+ async UniTask WaitForManualInit()
112
112
  {
113
113
  var counter = 0;
114
114
  const int INIT_TOLERANCE = 10 * 60;
@@ -117,7 +117,7 @@ namespace Amanotes.Core
117
117
  while (!_allowInit)
118
118
  {
119
119
  counter++;
120
- yield return null;
120
+ await UniTask.Yield();
121
121
 
122
122
  if (counter > INIT_TOLERANCE && counter % INIT_WARN_CYCLE == 1)
123
123
  {
@@ -126,17 +126,20 @@ namespace Amanotes.Core
126
126
  }
127
127
  }
128
128
 
129
- void InitModules()
129
+ async UniTask InitModules()
130
130
  {
131
131
  Profiler.BeginSample("AmaGDK.InitModules()");
132
132
  {
133
133
  User.InitModule();
134
- Consent.InitModule();
135
- Analytics.InitModule();
136
- Ads.InitModule();
137
- IAP.InitModule();
138
- RemoteConfig.InitModule();
139
- if (Config.enableAudioModule) Audio.InitModule();
134
+ if (Config.enableAudioModule)
135
+ {
136
+ Audio.InitModule();
137
+ }
138
+ await Consent.InitModule();
139
+ await Analytics.InitModule();
140
+ await Ads.InitModule();
141
+ await IAP.InitModule();
142
+ await RemoteConfig.InitModule();
140
143
  }
141
144
  Profiler.EndSample();
142
145
  }
@@ -154,13 +157,13 @@ namespace Amanotes.Core
154
157
  Profiler.EndSample();
155
158
  }
156
159
 
157
- IEnumerator InitAdapters(StringBuilder sb)
160
+ async UniTask InitAdapters(StringBuilder sb)
158
161
  {
159
162
  // sort on priority
160
163
  List<Adapter2> listAdapters = Adapter2.adapterMap
161
164
  .Values.ToList();
162
165
  listAdapters.Sort((a1, a2) => a1.initOrder.CompareTo(a2.initOrder));
163
-
166
+
164
167
  foreach (Adapter2 adapter in listAdapters)
165
168
  {
166
169
  if (!adapter.enabled)
@@ -168,7 +171,7 @@ namespace Amanotes.Core
168
171
  Log($"Adapter <{adapter.adapterId} is disabled!");
169
172
  continue;
170
173
  }
171
-
174
+
172
175
  // Log($"Initiating {adapter.adapterId} --> initOrder: {adapter.initOrder} | {adapter.status}");
173
176
  var startTime = Time.realtimeSinceStartup;
174
177
  Profiler.BeginSample($"AmaGDK.adapter.InitSDK-{adapter.adapterId}");
@@ -178,9 +181,9 @@ namespace Amanotes.Core
178
181
  if (adapter.status != Status.Ready)
179
182
  {
180
183
  var beginTime = Time.realtimeSinceStartup;
181
- yield return GDKUtils.Timeout(adapter.Timeout,() => adapter.status != Status.Initialize);
182
- var endTime = Time.realtimeSinceStartup;
183
-
184
+ await GDKUtils.TimeoutTask(adapter.Timeout,() => adapter.status != Status.Initialize);
185
+ var endTime = Time.realtimeSinceStartup;
186
+
184
187
  if (adapter.status == Status.Initialize)
185
188
  {
186
189
  LogWarning($"{adapter.adapterId} init time out: {adapter.Timeout:#0.00}");
@@ -189,65 +192,66 @@ namespace Amanotes.Core
189
192
  Log($"{adapter.adapterId} init took: {(endTime-beginTime):#0.00} s");
190
193
  }
191
194
  }
192
-
195
+
193
196
  GDKUtils.SafeInvoke(_onAdapterReady, adapter.adapterId);
194
197
  sb.AppendLine($"[{(adapter.IsReady ? " OK " : "FAIL")}] {adapter.adapterId} (~ {Time.realtimeSinceStartup - startTime:#0.00}s)");
195
-
198
+
196
199
  // Wait for postprocessing of each adapter
197
- yield return null;
200
+ await UniTask.Yield();
198
201
  }
199
202
  }
200
-
201
- IEnumerator InitRoutine()
203
+
204
+ public async UniTask InitRoutine()
202
205
  {
203
206
  _allowInit = _allowInit || autoInit;
204
-
207
+
205
208
  if (Config == null)
206
209
  {
207
210
  throw new Exception("[AmaGDK] " + CONFIG_NOT_FOUND);
208
211
  }
209
-
212
+
210
213
  var startTime = Time.realtimeSinceStartup;
211
214
  var sb = GDKPool.GetStringBuilder();
212
215
 
213
- InitModules();
214
-
216
+ await InitModules();
217
+ await UniTask.SwitchToMainThread();
215
218
  var moduleTime = Time.realtimeSinceStartup - startTime;
216
219
  sb.Append($"- InitModules() took (~ {moduleTime:#0.00}s)\n");
217
-
218
- yield return InitHook(); //Google CMP takes long at first time (~ 1.7s)
219
-
220
+
221
+ await InitHook(); //Google CMP takes long at first time (~ 1.7s)
222
+
220
223
  var hookTime = Time.realtimeSinceStartup - moduleTime - startTime;
221
224
  sb.Append($"- InitHook() took (~ {hookTime:#0.00}s)\n\n");
222
-
225
+
223
226
  _status = Status.Initialize;
224
227
  GDKUtils.SafeInvokeAndClear(ref _onGDKInit);
225
-
226
- if (!_allowInit) yield return WaitForManualInit();
227
- yield return InitAdapters(sb);
228
-
228
+
229
+ if (!_allowInit)
230
+ await WaitForManualInit();
231
+ await InitAdapters(sb);
232
+
229
233
  StartModules();
230
-
234
+
231
235
  ConfigGeoLocation();
232
236
  if (Config.enableServerTime)
233
237
  {
234
238
  ServerTime.UpdateTime();
235
239
  }
236
-
240
+
237
241
  ForceQuit.Init();
238
-
242
+
239
243
  _status = Status.Ready;
240
244
  GDKUtils.SafeInvokeAndClear(ref _onGDKReady);
241
245
 
242
246
  float initDuration = Time.realtimeSinceStartup - startTime;
243
247
 
244
- yield return LogEvent_GDKInitAsync(Mathf.RoundToInt(initDuration));
248
+ await LogEvent_GDKInitAsync(Mathf.RoundToInt(initDuration));
249
+
245
250
 
246
-
247
251
  sb.Append($"\nMapping UserId: {User.UserId}\n");
248
252
  ForceLog($"v{VERSION} | {READY} in {initDuration:#0.00}s\n\n{sb}");
249
253
  GDKPool.Return(sb);
250
-
254
+
251
255
  // also clear callbacks
252
256
  _onAdapterReady = null;
253
257
  }
@@ -272,7 +276,7 @@ namespace Amanotes.Core
272
276
  }
273
277
  }
274
278
 
275
- public static async Task<Dictionary<string, object>> ParseThirdPartyVersionsAsync()
279
+ public static async UniTask<Dictionary<string, object>> ParseThirdPartyVersionsAsync()
276
280
  {
277
281
  Dictionary<string, object> thirdPartyVersions = new Dictionary<string, object>();
278
282
  string fileContent = await GDKFileUtils.LoadFromStreamingAssets("SDKVersion.txt");
@@ -325,7 +329,7 @@ namespace Amanotes.Core
325
329
  }
326
330
  }
327
331
 
328
- private static async Task LogEvent_GDKInitAsync(int initDuration)
332
+ private static async UniTask LogEvent_GDKInitAsync(int initDuration)
329
333
  {
330
334
  var BToMB = 1.0 / (1024 * 1024);
331
335
  var reservedMemory = Math.Round(Profiler.GetTotalReservedMemoryLong() * BToMB, 2); // in MB
@@ -1,5 +1,6 @@
1
1
  using System;
2
2
  using Amanotes.Core.Internal;
3
+ using Cysharp.Threading.Tasks;
3
4
  using UnityEngine;
4
5
  using static Amanotes.Core.GDKDebug;
5
6
 
@@ -76,7 +77,7 @@ namespace Amanotes.Core
76
77
  ConsentHook.ATTRequest(isAllowed =>
77
78
  {
78
79
  data.att = isAllowed ? Status.Allowed : Status.NotAllowed;
79
- data.SaveJsonToFile();
80
+ data.SaveJsonToFile(false);
80
81
  onComplete?.Invoke(allowedATT);
81
82
  });
82
83
  }
@@ -119,7 +120,7 @@ namespace Amanotes.Core
119
120
  {
120
121
  data.canShowAds = canShowAd;
121
122
  data.consentAlreadyShown = true;
122
- data.SaveJsonToFile();
123
+ data.SaveJsonToFile(false);
123
124
  onComplete?.Invoke(canShowAd);
124
125
  }, onError);
125
126
  }
@@ -170,8 +171,7 @@ namespace Amanotes.Core
170
171
 
171
172
  public static partial class Consent //Internal
172
173
  {
173
- private static readonly Lazy<ConsentData> _data = new Lazy<ConsentData>(() => new ConsentData().LoadJsonFromFile());
174
- private static ConsentData data => _data.Value;
174
+ private static ConsentData data;
175
175
  private static Action _onConsentComplete;
176
176
 
177
177
  internal static bool checkedATT => data.att >= Status.Allowed;
@@ -185,13 +185,14 @@ namespace Amanotes.Core
185
185
  NotAllowed
186
186
  }
187
187
 
188
- internal static bool InitModule()
188
+ internal static async UniTask InitModule()
189
189
  {
190
+ await UniTask.SwitchToThreadPool();
190
191
  if (_config.autoGetConsent)
191
192
  {
192
193
  RequestAllConsents(null);
193
194
  }
194
- return true;
195
+ data = new ConsentData().LoadJsonFromFile();
195
196
  }
196
197
  }
197
198
  }
@@ -11,7 +11,8 @@ namespace Amanotes.Core
11
11
  Warning,
12
12
  Verbose
13
13
  }
14
-
14
+
15
+ #if GDK_REMOVE_STACKTRACE
15
16
  internal readonly struct NoStackTrace : IDisposable
16
17
  {
17
18
  internal readonly LogType logType;
@@ -41,10 +42,13 @@ namespace Amanotes.Core
41
42
 
42
43
  }
43
44
  }
45
+ #endif
44
46
 
45
47
  public class GDKDebug
46
48
  {
49
+ #if GDK_REMOVE_STACKTRACE
47
50
  internal static readonly IDisposable NoStackTraceNull = new NoStackTraceNull();
51
+ #endif
48
52
 
49
53
  public static Action<string> logNonfatalErrorHook = null;
50
54
  private static readonly HashSet<string> nonFatalDict = new HashSet<string>();
@@ -59,7 +63,9 @@ namespace Amanotes.Core
59
63
  if (!logWarningDict.Add(key)) return;
60
64
  }
61
65
 
66
+ #if GDK_REMOVE_STACKTRACE
62
67
  using (GDKUtils.isOnMainThread ? NoStackTrace.Warning: NoStackTraceNull)
68
+ #endif
63
69
  {
64
70
  Debug.LogWarning($"AmaGDK {message}");
65
71
  }
@@ -67,7 +73,9 @@ namespace Amanotes.Core
67
73
  public static void LogWarning(string message)
68
74
  {
69
75
  if (AmaGDK.logLevel < LogLevel.Warning) return;
76
+ #if GDK_REMOVE_STACKTRACE
70
77
  using (GDKUtils.isOnMainThread ? NoStackTrace.Warning: NoStackTraceNull)
78
+ #endif
71
79
  {
72
80
  Debug.LogWarning($"AmaGDK {message}");
73
81
  }
@@ -75,7 +83,9 @@ namespace Amanotes.Core
75
83
  public static void LogError(string message)
76
84
  {
77
85
  if (AmaGDK.logLevel < LogLevel.Error) return;
86
+ #if GDK_REMOVE_STACKTRACE
78
87
  using (GDKUtils.isOnMainThread ? NoStackTrace.Error: NoStackTraceNull)
88
+ #endif
79
89
  {
80
90
  Debug.LogError($"AmaGDK {message}");
81
91
  }
@@ -100,7 +110,9 @@ namespace Amanotes.Core
100
110
  public static void Log(string message)
101
111
  {
102
112
  if (AmaGDK.logLevel < LogLevel.Verbose) return;
113
+ #if GDK_REMOVE_STACKTRACE
103
114
  using (GDKUtils.isOnMainThread ? NoStackTrace.Log: NoStackTraceNull)
115
+ #endif
104
116
  {
105
117
  Debug.Log($"AmaGDK {message}");
106
118
  }
@@ -108,7 +120,9 @@ namespace Amanotes.Core
108
120
 
109
121
  public static void ForceLog(string message)
110
122
  {
123
+ #if GDK_REMOVE_STACKTRACE
111
124
  using (GDKUtils.isOnMainThread ? NoStackTrace.Log: NoStackTraceNull)
125
+ #endif
112
126
  {
113
127
  Debug.Log($"AmaGDK {message}");
114
128
  }
@@ -200,7 +200,7 @@ namespace Amanotes.Core.Internal
200
200
  return GDKFileUtils.LoadJsonFromFile(instance);
201
201
  }
202
202
 
203
- internal static void SaveJsonToFile<T>(this T instance, bool immediately = true) where T: IJsonFile
203
+ internal static void SaveJsonToFile<T>(this T instance, bool immediately) where T: IJsonFile
204
204
  {
205
205
  GDKFileUtils.SaveJsonToFile(instance, immediately);
206
206
  }
@@ -34,7 +34,7 @@ namespace Amanotes.Core.Internal
34
34
  internal void SaveIfDirty(bool forceSave = false)
35
35
  {
36
36
  if (!isDirty && !forceSave) return;
37
- this.SaveJsonToFile();
37
+ this.SaveJsonToFile(false);
38
38
  isDirty = false;
39
39
  }
40
40
  }
@@ -1,6 +1,11 @@
1
+ // #define GDK_DEBUG_FILE_IO
2
+
1
3
  using System;
4
+ using System.Collections.Concurrent;
2
5
  using System.Collections.Generic;
6
+ using System.Diagnostics;
3
7
  using System.IO;
8
+ using System.Threading;
4
9
  using System.Threading.Tasks;
5
10
  using UnityEngine;
6
11
  using UnityEngine.Networking;
@@ -11,64 +16,145 @@ namespace Amanotes.Core.Internal
11
16
  public static class GDKFileUtils
12
17
  {
13
18
  private static string _basePath;
19
+ private static readonly ConcurrentDictionary<string, string> _pendingWrites =
20
+ new ConcurrentDictionary<string, string>();
21
+ private static readonly List<KeyValuePair<string, string>> _itemsToWrite =
22
+ new List<KeyValuePair<string, string>>(8);
23
+
24
+ private static readonly object _fileLock = new object();
25
+ private static int _isProcessing = 0;
26
+
14
27
  public static string basePath
15
28
  {
16
29
  get
17
30
  {
18
31
  if (!string.IsNullOrEmpty(_basePath)) return _basePath;
32
+
19
33
  _basePath = Application.isEditor
20
34
  ? Path.Combine(Directory.GetCurrentDirectory(), "Library", "AmaGDK")
21
35
  : Path.Combine(Application.persistentDataPath, "AmaGDK");
22
36
 
23
- if (!Directory.Exists(_basePath)) Directory.CreateDirectory(_basePath);
37
+ try
38
+ {
39
+ if (!Directory.Exists(_basePath))
40
+ {
41
+ Directory.CreateDirectory(_basePath);
42
+ }
43
+ } catch (Exception ex)
44
+ {
45
+ LogError($"[GDKFileUtils] Failed to create directory: {ex.Message}");
46
+ _basePath = Application.persistentDataPath;
47
+ }
48
+
24
49
  return _basePath;
25
50
  }
26
51
  }
27
-
52
+
28
53
  internal static string GetPath(string fileName)
29
54
  {
30
- return Path.Combine(basePath, fileName);
55
+ if (string.IsNullOrEmpty(fileName)) return null;
56
+ return Path.Combine(basePath, Path.GetFileName(fileName));
31
57
  }
32
58
 
33
59
  internal static bool Exist(string fileName)
34
60
  {
35
- return File.Exists(GetPath(fileName));
61
+ if (string.IsNullOrEmpty(fileName)) return false;
62
+
63
+ string path = GetPath(fileName);
64
+ if (string.IsNullOrEmpty(path)) return false;
65
+
66
+ lock (_fileLock)
67
+ {
68
+ return File.Exists(path);
69
+ }
70
+ }
71
+
72
+ internal static int writePendingCount => _pendingWrites.Count;
73
+ internal static bool HasAnyWritePending => _pendingWrites.Count > 0;
74
+ internal static bool IsWritePending(string fileName)
75
+ {
76
+ return !string.IsNullOrEmpty(fileName) && _pendingWrites.ContainsKey(fileName);
36
77
  }
37
-
38
- public static void Save(string fileName, string content, bool immediately = true)
78
+
79
+ public static void Save(string fileName, string content, bool immediately)
39
80
  {
40
- if (string.IsNullOrWhiteSpace(fileName))
81
+ if (string.IsNullOrEmpty(fileName)) return;
82
+
83
+ if (immediately)
41
84
  {
42
- LogWarningOnce("File name must be not null or empty");
85
+ _pendingWrites.TryRemove(fileName, out _);
86
+ WriteFileDirectly(fileName, content);
43
87
  return;
44
88
  }
45
89
 
46
- if (string.IsNullOrWhiteSpace(content))
90
+ // Just replace any existing content
91
+ _pendingWrites[fileName] = content;
92
+
93
+ // Use atomic CompareExchange to ensure only one Task runs at a time
94
+ CheckSpawnThread();
95
+ }
96
+
97
+ private static void CheckSpawnThread()
98
+ {
99
+ if (Interlocked.CompareExchange(ref _isProcessing, 1, 0) == 0)
47
100
  {
48
- LogWarningOnce($"Content for {fileName} is empty");
101
+ Task.Factory.StartNew(ProcessWriteQueue, TaskCreationOptions.None);
49
102
  }
50
-
51
- ThreadWrite(fileName, content, immediately);
52
103
  }
53
104
 
54
- private static void ThreadWrite(string fileName, string content, bool immediately)
105
+ private static void ProcessWriteQueue()
55
106
  {
56
- // Only write on main thread if immediately is true
57
- if (!immediately && GDKUtils.isOnMainThread)
107
+ try
58
108
  {
59
- Task.Run(()=>ThreadWrite(fileName, content, true))
60
- .ConfigureAwait(false);
61
- return;
109
+ // var w = Stopwatch.StartNew();
110
+ while (!_pendingWrites.IsEmpty)
111
+ {
112
+ // Blocking delay so batching actually occurs
113
+ // In this case, it's actually better than await Task.Delay(500), why?
114
+ Thread.Sleep(500);
115
+
116
+ _itemsToWrite.Clear();
117
+ _itemsToWrite.AddRange(_pendingWrites);
118
+ _pendingWrites.Clear();
119
+
120
+ lock (_fileLock)
121
+ {
122
+ foreach (var kvp in _itemsToWrite)
123
+ {
124
+ WriteFileDirectly(kvp.Key, kvp.Value);
125
+ }
126
+ }
127
+ }
128
+
129
+ // w.Stop();
130
+ // LogWarning($"Process time: {w.ElapsedTicks} ticks | {w.ElapsedMilliseconds} milliseconds");
131
+ }
132
+ catch (Exception e)
133
+ {
134
+ LogError($"[GDKFileUtils] Write queue error: {e.Message}");
135
+ } finally
136
+ {
137
+ Interlocked.Exchange(ref _isProcessing, 0);
138
+ if (!_pendingWrites.IsEmpty) CheckSpawnThread(); // until next time!
62
139
  }
140
+ }
141
+
142
+ // Write directly to file without temp files
143
+ private static void WriteFileDirectly(string fileName, string content)
144
+ {
145
+ if (string.IsNullOrEmpty(fileName)) return;
63
146
 
64
147
  string filePath = GetPath(fileName);
65
- try
148
+ if (string.IsNullOrEmpty(filePath)) return;
149
+
150
+ // Lock only for the duration of actual I/O
151
+ lock (_fileLock)
66
152
  {
67
153
  File.WriteAllText(filePath, content);
68
- }
69
- catch (Exception ex)
70
- {
71
- LogWarningOnce($"Exception in ThreadWrite(): {filePath}\n{ex}");
154
+
155
+ #if GDK_DEBUG_FILE_IO
156
+ Log($"WriteFileDirectly threadId={Thread.CurrentThread.ManagedThreadId}: {fileName} -> {content}");
157
+ #endif
72
158
  }
73
159
  }
74
160
 
@@ -76,23 +162,28 @@ namespace Amanotes.Core.Internal
76
162
  {
77
163
  if (string.IsNullOrWhiteSpace(fileName))
78
164
  {
79
- LogWarningOnce("File name must be not null or empty");
165
+ LogWarningOnce("File name must not be null or empty");
80
166
  return;
81
167
  }
82
-
168
+
169
+ // Check if `contents` is null or empty
83
170
  if (contents == null || !contents.GetEnumerator().MoveNext())
84
171
  {
85
172
  LogWarningOnce($"Content for {fileName} is empty");
86
173
  }
87
174
 
88
175
  string path = GetPath(fileName);
89
- try
90
- {
91
- File.AppendAllLines(path, contents);
92
- }
93
- catch (Exception ex)
176
+
177
+ // Concurrency-safe write
178
+ lock (_fileLock)
94
179
  {
95
- LogWarningOnce(path + "\n" + ex);
180
+ try
181
+ {
182
+ File.AppendAllLines(path, contents);
183
+ } catch (Exception ex)
184
+ {
185
+ LogWarningOnce(path + "\n" + ex);
186
+ }
96
187
  }
97
188
  }
98
189
 
@@ -100,147 +191,192 @@ namespace Amanotes.Core.Internal
100
191
  {
101
192
  if (string.IsNullOrWhiteSpace(fileName))
102
193
  {
103
- LogWarningOnce("File name must be not null or empty");
194
+ LogWarningOnce("File name must not be null or empty");
104
195
  return;
105
196
  }
106
197
 
198
+ // Check if `content` is null or empty
107
199
  if (string.IsNullOrWhiteSpace(content))
108
200
  {
109
201
  LogWarningOnce($"Content for {fileName} is empty");
110
202
  }
111
203
 
112
204
  string path = GetPath(fileName);
113
- try
114
- {
115
- File.AppendAllText(path, content);
116
- }
117
- catch (Exception ex)
205
+
206
+ // Concurrency-safe write
207
+ lock (_fileLock)
118
208
  {
119
- LogWarningOnce(path + "\n" + ex);
209
+ try
210
+ {
211
+ File.AppendAllText(path, content);
212
+ } catch (Exception ex)
213
+ {
214
+ LogWarningOnce(path + "\n" + ex);
215
+ }
120
216
  }
121
217
  }
122
218
 
123
219
  public static string Load(string fileName)
124
220
  {
125
- var content = "";
221
+ if (string.IsNullOrEmpty(fileName)) return string.Empty;
126
222
 
127
- if (string.IsNullOrWhiteSpace(fileName))
223
+ // If there's a pending in-memory write, return that content first
224
+ if (_pendingWrites.TryGetValue(fileName, out var inMemoryContent))
128
225
  {
129
- LogWarningOnce("File name must be not null or empty");
130
- return content;
226
+ return inMemoryContent;
131
227
  }
132
228
 
133
229
  string path = GetPath(fileName);
134
- if (!File.Exists(path)) return content;
230
+ if (string.IsNullOrEmpty(path)) return string.Empty;
135
231
 
136
- try
137
- {
138
- content = File.ReadAllText(path);
139
- }
140
- catch (Exception ex)
232
+ lock (_fileLock)
141
233
  {
142
- LogWarningOnce(path + "\n" + ex);
234
+ try
235
+ {
236
+ return !File.Exists(path) ? string.Empty : File.ReadAllText(path);
237
+ } catch (Exception ex)
238
+ {
239
+ LogError($"[GDKFileUtils] Failed to read {fileName}: {ex.Message}");
240
+ return string.Empty;
241
+ }
143
242
  }
144
-
145
- return content;
146
243
  }
147
244
 
148
245
  public static async Task<string> LoadFromStreamingAssets(string fileName)
149
246
  {
247
+ if (string.IsNullOrEmpty(fileName)) return null;
150
248
  string filePath = Path.Combine(Application.streamingAssetsPath, fileName);
151
249
 
152
- // Android
153
250
  if (Application.platform == RuntimePlatform.Android)
154
251
  {
155
- using (UnityWebRequest request = UnityWebRequest.Get(filePath))
252
+ using (var request = UnityWebRequest.Get(filePath))
156
253
  {
157
- var operation = request.SendWebRequest();
158
- while (!operation.isDone)
254
+ try
159
255
  {
160
- await Task.Yield();
161
- }
162
- if (request.result != UnityWebRequest.Result.Success)
256
+ var operation = request.SendWebRequest();
257
+ while (!operation.isDone)
258
+ {
259
+ await Task.Yield();
260
+ }
261
+
262
+ return request.result != UnityWebRequest.Result.Success
263
+ ? null
264
+ : request.downloadHandler.text;
265
+ } catch (Exception ex)
163
266
  {
164
- LogWarning($"Error reading file: {request.error}");
267
+ LogError($"[GDKFileUtils] StreamingAssets load error: {ex.Message}");
165
268
  return null;
166
269
  }
167
- return request.downloadHandler.text;
168
270
  }
169
271
  }
170
272
 
171
- // Other
172
- try
173
- {
174
- return File.ReadAllText(filePath);
175
- }
176
- catch (Exception e)
273
+ lock (_fileLock)
177
274
  {
178
- LogWarning($"Error reading file: {e.Message}");
275
+ try
276
+ {
277
+ return File.ReadAllText(filePath);
278
+ } catch (Exception ex)
279
+ {
280
+ LogError($"[GDKFileUtils] StreamingAssets load error: {ex.Message}");
281
+ return null;
282
+ }
179
283
  }
180
- return null;
181
284
  }
182
285
 
183
-
184
286
  internal static void Delete(string fileName)
185
287
  {
288
+ if (string.IsNullOrEmpty(fileName)) return;
289
+
290
+ _pendingWrites.TryRemove(fileName, out _);
291
+
186
292
  string path = GetPath(fileName);
187
- if (!File.Exists(path)) return;
293
+ if (string.IsNullOrEmpty(path)) return;
188
294
 
189
- try
295
+ lock (_fileLock)
190
296
  {
191
- File.Delete(path);
192
- }
193
- catch (Exception ex)
194
- {
195
- LogWarningOnce(path + "\n" + ex);
297
+ try
298
+ {
299
+ if (File.Exists(path))
300
+ {
301
+ File.Delete(path);
302
+ }
303
+ } catch (Exception ex)
304
+ {
305
+ LogError($"[GDKFileUtils] Delete error: {ex.Message}");
306
+ }
196
307
  }
197
308
  }
198
-
309
+
199
310
  public static void ClearCacheData()
200
311
  {
201
- var sb = GDKPool.GetStringBuilder();
202
- sb.AppendLine("[AmaGDK] All cached data & stats cleared");
312
+ _pendingWrites.Clear();
313
+
314
+ if (!Directory.Exists(basePath)) return;
203
315
 
204
- if (!Directory.Exists(basePath))
316
+ lock (_fileLock)
205
317
  {
206
- sb.AppendLine("Cache path not exists: " + basePath);
207
- return;
318
+ foreach (string file in Directory.GetFiles(basePath))
319
+ {
320
+ try
321
+ {
322
+ File.Delete(file);
323
+ } catch (Exception ex)
324
+ {
325
+ LogWarning($"[GDKFileUtils] Failed to delete {file}: {ex.Message}");
326
+ }
327
+ }
208
328
  }
209
-
210
- string[] filePaths = Directory.GetFiles(basePath);
211
- foreach (string path in filePaths)
329
+ }
330
+
331
+ // Synchronously flush everything
332
+ public static void Shutdown()
333
+ {
334
+ // Write out all pending items directly
335
+ foreach (var kvp in _pendingWrites)
212
336
  {
213
- Delete(path);
214
- sb.AppendLine($"[deleted] - {path}");
337
+ try
338
+ {
339
+ WriteFileDirectly(kvp.Key, kvp.Value);
340
+ } catch (Exception ex)
341
+ {
342
+ LogError($"[GDKFileUtils] Shutdown write error: {ex.Message}");
343
+ }
215
344
  }
216
- Debug.Log(sb);
345
+
346
+ _pendingWrites.Clear();
217
347
  }
218
-
219
- public static T LoadJsonFromFile<T>(T instance) where T: new()
348
+
349
+ public static T LoadJsonFromFile<T>(T instance) where T : new()
220
350
  {
221
351
  instance ??= new T();
222
-
223
- string fileName = instance.GetType().Name;
224
- string json = Load(fileName);
225
- if (!string.IsNullOrEmpty(json))
352
+
353
+ try
226
354
  {
227
- try
355
+ string json = Load(instance.GetType().Name);
356
+ if (!string.IsNullOrEmpty(json))
228
357
  {
229
358
  JsonUtility.FromJsonOverwrite(json, instance);
230
- } catch (Exception e)
231
- {
232
- LogWarning("LoadJsonFromFile error: " + fileName + "\n" + e);
233
359
  }
360
+ } catch (Exception ex)
361
+ {
362
+ LogError($"[GDKFileUtils] JSON load error: {ex.Message}");
234
363
  }
235
-
364
+
236
365
  return instance;
237
366
  }
238
-
239
- public static void SaveJsonToFile<T>(T instance, bool immediately = true)
367
+
368
+ public static void SaveJsonToFile<T>(T instance, bool immediately)
240
369
  {
241
- string fileName = instance.GetType().Name;
242
- string json = JsonUtility.ToJson(instance);
243
- ThreadWrite(fileName, json, immediately);
370
+ if (instance == null) return;
371
+
372
+ try
373
+ {
374
+ string json = JsonUtility.ToJson(instance);
375
+ Save(instance.GetType().Name, json, immediately);
376
+ } catch (Exception ex)
377
+ {
378
+ LogError($"[GDKFileUtils] JSON save error: {ex.Message}");
379
+ }
244
380
  }
245
381
  }
246
382
  }
package/package.json CHANGED
@@ -1,20 +1,32 @@
1
1
  {
2
- "name": "com.amanotes.gdk",
3
- "version": "0.2.81-alpha.13",
4
- "displayName": "AmaGDK",
5
- "description": "Amanotes Game Development Kit",
6
- "unity": "2019.4",
7
- "keywords": [
8
- "Amanotes",
9
- "Amanotes GDK",
10
- "vh-name:AmaGDK"
11
- ],
12
- "author": {
13
- "name": "Amanotes",
14
- "email": "tech.support@amanotes.com",
15
- "url": "https://amanotes.com"
16
- },
17
- "changelogUrl": "https://amagdk.notion.site/Change-Log-6dfd4b67d64f496bbd9eb04ccb4ef95a",
18
- "documentationUrl": "https://amagdk.notion.site/amagdk/Ama-GDK-a73596617ab247b6a43c8f12a1581415",
19
- "license": "MIT"
2
+ "name": "com.amanotes.gdk",
3
+ "version": "0.2.81-alpha.17",
4
+ "displayName": "AmaGDK",
5
+ "description": "Amanotes Game Development Kit",
6
+ "unity": "2019.4",
7
+ "keywords": [
8
+ "Amanotes",
9
+ "Amanotes GDK",
10
+ "vh-name:AmaGDK"
11
+ ],
12
+ "author": {
13
+ "name": "Amanotes",
14
+ "email": "tech.support@amanotes.com",
15
+ "url": "https://amanotes.com"
16
+ },
17
+ "changelogUrl": "https://amagdk.notion.site/Change-Log-6dfd4b67d64f496bbd9eb04ccb4ef95a",
18
+ "documentationUrl": "https://amagdk.notion.site/amagdk/Ama-GDK-a73596617ab247b6a43c8f12a1581415",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "com.cysharp.unitask": "2.5.10"
22
+ },
23
+ "scopedRegistries": [
24
+ {
25
+ "name": "OpenUPM",
26
+ "url": "https://package.openupm.com",
27
+ "scopes": [
28
+ "com.cysharp"
29
+ ]
30
+ }
31
+ ]
20
32
  }