Skip to content

Commit

Permalink
Merge pull request #10 from Devwarlt/dev
Browse files Browse the repository at this point in the history
PSTk.Core 1.0.1 + PSTk.Threading 1.1.0 features
  • Loading branch information
Devwarlt authored Dec 11, 2020
2 parents ea54a7b + 6c0f12e commit 7e6e2a2
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 14 deletions.
1 change: 1 addition & 0 deletions PSTk.Core/PSTk.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<PackageId>PSTk.Core</PackageId>
<ApplicationIcon>ICON.ico</ApplicationIcon>
<Version>1.0.1</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down
1 change: 1 addition & 0 deletions PSTk.Threading/PSTk.Threading.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<RepositoryType>Git</RepositoryType>
<PackageTags>dotnet;toolkit;game-tools;pserver</PackageTags>
<ApplicationIcon>ICON.ico</ApplicationIcon>
<Version>1.1.0</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down
4 changes: 2 additions & 2 deletions PSTk.Threading/Tasks/AutomatedRestarter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ public sealed class AutomatedRestarter
/// <exception cref="ArgumentOutOfRangeException"></exception>
public AutomatedRestarter(TimeSpan timeout, int routineMs = 1000, Action<string> errorLogger = null)
{
if (routineMs <= 0) throw new ArgumentOutOfRangeException("routineMs", "Only non-zero and non-negative values are permitted.");
if (routineMs <= 0) throw new ArgumentOutOfRangeException(nameof(routineMs), "Only non-zero and non-negative values are permitted.");

if (timeout == null) throw new ArgumentNullException("timeout");
if (timeout == null) throw new ArgumentNullException(nameof(timeout));

this.routineMs = routineMs;
this.timeout = timeout;
Expand Down
6 changes: 3 additions & 3 deletions PSTk.Threading/Tasks/InternalRoutine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ public InternalRoutine(int timeout, Action routine)
public InternalRoutine(int timeout, Action<int> routine, Action<string> errorLogger = null)
{
if (timeout <= 0)
throw new ArgumentOutOfRangeException("timeout", "Only non-zero and non-negative values are permitted.");
throw new ArgumentOutOfRangeException(nameof(timeout), "Only non-zero and non-negative values are permitted.");

if (routine == null)
throw new ArgumentNullException("routine");
throw new ArgumentNullException(nameof(routine));

this.timeout = timeout;
this.routine = (delta, cancel) => { if (!cancel) routine.Invoke(delta); };
Expand Down Expand Up @@ -86,7 +86,7 @@ public InternalRoutine(int timeout, Action<int> routine, Action<string> errorLog
/// Attach a process to parent in case of external task cancellation request.
/// </summary>
/// <param name="token"></param>
public void AttachToParent(CancellationToken token) => this.Token = token;
public void AttachToParent(CancellationToken token) => Token = token;

/// <summary>
/// Initialize and starts the core routine, to stop it must use <see cref="CancellationTokenSource.Cancel(bool)"/>.
Expand Down
6 changes: 3 additions & 3 deletions PSTk.Threading/Tasks/InternalRoutineEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class InternalRoutineEventArgs : EventArgs
/// <param name="delta"></param>
/// <param name="ticksPerSecond"></param>
/// <param name="timeout"></param>
public InternalRoutineEventArgs(int delta, int ticksPerSecond, int timeout)
public InternalRoutineEventArgs(long delta, int ticksPerSecond, long timeout)
: base()
{
Delta = delta;
Expand All @@ -25,7 +25,7 @@ public InternalRoutineEventArgs(int delta, int ticksPerSecond, int timeout)
/// <summary>
/// Delta variation of <see cref="InternalRoutine.routine"/>.
/// </summary>
public int Delta { get; }
public long Delta { get; }

/// <summary>
/// Ticks per second for <see cref="InternalRoutine.routine"/> invoke on <see cref="TaskScheduler"/>.
Expand All @@ -35,6 +35,6 @@ public InternalRoutineEventArgs(int delta, int ticksPerSecond, int timeout)
/// <summary>
/// Timeout in milliseconds for <see cref="InternalRoutine.routine"/> invoke on <see cref="TaskScheduler"/>.
/// </summary>
public int Timeout { get; }
public long Timeout { get; }
}
}
193 changes: 193 additions & 0 deletions PSTk.Threading/Tasks/InternalRoutineSlim.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
using PSTk.Threading.Tasks.Procedures;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace PSTk.Threading.Tasks
{
/// <summary>
/// This class is a lightweight version of <see cref="InternalRoutine"/>. Most of exist
/// features part of implementation from <see cref="InternalRoutineSlim"/> got revised
/// and optimized to avoid extra cost for performance.
/// <para>
/// <see cref="InternalRoutineSlim"/> runs <see cref="TickCore"/> process on a <see cref="Task"/>
/// attached on main <see cref="Thread"/> to avoid thread-unsafe issues.
/// </para>
/// </summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="OperationCanceledException"></exception>
public sealed class InternalRoutineSlim : IAttachedTask
{
private readonly Action<long> routine;
private readonly int timeout;

private Task<bool> coreTask;
private Stopwatch stopwatch;
private int ticksPerSecond;

/// <summary>
/// Create a new instance of <see cref="InternalRoutineSlim"/> without log tracking.
/// </summary>
/// <param name="timeout"></param>
/// <param name="routine"></param>
public InternalRoutineSlim(int timeout, Action routine)
: this(timeout, routine, null)
{ }

/// <summary>
/// Create a new instance of <see cref="InternalRoutineSlim"/> with log tracking.
/// </summary>
/// <param name="timeout"></param>
/// <param name="routine"></param>
/// <param name="errorLogger"></param>
public InternalRoutineSlim(int timeout, Action routine, Action<string> errorLogger = null)
{
if (timeout <= 0)
throw new ArgumentOutOfRangeException(nameof(timeout), "Only non-zero and non-negative values are permitted.");
if (routine == null)
throw new ArgumentNullException(nameof(routine));

this.timeout = timeout;
this.routine = (delta) => routine.Invoke();
onError += (s, e) =>
{
errorLogger?.Invoke(e.ToString());
Finish();
};
}

/// <summary>
/// Create a new instance of <see cref="InternalRoutineSlim"/> with log and delta per tick tracking.
/// </summary>
/// <param name="timeout"></param>
/// <param name="routine"></param>
/// <param name="errorLogger"></param>
public InternalRoutineSlim(int timeout, Action<long> routine, Action<string> errorLogger = null)
{
if (timeout <= 0)
throw new ArgumentOutOfRangeException(nameof(timeout), "Only non-zero and non-negative values are permitted.");

this.timeout = timeout;
this.routine = routine ?? throw new ArgumentNullException(nameof(routine));
onError += (s, e) =>
{
errorLogger?.Invoke(e.ToString());
Finish();
};
}

/// <summary>
/// When routine <see cref="timeout"/> takes more time than usual to execute.
/// </summary>
public event EventHandler<InternalRoutineEventArgs> OnDeltaVariation;

/// <summary>
/// When routine finished its task.
/// </summary>
public event EventHandler OnFinished;

private event EventHandler<Exception> onError;

/// <summary>
/// Gets the elapsed delta time per tick loop. This is the delta variation of <see cref="RoutineLoop"/>.
/// </summary>
public long Delta { get; private set; } = 0L;

/// <summary>
/// Gets if <see cref="TickCore"/> is canceled by <see cref="Token"/>.
/// </summary>
public bool IsCanceled { get; private set; } = false;

/// <summary>
/// Gets if <see cref="TickCore"/> is running.
/// </summary>
public bool IsRunning { get; private set; } = false;

/// <summary>
/// Get the <see cref="CancellationToken"/> of attached task.
/// </summary>
public CancellationToken Token { get; private set; } = default;

/// <summary>
/// Gets total <see cref="Stopwatch.ElapsedMilliseconds"/> since <see cref="Start"/> of <see cref="TickCore"/>.
/// </summary>
public long TotalElapsedMilliseconds => stopwatch.ElapsedMilliseconds;

/// <summary>
/// Attach a process to parent in case of external task cancellation request.
/// </summary>
/// <param name="token"></param>
public void AttachToParent(CancellationToken token) => Token = token;

/// <summary>
/// Initialize and starts the <see cref="TickCore"/>.
/// </summary>
public void Start()
{
if (IsCanceled || IsRunning)
return;

TickCore();
}

/// <summary>
/// Stop on next tick of execution the <see cref="TickCore"/>.
/// </summary>
public void Stop()
{
IsRunning = false;
IsCanceled = false;
}

private void Finish()
{
IsCanceled = true;
OnFinished?.Invoke(this, null);
}

private bool RoutineLoop()
{
routine.Invoke(Delta = Math.Max(0, Math.Abs(Delta - stopwatch.ElapsedMilliseconds)));
if (Delta > timeout)
OnDeltaVariation?.Invoke(this, new InternalRoutineEventArgs(Delta, ticksPerSecond, timeout));
return IsRunning && !IsCanceled;
}

private async void TickCore()
{
ticksPerSecond = 1000 / timeout;
stopwatch = Stopwatch.StartNew();
IsRunning = true;
if (Token == default)
coreTask = Task.Run(() =>
{
while (RoutineLoop()) ;
return true;
});
else
coreTask = Task.Run(() =>
{
try
{
while (RoutineLoop())
{
IsCanceled = Token.IsCancellationRequested;
Token.ThrowIfCancellationRequested();
}
return true;
}
catch (OperationCanceledException) { Finish(); }
return false;
}, Token);

coreTask?.ContinueWith(t =>
onError.Invoke(this, t.Exception.InnerException),
TaskContinuationOptions.OnlyOnFaulted
);
IsRunning = await coreTask;
stopwatch.Stop();
}
}
}
4 changes: 2 additions & 2 deletions PSTk.Threading/Tasks/Procedures/AsyncProcedure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ public sealed class AsyncProcedure<TInput> : IAsyncProcedure
public AsyncProcedure(string name, TInput input, Func<AsyncProcedure<TInput>, string, TInput, AsyncProcedureEventArgs<TInput>> procedure, Action<string> errorLogger = null)
{
if (input == null)
throw new ArgumentNullException("input");
throw new ArgumentNullException(nameof(input));

if (procedure == null)
throw new ArgumentNullException("procedure");
throw new ArgumentNullException(nameof(procedure));

Name = name;

Expand Down
8 changes: 4 additions & 4 deletions PSTk.Threading/Tasks/Procedures/AsyncProcedurePool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ public AsyncProcedurePool(IAsyncProcedure[] pool, CancellationTokenSource source

{
if (pool == null)
throw new ArgumentNullException("pool");
throw new ArgumentNullException(nameof(pool));

if (pool.Length == 0)
throw new ArgumentOutOfRangeException("pool", "Required at least 1 AsyncProcedure.");
throw new ArgumentOutOfRangeException(nameof(pool), "Required at least 1 AsyncProcedure.");

this.pool = pool;
this.source = source ?? new CancellationTokenSource();
Expand Down Expand Up @@ -66,9 +66,9 @@ public IAsyncProcedure this[int index]
/// <param name="token"></param>
public void AttachToParent(CancellationToken token)
{
this.Token = token;
Token = token;

AttachPoolToContext(this.Token);
AttachPoolToContext(Token);
}

/// <summary>
Expand Down

0 comments on commit 7e6e2a2

Please sign in to comment.