{"id":21224519,"url":"https://github.com/mucksponge/unityasync","last_synced_at":"2025-08-19T05:06:02.664Z","repository":{"id":45677333,"uuid":"156968572","full_name":"muckSponge/UnityAsync","owner":"muckSponge","description":"An allocation-free, high performance coroutine framework for Unity built around the async API","archived":false,"fork":false,"pushed_at":"2020-09-30T11:19:26.000Z","size":87,"stargazers_count":296,"open_issues_count":10,"forks_count":33,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-04-24T23:35:39.435Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/muckSponge.png","metadata":{"files":{"readme":"README.MD","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-10T09:58:01.000Z","updated_at":"2025-04-09T05:15:32.000Z","dependencies_parsed_at":"2022-08-20T13:31:02.595Z","dependency_job_id":null,"html_url":"https://github.com/muckSponge/UnityAsync","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/muckSponge/UnityAsync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muckSponge%2FUnityAsync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muckSponge%2FUnityAsync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muckSponge%2FUnityAsync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muckSponge%2FUnityAsync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/muckSponge","download_url":"https://codeload.github.com/muckSponge/UnityAsync/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muckSponge%2FUnityAsync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271103202,"owners_count":24699646,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-19T02:00:09.176Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-20T22:58:43.897Z","updated_at":"2025-08-19T05:06:02.612Z","avatar_url":"https://github.com/muckSponge.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# UnityAsync\nUnityAsync is a coroutine framework for Unity built around the async API. This is not only a more efficient and effective replacement of Unity's IEnumerator coroutines, but also seamlessly integrates Unity with .NET 4+ asynchronous APIs.\n\nWith this library you can:\n* Write allocation-free coroutines\n* Seamlessly integrate with Task-based and async APIs\n* Integrate with IEnumerator-based coroutines and YieldInstructions\n* Easily switch sync contexts (main to background and vice-versa)\n* Define your own custom await instructions (allocation free, no boxing!)\n* Return results at the end of your coroutine\n# Performance\nRest assured; UnityAsync coroutines will generally perform better than Unity's built-in coroutines because:\n* They rarely cause heap allocations\n* They don't weave in and out of native code\n* They don't rely on a monolithic state machine\n\nComparing with standard `yield return null` update loops, performance is 150% improved. Uncached `WaitForSeconds` loops yield a performance increase of over 290%. The benchmarks included the time it took to instantiate the coroutines.\n\nThe one caveat is, in order to store the coroutine, a `Task` object must be allocated. A `Task` object is a bigger allocation compared to a `Coroutine` object. No way around it, if you want to integrate with `Task`-based APIs, you need to use `Tasks`. Often, you just need to fire the coroutine and forget about it, and in such cases, simply use `void` return type to avoid allocations altogether.\n\n# Usage\n## Installation\nClone the repo into your assets folder or download the [latest release](https://github.com/muckSponge/UnityAsync/releases/latest) and open the .unitypackage.\n\n## Replacing existing coroutines\nLet's say we want to replace a pretty straight-forward update loop `IEnumerator` coroutine:\n```c#\nusing UnityEngine;\nusing System.Collections;\n\n...\n\nIEnumerator UpdateLoop()\n{\n\twhile(true)\n\t\tyield return null;\n}\n\nvoid Start()\n{\n\tStartCoroutine(UpdateLoop());\n}\n\n...\n\n```\nUnityAsync coroutines are defined by async methods, which can return `void`, `Task`, or `Task\u003cTResult\u003e`:\n```c#\nusing UnityEngine;\nusing UnityAsync;\n\n...\n\nasync void UpdateLoop()\n{\n\twhile(true)\n\t\tawait new WaitForFrames(1);\n}\n\nvoid Start()\n{\n\tUpdateLoop();\n}\n\n...\n```\nEasy-peasy, right? WaitForFrames is an `IAwaitInstruction`. When you await it, it spawns a `Continuation\u003cT\u003e`, which is (automatically) inserted into a queue and evaluated every frame until it is finished; in this case it will take one frame. Once finished, whatever is after the `IAwaitInstruction` is invoked. We could return `Task` instead of `void` if we wanted to store the coroutine for nesting.\n\nIf you want to link the lifetime of an async coroutine (or part of the coroutine) to a `UnityEngine.Object`, like the built-in `Coroutine`s do, see the ConfigureAwait section.\n\n## Returning a result\nLet's say we have some work we want to perform across the main thread, but it's too much to perform in a single frame. We can create a coroutine for this and return the result of the work once it is finished.\n```c#\nusing UnityEngine;\nusing UnityAsync;\nusing System.Threading.Tasks;\n\n...\n\nasync Task\u003cint\u003e GenerateResult()\n{\n\t// contrived examples are the best examples, right?\n\tint result = 1;\n\t\n\tfor(int i = 0; i \u003c 10; ++i)\n\t{\n\t\tfor(int j = 0; j \u003c 100000; ++j)\n\t\t\tresult = Mathf.Sin(result * j); \n\t\t\t\n\t\tawait new WaitForFrames(1);\n\t}\n\t\n\treturn result;\n}\n\nasync void Start()\n{\n\tint result = await GenerateResult();\n\n\t// ...10 frames later\n\tDebug.Log(result);\n}\n\n...\n```\nWe are awaiting the result in `Start()` so it needs to be an async method (this doesn't impact on how Unity calls it). We now return `Task\u003cint\u003e` and the result is available after 10 frames.\n\n## Switching contexts\nSometimes you'll want to perform some task on the thread pool and return to the main thread once this is completed without blocking. This could be done via `Task.ConfigureAwait()` but now you can await directly on a `SynchronizationContext` which makes it super easy to swap back and forth:\n```c#\nusing UnityAsync;\n\n...\n\nasync void Start()\n{\n\t// do some work on the thread pool\n\tawait Await.BackgroundSyncContext();\n\t\n\t// ...\n\t\n\t// resume on the main thread\n\tawait Await.UnitySyncContext();\n\t\n\t// update Transforms, etc.\n}\n\n...\n\n```\n`Await` actually contains many shortcut functions to streamline your code.\n\n## ConfigureAwait\nJust like with `Task`s, UnityAsync `IAwaitInstruction`s can be configured. You can set the scheduler (Update, LateUpdate, FixedUpdate) and also link its lifespan to a `UnityEngine.Object` and/or `CancellationToken`. Anything after the `await` line will not be reached if the `IAwaitInstruction`'s linked `UnityEngine.Object` was destroyed. If a cancellation was requested, an exception is thrown after the `await` line, which can be handled as you would when a `Task` is cancelled.\n```c#\nusing UnityEngine;\nusing UnityAsync;\n\n...\n\nasync void Start()\n{\n\t// continuation will finish if:\n\t// - 10 seconds pass or\n\t// - this MonoBehaviour is destroyed\n\t// time is evaluated every fixed update\n\tawait Await.Seconds(10).ConfigureAwait(this, FrameScheduler.FixedUpdate);\n\t\n\t// ... if the calling MonoBehaviour was destroyed, we won't get this far\n\t// if it is still alive, we'll be in FixedUpdate\n\t\n\tvar c = new CancellationSource(100);\n\t\n\ttry\n\t{\n\t\tawait Await.Seconds(10).ConfigureAwait(c.Token);\n\t}\n\tcatch(OperationCancelledException)\n\t{\n\t\t// exception will be caught here after 100ms\n\t}\n}\n\n...\n\n```\n\n## Yielding a task\nYou may run into a situation where some of your coroutine code is async, but it is called from an `IEnumerator`. In such a situation you can use `Task.AsYieldInstruction` or `Task\u003cTResult\u003e.AsYieldInstruction`.\n```c#\nusing UnityEngine;\nusing UnityAsync;\n\n...\n\nIEnumerator Start()\n{\n\tyield return new WaitForSeconds(1);\n\t\n\tDebug.Log(\"Click to continue...\");\n\t\n\tyield return WaitForMouse().AsYieldInstruction();\n\t\n\tDebug.Log(\"Click!\");\n}\n\nasync Task WaitForMouse()\n{\n\tawait Await.Until(() =\u003e Input.GetMouseDown(0));\n}\n\n...\n\n```\n\n# Await instructions / awaitables\nBuilt-in:\n* `WaitForFrames`\n* `WaitForSeconds`\n* `WaitForSecondsRealtime`\n* `WaitUntil`\u003csup\u003e1\u003c/sup\u003e\n* `WaitWhile`\u003csup\u003e1\u003c/sup\u003e\n\nUnity:\n* `IEnumerator`\u003csup\u003e2\u003c/sup\u003e\n* `YieldInstruction`\u003csup\u003e2\u003c/sup\u003e\n* `AsyncOperation`\n* `ResourceRequest`\u003csup\u003e3\u003c/sup\u003e\n\nOthers:\n* `Task`\n* `Task\u003c\u003e`\n* …anything that implements `GetAwaiter()`\n\n\u003csup\u003e1\u003c/sup\u003eUse the generic variants to pass a state object and avoid closures.\n\n\u003csup\u003e2\u003c/sup\u003eWill spin up a Unity `Coroutine`, causing allocations. Note, `CustomYieldInstruction` implements `IEnumerator`.\n\n\u003csup\u003e3\u003c/sup\u003eVery small delegate allocation.\n\nAnything that implements `IAwaitInstruction` can be awaited and will be evaluated in Update, LateUpdate, or FixedUpdate. This is how the built-in await instructions are implemented. The advantage over `Task`s or `CustomYieldInstruction`s is they don't cause any allocations if implemented as structs.\n# Custom await instructions\nYou can implement your own await instructions by implementing `IAwaitInstruction`.\n```c#\npublic interface IAwaitInstruction\n{\n\tbool IsCompleted();\n}\n```\nIt's dead simple, just place your behaviour in `IsCompleted` and return `true` when your criteria are met. `IsCompleted` will be evaluated every frame (depending on the `FrameScheduler`). Use a struct to avoid unnecessary allocations.\n\nOne more thing to note; `GetAwaiter()` is already provided through an extension method so that the instruction can encapsulate itself in an `AwaitInstructionAwaiter` when awaited. It is this struct which actually ends up being awaited - as a kind of decoration to make custom await instructions more straight-forward to implement (the alternative is inheritance which causes heap allocations).\n\n```c#\nusing UnityAsync;\n\npublic struct WaitForTimeSpan : IAwaitInstruction\n{\n\treadonly float finishTime;\n\n\tbool IAwaitInstruction.IsCompleted() =\u003e AsyncManager.currentTime \u003e= finishTime;\n\t\n\tpublic WaitForFrames(TimeSpan timeSpan)\n\t{\n\t\t// we use AsyncManager.currentTime here (and above) because it's slightly more efficient vs Time.time\n\t\tfinishFrame = AsyncManager.currentTime + (float)timeSpan.TotalSeconds;\n\t}\t\n}\n```\n\n# Usage with Unity EditorTests (unit tests with NUnit)\nWhen running unit tests, `AsyncManager` is not automatically initialized. Call `AsyncManager.InitializeForEditorTests()` in your test OneTimeSetUp method, for example like this:\n\n```c#\n// In your test class\n\n[OneTimeSetUp]\npublic void OneTimeSetUp()\n{\n\tAsyncManager.InitializeForEditorTests();\n\n\t// Other setup code here\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmucksponge%2Funityasync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmucksponge%2Funityasync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmucksponge%2Funityasync/lists"}