diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b08d413 --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +# This .gitignore file should be placed at the root of your Unity project directory +# +# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore +# +/[Ll]ibrary/ +/[Tt]emp/ +/[Oo]bj/ +/[Bb]uild/ +/[Bb]uilds/ +/[Ll]ogs/ +/[Uu]ser[Ss]ettings/ + +# MemoryCaptures can get excessive in size. +# They also could contain extremely sensitive data +/[Mm]emoryCaptures/ + +# Recordings can get excessive in size +/[Rr]ecordings/ + +# Uncomment this line if you wish to ignore the asset store tools plugin +# /[Aa]ssets/AssetStoreTools* + +# Autogenerated Jetbrains Rider plugin +/[Aa]ssets/Plugins/Editor/JetBrains* + +# Visual Studio cache directory +.vs/ + +# Gradle cache directory +.gradle/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb +*.mdb +*.opendb +*.VC.db + +# Unity3D generated meta files +*.pidb.meta +*.pdb.meta +*.mdb.meta + +# Unity3D generated file on crash reports +sysinfo.txt + +# Builds +*.apk +*.aab +*.unitypackage +*.app + +# Crashlytics generated file +crashlytics-build.properties + +# Packed Addressables +/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* + +# Temporary auto-generated Android Assets +/[Aa]ssets/[Ss]treamingAssets/aa.meta +/[Aa]ssets/[Ss]treamingAssets/aa/* + +# IDEA +.idea/ \ No newline at end of file diff --git a/Runtime/Scripts/Core/Request/WebRequestInternal.cs b/Runtime/Scripts/Core/Request/WebRequestInternal.cs index 42f095e..ba97660 100644 --- a/Runtime/Scripts/Core/Request/WebRequestInternal.cs +++ b/Runtime/Scripts/Core/Request/WebRequestInternal.cs @@ -1,7 +1,10 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Text; +using System.Threading; +using System.Threading.Tasks; using JetBrains.Annotations; using UnityEngine; using UnityEngine.Networking; @@ -14,8 +17,8 @@ public class WebRequestInternal public event Action OnRequestDone; public event Action OnRequestWillRestart; - private const int RequestTimeOut = 8; - private int _requestAttemptsLimit = 3; + private const int RequestTimeOut = 4; + private int _requestAttemptsLimit = 1; public WebRequestState RequestState { get; private set; } @@ -24,6 +27,7 @@ public class WebRequestInternal private int _requestTimeOutDuration; private int _requestAttempts; + private CancellationTokenSource _cancellationTokenSource; public WebRequestInternal(WebRequest.Configuration configuration) { @@ -47,7 +51,8 @@ private void StartRequest() if (RequestState == WebRequestState.None) { RequestState = WebRequestState.Pending; - WebRequestManager.Instance.StartRequest(this, RequestRoutine()); + _cancellationTokenSource = new CancellationTokenSource(); + RequestRoutineAsync(_cancellationTokenSource.Token); } } @@ -56,7 +61,8 @@ private void StopRequest() if (RequestState == WebRequestState.Pending) { RequestState = WebRequestState.None; - WebRequestManager.Instance.StopRequest(this); + _cancellationTokenSource?.Cancel(); + _request?.Abort(); DisposeRequest(true); } } @@ -72,7 +78,8 @@ private void RestartRequest() OnRequestWillRestart?.Invoke(_requestAttempts); SetUpRequestConfiguration(_configuration); - WebRequestManager.Instance.RestartRequest(this, RequestRoutine()); + _cancellationTokenSource = new CancellationTokenSource(); + RequestRoutineAsync(_cancellationTokenSource.Token); } } @@ -116,57 +123,68 @@ private bool SetUpRequestConfiguration(WebRequest.Configuration configuration) return configured; } - private IEnumerator RequestRoutine() + private async Task RequestRoutineAsync(CancellationToken cancellationToken) { - if (RequestState == WebRequestState.Pending) + try { - _requestTimeOutDuration = RequestTimeOut + (RequestTimeOut / 2 * _requestAttempts); - _requestAttempts++; + if (RequestState == WebRequestState.Pending) + { + _requestTimeOutDuration = RequestTimeOut + (RequestTimeOut / 2 * _requestAttempts); + _requestAttempts++; - _request.SendWebRequest(); - } + var operation = _request.SendWebRequest(); + } - float requestProgress = -1f; - float requestStuckTime = 0f; + float requestProgress = -1f; + Stopwatch requestStuckTime = Stopwatch.StartNew(); - while (!_request.isDone) - { - bool requestNotProgressing = Mathf.Approximately(requestProgress, _request.uploadProgress + _request.downloadProgress); - - if (requestNotProgressing) + while (!_request.isDone) { - requestStuckTime += Time.deltaTime; + cancellationToken.ThrowIfCancellationRequested(); + + bool requestNotProgressing = Mathf.Approximately(requestProgress, _request.uploadProgress + _request.downloadProgress); - if (requestStuckTime >= _requestTimeOutDuration) + if (requestNotProgressing) { - RequestState = WebRequestState.Timeout; - HandleOnRequestTimeOut(); + if (requestStuckTime.Elapsed.TotalSeconds >= _requestTimeOutDuration) + { + RequestState = WebRequestState.Timeout; + HandleOnRequestTimeOut(); - yield break; + return; + } } + else + { + requestStuckTime.Restart(); + requestProgress = _request.uploadProgress + _request.downloadProgress; + } + + await Task.Yield(); + } + + RequestState = WebRequestState.Completed; + + if (_request.result is UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.DataProcessingError or UnityWebRequest.Result.ProtocolError) + { + JoystickLogger.LogError($"Request result: {_request.result} | ErrorInfo: {_request.error} | Url: {_request.url}"); } else { - requestStuckTime = 0f; - requestProgress = _request.uploadProgress + _request.downloadProgress; + JoystickLogger.Log($"Url: {_request.url} | Response Code:{_request.responseCode} | Response Data: {_request.downloadHandler.text}"); } - yield return null; + HandleOnRequestDone(); + DisposeRequest(true); } - - RequestState = WebRequestState.Completed; - - if (_request.result is UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.DataProcessingError or UnityWebRequest.Result.ProtocolError) + catch (OperationCanceledException) { - JoystickLogger.LogError($"Request result: {_request.result} | ErrorInfo: {_request.error} | Url: {_request.url}"); + JoystickLogger.Log("Request was cancelled."); } - else + catch (Exception e) { - JoystickLogger.Log($"Url: {_request.url} | Response Code:{_request.responseCode} | Response Data: {_request.downloadHandler.text}"); + JoystickLogger.LogError($"An error occurred during the request: {e.Message}"); } - - HandleOnRequestDone(); - DisposeRequest(true); } private void HandleOnRequestTimeOut() @@ -224,6 +242,8 @@ private void DisposeRequest(bool disposeUploadHandler) _request.disposeUploadHandlerOnDispose = disposeUploadHandler; _request.Dispose(); _request = null; + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; } } -} \ No newline at end of file +} diff --git a/Runtime/Scripts/Core/Request/WebRequestManager.cs b/Runtime/Scripts/Core/Request/WebRequestManager.cs deleted file mode 100644 index dfdb591..0000000 --- a/Runtime/Scripts/Core/Request/WebRequestManager.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace JoystickRemoteConfig.Core.Web -{ - public class WebRequestManager : MonoBehaviour - { - private const int ConcurrentRequestsCount = 6; - - private readonly Dictionary _pendingRequestsList = new(); - private readonly List> _waitingRequestsList = new(); - - private static WebRequestManager _instance; - - public static WebRequestManager Instance - { - get - { - if (_instance == null) - { - _instance = new GameObject(nameof(WebRequestManager)).AddComponent(); - - DontDestroyOnLoad(_instance); - } - - return _instance; - } - } - - public void StartRequest(WebRequestInternal request, IEnumerator requestCoroutine) - { - if (_pendingRequestsList.Count < ConcurrentRequestsCount) - { - if (!_pendingRequestsList.ContainsKey(request)) - { - _pendingRequestsList.Add(request, StartCoroutine(requestCoroutine)); - request.OnRequestDone += responseData => HandleOnRequestDone(request); - } - else - { - JoystickLogger.LogError($"Already has the same request: {request.GetHashCode()} in pending requests list."); - } - } - else - { - _waitingRequestsList.Add(new KeyValuePair(request, requestCoroutine)); - } - } - - public void StopRequest(WebRequestInternal request) - { - if (_pendingRequestsList.ContainsKey(request)) - { - StopCoroutine(_pendingRequestsList[request]); - _pendingRequestsList.Remove(request); - } - else - { - _waitingRequestsList.Remove(_waitingRequestsList.FirstOrDefault(keyValuePair => keyValuePair.Key == request)); - } - } - - public void RestartRequest(WebRequestInternal request, IEnumerator requestCoroutine) - { - if (_pendingRequestsList.ContainsKey(request)) - { - _pendingRequestsList.Remove(request); - - _pendingRequestsList.Add(request, StartCoroutine(requestCoroutine)); - } - else - { - JoystickLogger.LogError($"Not has the request: {request.GetHashCode()} in pending list."); - } - } - - private void TryStartNextPendingRequest() - { - if (_waitingRequestsList.Count > 0) - { - StartRequest(_waitingRequestsList[0].Key, _waitingRequestsList[0].Value); - _waitingRequestsList.RemoveAt(0); - } - } - - private void HandleOnRequestDone(WebRequestInternal request) - { - StopRequest(request); - TryStartNextPendingRequest(); - } - } -} \ No newline at end of file diff --git a/Runtime/Scripts/Core/Request/WebRequestManager.cs.meta b/Runtime/Scripts/Core/Request/WebRequestManager.cs.meta deleted file mode 100644 index fb19a32..0000000 --- a/Runtime/Scripts/Core/Request/WebRequestManager.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 7be7ab026c5e845a3a0399243701349c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/Scripts/Core/Utilities/JoystickUtilities.cs b/Runtime/Scripts/Core/Utilities/JoystickUtilities.cs index 2195503..20fdd42 100644 --- a/Runtime/Scripts/Core/Utilities/JoystickUtilities.cs +++ b/Runtime/Scripts/Core/Utilities/JoystickUtilities.cs @@ -1,3 +1,4 @@ +using System; using System.Text; using JoystickRemoteConfig.Core.Data; using UnityEngine; @@ -27,8 +28,9 @@ public static string GetConfigContentAPIUrl(string[] contentIds) for (int i = 0; i < contentIds.Length; i++) { + // Ensure each content ID is properly encoded stringBuilder.Append("\""); - stringBuilder.Append(contentIds[i]); + stringBuilder.Append(Uri.EscapeDataString(contentIds[i])); stringBuilder.Append("\""); if (i < contentIds.Length - 1) @@ -43,7 +45,10 @@ public static string GetConfigContentAPIUrl(string[] contentIds) string responseTypeParam = "&responseType=serialized"; string appendParam = shouldSerialized ? responseTypeParam : string.Empty; - return $"https://api.getjoystick.com/api/v1/combine/?c=[{stringBuilder}]&dynamic=true{appendParam}"; + // Properly encode the entire 'c=[...]' query parameter + string encodedCParameter = Uri.EscapeDataString($"[{stringBuilder}]"); + + return $"https://api.getjoystick.com/api/v1/combine/?c={encodedCParameter}&dynamic=true{appendParam}"; } public static string GetCatalogAPIUrl()