三国卡牌客户端基础资源仓库
yyl
2025-08-25 214fe94eaf7f09741a7857775dfffe8c3b83c75c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#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;
            }
        }
    }
}