TimedAction.cs
1 using System; 2 using System.Threading; 3 4 namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard 5 { 6 /// <summary> 7 /// A threaded executor of periodic actions that can be cancelled. The total execution time is optional 8 /// and, in this case, a progress is reported back to the action. 9 /// </summary> 10 class TimedAction 11 { 12 public const int MaxThreadSleep = 100; 13 14 private class SleepSubstepData 15 { 16 public readonly int SleepMilliseconds; 17 public readonly int SleepCount; 18 public readonly int SleepRemainderMilliseconds; 19 20 public SleepSubstepData(int sleepMilliseconds) 21 { 22 SleepMilliseconds = Math.Min(sleepMilliseconds, MaxThreadSleep); 23 SleepCount = sleepMilliseconds / SleepMilliseconds; 24 SleepRemainderMilliseconds = sleepMilliseconds - SleepCount * SleepMilliseconds; 25 } 26 } 27 28 private TRef<bool> _cancelled = null; 29 private Thread _thread = null; 30 private readonly object _lock = new(); 31 32 public bool IsRunning 33 { 34 get 35 { 36 lock (_lock) 37 { 38 if (_thread == null) 39 { 40 return false; 41 } 42 43 return _thread.IsAlive; 44 } 45 } 46 } 47 48 public void RequestCancel() 49 { 50 lock (_lock) 51 { 52 if (_cancelled != null) 53 { 54 Volatile.Write(ref _cancelled.Value, true); 55 } 56 } 57 } 58 59 public TimedAction() { } 60 61 private void Reset(Thread thread, TRef<bool> cancelled) 62 { 63 lock (_lock) 64 { 65 // Cancel the current task. 66 if (_cancelled != null) 67 { 68 Volatile.Write(ref _cancelled.Value, true); 69 } 70 71 _cancelled = cancelled; 72 73 _thread = thread; 74 _thread.IsBackground = true; 75 _thread.Start(); 76 } 77 } 78 79 public void Reset(Action<float> action, int totalMilliseconds, int sleepMilliseconds) 80 { 81 // Create a dedicated cancel token for each task. 82 var cancelled = new TRef<bool>(false); 83 84 Reset(new Thread(() => 85 { 86 var substepData = new SleepSubstepData(sleepMilliseconds); 87 88 int totalCount = totalMilliseconds / sleepMilliseconds; 89 int totalRemainder = totalMilliseconds - totalCount * sleepMilliseconds; 90 91 if (Volatile.Read(ref cancelled.Value)) 92 { 93 action(-1); 94 95 return; 96 } 97 98 action(0); 99 100 for (int i = 1; i <= totalCount; i++) 101 { 102 if (SleepWithSubstep(substepData, cancelled)) 103 { 104 action(-1); 105 106 return; 107 } 108 109 action((float)(i * sleepMilliseconds) / totalMilliseconds); 110 } 111 112 if (totalRemainder > 0) 113 { 114 if (SleepWithSubstep(substepData, cancelled)) 115 { 116 action(-1); 117 118 return; 119 } 120 121 action(1); 122 } 123 }), cancelled); 124 } 125 126 public void Reset(Action action, int sleepMilliseconds) 127 { 128 // Create a dedicated cancel token for each task. 129 var cancelled = new TRef<bool>(false); 130 131 Reset(new Thread(() => 132 { 133 var substepData = new SleepSubstepData(sleepMilliseconds); 134 135 while (!Volatile.Read(ref cancelled.Value)) 136 { 137 action(); 138 139 if (SleepWithSubstep(substepData, cancelled)) 140 { 141 return; 142 } 143 } 144 }), cancelled); 145 } 146 147 public void Reset(Action action) 148 { 149 // Create a dedicated cancel token for each task. 150 var cancelled = new TRef<bool>(false); 151 152 Reset(new Thread(() => 153 { 154 while (!Volatile.Read(ref cancelled.Value)) 155 { 156 action(); 157 } 158 }), cancelled); 159 } 160 161 private static bool SleepWithSubstep(SleepSubstepData substepData, TRef<bool> cancelled) 162 { 163 for (int i = 0; i < substepData.SleepCount; i++) 164 { 165 if (Volatile.Read(ref cancelled.Value)) 166 { 167 return true; 168 } 169 170 Thread.Sleep(substepData.SleepMilliseconds); 171 } 172 173 if (substepData.SleepRemainderMilliseconds > 0) 174 { 175 if (Volatile.Read(ref cancelled.Value)) 176 { 177 return true; 178 } 179 180 Thread.Sleep(substepData.SleepRemainderMilliseconds); 181 } 182 183 return Volatile.Read(ref cancelled.Value); 184 } 185 } 186 }