aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TimedAction.cs
blob: 3eaf64596e21cde3dd8d10ce270a902d0c7955bb (plain) (blame)
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
using System;
using System.Threading;

namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
    /// <summary>
    /// A threaded executor of periodic actions that can be cancelled. The total execution time is optional
    /// and, in this case, a progress is reported back to the action.
    /// </summary>
    class TimedAction
    {
        public const int MaxThreadSleep = 100;

        private class SleepSubstepData
        {
            public readonly int SleepMilliseconds;
            public readonly int SleepCount;
            public readonly int SleepRemainderMilliseconds;

            public SleepSubstepData(int sleepMilliseconds)
            {
                SleepMilliseconds = Math.Min(sleepMilliseconds, MaxThreadSleep);
                SleepCount = sleepMilliseconds / SleepMilliseconds;
                SleepRemainderMilliseconds = sleepMilliseconds - SleepCount * SleepMilliseconds;
            }
        }

        private TRef<bool> _cancelled = null;
        private Thread _thread = null;
        private readonly object _lock = new();

        public bool IsRunning
        {
            get
            {
                lock (_lock)
                {
                    if (_thread == null)
                    {
                        return false;
                    }

                    return _thread.IsAlive;
                }
            }
        }

        public void RequestCancel()
        {
            lock (_lock)
            {
                if (_cancelled != null)
                {
                    Volatile.Write(ref _cancelled.Value, true);
                }
            }
        }

        public TimedAction() { }

        private void Reset(Thread thread, TRef<bool> cancelled)
        {
            lock (_lock)
            {
                // Cancel the current task.
                if (_cancelled != null)
                {
                    Volatile.Write(ref _cancelled.Value, true);
                }

                _cancelled = cancelled;

                _thread = thread;
                _thread.IsBackground = true;
                _thread.Start();
            }
        }

        public void Reset(Action<float> action, int totalMilliseconds, int sleepMilliseconds)
        {
            // Create a dedicated cancel token for each task.
            var cancelled = new TRef<bool>(false);

            Reset(new Thread(() =>
            {
                var substepData = new SleepSubstepData(sleepMilliseconds);

                int totalCount = totalMilliseconds / sleepMilliseconds;
                int totalRemainder = totalMilliseconds - totalCount * sleepMilliseconds;

                if (Volatile.Read(ref cancelled.Value))
                {
                    action(-1);

                    return;
                }

                action(0);

                for (int i = 1; i <= totalCount; i++)
                {
                    if (SleepWithSubstep(substepData, cancelled))
                    {
                        action(-1);

                        return;
                    }

                    action((float)(i * sleepMilliseconds) / totalMilliseconds);
                }

                if (totalRemainder > 0)
                {
                    if (SleepWithSubstep(substepData, cancelled))
                    {
                        action(-1);

                        return;
                    }

                    action(1);
                }
            }), cancelled);
        }

        public void Reset(Action action, int sleepMilliseconds)
        {
            // Create a dedicated cancel token for each task.
            var cancelled = new TRef<bool>(false);

            Reset(new Thread(() =>
            {
                var substepData = new SleepSubstepData(sleepMilliseconds);

                while (!Volatile.Read(ref cancelled.Value))
                {
                    action();

                    if (SleepWithSubstep(substepData, cancelled))
                    {
                        return;
                    }
                }
            }), cancelled);
        }

        public void Reset(Action action)
        {
            // Create a dedicated cancel token for each task.
            var cancelled = new TRef<bool>(false);

            Reset(new Thread(() =>
            {
                while (!Volatile.Read(ref cancelled.Value))
                {
                    action();
                }
            }), cancelled);
        }

        private static bool SleepWithSubstep(SleepSubstepData substepData, TRef<bool> cancelled)
        {
            for (int i = 0; i < substepData.SleepCount; i++)
            {
                if (Volatile.Read(ref cancelled.Value))
                {
                    return true;
                }

                Thread.Sleep(substepData.SleepMilliseconds);
            }

            if (substepData.SleepRemainderMilliseconds > 0)
            {
                if (Volatile.Read(ref cancelled.Value))
                {
                    return true;
                }

                Thread.Sleep(substepData.SleepRemainderMilliseconds);
            }

            return Volatile.Read(ref cancelled.Value);
        }
    }
}