com.amanotes.gdk 0.2.81-alpha.12 → 0.2.81-alpha.14
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/Extra/AmaGDKInstaller.unitypackage +0 -0
- package/Extra/AutoEventQC.unitypackage +0 -0
- package/Extra/CheckDiskSpace.unitypackage +0 -0
- package/Extra/Consent.unitypackage +0 -0
- package/Extra/CrashlyticHook.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.AdQuality.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 +44 -41
- package/Runtime/Ad/AdModuleConfig.cs +3 -0
- package/Runtime/Ad/AmaGDK.Ads.cs +1 -1
- package/Runtime/AmaGDK.Analytics.cs +14 -7
- package/Runtime/AmaGDK.IAP.cs +35 -60
- package/Runtime/AmaGDK.RemoteConfig.cs +1 -1
- package/Runtime/AmaGDK.UserProfile.cs +2 -2
- package/Runtime/AmaGDK.cs +1 -1
- package/Runtime/Consent/AmaGDK.Consent.cs +2 -2
- package/Runtime/Core/GDKDebug.cs +15 -1
- package/Runtime/Internal/AmaGDK.Utils.cs +1 -1
- package/Runtime/Internal/ForceQuitMonitor.cs +1 -1
- package/Runtime/Utils/GDKFileUtils.cs +240 -104
- package/Runtime/Utils/GDKUtils.cs +33 -0
- package/package.json +1 -1
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/Runtime/Ad/AdLogic.cs
CHANGED
|
@@ -8,7 +8,6 @@ namespace Amanotes.Core.Internal
|
|
|
8
8
|
{
|
|
9
9
|
public abstract class AdLogic
|
|
10
10
|
{
|
|
11
|
-
private const int MAX_FRAMES_WAIT_FOR_CALLBACK = 60;
|
|
12
11
|
protected AdModuleConfig adConfig => Config.ad;
|
|
13
12
|
protected AdShowContext context => Ads.context;
|
|
14
13
|
|
|
@@ -256,20 +255,18 @@ namespace Amanotes.Core.Internal
|
|
|
256
255
|
}
|
|
257
256
|
};
|
|
258
257
|
unityCallbacks.OnApplicationFocus += onApplicationFocusCallback;
|
|
259
|
-
int counter = 0;
|
|
260
258
|
while (context != null && !context.potentiallyCompleted)
|
|
261
259
|
{
|
|
262
260
|
if (hasFocusAgain && hasOutOfFocus)
|
|
263
261
|
{
|
|
264
|
-
// Start counting
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
break;
|
|
262
|
+
// Start counting only after closed the ad
|
|
263
|
+
yield return GDKUtils.WaitForSecondsRealtime(Config.ad.adCallBackTimeOut);
|
|
264
|
+
if (context != null && !context.potentiallyCompleted)
|
|
265
|
+
{
|
|
266
|
+
LogError("Force close the ad");
|
|
267
|
+
StopShowAd();
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
273
270
|
}
|
|
274
271
|
yield return null;
|
|
275
272
|
}
|
|
@@ -351,24 +348,27 @@ namespace Amanotes.Core.Internal
|
|
|
351
348
|
|
|
352
349
|
private void TriggerBeforeAdShow()
|
|
353
350
|
{
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
351
|
+
if (Application.platform == RuntimePlatform.IPhonePlayer)
|
|
352
|
+
{
|
|
353
|
+
bool willSaveVolume = Config.ad.autoMute
|
|
354
|
+
&& AudioListener.volume > 0
|
|
355
|
+
&& Ads._savedVolume < 0;
|
|
357
356
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
357
|
+
bool willSaveTimeScale = Config.ad.autoTimeScale
|
|
358
|
+
&& Time.timeScale > 0
|
|
359
|
+
&& Ads._savedTimeScale < 0;
|
|
361
360
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
361
|
+
if (willSaveVolume)
|
|
362
|
+
{
|
|
363
|
+
Ads._savedVolume = AudioListener.volume;
|
|
364
|
+
AudioListener.volume = 0;
|
|
365
|
+
}
|
|
367
366
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
367
|
+
if (willSaveTimeScale)
|
|
368
|
+
{
|
|
369
|
+
Ads._savedTimeScale = Time.timeScale;
|
|
370
|
+
Time.timeScale = 0;
|
|
371
|
+
}
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
dispatcher.Dispatch(_isInterstitial ? AmaGDK.Event.INTER_SHOW_BEGIN : AmaGDK.Event.REWARD_VIDEO_SHOW_BEGIN);
|
|
@@ -377,24 +377,27 @@ namespace Amanotes.Core.Internal
|
|
|
377
377
|
|
|
378
378
|
private void TriggerOnAfterAdShow()
|
|
379
379
|
{
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
380
|
+
if (Application.platform == RuntimePlatform.IPhonePlayer)
|
|
381
|
+
{
|
|
382
|
+
bool willRestoreVolume = Config.ad.autoMute
|
|
383
|
+
&& Mathf.Approximately(AudioListener.volume, 0)
|
|
384
|
+
&& Ads._savedVolume > 0;
|
|
383
385
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
386
|
+
bool willRestoreTimeScale = Config.ad.autoTimeScale
|
|
387
|
+
&& Mathf.Approximately(Time.timeScale, 0)
|
|
388
|
+
&& Ads._savedTimeScale > 0;
|
|
387
389
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
390
|
+
if (willRestoreVolume)
|
|
391
|
+
{
|
|
392
|
+
AudioListener.volume = Ads._savedVolume;
|
|
393
|
+
Ads._savedVolume = -1;
|
|
394
|
+
}
|
|
393
395
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
396
|
+
if (willRestoreTimeScale)
|
|
397
|
+
{
|
|
398
|
+
Time.timeScale = Ads._savedTimeScale;
|
|
399
|
+
Ads._savedTimeScale = -1;
|
|
400
|
+
}
|
|
398
401
|
}
|
|
399
402
|
|
|
400
403
|
bool isSuccess = _showState == ShowAdsState.ShowSuccess;
|
|
@@ -45,6 +45,9 @@ namespace Amanotes.Core.Internal
|
|
|
45
45
|
[Header("EDITOR")]
|
|
46
46
|
[Tooltip("Only applicable in Editor\n0 = 0% success\n1 = 100% success")]
|
|
47
47
|
[Range(0f, 1f)] public float rewardAdShowSuccessRate = 1;
|
|
48
|
+
|
|
49
|
+
[Tooltip(("The maximum time (in seconds) to wait for the callback response after the ad activity has finished and the app returns to the Unity activity (default = 5s)"))]
|
|
50
|
+
public float adCallBackTimeOut = 5f;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
public partial class ConfigAsset
|
package/Runtime/Ad/AmaGDK.Ads.cs
CHANGED
|
@@ -130,12 +130,11 @@ namespace Amanotes.Core
|
|
|
130
130
|
if (Config.analytics.autoFlushEventDetail)
|
|
131
131
|
{
|
|
132
132
|
LogWarning("[Analytics] This function FlushEventDetails() is not supposed to be called manually!");
|
|
133
|
-
return;
|
|
134
133
|
}
|
|
135
134
|
|
|
136
135
|
if (!localData._dirty) return;
|
|
137
136
|
localData._dirty = false;
|
|
138
|
-
localData.SaveJsonToFile();
|
|
137
|
+
localData.SaveJsonToFile(false);
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
public static IEventParamsBuilder LogFunnelEvent(string eventName, string signature = "")
|
|
@@ -648,7 +647,12 @@ namespace Amanotes.Core
|
|
|
648
647
|
internal static void PostPrepareEventProcess(EventParams eventData)
|
|
649
648
|
{
|
|
650
649
|
if (!eventData.parameters.ContainsKey("ama_device_id"))
|
|
651
|
-
|
|
650
|
+
{
|
|
651
|
+
if (!string.IsNullOrWhiteSpace(User.AmaDeviceId))
|
|
652
|
+
{
|
|
653
|
+
eventData.AddParam("ama_device_id", User.AmaDeviceId);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
652
656
|
}
|
|
653
657
|
|
|
654
658
|
internal static bool ShouldSendEvent(EventParams eventData)
|
|
@@ -715,10 +719,13 @@ namespace Amanotes.Core
|
|
|
715
719
|
{
|
|
716
720
|
sb.Append(eventNameToSend);
|
|
717
721
|
sb.Append("\t");
|
|
718
|
-
|
|
722
|
+
if (adapters != null && adapters.Count > 0)
|
|
719
723
|
{
|
|
720
|
-
|
|
721
|
-
|
|
724
|
+
for (var i = 0; i < adapters.Count; i++)
|
|
725
|
+
{
|
|
726
|
+
sb.Append(adapters[i]);
|
|
727
|
+
if (i < adapters.Count - 1) sb.Append(",");
|
|
728
|
+
}
|
|
722
729
|
}
|
|
723
730
|
sb.Append("\t");
|
|
724
731
|
sb.AppendLine(eventParamsInJSON);
|
|
@@ -843,7 +850,7 @@ namespace Amanotes.Core
|
|
|
843
850
|
if (!Config.analytics.autoFlushEventDetail) return;
|
|
844
851
|
if (!_dirty) return;
|
|
845
852
|
_dirty = false;
|
|
846
|
-
this.SaveJsonToFile();
|
|
853
|
+
this.SaveJsonToFile(false);
|
|
847
854
|
}
|
|
848
855
|
|
|
849
856
|
internal void UpdateMigrationLog()
|
package/Runtime/AmaGDK.IAP.cs
CHANGED
|
@@ -21,7 +21,10 @@ namespace Amanotes.Core
|
|
|
21
21
|
public static DateTime? SubscriptionExpiredDate => localData.SubscriptionExpiredDate;
|
|
22
22
|
public static DateTime? UnsubscribedDate => localData.UnsubscribedDate;
|
|
23
23
|
public static readonly Dictionary<string, ProductInfo> AllProducts = new Dictionary<string, ProductInfo>();
|
|
24
|
+
|
|
24
25
|
private const string DATE_FORMAT = "yyyy/MM/yy HH:mm:ss";
|
|
26
|
+
private static readonly GDKTaskTracker<bool> _syncTaskTracker = new GDKTaskTracker<bool>();
|
|
27
|
+
private static readonly GDKTaskTracker _getProductsTaskTracker = new GDKTaskTracker();
|
|
25
28
|
|
|
26
29
|
public static void SetCallback_OnModuleReady(Action onReady)
|
|
27
30
|
{
|
|
@@ -37,42 +40,32 @@ namespace Amanotes.Core
|
|
|
37
40
|
_onModuleReady += onReady;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
public static void SetCallback_OnGetProductsComplete(Action onComplete)
|
|
41
|
-
{
|
|
42
|
-
if (onComplete == null) return;
|
|
43
|
-
|
|
44
|
-
if (isGetProductComplete)
|
|
45
|
-
{
|
|
46
|
-
onComplete();
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
_onGetProductsComplete -= onComplete;
|
|
51
|
-
_onGetProductsComplete += onComplete;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public static void SetCallback_OnGetCustomerInfoComplete(Action onComplete)
|
|
55
|
-
{
|
|
56
|
-
if (onComplete == null) return;
|
|
57
|
-
|
|
58
|
-
if (isGetCustomerInfoComplete)
|
|
59
|
-
{
|
|
60
|
-
onComplete();
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
_onGetCustomerInfoComplete -= onComplete;
|
|
65
|
-
_onGetCustomerInfoComplete += onComplete;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
43
|
public static void SyncCustomerInfo(Action<bool> onComplete = null)
|
|
69
44
|
{
|
|
70
|
-
if (
|
|
45
|
+
if (Adapter == null)
|
|
71
46
|
{
|
|
72
47
|
onComplete?.Invoke(false);
|
|
73
48
|
return;
|
|
74
49
|
}
|
|
75
|
-
|
|
50
|
+
|
|
51
|
+
_syncTaskTracker.RegisterOnCompleteCallback(onComplete);
|
|
52
|
+
if (_syncTaskTracker.HasStarted) return;
|
|
53
|
+
|
|
54
|
+
_syncTaskTracker.MarkStarted();
|
|
55
|
+
Adapter.SyncCustomerInfo(getSuccess =>
|
|
56
|
+
{
|
|
57
|
+
if (getSuccess)
|
|
58
|
+
{
|
|
59
|
+
localData.Sync();
|
|
60
|
+
_syncTaskTracker.MarkCompleted(true);
|
|
61
|
+
}
|
|
62
|
+
else
|
|
63
|
+
{
|
|
64
|
+
_syncTaskTracker.MarkCompleted(false);
|
|
65
|
+
_syncTaskTracker.Reset();
|
|
66
|
+
LogWarning("[IAP] Fail to sync customer info. Please try again later.");
|
|
67
|
+
}
|
|
68
|
+
});
|
|
76
69
|
}
|
|
77
70
|
|
|
78
71
|
public static ISetupBuilder GetProducts(bool appendExistingList = false)
|
|
@@ -181,13 +174,8 @@ namespace Amanotes.Core
|
|
|
181
174
|
}
|
|
182
175
|
}
|
|
183
176
|
|
|
184
|
-
private static bool
|
|
185
|
-
private static bool isGetCustomerInfoComplete;
|
|
186
|
-
private static bool IsModuleReady => isGetProductComplete && isGetCustomerInfoComplete;
|
|
187
|
-
private static Action _onGetProductsComplete;
|
|
188
|
-
private static Action _onGetCustomerInfoComplete;
|
|
177
|
+
private static bool IsModuleReady => _syncTaskTracker.HasCompleted && _getProductsTaskTracker.HasCompleted;
|
|
189
178
|
private static Action _onModuleReady;
|
|
190
|
-
|
|
191
179
|
internal static bool InitModule()
|
|
192
180
|
{
|
|
193
181
|
_adapter = Adapter2.GetAdapter<IAPAdapter>();
|
|
@@ -199,14 +187,8 @@ namespace Amanotes.Core
|
|
|
199
187
|
internal static void StartModule()
|
|
200
188
|
{
|
|
201
189
|
if (_adapter == null) return;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
{
|
|
205
|
-
isGetCustomerInfoComplete = true;
|
|
206
|
-
GDKUtils.SafeInvokeAndClear(ref _onGetCustomerInfoComplete);
|
|
207
|
-
if (IsModuleReady) GDKUtils.SafeInvokeAndClear(ref _onModuleReady);
|
|
208
|
-
});
|
|
209
|
-
localData.Sync();
|
|
190
|
+
SyncCustomerInfo(_ => CheckModuleReady());
|
|
191
|
+
_getProductsTaskTracker.MarkStarted();
|
|
210
192
|
if (Config.iap.autoGetProducts)
|
|
211
193
|
{
|
|
212
194
|
GetProducts()
|
|
@@ -218,11 +200,15 @@ namespace Amanotes.Core
|
|
|
218
200
|
OnGetProductsComplete();
|
|
219
201
|
}
|
|
220
202
|
}
|
|
221
|
-
|
|
203
|
+
|
|
222
204
|
private static void OnGetProductsComplete()
|
|
223
205
|
{
|
|
224
|
-
|
|
225
|
-
|
|
206
|
+
_getProductsTaskTracker.MarkCompleted();
|
|
207
|
+
CheckModuleReady();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private static void CheckModuleReady()
|
|
211
|
+
{
|
|
226
212
|
if (IsModuleReady) GDKUtils.SafeInvokeAndClear(ref _onModuleReady);
|
|
227
213
|
}
|
|
228
214
|
}
|
|
@@ -284,19 +270,8 @@ namespace Amanotes.Core
|
|
|
284
270
|
}
|
|
285
271
|
}
|
|
286
272
|
|
|
287
|
-
|
|
273
|
+
internal void Sync()
|
|
288
274
|
{
|
|
289
|
-
GDKUtils.StartCoroutine(SyncRoutine());
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
IEnumerator SyncRoutine()
|
|
293
|
-
{
|
|
294
|
-
if (Adapter == null)
|
|
295
|
-
{
|
|
296
|
-
LogWarning("[IAP] Adapter is not available");
|
|
297
|
-
yield break;
|
|
298
|
-
}
|
|
299
|
-
yield return new WaitUntil(() => Adapter.IsReady);
|
|
300
275
|
activeSubscriptionId = Adapter.GetActiveSubscriptionId();
|
|
301
276
|
subscriptionPeriodType = Adapter.GetSubscriptionPeriodType();
|
|
302
277
|
purchasedProducts = Adapter.GetPurchasedProducts();
|
|
@@ -306,7 +281,7 @@ namespace Amanotes.Core
|
|
|
306
281
|
subscriptionExpiredDate = _subscriptionExpiredDate?.ToString(DATE_FORMAT);
|
|
307
282
|
_unsubscribedDate = Adapter.GetUnsubscribedDate();
|
|
308
283
|
unsubscribedDate = _unsubscribedDate?.ToString(DATE_FORMAT);
|
|
309
|
-
this.SaveJsonToFile();
|
|
284
|
+
this.SaveJsonToFile(false);
|
|
310
285
|
}
|
|
311
286
|
}
|
|
312
287
|
}
|
|
@@ -235,7 +235,7 @@ namespace Amanotes.Core
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
string filePath = GDKFileUtils.GetPath(fileName);
|
|
238
|
-
GDKFileUtils.Save(filePath, sb.ToString());
|
|
238
|
+
GDKFileUtils.Save(filePath, sb.ToString(), false);
|
|
239
239
|
GDKPool.Return(sb);
|
|
240
240
|
|
|
241
241
|
Log($"[RemoteConfig] Saved remote config to {filePath}");
|
|
@@ -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
|
|
package/Runtime/AmaGDK.cs
CHANGED
|
@@ -27,7 +27,7 @@ namespace Amanotes.Core
|
|
|
27
27
|
{
|
|
28
28
|
public partial class AmaGDK : MonoBehaviour
|
|
29
29
|
{
|
|
30
|
-
public const string VERSION = "v0.2.81-alpha.
|
|
30
|
+
public const string VERSION = "v0.2.81-alpha.14";
|
|
31
31
|
|
|
32
32
|
internal static Status _status = Status.None;
|
|
33
33
|
internal static ConfigAsset _config = null;
|
|
@@ -76,7 +76,7 @@ namespace Amanotes.Core
|
|
|
76
76
|
ConsentHook.ATTRequest(isAllowed =>
|
|
77
77
|
{
|
|
78
78
|
data.att = isAllowed ? Status.Allowed : Status.NotAllowed;
|
|
79
|
-
data.SaveJsonToFile();
|
|
79
|
+
data.SaveJsonToFile(false);
|
|
80
80
|
onComplete?.Invoke(allowedATT);
|
|
81
81
|
});
|
|
82
82
|
}
|
|
@@ -119,7 +119,7 @@ namespace Amanotes.Core
|
|
|
119
119
|
{
|
|
120
120
|
data.canShowAds = canShowAd;
|
|
121
121
|
data.consentAlreadyShown = true;
|
|
122
|
-
data.SaveJsonToFile();
|
|
122
|
+
data.SaveJsonToFile(false);
|
|
123
123
|
onComplete?.Invoke(canShowAd);
|
|
124
124
|
}, onError);
|
|
125
125
|
}
|
package/Runtime/Core/GDKDebug.cs
CHANGED
|
@@ -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
|
|
203
|
+
internal static void SaveJsonToFile<T>(this T instance, bool immediately) where T: IJsonFile
|
|
204
204
|
{
|
|
205
205
|
GDKFileUtils.SaveJsonToFile(instance, immediately);
|
|
206
206
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
78
|
+
|
|
79
|
+
public static void Save(string fileName, string content, bool immediately)
|
|
39
80
|
{
|
|
40
|
-
if (string.
|
|
81
|
+
if (string.IsNullOrEmpty(fileName)) return;
|
|
82
|
+
|
|
83
|
+
if (immediately)
|
|
41
84
|
{
|
|
42
|
-
|
|
85
|
+
_pendingWrites.TryRemove(fileName, out _);
|
|
86
|
+
WriteFileDirectly(fileName, content);
|
|
43
87
|
return;
|
|
44
88
|
}
|
|
45
89
|
|
|
46
|
-
|
|
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
|
-
|
|
101
|
+
Task.Factory.StartNew(ProcessWriteQueue, TaskCreationOptions.None);
|
|
49
102
|
}
|
|
50
|
-
|
|
51
|
-
ThreadWrite(fileName, content, immediately);
|
|
52
103
|
}
|
|
53
104
|
|
|
54
|
-
private static void
|
|
105
|
+
private static void ProcessWriteQueue()
|
|
55
106
|
{
|
|
56
|
-
|
|
57
|
-
if (!immediately && GDKUtils.isOnMainThread)
|
|
107
|
+
try
|
|
58
108
|
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
catch (Exception ex)
|
|
176
|
+
|
|
177
|
+
// Concurrency-safe write
|
|
178
|
+
lock (_fileLock)
|
|
94
179
|
{
|
|
95
|
-
|
|
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
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
catch (Exception ex)
|
|
205
|
+
|
|
206
|
+
// Concurrency-safe write
|
|
207
|
+
lock (_fileLock)
|
|
118
208
|
{
|
|
119
|
-
|
|
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
|
-
|
|
221
|
+
if (string.IsNullOrEmpty(fileName)) return string.Empty;
|
|
126
222
|
|
|
127
|
-
|
|
223
|
+
// If there's a pending in-memory write, return that content first
|
|
224
|
+
if (_pendingWrites.TryGetValue(fileName, out var inMemoryContent))
|
|
128
225
|
{
|
|
129
|
-
|
|
130
|
-
return content;
|
|
226
|
+
return inMemoryContent;
|
|
131
227
|
}
|
|
132
228
|
|
|
133
229
|
string path = GetPath(fileName);
|
|
134
|
-
if (
|
|
230
|
+
if (string.IsNullOrEmpty(path)) return string.Empty;
|
|
135
231
|
|
|
136
|
-
|
|
137
|
-
{
|
|
138
|
-
content = File.ReadAllText(path);
|
|
139
|
-
}
|
|
140
|
-
catch (Exception ex)
|
|
232
|
+
lock (_fileLock)
|
|
141
233
|
{
|
|
142
|
-
|
|
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 (
|
|
252
|
+
using (var request = UnityWebRequest.Get(filePath))
|
|
156
253
|
{
|
|
157
|
-
|
|
158
|
-
while (!operation.isDone)
|
|
254
|
+
try
|
|
159
255
|
{
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
-
|
|
172
|
-
try
|
|
173
|
-
{
|
|
174
|
-
return File.ReadAllText(filePath);
|
|
175
|
-
}
|
|
176
|
-
catch (Exception e)
|
|
273
|
+
lock (_fileLock)
|
|
177
274
|
{
|
|
178
|
-
|
|
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 (
|
|
293
|
+
if (string.IsNullOrEmpty(path)) return;
|
|
188
294
|
|
|
189
|
-
|
|
295
|
+
lock (_fileLock)
|
|
190
296
|
{
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
202
|
-
|
|
312
|
+
_pendingWrites.Clear();
|
|
313
|
+
|
|
314
|
+
if (!Directory.Exists(basePath)) return;
|
|
203
315
|
|
|
204
|
-
|
|
316
|
+
lock (_fileLock)
|
|
205
317
|
{
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
211
|
-
|
|
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
|
-
|
|
214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
string json = Load(fileName);
|
|
225
|
-
if (!string.IsNullOrEmpty(json))
|
|
352
|
+
|
|
353
|
+
try
|
|
226
354
|
{
|
|
227
|
-
|
|
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
|
|
367
|
+
|
|
368
|
+
public static void SaveJsonToFile<T>(T instance, bool immediately)
|
|
240
369
|
{
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
using System;
|
|
2
|
+
using System.Collections;
|
|
2
3
|
using UnityEngine;
|
|
3
4
|
using static Amanotes.Core.GDKDebug;
|
|
4
5
|
|
|
@@ -96,5 +97,37 @@ namespace Amanotes.Core.Internal
|
|
|
96
97
|
|
|
97
98
|
LogWarning(message);
|
|
98
99
|
}
|
|
100
|
+
|
|
101
|
+
public static IEnumerator WaitForSecondsRealtime(float duration, bool focusOnly = true)
|
|
102
|
+
{
|
|
103
|
+
if (!focusOnly)
|
|
104
|
+
{
|
|
105
|
+
yield return new WaitForSecondsRealtime(duration);
|
|
106
|
+
yield break;
|
|
107
|
+
}
|
|
108
|
+
float elapsedTime = 0f;
|
|
109
|
+
float lastTimeStamp = Time.realtimeSinceStartup;
|
|
110
|
+
bool focusChangedSinceLastFrame = false;
|
|
111
|
+
Action<bool> onApplicationFocusCallback = _ => focusChangedSinceLastFrame = true;
|
|
112
|
+
Application.focusChanged += onApplicationFocusCallback;
|
|
113
|
+
|
|
114
|
+
while (elapsedTime < duration)
|
|
115
|
+
{
|
|
116
|
+
if (Application.isFocused)
|
|
117
|
+
{
|
|
118
|
+
if (focusChangedSinceLastFrame)
|
|
119
|
+
{
|
|
120
|
+
lastTimeStamp = Time.realtimeSinceStartup;
|
|
121
|
+
focusChangedSinceLastFrame = false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
elapsedTime += Time.realtimeSinceStartup - lastTimeStamp;
|
|
125
|
+
lastTimeStamp = Time.realtimeSinceStartup;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
yield return null;
|
|
129
|
+
}
|
|
130
|
+
Application.focusChanged -= onApplicationFocusCallback;
|
|
131
|
+
}
|
|
99
132
|
}
|
|
100
133
|
}
|