Skip to content

Commit 55a3949

Browse files
committed
Added better support for multiple threads
1 parent 660550b commit 55a3949

File tree

7 files changed

+292
-84
lines changed

7 files changed

+292
-84
lines changed

UnityProject/Assets/AsyncUtil/Source/IEnumeratorAwaitExtensions.cs

Lines changed: 190 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Runtime.CompilerServices;
5+
using System.Threading;
56
using System.Threading.Tasks;
67
using UnityEngine;
78
using UnityAsyncAwaitUtil;
@@ -11,158 +12,230 @@
1112
// that make the most sense for the specific instruction type
1213
public static class IEnumeratorAwaitExtensions
1314
{
14-
public static TaskAwaiter<AsyncOperation> GetAwaiter(this AsyncOperation instruction)
15+
public static SimpleCoroutineAwaiter GetAwaiter(this WaitForSeconds instruction)
1516
{
16-
return GetAwaiterReturnSelf(instruction);
17+
return GetAwaiterReturnVoid(instruction);
1718
}
1819

19-
public static TaskAwaiter<object> GetAwaiter(this WaitForSeconds instruction)
20+
public static SimpleCoroutineAwaiter GetAwaiter(this WaitForUpdate instruction)
2021
{
21-
return GetAwaiterReturnNull(instruction);
22+
return GetAwaiterReturnVoid(instruction);
2223
}
2324

24-
public static TaskAwaiter<object> GetAwaiter(this WaitForUpdate instruction)
25+
public static SimpleCoroutineAwaiter GetAwaiter(this WaitForEndOfFrame instruction)
2526
{
26-
return GetAwaiterReturnNull(instruction);
27+
return GetAwaiterReturnVoid(instruction);
2728
}
2829

29-
public static TaskAwaiter<object> GetAwaiter(this WaitForEndOfFrame instruction)
30+
public static SimpleCoroutineAwaiter GetAwaiter(this WaitForFixedUpdate instruction)
3031
{
31-
return GetAwaiterReturnNull(instruction);
32+
return GetAwaiterReturnVoid(instruction);
3233
}
3334

34-
public static TaskAwaiter<object> GetAwaiter(this WaitForFixedUpdate instruction)
35+
public static SimpleCoroutineAwaiter GetAwaiter(this WaitForSecondsRealtime instruction)
3536
{
36-
return GetAwaiterReturnNull(instruction);
37+
return GetAwaiterReturnVoid(instruction);
3738
}
3839

39-
public static TaskAwaiter<object> GetAwaiter(this WaitForSecondsRealtime instruction)
40+
public static SimpleCoroutineAwaiter GetAwaiter(this WaitUntil instruction)
4041
{
41-
return GetAwaiterReturnNull(instruction);
42+
return GetAwaiterReturnVoid(instruction);
4243
}
4344

44-
public static TaskAwaiter<object> GetAwaiter(this WaitUntil instruction)
45+
public static SimpleCoroutineAwaiter GetAwaiter(this WaitWhile instruction)
4546
{
46-
return GetAwaiterReturnNull(instruction);
47+
return GetAwaiterReturnVoid(instruction);
4748
}
4849

49-
public static TaskAwaiter<object> GetAwaiter(this WaitWhile instruction)
50+
public static SimpleCoroutineAwaiter<AsyncOperation> GetAwaiter(this AsyncOperation instruction)
5051
{
51-
return GetAwaiterReturnNull(instruction);
52+
return GetAwaiterReturnSelf(instruction);
5253
}
5354

54-
public static TaskAwaiter<UnityEngine.Object> GetAwaiter(this ResourceRequest instruction)
55+
public static SimpleCoroutineAwaiter<UnityEngine.Object> GetAwaiter(this ResourceRequest instruction)
5556
{
56-
var tcs = new TaskCompletionSource<UnityEngine.Object>();
57-
AsyncCoroutineRunner.Instance.StartCoroutine(
58-
InstructionWrappers.ResourceRequest(tcs, instruction));
59-
return tcs.Task.GetAwaiter();
57+
var awaiter = new SimpleCoroutineAwaiter<UnityEngine.Object>();
58+
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
59+
InstructionWrappers.ResourceRequest(awaiter, instruction)));
60+
return awaiter;
6061
}
6162

62-
public static TaskAwaiter<UnityEngine.iOS.OnDemandResourcesRequest> GetAwaiter(this UnityEngine.iOS.OnDemandResourcesRequest instruction)
63+
public static SimpleCoroutineAwaiter<UnityEngine.iOS.OnDemandResourcesRequest> GetAwaiter(this UnityEngine.iOS.OnDemandResourcesRequest instruction)
6364
{
6465
return GetAwaiterReturnSelf(instruction);
6566
}
6667

6768
// Return itself so you can do things like (await new WWW(url)).bytes
68-
public static TaskAwaiter<WWW> GetAwaiter(this WWW instruction)
69+
public static SimpleCoroutineAwaiter<WWW> GetAwaiter(this WWW instruction)
6970
{
7071
return GetAwaiterReturnSelf(instruction);
7172
}
7273

73-
public static TaskAwaiter<AssetBundle> GetAwaiter(this AssetBundleCreateRequest instruction)
74+
public static SimpleCoroutineAwaiter<AssetBundle> GetAwaiter(this AssetBundleCreateRequest instruction)
7475
{
75-
var tcs = new TaskCompletionSource<AssetBundle>();
76-
AsyncCoroutineRunner.Instance.StartCoroutine(
77-
InstructionWrappers.AssetBundleCreateRequest(tcs, instruction));
78-
return tcs.Task.GetAwaiter();
76+
var awaiter = new SimpleCoroutineAwaiter<AssetBundle>();
77+
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
78+
InstructionWrappers.AssetBundleCreateRequest(awaiter, instruction)));
79+
return awaiter;
7980
}
8081

81-
public static TaskAwaiter<UnityEngine.Object> GetAwaiter(this AssetBundleRequest instruction)
82+
public static SimpleCoroutineAwaiter<UnityEngine.Object> GetAwaiter(this AssetBundleRequest instruction)
8283
{
83-
var tcs = new TaskCompletionSource<UnityEngine.Object>();
84-
AsyncCoroutineRunner.Instance.StartCoroutine(
85-
InstructionWrappers.AssetBundleRequest(tcs, instruction));
86-
return tcs.Task.GetAwaiter();
84+
var awaiter = new SimpleCoroutineAwaiter<UnityEngine.Object>();
85+
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
86+
InstructionWrappers.AssetBundleRequest(awaiter, instruction)));
87+
return awaiter;
8788
}
8889

89-
public static TaskAwaiter<object> GetAwaiter(this IEnumerator coroutine)
90+
public static SimpleCoroutineAwaiter<object> GetAwaiter(this IEnumerator coroutine)
9091
{
91-
var tcs = new TaskCompletionSource<object>();
92-
var wrapper = new CoroutineWrapper(coroutine, tcs);
93-
AsyncCoroutineRunner.Instance.StartCoroutine(wrapper.Run());
94-
return tcs.Task.GetAwaiter();
92+
var awaiter = new SimpleCoroutineAwaiter<object>();
93+
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
94+
new CoroutineWrapper(coroutine, awaiter).Run()));
95+
return awaiter;
9596
}
9697

97-
// We'd prefer to return TaskAwaiter here instead since there is never a return
98-
// value for yield instructions, but I'm not sure how to get that working here
99-
// since TaskAwaiter<> does not inherit from TaskAwaiter and there isn't a
100-
// non generic version of TaskCompletionSource<>
101-
static TaskAwaiter<object> GetAwaiterReturnNull(object instruction)
98+
static SimpleCoroutineAwaiter GetAwaiterReturnVoid(object instruction)
10299
{
103-
var tcs = new TaskCompletionSource<object>();
104-
AsyncCoroutineRunner.Instance.StartCoroutine(
105-
InstructionWrappers.ReturnNullValue(tcs, instruction));
106-
return tcs.Task.GetAwaiter();
100+
var awaiter = new SimpleCoroutineAwaiter();
101+
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
102+
InstructionWrappers.ReturnVoid(awaiter, instruction)));
103+
return awaiter;
107104
}
108105

109-
static TaskAwaiter<T> GetAwaiterReturnSelf<T>(T instruction)
106+
static SimpleCoroutineAwaiter<T> GetAwaiterReturnSelf<T>(T instruction)
110107
{
111-
var tcs = new TaskCompletionSource<T>();
112-
AsyncCoroutineRunner.Instance.StartCoroutine(
113-
InstructionWrappers.ReturnSelf(tcs, instruction));
114-
return tcs.Task.GetAwaiter();
108+
var awaiter = new SimpleCoroutineAwaiter<T>();
109+
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
110+
InstructionWrappers.ReturnSelf(awaiter, instruction)));
111+
return awaiter;
115112
}
116113

117-
static class InstructionWrappers
114+
static void RunOnUnityScheduler(Action action)
118115
{
119-
public static IEnumerator ReturnNullValue(
120-
TaskCompletionSource<object> tcs, object instruction)
116+
if (SynchronizationContext.Current == SyncContextUtil.UnitySynchronizationContext)
121117
{
122-
yield return instruction;
123-
tcs.SetResult(null);
118+
action();
119+
}
120+
else
121+
{
122+
SyncContextUtil.UnitySynchronizationContext.Post(_ => action(), null);
124123
}
124+
}
125125

126-
public static IEnumerator AssetBundleCreateRequest(
127-
TaskCompletionSource<AssetBundle> tcs, AssetBundleCreateRequest instruction)
126+
static void Assert(bool condition)
127+
{
128+
if (!condition)
128129
{
129-
yield return instruction;
130-
tcs.SetResult(instruction.assetBundle);
130+
throw new Exception("Assert hit in UnityAsyncUtil package!");
131131
}
132+
}
132133

133-
public static IEnumerator AssetBundleRequest(
134-
TaskCompletionSource<UnityEngine.Object> tcs, AssetBundleRequest instruction)
134+
public class SimpleCoroutineAwaiter<T> : INotifyCompletion
135+
{
136+
bool _isDone;
137+
Exception _exception;
138+
Action _continuation;
139+
T _result;
140+
141+
public bool IsCompleted
135142
{
136-
yield return instruction;
137-
tcs.SetResult(instruction.asset);
143+
get { return _isDone; }
138144
}
139145

140-
public static IEnumerator ResourceRequest(
141-
TaskCompletionSource<UnityEngine.Object> tcs, ResourceRequest instruction)
146+
public T GetResult()
142147
{
143-
yield return instruction;
144-
tcs.SetResult(instruction.asset);
148+
Assert(_isDone);
149+
150+
if (_exception != null)
151+
{
152+
throw _exception;
153+
}
154+
155+
return _result;
145156
}
146157

147-
public static IEnumerator ReturnSelf<T>(
148-
TaskCompletionSource<T> tcs, T instruction)
158+
public void Complete(T result, Exception e)
149159
{
150-
yield return instruction;
151-
tcs.SetResult(instruction);
160+
Assert(!_isDone);
161+
162+
_isDone = true;
163+
_exception = e;
164+
_result = result;
165+
166+
// Always trigger the continuation on the unity thread when awaiting on unity yield
167+
// instructions
168+
if (_continuation != null)
169+
{
170+
RunOnUnityScheduler(_continuation);
171+
}
172+
}
173+
174+
void INotifyCompletion.OnCompleted(Action continuation)
175+
{
176+
Assert(_continuation == null);
177+
Assert(!_isDone);
178+
179+
_continuation = continuation;
180+
}
181+
}
182+
183+
public class SimpleCoroutineAwaiter : INotifyCompletion
184+
{
185+
bool _isDone;
186+
Exception _exception;
187+
Action _continuation;
188+
189+
public bool IsCompleted
190+
{
191+
get { return _isDone; }
192+
}
193+
194+
public void GetResult()
195+
{
196+
Assert(_isDone);
197+
198+
if (_exception != null)
199+
{
200+
throw _exception;
201+
}
202+
}
203+
204+
public void Complete(Exception e)
205+
{
206+
Assert(!_isDone);
207+
208+
_isDone = true;
209+
_exception = e;
210+
211+
// Always trigger the continuation on the unity thread when awaiting on unity yield
212+
// instructions
213+
if (_continuation != null)
214+
{
215+
RunOnUnityScheduler(_continuation);
216+
}
217+
}
218+
219+
void INotifyCompletion.OnCompleted(Action continuation)
220+
{
221+
Assert(_continuation == null);
222+
Assert(!_isDone);
223+
224+
_continuation = continuation;
152225
}
153226
}
154227

155228
class CoroutineWrapper
156229
{
157-
readonly TaskCompletionSource<object> _tcs;
230+
readonly SimpleCoroutineAwaiter<object> _awaiter;
158231
readonly Stack<IEnumerator> _processStack;
159232

160233
public CoroutineWrapper(
161-
IEnumerator coroutine, TaskCompletionSource<object> tcs)
234+
IEnumerator coroutine, SimpleCoroutineAwaiter<object> awaiter)
162235
{
163236
_processStack = new Stack<IEnumerator>();
164237
_processStack.Push(coroutine);
165-
_tcs = tcs;
238+
_awaiter = awaiter;
166239
}
167240

168241
public IEnumerator Run()
@@ -179,7 +252,7 @@ public IEnumerator Run()
179252
}
180253
catch (Exception e)
181254
{
182-
_tcs.SetException(e);
255+
_awaiter.Complete(null, e);
183256
yield break;
184257
}
185258

@@ -189,7 +262,7 @@ public IEnumerator Run()
189262

190263
if (_processStack.Count == 0)
191264
{
192-
_tcs.SetResult(topWorker.Current);
265+
_awaiter.Complete(topWorker.Current, null);
193266
yield break;
194267
}
195268
}
@@ -210,4 +283,43 @@ public IEnumerator Run()
210283
}
211284
}
212285
}
286+
287+
static class InstructionWrappers
288+
{
289+
public static IEnumerator ReturnVoid(
290+
SimpleCoroutineAwaiter awaiter, object instruction)
291+
{
292+
// For simple instructions we assume that they don't throw exceptions
293+
yield return instruction;
294+
awaiter.Complete(null);
295+
}
296+
297+
public static IEnumerator AssetBundleCreateRequest(
298+
SimpleCoroutineAwaiter<AssetBundle> awaiter, AssetBundleCreateRequest instruction)
299+
{
300+
yield return instruction;
301+
awaiter.Complete(instruction.assetBundle, null);
302+
}
303+
304+
public static IEnumerator ReturnSelf<T>(
305+
SimpleCoroutineAwaiter<T> awaiter, T instruction)
306+
{
307+
yield return instruction;
308+
awaiter.Complete(instruction, null);
309+
}
310+
311+
public static IEnumerator AssetBundleRequest(
312+
SimpleCoroutineAwaiter<UnityEngine.Object> awaiter, AssetBundleRequest instruction)
313+
{
314+
yield return instruction;
315+
awaiter.Complete(instruction.asset, null);
316+
}
317+
318+
public static IEnumerator ResourceRequest(
319+
SimpleCoroutineAwaiter<UnityEngine.Object> awaiter, ResourceRequest instruction)
320+
{
321+
yield return instruction;
322+
awaiter.Complete(instruction.asset, null);
323+
}
324+
}
213325
}

UnityProject/Assets/AsyncUtil/Source/Internal/Awaiter.cs.meta

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)