Skip to content

Commit 5430f35

Browse files
committed
Reduce memory overhead, refactor, more
- ContinuationProcessor queues expand as needed (slight perf. impact) - Refactor: Continuation<T> -> AwaitInstructionAwaiter<T>, IAwaitInstructions no longer need a GetAwaiter method, Task<T>.AsIEnumerator -> Task<T>.AsYieldInstruction - Perf tweaks: Use cached Object.IsAlive vs built-in check, use in/ref keywords to reduce struct copies, AwaitInstructionAwaiter StructLayout tweaks - Closure free WaitUntil and WaitWhile await instructions - More ConfigureAwait combinations - Yes, I’m sorry this isn’t an atomic commit
1 parent cba1940 commit 5430f35

20 files changed

+306
-150
lines changed

UnityAsync/Assets/UnityAsync/Await/Await.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ namespace UnityAsync
66
{
77
public static partial class Await
88
{
9-
static readonly Continuation<WaitForFrames> nextUpdate = new Continuation<WaitForFrames>(new WaitForFrames(1));
10-
static readonly Continuation<WaitForFrames> nextLateUpdate = new Continuation<WaitForFrames>(new WaitForFrames(1), FrameScheduler.LateUpdate);
11-
static readonly Continuation<WaitForFrames> nextFixedUpdate = new Continuation<WaitForFrames>(new WaitForFrames(1), FrameScheduler.FixedUpdate);
9+
static readonly AwaitInstructionAwaiter<WaitForFrames> nextUpdate = new AwaitInstructionAwaiter<WaitForFrames>(new WaitForFrames(1));
10+
static readonly AwaitInstructionAwaiter<WaitForFrames> nextLateUpdate = new AwaitInstructionAwaiter<WaitForFrames>(new WaitForFrames(1), FrameScheduler.LateUpdate);
11+
static readonly AwaitInstructionAwaiter<WaitForFrames> nextFixedUpdate = new AwaitInstructionAwaiter<WaitForFrames>(new WaitForFrames(1), FrameScheduler.FixedUpdate);
1212

1313
/// <summary>
1414
/// Quick access to Unity's <see cref="System.Threading.SynchronizationContext"/>.
@@ -26,38 +26,38 @@ public static partial class Await
2626
/// Convenience function to skip a single frame, equivalent to Unity's <c>yield return null</c>.
2727
/// </summary>
2828
[MethodImpl(MethodImplOptions.AggressiveInlining)]
29-
public static Continuation<WaitForFrames> NextUpdate() => nextUpdate;
29+
public static AwaitInstructionAwaiter<WaitForFrames> NextUpdate() => nextUpdate;
3030

3131
/// <summary>
3232
/// Convenience function to skip a number of frames, equivalent to multiple <c>yield return null</c>s.
3333
/// </summary>
3434
[MethodImpl(MethodImplOptions.AggressiveInlining)]
35-
public static Continuation<WaitForFrames> Updates(int count) => new Continuation<WaitForFrames>(new WaitForFrames(count));
35+
public static AwaitInstructionAwaiter<WaitForFrames> Updates(int count) => new AwaitInstructionAwaiter<WaitForFrames>(new WaitForFrames(count));
3636

3737
/// <summary>
3838
/// Convenience function to skip a single frame and continue in the LateUpdate loop.
3939
/// </summary>
4040
[MethodImpl(MethodImplOptions.AggressiveInlining)]
41-
public static Continuation<WaitForFrames> NextLateUpdate() => nextLateUpdate;
41+
public static AwaitInstructionAwaiter<WaitForFrames> NextLateUpdate() => nextLateUpdate;
4242

4343
/// <summary>
4444
/// Convenience function to skip multiple frames and continue in the LateUpdate loop.
4545
/// </summary>
4646
[MethodImpl(MethodImplOptions.AggressiveInlining)]
47-
public static Continuation<WaitForFrames> LateUpdates(int count) => new Continuation<WaitForFrames>(new WaitForFrames(count), FrameScheduler.LateUpdate);
47+
public static AwaitInstructionAwaiter<WaitForFrames> LateUpdates(int count) => new AwaitInstructionAwaiter<WaitForFrames>(new WaitForFrames(count), FrameScheduler.LateUpdate);
4848

4949
/// <summary>
5050
/// Convenience function to skip a single FixedUpdate frame and continue in the FixedUpdate loop. Equivalent to
5151
/// Unity's <see cref="UnityEngine.WaitForFixedUpdate"/>.
5252
/// </summary>
5353
[MethodImpl(MethodImplOptions.AggressiveInlining)]
54-
public static Continuation<WaitForFrames> NextFixedUpdate() => nextFixedUpdate;
54+
public static AwaitInstructionAwaiter<WaitForFrames> NextFixedUpdate() => nextFixedUpdate;
5555

5656
/// <summary>
5757
/// Convenience function to skip multiple FixedUpdate frames and continue in the FixedUpdate loop.
5858
/// </summary>
5959
[MethodImpl(MethodImplOptions.AggressiveInlining)]
60-
public static Continuation<WaitForFrames> FixedUpdates(int count) => new Continuation<WaitForFrames>(new WaitForFrames(count), FrameScheduler.FixedUpdate);
60+
public static AwaitInstructionAwaiter<WaitForFrames> FixedUpdates(int count) => new AwaitInstructionAwaiter<WaitForFrames>(new WaitForFrames(count), FrameScheduler.FixedUpdate);
6161

6262
/// <summary>
6363
/// Convenience function to wait for a number of in-game seconds before continuing, equivalent to Unity's
@@ -86,5 +86,19 @@ public static partial class Await
8686
/// </summary>
8787
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8888
public static WaitWhile While(Func<bool> condition) => new WaitWhile(condition);
89+
90+
/// <summary>
91+
/// Convenience function to wait for a condition to return true. Equivalent to Unity's
92+
/// <see cref="UnityEngine.WaitUntil"/> but state is passed to avoid closure.
93+
/// </summary>
94+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
95+
public static WaitUntil<TState> Until<TState>(TState state, Func<TState, bool> condition) => new WaitUntil<TState>(state, condition);
96+
97+
/// <summary>
98+
/// Convenience function to wait for a condition to return false. Equivalent to Unity's
99+
/// <see cref="UnityEngine.WaitWhile"/> but state is passed to avoid closure.
100+
/// </summary>
101+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
102+
public static WaitWhile<TState> While<TState>(TState state, Func<TState, bool> condition) => new WaitWhile<TState>(state, condition);
89103
}
90104
}
Lines changed: 44 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
using System;
22
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
34
using System.Threading;
4-
using UnityEngine;
55
using Object = UnityEngine.Object;
66

77
namespace UnityAsync
88
{
9-
public interface IContinuation
9+
public interface IAwaitInstructionAwaiter
1010
{
1111
bool Evaluate();
1212
FrameScheduler Scheduler { get; }
@@ -17,7 +17,8 @@ public interface IContinuation
1717
/// will be queued and executed. Continuations are intended to be awaited after or shortly after instantiation.
1818
/// </summary>
1919
/// <typeparam name="T">The type of <see cref="UnityAsync.IAwaitInstruction"/> to encapsulate.</typeparam>
20-
public struct Continuation<T> : IContinuation, INotifyCompletion where T : IAwaitInstruction
20+
[StructLayout(LayoutKind.Sequential, Pack = 1)]
21+
public struct AwaitInstructionAwaiter<T> : IAwaitInstructionAwaiter, INotifyCompletion where T : IAwaitInstruction
2122
{
2223
static Exception exception;
2324

@@ -35,6 +36,7 @@ public struct Continuation<T> : IContinuation, INotifyCompletion where T : IAwai
3536
/// <code>true</code> if the <see cref="UnityAsync.IAwaitInstruction"/> is finished, its owner destroyed,
3637
/// or has been cancelled, otherwise <code>false</code>.
3738
/// </returns>
39+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3840
public bool Evaluate()
3941
{
4042
try
@@ -45,7 +47,7 @@ public bool Evaluate()
4547
// if owner is destroyed, behaves like a UnityEngine.Coroutine, ie:
4648
// "With this object's death, the thread of prophecy is severed. Restore a saved game to restore the
4749
// weave of fate, or persist in the doomed world you have created."
48-
if(!owner)
50+
if(!owner.IsAlive())
4951
return true;
5052

5153
// if not completed, return false to put it back into a queue for next frame
@@ -74,7 +76,7 @@ public bool Evaluate()
7476
T instruction;
7577
Action continuation;
7678

77-
public Continuation(T inst)
79+
public AwaitInstructionAwaiter(in T inst)
7880
{
7981
instruction = inst;
8082
continuation = null;
@@ -83,7 +85,7 @@ public Continuation(T inst)
8385
Scheduler = FrameScheduler.Update;
8486
}
8587

86-
public Continuation(T inst, FrameScheduler scheduler)
88+
public AwaitInstructionAwaiter(in T inst, FrameScheduler scheduler)
8789
{
8890
instruction = inst;
8991
continuation = null;
@@ -92,7 +94,7 @@ public Continuation(T inst, FrameScheduler scheduler)
9294
Scheduler = scheduler;
9395
}
9496

95-
public Continuation(T inst, CancellationToken cancellationToken, FrameScheduler scheduler)
97+
public AwaitInstructionAwaiter(in T inst, CancellationToken cancellationToken, FrameScheduler scheduler)
9698
{
9799
instruction = inst;
98100
continuation = null;
@@ -102,7 +104,7 @@ public Continuation(T inst, CancellationToken cancellationToken, FrameScheduler
102104
Scheduler = scheduler;
103105
}
104106

105-
public Continuation(T inst, Object owner, FrameScheduler scheduler)
107+
public AwaitInstructionAwaiter(in T inst, Object owner, FrameScheduler scheduler)
106108
{
107109
instruction = inst;
108110
continuation = null;
@@ -113,83 +115,75 @@ public Continuation(T inst, Object owner, FrameScheduler scheduler)
113115

114116
public bool IsCompleted => false;
115117

118+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
116119
public void OnCompleted(Action continuation)
117120
{
118121
this.continuation = continuation;
119122
AsyncManager.AddContinuation(this);
120123
}
121124

122-
/// <summary>
123-
/// Link the continuation's lifespan to a <see cref="UnityEngine.Object"/> and configure the type of update
124-
/// cycle it should be evaluated on.
125-
/// </summary>
126-
/// <returns>A new continuation with updated params.</returns>
127125
[MethodImpl(MethodImplOptions.AggressiveInlining)]
128-
public Continuation<T> ConfigureAwait(Object owner, FrameScheduler scheduler)
126+
public void GetResult()
127+
{
128+
if(exception != null)
129+
{
130+
var e = exception;
131+
exception = null;
132+
133+
throw e;
134+
}
135+
}
136+
137+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
138+
public AwaitInstructionAwaiter<T> ConfigureAwait(Object owner)
129139
{
130140
this.owner = owner;
131-
Scheduler = scheduler;
141+
132142
return this;
133143
}
134-
135-
/// <summary>
136-
/// Link the continuation's lifespan to a <see cref="UnityEngine.Object"/>.
137-
/// </summary>
138-
/// <returns>A new continuation with updated params.</returns>
144+
139145
[MethodImpl(MethodImplOptions.AggressiveInlining)]
140-
public Continuation<T> ConfigureAwait(Object owner)
146+
public AwaitInstructionAwaiter<T> ConfigureAwait(CancellationToken cancellationToken)
141147
{
142-
this.owner = owner;
148+
this.cancellationToken = cancellationToken;
149+
143150
return this;
144151
}
145152

146-
/// <summary>
147-
/// Configure the type of update cycle it should be evaluated on.
148-
/// </summary>
149-
/// <returns>A new continuation with updated params.</returns>
150153
[MethodImpl(MethodImplOptions.AggressiveInlining)]
151-
public Continuation<T> ConfigureAwait(FrameScheduler scheduler)
154+
public AwaitInstructionAwaiter<T> ConfigureAwait(FrameScheduler scheduler)
152155
{
153156
Scheduler = scheduler;
157+
154158
return this;
155159
}
156160

157-
/// <summary>
158-
/// Link the continuation's lifespan to a <see cref="System.Threading.CancellationToken"/> and configure the
159-
/// type of update cycle it should be evaluated on.
160-
/// </summary>
161-
/// <returns>A new continuation with updated params.</returns>
162161
[MethodImpl(MethodImplOptions.AggressiveInlining)]
163-
public Continuation<T> ConfigureAwait(CancellationToken cancellationToken, FrameScheduler scheduler)
162+
public AwaitInstructionAwaiter<T> ConfigureAwait(Object owner, FrameScheduler scheduler)
164163
{
165-
this.cancellationToken = cancellationToken;
164+
this.owner = owner;
166165
Scheduler = scheduler;
166+
167167
return this;
168168
}
169169

170-
/// <summary>
171-
/// Link the continuation's lifespan to a <see cref="System.Threading.CancellationToken"/>.
172-
/// </summary>
173-
/// <returns>A new continuation with updated params.</returns>
174170
[MethodImpl(MethodImplOptions.AggressiveInlining)]
175-
public Continuation<T> ConfigureAwait(CancellationToken cancellationToken)
171+
public AwaitInstructionAwaiter<T> ConfigureAwait(CancellationToken cancellationToken, FrameScheduler scheduler)
176172
{
177173
this.cancellationToken = cancellationToken;
174+
Scheduler = scheduler;
175+
178176
return this;
179177
}
180-
181-
public void GetResult()
178+
179+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
180+
public AwaitInstructionAwaiter<T> ConfigureAwait(Object owner, CancellationToken cancellationToken, FrameScheduler scheduler)
182181
{
183-
if(exception != null)
184-
{
185-
var e = exception;
186-
exception = null;
182+
this.owner = owner;
183+
this.cancellationToken = cancellationToken;
184+
Scheduler = scheduler;
187185

188-
throw e;
189-
}
186+
return this;
190187
}
191-
192-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
193-
public Continuation<T> GetAwaiter() => this;
194188
}
195189
}

UnityAsync/Assets/UnityAsync/Continuation.cs.meta renamed to UnityAsync/Assets/UnityAsync/AwaitInstructionAwaiter.cs.meta

File renamed without changes.

UnityAsync/Assets/UnityAsync/AwaitInstructions/WaitForFrames.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,5 @@ public WaitForFrames(int count)
2525

2626
finishFrame = AsyncManager.CurrentFrameCount + count;
2727
}
28-
29-
public Continuation<WaitForFrames> GetAwaiter() => new Continuation<WaitForFrames>(this);
3028
}
3129
}
Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System;
2-
3-
namespace UnityAsync
1+
namespace UnityAsync
42
{
53
public struct WaitForSeconds : IAwaitInstruction
64
{
@@ -11,11 +9,6 @@ public struct WaitForSeconds : IAwaitInstruction
119
/// <summary>
1210
/// Waits for the specified number of seconds to pass before continuing.
1311
/// </summary>
14-
public WaitForSeconds(float seconds)
15-
{
16-
finishTime = AsyncManager.CurrentTime + seconds;
17-
}
18-
19-
public Continuation<WaitForSeconds> GetAwaiter() => new Continuation<WaitForSeconds>(this);
12+
public WaitForSeconds(float seconds) => finishTime = AsyncManager.CurrentTime + seconds;
2013
}
2114
}
Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System;
2-
3-
namespace UnityAsync
1+
namespace UnityAsync
42
{
53
public struct WaitForSecondsRealtime : IAwaitInstruction
64
{
@@ -11,11 +9,6 @@ public struct WaitForSecondsRealtime : IAwaitInstruction
119
/// <summary>
1210
/// Waits for the specified number of (unscaled) seconds to pass before continuing.
1311
/// </summary>
14-
public WaitForSecondsRealtime(float seconds)
15-
{
16-
finishTime = AsyncManager.CurrentUnscaledTime + seconds;
17-
}
18-
19-
public Continuation<WaitForSecondsRealtime> GetAwaiter() => new Continuation<WaitForSecondsRealtime>(this);
12+
public WaitForSecondsRealtime(float seconds) => finishTime = AsyncManager.CurrentUnscaledTime + seconds;
2013
}
2114
}
Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
using System;
22

3-
#if UNITY_EDITOR
4-
using UnityEngine;
5-
#endif
6-
73
namespace UnityAsync
84
{
95
public struct WaitUntil : IAwaitInstruction
@@ -19,15 +15,10 @@ public WaitUntil(Func<bool> condition)
1915
{
2016
#if UNITY_EDITOR
2117
if(condition == null)
22-
{
23-
condition = () => true;
24-
Debug.LogError($"{nameof(condition)} should not be null. This check will only appear in edit mode.");
25-
}
18+
throw new ArgumentNullException(nameof(condition), "This check only occurs in edit mode.");
2619
#endif
2720

2821
this.condition = condition;
2922
}
30-
31-
public Continuation<WaitUntil> GetAwaiter() => new Continuation<WaitUntil>(this);
3223
}
3324
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
3+
namespace UnityAsync
4+
{
5+
public struct WaitUntil<TState> : IAwaitInstruction
6+
{
7+
readonly Func<TState, bool> condition;
8+
readonly TState state;
9+
10+
bool IAwaitInstruction.IsCompleted() => condition(state);
11+
12+
/// <summary>
13+
/// Waits until the condition returns true before continuing.
14+
/// </summary>
15+
public WaitUntil(TState state, Func<TState, bool> condition)
16+
{
17+
#if UNITY_EDITOR
18+
if(condition == null)
19+
throw new ArgumentNullException(nameof(condition), "This check only occurs in edit mode.");
20+
#endif
21+
22+
this.condition = condition;
23+
this.state = state;
24+
}
25+
}
26+
}

UnityAsync/Assets/UnityAsync/AwaitInstructions/WaitUntil`T.cs.meta

Lines changed: 3 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)