diff --git a/CSharp/FrameRateTask/Exceptions.cs b/CSharp/FrameRateTask/Exceptions.cs index 306d7ad..350719b 100644 --- a/CSharp/FrameRateTask/Exceptions.cs +++ b/CSharp/FrameRateTask/Exceptions.cs @@ -14,40 +14,40 @@ namespace Timothy.FrameRateTask { - /// - /// This exception will be thrown when the user gets the return value without the task finished. - /// - public class TaskNotFinishedException : Exception - { - /// - public override string Message => "The task has not finished!"; - } - - /// - /// This exception will be thrown when the time interval specified is invalid. - /// - public class IllegalTimeIntervalException : Exception - { - /// - public override string Message => "The time interval should be positive and no more than 1000ms!"; - } - - /// - /// This exception will be thrown when time exceeds but time exceeding is not allowed. - /// - public class TimeExceedException : Exception - { - /// - public override string Message => "The loop runs too slow that it cannot finish the task in the given time!"; - } - - /// - /// This exception will be thrown when the user trys to start a task which has started. - /// - public class TaskStartedMoreThanOnceException : Exception - { - /// - public override string Message => "The task has started more than once!"; - } + /// + /// The exception that is thrown when the user gets the return value while the task has not yet finished. + /// + public class TaskNotFinishedException : InvalidOperationException + { + /// + public override string Message => "The task has not yet finished!"; + } + + /// + /// The exception that is thrown when the specified time interval is invalid. + /// + public class IllegalTimeIntervalException : ArgumentOutOfRangeException + { + /// + public override string Message => "The time interval should be a positive number and not exceed 1000ms!"; + } + + /// + /// The exception that is thrown when the task times out. + /// + public class TimeExceedException : TimeoutException + { + /// + public override string Message => "The loop runs so slowly that it cannot complete the task in the given time!"; + } + + /// + /// The exception that is thrown when the user tries to start a task that has already been started. + /// + public class TaskStartedMoreThanOnceException : InvalidOperationException + { + /// + public override string Message => "The task has been started more than once!"; + } } diff --git a/CSharp/FrameRateTask/FrameRateTask.cs b/CSharp/FrameRateTask/FrameRateTask.cs index 02db2e9..12f8386 100644 --- a/CSharp/FrameRateTask/FrameRateTask.cs +++ b/CSharp/FrameRateTask/FrameRateTask.cs @@ -16,239 +16,239 @@ namespace Timothy.FrameRateTask { - /// - /// The class intends to execute a task which needs to be executed repeatedly every less than one second accurately. - /// - /// The type of the return value of the task. - public class FrameRateTaskExecutor - { - /// - /// The current actual frame rate. - /// - public uint FrameRate - { - get => (uint)Interlocked.CompareExchange(ref frameRate, 0, 0); - private set => Interlocked.Exchange(ref frameRate, value); - } - private long frameRate; - - /// - /// Gets a value indicating whether or not the task has finished. - /// - /// - /// true if the task has finished; otherwise, false. - /// - public bool Finished - { - get => Interlocked.CompareExchange(ref finished, 0, 0) != 0; - set => Interlocked.Exchange(ref finished, value ? 1 : 0); - } - private int finished = 0; - - /// - /// Gets a value indicating whether or not the task has started. - /// - /// - /// true if the task has started; otherwise, false. - /// - public bool HasExecuted { get => Interlocked.CompareExchange(ref hasExecuted, 0, 0) != 0; } - private int hasExecuted = 0; - private bool TrySetExecute() - { - if (Interlocked.Exchange(ref hasExecuted, 1) != 0) - { - return false; - } - return true; - } - - private TResult result; - /// - /// Get the return value of the task. - /// - /// - /// The task hasn't finished. - /// - public TResult Result - { - get - { - if (!Finished) throw new TaskNotFinishedException(); - return result; - } - private set => result = value; - } - - /// - /// Gets or sets whether it allows time exceeding. - /// - /// - /// If it is set false, the task will throw Timothy.FrameRateTask.TimeExceedException when the task cannot finish in the given time. - /// The default value is true. - /// - public bool AllowTimeExceed - { - get; + /// + /// This class intends to perform a task that needs to be executed repeatedly at exact intervals. + /// + /// The type of the return value of the task. + public class FrameRateTaskExecutor + { + /// + /// Gets the current actual frame rate. + /// + public uint FrameRate + { + get => (uint)Interlocked.CompareExchange(ref frameRate, 0, 0); + private set => Interlocked.Exchange(ref frameRate, value); + } + private long frameRate; + + /// + /// Gets whether the task has finished. + /// + /// + /// true if the task has finished; otherwise, false. + /// + public bool Finished + { + get => Interlocked.CompareExchange(ref finished, 0, 0) != 0; + set => Interlocked.Exchange(ref finished, value ? 1 : 0); + } + private int finished = 0; + + /// + /// Gets whether the task has started + /// + /// + /// true if the task has started; otherwise, false. + /// + public bool HasExecuted { get => Interlocked.CompareExchange(ref hasExecuted, 0, 0) != 0; } + private int hasExecuted = 0; + private bool TrySetExecute() + { + if (Interlocked.Exchange(ref hasExecuted, 1) != 0) + { + return false; + } + return true; + } + + private TResult result; + /// + /// Gets the return value of the task. + /// + /// + /// This task has not yet finished. + /// + public TResult Result + { + get + { + if (!Finished) throw new TaskNotFinishedException(); + return result; + } + private set => result = value; + } + + /// + /// Gets or sets whether to allow timeout. + /// + /// + /// If it is set to false and the task fails to complete in the given time, the task will throw Timothy.FrameRateTask.TimeExceedException. + /// The default value is true. + /// + public bool AllowTimeExceed + { + get; #if NET5_0_OR_GREATER init; #else - set; + set; #endif - } = true; - - /// - /// It will be called once time exceeds. - /// - /// - /// parameter bool: If it is called because of the number of time exceeding is greater than MaxTolerantTimeExceedCount, the argument is true; if it is called because of exceeding once, the argument is false. - /// - public Action TimeExceedAction - { - get; + } = true; + + /// + /// Sets the method to be called when the task execution times out. + /// + /// + /// Parameter bool: true if the timeout count is greater than MaxTolerantTimeExceedCount when called; otherwise, false. + /// + public Action TimeExceedAction + { + private get; #if NET5_0_OR_GREATER init; #else - set; + set; #endif - } = callByExceed => { }; - - /// - /// Gets or sets the maximum count of time exceeding continuously. - /// - /// - /// The value is 5 for default. - /// - public ulong MaxTolerantTimeExceedCount - { - get; + } = callByExceed => { }; + + /// + /// Gets or sets the maximum number of consecutive timeouts. + /// + /// + /// The default value is 5. + /// + public ulong MaxTolerantTimeExceedCount + { + get; #if NET5_0_OR_GREATER init; #else - set; + set; #endif - } = 5; - - /// - /// Start this task synchronously. - /// - /// - /// the task has started. - /// - public void Start() - { - if (!TryStart()) throw new TaskStartedMoreThanOnceException(); - } - /// - /// Try to start this task synchronously. - /// - /// - /// true if the task starts successfully; false if the task has started. - /// - public bool TryStart() - { - if (!TrySetExecute()) return false; - loopFunc(); - return true; - } - - private Action loopFunc; - - /// - /// Constructor - /// - /// If you want to continue to loop, return true; otherwise, return false. - /// If you want to break out, return false; otherwise, return true. - /// The time interval between two execution. - /// Used to set the return value. It will be called after the loop. - /// The maximum time for the loop to run. - public FrameRateTaskExecutor - ( - Func loopCondition, - Func loopToDo, - long timeInterval, - Func finallyReturn, - long maxTotalDuration = long.MaxValue - ) - { - - if (timeInterval <= 0L && timeInterval > 1000L) - { - throw new IllegalTimeIntervalException(); - } - FrameRate = (uint)(1000L / timeInterval); - - loopFunc = () => - { - ulong timeExceedCount = 0UL; - long lastLoopEndingTickCount, beginTickCount; - - var nextTime = (lastLoopEndingTickCount = beginTickCount = Environment.TickCount64) + timeInterval; - var endTime = beginTickCount < long.MaxValue - maxTotalDuration ? beginTickCount + maxTotalDuration : long.MaxValue; - - uint loopCnt = 0; - var nextCntTime = beginTickCount + 1000L; - - while (loopCondition() && nextTime <= endTime) - { - if (!loopToDo()) break; - - var nowTime = Environment.TickCount64; - if (nextTime >= nowTime) - { - timeExceedCount = 0UL; - Thread.Sleep((int)(nextTime - nowTime)); - } - else - { - ++timeExceedCount; - if (timeExceedCount > MaxTolerantTimeExceedCount) - { - if (AllowTimeExceed) - { - TimeExceedAction(true); - timeExceedCount = 0UL; - nextTime = Environment.TickCount64; - } - else - { - throw new TimeExceedException(); - } - } - else if (AllowTimeExceed) TimeExceedAction(false); - } - - lastLoopEndingTickCount = nextTime; - nextTime += timeInterval; - ++loopCnt; - if (Environment.TickCount64 >= nextCntTime) - { - nextCntTime = Environment.TickCount64 + 1000L; - FrameRate = loopCnt; - loopCnt = 0; - } - } - - result = finallyReturn(); - Interlocked.MemoryBarrierProcessWide(); - Finished = true; - }; - } - - /// - /// Constructor - /// - /// If you want to continue to loop, return true; otherwise, return false. - /// Loop to do. - /// The time interval between two execution. - /// Used to set the return value. It will be called after the loop. - /// The maximum time for the loop to run. - public FrameRateTaskExecutor - ( - Func loopCondition, - Action loopToDo, - long timeInterval, - Func finallyReturn, - long maxTotalDuration = long.MaxValue - ) : this(loopCondition, () => { loopToDo(); return true; }, timeInterval, finallyReturn, maxTotalDuration) { } - } + } = 5; + + /// + /// Starts this task synchronously. + /// + /// + /// The task has already started. + /// + public void Start() + { + if (!TryStart()) throw new TaskStartedMoreThanOnceException(); + } + /// + /// Tries to start this task synchronously. + /// + /// + /// true if the task is started successfully; false if it has been started. + /// + public bool TryStart() + { + if (!TrySetExecute()) return false; + loopFunc(); + return true; + } + + private Action loopFunc; + + /// + /// Constructor + /// + /// The judgment condition of the loop. Returns true if you want to continue the loop; otherwise, returns false. + /// The loop body. Returns false if you want to jump out of the loop; otherwise, returns true. + /// The time interval between two executions. + /// The method used to set the return value, which will be called after the loop. + /// The maximum time for which the loop will run. + public FrameRateTaskExecutor + ( + Func loopCondition, + Func loopToDo, + long timeInterval, + Func finallyReturn, + long maxTotalDuration = long.MaxValue + ) + { + + if (timeInterval <= 0L && timeInterval > 1000L) + { + throw new IllegalTimeIntervalException(); + } + FrameRate = (uint)(1000L / timeInterval); + + loopFunc = () => + { + ulong timeExceedCount = 0UL; + long lastLoopEndingTickCount, beginTickCount; + + var nextTime = (lastLoopEndingTickCount = beginTickCount = Environment.TickCount64) + timeInterval; + var endTime = beginTickCount < long.MaxValue - maxTotalDuration ? beginTickCount + maxTotalDuration : long.MaxValue; + + uint loopCnt = 0; + var nextCntTime = beginTickCount + 1000L; + + while (loopCondition() && nextTime <= endTime) + { + if (!loopToDo()) break; + + var nowTime = Environment.TickCount64; + if (nextTime >= nowTime) + { + timeExceedCount = 0UL; + Thread.Sleep((int)(nextTime - nowTime)); + } + else + { + ++timeExceedCount; + if (timeExceedCount > MaxTolerantTimeExceedCount) + { + if (AllowTimeExceed) + { + TimeExceedAction(true); + timeExceedCount = 0UL; + nextTime = Environment.TickCount64; + } + else + { + throw new TimeExceedException(); + } + } + else if (AllowTimeExceed) TimeExceedAction(false); + } + + lastLoopEndingTickCount = nextTime; + nextTime += timeInterval; + ++loopCnt; + if (Environment.TickCount64 >= nextCntTime) + { + nextCntTime = Environment.TickCount64 + 1000L; + FrameRate = loopCnt; + loopCnt = 0; + } + } + + result = finallyReturn(); + Interlocked.MemoryBarrierProcessWide(); + Finished = true; + }; + } + + /// + /// Constructor + /// + /// The judgment condition of the loop. Returns true if you want to continue the loop; otherwise, returns false. + /// The loop body. + /// The time interval between two executions. + /// The method used to set the return value, which will be called after the loop. + /// The maximum time for which the loop will run. + public FrameRateTaskExecutor + ( + Func loopCondition, + Action loopToDo, + long timeInterval, + Func finallyReturn, + long maxTotalDuration = long.MaxValue + ) : this(loopCondition, () => { loopToDo(); return true; }, timeInterval, finallyReturn, maxTotalDuration) { } + } } diff --git a/README.md b/README.md index 66cd6fa..e4720be 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Please read [CONTRIBUTING](./CONTRIBUTING.md) carefully before contributing to t Whether the engine allows time exceeding, `true` for default. See more details under `MaxTolerantTimeExceedCount`. -+ `public Action TimeExceedAction { get; init; }` ++ `public Action TimeExceedAction { init; }` It will be called when time exceeds. See more details under `MaxTolerantTimeExceedCount`.