diff --git a/src/UniTask.NetCore/UniTask.NetCore.csproj b/src/UniTask.NetCore/UniTask.NetCore.csproj
index d335bd7..0060575 100644
--- a/src/UniTask.NetCore/UniTask.NetCore.csproj
+++ b/src/UniTask.NetCore/UniTask.NetCore.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/src/UniTask/Assets/Plugins/UniTask/IAsyncEnumerable.cs b/src/UniTask/Assets/Plugins/UniTask/IUniTaskAsyncEnumerable.cs
similarity index 100%
rename from src/UniTask/Assets/Plugins/UniTask/IAsyncEnumerable.cs
rename to src/UniTask/Assets/Plugins/UniTask/IUniTaskAsyncEnumerable.cs
diff --git a/src/UniTask/Assets/Plugins/UniTask/IAsyncEnumerable.cs.meta b/src/UniTask/Assets/Plugins/UniTask/IUniTaskAsyncEnumerable.cs.meta
similarity index 100%
rename from src/UniTask/Assets/Plugins/UniTask/IAsyncEnumerable.cs.meta
rename to src/UniTask/Assets/Plugins/UniTask/IUniTaskAsyncEnumerable.cs.meta
diff --git a/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions.meta b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions.meta
new file mode 100644
index 0000000..1097c37
--- /dev/null
+++ b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 669f5459819f7284ca1b35f4d55fe226
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/EveryUpdate.cs b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/EveryUpdate.cs
new file mode 100644
index 0000000..ccb5727
--- /dev/null
+++ b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/EveryUpdate.cs
@@ -0,0 +1,79 @@
+using Cysharp.Threading.Tasks.Internal;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Cysharp.Threading.Tasks.Linq
+{
+ public static partial class UniTaskAsyncEnumerable
+ {
+ public static IUniTaskAsyncEnumerable EveryUpdate(PlayerLoopTiming updateTiming = PlayerLoopTiming.Update)
+ {
+ return new EveryUpdate(updateTiming);
+ }
+ }
+
+ internal class EveryUpdate : IUniTaskAsyncEnumerable
+ {
+ readonly PlayerLoopTiming updateTiming;
+
+ public EveryUpdate(PlayerLoopTiming updateTiming)
+ {
+ this.updateTiming = updateTiming;
+ }
+
+ public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
+ {
+ return new Enumerator(updateTiming, cancellationToken);
+ }
+
+ class Enumerator : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem
+ {
+ readonly PlayerLoopTiming updateTiming;
+ CancellationToken cancellationToken;
+
+ bool disposed;
+
+ public Enumerator(PlayerLoopTiming updateTiming, CancellationToken cancellationToken)
+ {
+ this.updateTiming = updateTiming;
+
+ TaskTracker.TrackActiveTask(this, 2);
+ PlayerLoopHelper.AddAction(updateTiming, this);
+ }
+
+ public AsyncUnit Current => default;
+
+ public UniTask MoveNextAsync()
+ {
+ // return false instead of throw
+ if (disposed || cancellationToken.IsCancellationRequested) return CompletedTasks.False;
+
+ completionSource.Reset();
+ return new UniTask(this, completionSource.Version);
+ }
+
+ public UniTask DisposeAsync()
+ {
+ if (!disposed)
+ {
+ disposed = true;
+ TaskTracker.RemoveTracking(this);
+ }
+ return default;
+ }
+
+ public bool MoveNext()
+ {
+ if (disposed || cancellationToken.IsCancellationRequested)
+ {
+ completionSource.TrySetResult(false);
+ return false;
+ }
+
+ completionSource.TrySetResult(true);
+ return true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/EveryUpdate.cs.meta b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/EveryUpdate.cs.meta
new file mode 100644
index 0000000..6336e0e
--- /dev/null
+++ b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/EveryUpdate.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 00520eb52e49b5b4e8d9870d6ff1aced
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/Interval.cs b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/Interval.cs
new file mode 100644
index 0000000..8b9e529
--- /dev/null
+++ b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/Interval.cs
@@ -0,0 +1,275 @@
+using System;
+using System.Threading;
+
+namespace Cysharp.Threading.Tasks.Linq
+{
+ public static partial class UniTaskAsyncEnumerable
+ {
+ public static IUniTaskAsyncEnumerable Timer(TimeSpan dueTime, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false)
+ {
+ return new Timer(dueTime, null, updateTiming, ignoreTimeScale);
+ }
+
+ public static IUniTaskAsyncEnumerable Timer(TimeSpan dueTime, TimeSpan period, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false)
+ {
+ return new Timer(dueTime, period, updateTiming, ignoreTimeScale);
+ }
+
+ public static IUniTaskAsyncEnumerable Interval(TimeSpan period, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false)
+ {
+ return new Timer(period, period, updateTiming, ignoreTimeScale);
+ }
+
+ public static IUniTaskAsyncEnumerable TimerFrame(int dueTimeFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update)
+ {
+ return new TimerFrame(dueTimeFrameCount, null, updateTiming);
+ }
+
+ public static IUniTaskAsyncEnumerable TimerFrame(int dueTimeFrameCount, int periodFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update)
+ {
+ return new TimerFrame(dueTimeFrameCount, periodFrameCount, updateTiming);
+ }
+
+ public static IUniTaskAsyncEnumerable IntervalFrame(int intervalFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update)
+ {
+ return new TimerFrame(intervalFrameCount, intervalFrameCount, updateTiming);
+ }
+ }
+
+ internal class Timer : IUniTaskAsyncEnumerable
+ {
+ readonly PlayerLoopTiming updateTiming;
+ readonly TimeSpan dueTime;
+ readonly TimeSpan? period;
+ readonly bool ignoreTimeScale;
+
+ public Timer(TimeSpan dueTime, TimeSpan? period, PlayerLoopTiming updateTiming, bool ignoreTimeScale)
+ {
+ this.updateTiming = updateTiming;
+ this.dueTime = dueTime;
+ this.period = period;
+ this.ignoreTimeScale = ignoreTimeScale;
+ }
+
+ public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
+ {
+ return new Enumerator(dueTime, period, updateTiming, ignoreTimeScale, cancellationToken);
+ }
+
+ class Enumerator : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem
+ {
+ readonly float dueTime;
+ readonly float? period;
+ readonly PlayerLoopTiming updateTiming;
+ readonly bool ignoreTimeScale;
+ CancellationToken cancellationToken;
+
+ float elapsed;
+ bool dueTimePhase;
+ bool disposed;
+
+ public Enumerator(TimeSpan dueTime, TimeSpan? period, PlayerLoopTiming updateTiming, bool ignoreTimeScale, CancellationToken cancellationToken)
+ {
+ this.dueTime = (float)dueTime.TotalSeconds;
+ this.period = (period == null) ? null : (float?)period.Value.TotalSeconds;
+
+ if (this.dueTime <= 0) this.dueTime = 0;
+ if (this.period != null)
+ {
+ if (this.period <= 0) this.period = 1;
+ }
+
+ this.dueTimePhase = true;
+ this.updateTiming = updateTiming;
+ this.ignoreTimeScale = ignoreTimeScale;
+ TaskTracker.TrackActiveTask(this, 2);
+ PlayerLoopHelper.AddAction(updateTiming, this);
+ }
+
+ public AsyncUnit Current => default;
+
+ public UniTask MoveNextAsync()
+ {
+ // return false instead of throw
+ if (disposed || cancellationToken.IsCancellationRequested) return CompletedTasks.False;
+
+ completionSource.Reset();
+ return new UniTask(this, completionSource.Version);
+ }
+
+ public UniTask DisposeAsync()
+ {
+ if (!disposed)
+ {
+ disposed = true;
+ TaskTracker.RemoveTracking(this);
+ }
+ return default;
+ }
+
+ public bool MoveNext()
+ {
+ if (disposed || cancellationToken.IsCancellationRequested)
+ {
+ completionSource.TrySetResult(false);
+ return false;
+ }
+
+ AddElapsed();
+ if (dueTimePhase)
+ {
+ if (elapsed >= dueTime)
+ {
+ dueTimePhase = false;
+ elapsed = 0;
+ completionSource.TrySetResult(true);
+ }
+ }
+ else
+ {
+ if (period == null)
+ {
+ completionSource.TrySetResult(false);
+ return false;
+ }
+
+ if (elapsed >= period)
+ {
+ elapsed = 0;
+ completionSource.TrySetResult(true);
+ }
+ }
+
+ return true;
+ }
+
+ void AddElapsed()
+ {
+ if (updateTiming == PlayerLoopTiming.FixedUpdate)
+ {
+ if (ignoreTimeScale)
+ {
+ elapsed += UnityEngine.Time.fixedUnscaledDeltaTime;
+ }
+ else
+ {
+ elapsed += UnityEngine.Time.fixedDeltaTime;
+ }
+ }
+ else
+ {
+ if (ignoreTimeScale)
+ {
+ elapsed += UnityEngine.Time.unscaledDeltaTime;
+ }
+ else
+ {
+ elapsed += UnityEngine.Time.deltaTime;
+ }
+ }
+ }
+ }
+ }
+
+ internal class TimerFrame : IUniTaskAsyncEnumerable
+ {
+ readonly PlayerLoopTiming updateTiming;
+ readonly int dueTimeFrameCount;
+ readonly int? periodFrameCount;
+
+ public TimerFrame(int dueTimeFrameCount, int? periodFrameCount, PlayerLoopTiming updateTiming)
+ {
+ this.updateTiming = updateTiming;
+ this.dueTimeFrameCount = dueTimeFrameCount;
+ this.periodFrameCount = periodFrameCount;
+ }
+
+ public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
+ {
+ return new Enumerator(dueTimeFrameCount, periodFrameCount, updateTiming, cancellationToken);
+ }
+
+ class Enumerator : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem
+ {
+ readonly int dueTimeFrameCount;
+ readonly int? periodFrameCount;
+ CancellationToken cancellationToken;
+
+ int currentFrame;
+ bool dueTimePhase;
+ bool disposed;
+
+ public Enumerator(int dueTimeFrameCount, int? periodFrameCount, PlayerLoopTiming updateTiming, CancellationToken cancellationToken)
+ {
+ if (dueTimeFrameCount <= 0) dueTimeFrameCount = 0;
+ if (periodFrameCount != null)
+ {
+ if (periodFrameCount <= 0) periodFrameCount = 1;
+ }
+
+ this.dueTimePhase = true;
+ this.dueTimeFrameCount = dueTimeFrameCount;
+ this.periodFrameCount = periodFrameCount;
+
+ TaskTracker.TrackActiveTask(this, 2);
+ PlayerLoopHelper.AddAction(updateTiming, this);
+ }
+
+ public AsyncUnit Current => default;
+
+ public UniTask MoveNextAsync()
+ {
+ // return false instead of throw
+ if (disposed || cancellationToken.IsCancellationRequested) return CompletedTasks.False;
+
+ completionSource.Reset();
+ return new UniTask(this, completionSource.Version);
+ }
+
+ public UniTask DisposeAsync()
+ {
+ if (!disposed)
+ {
+ disposed = true;
+ TaskTracker.RemoveTracking(this);
+ }
+ return default;
+ }
+
+ public bool MoveNext()
+ {
+ if (disposed || cancellationToken.IsCancellationRequested)
+ {
+ completionSource.TrySetResult(false);
+ return false;
+ }
+
+ if (dueTimePhase)
+ {
+ if (currentFrame++ == dueTimeFrameCount)
+ {
+ dueTimePhase = false;
+ completionSource.TrySetResult(true);
+ currentFrame = -1;
+ }
+ }
+ else
+ {
+ if (periodFrameCount == null)
+ {
+ completionSource.TrySetResult(false);
+ return false;
+ }
+
+ if (++currentFrame == periodFrameCount)
+ {
+ completionSource.TrySetResult(true);
+ currentFrame = 0;
+ }
+ }
+
+ return true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/Interval.cs.meta b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/Interval.cs.meta
new file mode 100644
index 0000000..ffb5f6d
--- /dev/null
+++ b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/Interval.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 14e8116614488eb43b1428191cef8fe5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/Timer.cs b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/Timer.cs
new file mode 100644
index 0000000..32993c1
--- /dev/null
+++ b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/Timer.cs
@@ -0,0 +1,76 @@
+//using Cysharp.Threading.Tasks.Internal;
+//using System;
+//using System.Collections.Generic;
+//using System.Threading;
+
+//namespace Cysharp.Threading.Tasks.Linq
+//{
+// public static partial class UniTaskAsyncEnumerable
+// {
+// public static IUniTaskAsyncEnumerable EveryUpdate(PlayerLoopTiming updateTiming = PlayerLoopTiming.Update)
+// {
+// return new EveryUpdate(updateTiming);
+// }
+// }
+
+// internal class EveryUpdate : IUniTaskAsyncEnumerable
+// {
+// readonly PlayerLoopTiming updateTiming;
+
+// public EveryUpdate(PlayerLoopTiming updateTiming)
+// {
+// this.updateTiming = updateTiming;
+// }
+
+// public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
+// {
+// return new Enumerator(updateTiming, cancellationToken);
+// }
+
+// class Enumerator : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem
+// {
+// readonly PlayerLoopTiming updateTiming;
+// CancellationToken cancellationToken;
+
+// bool disposed;
+
+// public Enumerator(PlayerLoopTiming updateTiming, CancellationToken cancellationToken)
+// {
+// this.updateTiming = updateTiming;
+
+// TaskTracker.TrackActiveTask(this, 2);
+// PlayerLoopHelper.AddAction(updateTiming, this);
+// }
+
+// public AsyncUnit Current => default;
+
+// public UniTask MoveNextAsync()
+// {
+// return false instead of throw
+// if (disposed || cancellationToken.IsCancellationRequested) return CompletedTasks.False;
+
+// completionSource.Reset();
+// return new UniTask(this, completionSource.Version);
+// }
+
+// public UniTask DisposeAsync()
+// {
+// if (!disposed)
+// {
+// disposed = true;
+// TaskTracker.RemoveTracking(this);
+// }
+// return default;
+// }
+
+// public bool MoveNext()
+// {
+// if (disposed) return false;
+// if (cancellationToken.IsCancellationRequested) return false;
+
+// completionSource.TrySetResult(true);
+// return true;
+// }
+// }
+// }
+//}
\ No newline at end of file
diff --git a/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/Timer.cs.meta b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/Timer.cs.meta
new file mode 100644
index 0000000..aa790c5
--- /dev/null
+++ b/src/UniTask/Assets/Plugins/UniTask/Linq/UnityExtensions/Timer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 382caacde439855418709c641e4d7b04
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/UniTask/Assets/Scenes/SandboxMain.cs b/src/UniTask/Assets/Scenes/SandboxMain.cs
index 79cf010..64f3e25 100644
--- a/src/UniTask/Assets/Scenes/SandboxMain.cs
+++ b/src/UniTask/Assets/Scenes/SandboxMain.cs
@@ -146,10 +146,12 @@ public class SandboxMain : MonoBehaviour
//StartCoroutine(cor);
- await okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).ForEachAsync(_ =>
+ Debug.Log("E:" + DateTime.Now.ToString());
+
+ await UniTaskAsyncEnumerable.Timer(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5), PlayerLoopTiming.Update).ForEachAsync(_ =>
{
- Debug.Log("Call");
- });
+ Debug.Log("Call:" + DateTime.Now.ToString());
+ }, cancellationToken: this.GetCancellationTokenOnDestroy());
//try
//{