129 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			C#
		
	
	
	
			
		
		
	
	
			129 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			C#
		
	
	
	
| #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
 | |
| 
 | |
| using System;
 | |
| using System.Threading;
 | |
| 
 | |
| namespace Cysharp.Threading.Tasks
 | |
| {
 | |
|     // CancellationTokenSource itself can not reuse but CancelAfter(Timeout.InfiniteTimeSpan) allows reuse if did not reach timeout.
 | |
|     // Similar discussion:
 | |
|     // https://github.com/dotnet/runtime/issues/4694
 | |
|     // https://github.com/dotnet/runtime/issues/48492
 | |
|     // This TimeoutController emulate similar implementation, using CancelAfterSlim; to achieve zero allocation timeout.
 | |
| 
 | |
|     public sealed class TimeoutController : IDisposable
 | |
|     {
 | |
|         readonly static Action<object> CancelCancellationTokenSourceStateDelegate = new Action<object>(CancelCancellationTokenSourceState);
 | |
| 
 | |
|         static void CancelCancellationTokenSourceState(object state)
 | |
|         {
 | |
|             var cts = (CancellationTokenSource)state;
 | |
|             cts.Cancel();
 | |
|         }
 | |
| 
 | |
|         CancellationTokenSource timeoutSource;
 | |
|         CancellationTokenSource linkedSource;
 | |
|         PlayerLoopTimer timer;
 | |
|         bool isDisposed;
 | |
| 
 | |
|         readonly DelayType delayType;
 | |
|         readonly PlayerLoopTiming delayTiming;
 | |
|         readonly CancellationTokenSource originalLinkCancellationTokenSource;
 | |
| 
 | |
|         public TimeoutController(DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
 | |
|         {
 | |
|             this.timeoutSource = new CancellationTokenSource();
 | |
|             this.originalLinkCancellationTokenSource = null;
 | |
|             this.linkedSource = null;
 | |
|             this.delayType = delayType;
 | |
|             this.delayTiming = delayTiming;
 | |
|         }
 | |
| 
 | |
|         public TimeoutController(CancellationTokenSource linkCancellationTokenSource, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
 | |
|         {
 | |
|             this.timeoutSource = new CancellationTokenSource();
 | |
|             this.originalLinkCancellationTokenSource = linkCancellationTokenSource;
 | |
|             this.linkedSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, linkCancellationTokenSource.Token);
 | |
|             this.delayType = delayType;
 | |
|             this.delayTiming = delayTiming;
 | |
|         }
 | |
| 
 | |
|         public CancellationToken Timeout(int millisecondsTimeout)
 | |
|         {
 | |
|             return Timeout(TimeSpan.FromMilliseconds(millisecondsTimeout));
 | |
|         }
 | |
| 
 | |
|         public CancellationToken Timeout(TimeSpan timeout)
 | |
|         {
 | |
|             if (originalLinkCancellationTokenSource != null && originalLinkCancellationTokenSource.IsCancellationRequested)
 | |
|             {
 | |
|                 return originalLinkCancellationTokenSource.Token;
 | |
|             }
 | |
| 
 | |
|             // Timeouted, create new source and timer.
 | |
|             if (timeoutSource.IsCancellationRequested)
 | |
|             {
 | |
|                 timeoutSource.Dispose();
 | |
|                 timeoutSource = new CancellationTokenSource();
 | |
|                 if (linkedSource != null)
 | |
|                 {
 | |
|                     this.linkedSource.Cancel();
 | |
|                     this.linkedSource.Dispose();
 | |
|                     this.linkedSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, originalLinkCancellationTokenSource.Token);
 | |
|                 }
 | |
| 
 | |
|                 timer?.Dispose();
 | |
|                 timer = null;
 | |
|             }
 | |
| 
 | |
|             var useSource = (linkedSource != null) ? linkedSource : timeoutSource;
 | |
|             var token = useSource.Token;
 | |
|             if (timer == null)
 | |
|             {
 | |
|                 // Timer complete => timeoutSource.Cancel() -> linkedSource will be canceled.
 | |
|                 // (linked)token is canceled => stop timer
 | |
|                 timer = PlayerLoopTimer.StartNew(timeout, false, delayType, delayTiming, token, CancelCancellationTokenSourceStateDelegate, timeoutSource);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 timer.Restart(timeout);
 | |
|             }
 | |
| 
 | |
|             return token;
 | |
|         }
 | |
| 
 | |
|         public bool IsTimeout()
 | |
|         {
 | |
|             return timeoutSource.IsCancellationRequested;
 | |
|         }
 | |
| 
 | |
|         public void Reset()
 | |
|         {
 | |
|             timer?.Stop();
 | |
|         }
 | |
| 
 | |
|         public void Dispose()
 | |
|         {
 | |
|             if (isDisposed) return;
 | |
| 
 | |
|             try
 | |
|             {
 | |
|                 // stop timer.
 | |
|                 timer?.Dispose();
 | |
| 
 | |
|                 // cancel and dispose.
 | |
|                 timeoutSource.Cancel();
 | |
|                 timeoutSource.Dispose();
 | |
|                 if (linkedSource != null)
 | |
|                 {
 | |
|                     linkedSource.Cancel();
 | |
|                     linkedSource.Dispose();
 | |
|                 }
 | |
|             }
 | |
|             finally
 | |
|             {
 | |
|                 isDisposed = true;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| } |