diff --git a/CHANGELOG.md b/CHANGELOG.md index 178e30d7..348bbae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - [Bug](https://github.com/trilitech/tezos-unity-sdk/issues/57) with BeaconConnectorWebGl +### Added +- Better [coroutine error handling](https://github.com/trilitech/tezos-unity-sdk/issues/39) + ## [1.3.1] - 2023-04-27 ### Changed diff --git a/Runtime/Scripts/Helpers/CoroutineWrapper.cs b/Runtime/Scripts/Helpers/CoroutineWrapper.cs index 5a053885..2d2bb0fd 100644 --- a/Runtime/Scripts/Helpers/CoroutineWrapper.cs +++ b/Runtime/Scripts/Helpers/CoroutineWrapper.cs @@ -12,6 +12,10 @@ public class CoroutineWrapper : IEnumerator /// Event raised when the coroutine is complete /// public readonly Action Completed; + /// + /// Event raised when the coroutine throws an exception + /// + public readonly Action ErrorHandler; private readonly IEnumerator _targetCoroutine; @@ -33,13 +37,18 @@ public class CoroutineWrapper : IEnumerator /// /// Coroutine that will be executed /// Callback that will be called when the coroutine is complete - public CoroutineWrapper(IEnumerator coroutine, Action callback = null) + /// Callback that will be called when the coroutine throws an exception + public CoroutineWrapper(IEnumerator coroutine, Action callback = null, Action errorHandler = null) { _targetCoroutine = coroutine; if (callback != null) { Completed += callback; } + if (errorHandler != null) + { + ErrorHandler += errorHandler; + } } /// @@ -60,9 +69,15 @@ public bool MoveNext() } catch (Exception e) { - Debug.LogError("Exception " + e.Message); Exception = e; - Completed?.Invoke(default); + if (ErrorHandler == null) + { + Debug.LogError($"Exception: {e.Message}"); + } + else + { + ErrorHandler?.Invoke(e); + } return false; } } @@ -92,8 +107,13 @@ public static CoroutineRunner Instance } } + public Coroutine StartCoroutineWrapper(IEnumerator coroutine) + { + return StartCoroutine(new CoroutineWrapper(coroutine, null, (exception) => Debug.LogError($"Exception on Coroutine: {exception.Message}"))); + } + private void Awake() { DontDestroyOnLoad(gameObject); } -} \ No newline at end of file +} diff --git a/Runtime/Scripts/Helpers/NetezosExtensions.cs b/Runtime/Scripts/Helpers/NetezosExtensions.cs index d3f74f66..091d5a46 100644 --- a/Runtime/Scripts/Helpers/NetezosExtensions.cs +++ b/Runtime/Scripts/Helpers/NetezosExtensions.cs @@ -22,7 +22,7 @@ public static IEnumerator ReadTZBalance(string rpcUri, string sender, Action(sender); - return HttpClient.WrappedRequest(getBalanceRequest, callback); + return new CoroutineWrapper(getBalanceRequest, callback); } public static IEnumerator ReadView(string rpcUri, string destination, string entrypoint, @@ -31,7 +31,7 @@ public static IEnumerator ReadView(string rpcUri, string destination, string ent var rpc = new Rpc(rpcUri); var runViewOp = rpc.RunView(destination, entrypoint, input); - return HttpClient.WrappedRequest(runViewOp, (JsonElement result) => + return new CoroutineWrapper(runViewOp, (JsonElement result) => { if (result.ValueKind != JsonValueKind.Null && result.ValueKind != JsonValueKind.Undefined && result.TryGetProperty("data", out var val)) @@ -57,7 +57,7 @@ private static IEnumerator FetchContractCode(string rpcUri, string contract) if (_contracts.ContainsKey(contract)) yield break; var rpc = new Rpc(rpcUri); var scriptOp = rpc.GetContractCode(contract); - yield return HttpClient.WrappedRequest(scriptOp, (JsonElement script) => + yield return new CoroutineWrapper(scriptOp, (JsonElement script) => { var codeElement = script.GetProperty("code").GetRawText(); var code = Micheline.FromJson(codeElement); diff --git a/Runtime/Scripts/Helpers/UnityMainThreadDispatcher.cs b/Runtime/Scripts/Helpers/UnityMainThreadDispatcher.cs index deaff111..04de46fa 100644 --- a/Runtime/Scripts/Helpers/UnityMainThreadDispatcher.cs +++ b/Runtime/Scripts/Helpers/UnityMainThreadDispatcher.cs @@ -45,7 +45,10 @@ public void Enqueue(IEnumerator action) { lock (_executionQueue) { - _executionQueue.Enqueue(() => { StartCoroutine(action); }); + var coroutine = new CoroutineWrapper(action, null, (exception) => Debug.LogError($"Exception on MainThread Queue: {exception.Message}")); + _executionQueue.Enqueue(() => { + StartCoroutine(coroutine); + }); } } diff --git a/Runtime/Scripts/TezosAPI/HttpClient.cs b/Runtime/Scripts/TezosAPI/HttpClient.cs index d9262261..1a2dd2ec 100644 --- a/Runtime/Scripts/TezosAPI/HttpClient.cs +++ b/Runtime/Scripts/TezosAPI/HttpClient.cs @@ -61,14 +61,6 @@ private UnityWebRequest GetUnityWebRequest(string method, string path) request.timeout = RequestTimeout; return request; } - - public static IEnumerator WrappedRequest(IEnumerator op, Action callback) - { - var counterRoutine = new CoroutineWrapper(op); - yield return counterRoutine; - var counter = counterRoutine.Result; - callback?.Invoke(counter); - } } internal static class HttpHeaders diff --git a/Runtime/Scripts/TezosAPI/Tezos.cs b/Runtime/Scripts/TezosAPI/Tezos.cs index 469a47b2..3fb4d802 100644 --- a/Runtime/Scripts/TezosAPI/Tezos.cs +++ b/Runtime/Scripts/TezosAPI/Tezos.cs @@ -84,7 +84,7 @@ private void InitBeaconConnector() { var json = JsonSerializer.Deserialize(transaction); var transactionHash = json.GetProperty("transactionHash").GetString(); - MessageReceiver.StartCoroutine(MessageReceiver.ContractCallInjection(_indexerNode, transactionHash)); + MessageReceiver.StartCoroutine(new CoroutineWrapper(MessageReceiver.ContractCallInjection(_indexerNode, transactionHash))); }; } @@ -167,7 +167,7 @@ TokensForOwnerOrder orderBy $"limit={maxItems}"; var requestRoutine = GetJson>(url); - return WrappedRequest(requestRoutine, cb); + return new CoroutineWrapper>(requestRoutine, cb); } } } \ No newline at end of file diff --git a/Samples~/Scripts/DemoExample/CopyToClipboard.cs b/Samples~/Scripts/DemoExample/CopyToClipboard.cs index f97a1a3b..f110dc3f 100644 --- a/Samples~/Scripts/DemoExample/CopyToClipboard.cs +++ b/Samples~/Scripts/DemoExample/CopyToClipboard.cs @@ -28,7 +28,7 @@ public void OnPointerClick(PointerEventData eventData) // copy text to the clipboard GUIUtility.systemCopyBuffer = text.text; - CoroutineRunner.Instance.StartCoroutine(OnTextCopied()); + CoroutineRunner.Instance.StartCoroutineWrapper(OnTextCopied()); } IEnumerator OnTextCopied() diff --git a/Samples~/Scripts/DemoExample/Core/ExampleManager.cs b/Samples~/Scripts/DemoExample/Core/ExampleManager.cs index 327a29a4..cc18452d 100644 --- a/Samples~/Scripts/DemoExample/Core/ExampleManager.cs +++ b/Samples~/Scripts/DemoExample/Core/ExampleManager.cs @@ -42,12 +42,12 @@ public void FetchInventoryItems(Action> callback) var entrypoint = "view_items_of"; var input = new { @string = sender }; - CoroutineRunner.Instance.StartCoroutine( + CoroutineRunner.Instance.StartCoroutineWrapper( _tezos.ReadView(contractAddress, entrypoint, input, result => { Debug.Log("READING INVENTORY DATA"); // deserialize the json data to inventory items - CoroutineRunner.Instance.StartCoroutine( + CoroutineRunner.Instance.StartCoroutineWrapper( BeaconSDK.NetezosExtensions.HumanizeValue(result, _networkRPC, destination, "humanizeInventory", (ContractInventoryViewResult[] inventory) => OnInventoryFetched(inventory, callback)) ); @@ -127,11 +127,11 @@ public void FetchMarketItems(Action> callback) Prim = PrimType.Unit }; - CoroutineRunner.Instance.StartCoroutine( + CoroutineRunner.Instance.StartCoroutineWrapper( _tezos.ReadView(contractAddress, entrypoint, input, result => { // deserialize the json data to market items - CoroutineRunner.Instance.StartCoroutine( + CoroutineRunner.Instance.StartCoroutineWrapper( BeaconSDK.NetezosExtensions.HumanizeValue(result, _networkRPC, destination, "humanizeMarketplace", (ContractMarketplaceViewResult[] market) => OnMarketplaceFetched(market, callback)) ); @@ -216,7 +216,7 @@ public User GetCurrentUser() public void GetBalance(Action callback) { var routine = _tezos.ReadBalance(callback); - CoroutineRunner.Instance.StartCoroutine(routine); + CoroutineRunner.Instance.StartCoroutineWrapper(routine); } public void GetSoftBalance(Action callback) @@ -238,7 +238,7 @@ private void GetSoftBalanceRoutine(Action callback) } }; - CoroutineRunner.Instance.StartCoroutine( + CoroutineRunner.Instance.StartCoroutineWrapper( _tezos.ReadView(contractAddress, "get_balance", input, result => { var intProp = result.GetProperty("int"); @@ -343,7 +343,7 @@ public void IsItemOnMarket(int itemID, string owner, Action callback) } }; - CoroutineRunner.Instance.StartCoroutine( + CoroutineRunner.Instance.StartCoroutineWrapper( _tezos.ReadView(contractAddress, entrypoint, input, result => { var boolString = result.GetProperty("prim"); diff --git a/Samples~/Scripts/DemoExample/UIManager.cs b/Samples~/Scripts/DemoExample/UIManager.cs index 0127e6bf..5ed01a4e 100644 --- a/Samples~/Scripts/DemoExample/UIManager.cs +++ b/Samples~/Scripts/DemoExample/UIManager.cs @@ -98,7 +98,7 @@ private void PopulateInventory(List items) inventory.Init(items); loadingPanel.SetActive(false); }; - StartCoroutine(DoActionNextFrame(action)); + CoroutineRunner.Instance.StartCoroutineWrapper(DoActionNextFrame(action)); } private void PopulateMarket(List items) @@ -108,7 +108,7 @@ private void PopulateMarket(List items) market.Init(items); loadingPanel.SetActive(false); }; - StartCoroutine(DoActionNextFrame(action)); + CoroutineRunner.Instance.StartCoroutineWrapper(DoActionNextFrame(action)); } private IEnumerator DoActionNextFrame(Action action)