/ src / Ryujinx.HLE / HOS / Applets / SoftwareKeyboard / TimedAction.cs
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  }