2022-02-07 19:43:36 -05:00
|
|
|
|
using System;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using Jint.Native;
|
|
|
|
|
using Jint.Runtime;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using SharedLibraryCore.Interfaces;
|
|
|
|
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
|
|
|
|
|
|
|
|
|
namespace IW4MAdmin.Application.Misc;
|
|
|
|
|
|
|
|
|
|
public class ScriptPluginTimerHelper : IScriptPluginTimerHelper
|
|
|
|
|
{
|
|
|
|
|
private Timer _timer;
|
|
|
|
|
private Action _actions;
|
|
|
|
|
private Delegate _jsAction;
|
|
|
|
|
private string _actionName;
|
|
|
|
|
private const int DefaultDelay = 0;
|
|
|
|
|
private const int DefaultInterval = 1000;
|
|
|
|
|
private readonly ILogger _logger;
|
2022-10-17 11:45:42 -04:00
|
|
|
|
private readonly SemaphoreSlim _onRunningTick = new(1, 1);
|
2022-02-07 19:43:36 -05:00
|
|
|
|
private SemaphoreSlim _onDependentAction;
|
|
|
|
|
|
|
|
|
|
public ScriptPluginTimerHelper(ILogger<ScriptPluginTimerHelper> logger)
|
|
|
|
|
{
|
|
|
|
|
_logger = logger;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~ScriptPluginTimerHelper()
|
|
|
|
|
{
|
|
|
|
|
if (_timer != null)
|
|
|
|
|
{
|
|
|
|
|
Stop();
|
|
|
|
|
}
|
2022-10-17 11:45:42 -04:00
|
|
|
|
|
2022-02-07 19:43:36 -05:00
|
|
|
|
_onRunningTick.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Start(int delay, int interval)
|
|
|
|
|
{
|
|
|
|
|
if (_actions is null)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("Timer action must be defined before starting");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (delay < 0)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Timer delay must be >= 0");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (interval < 20)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Timer interval must be at least 20ms");
|
|
|
|
|
}
|
2022-10-17 11:45:42 -04:00
|
|
|
|
|
2022-02-07 19:43:36 -05:00
|
|
|
|
Stop();
|
2022-10-17 11:45:42 -04:00
|
|
|
|
|
2022-02-07 19:43:36 -05:00
|
|
|
|
_logger.LogDebug("Starting script timer...");
|
|
|
|
|
|
|
|
|
|
_timer ??= new Timer(callback => _actions(), null, delay, interval);
|
|
|
|
|
IsRunning = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Start(int interval)
|
|
|
|
|
{
|
|
|
|
|
Start(DefaultDelay, interval);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Start()
|
|
|
|
|
{
|
|
|
|
|
Start(DefaultDelay, DefaultInterval);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Stop()
|
|
|
|
|
{
|
|
|
|
|
if (_timer == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-10-17 11:45:42 -04:00
|
|
|
|
|
2022-02-07 19:43:36 -05:00
|
|
|
|
_logger.LogDebug("Stopping script timer...");
|
|
|
|
|
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
|
|
|
_timer.Dispose();
|
|
|
|
|
_timer = null;
|
|
|
|
|
IsRunning = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnTick(Delegate action, string actionName)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(actionName))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("actionName must be provided", nameof(actionName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (action is null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("action must be provided", nameof(action));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("Adding new action with name {ActionName}", actionName);
|
|
|
|
|
|
|
|
|
|
_jsAction = action;
|
|
|
|
|
_actionName = actionName;
|
2022-10-17 11:45:42 -04:00
|
|
|
|
_actions = OnTickInternal;
|
2022-02-07 19:43:36 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ReleaseThreads()
|
|
|
|
|
{
|
2022-10-17 11:45:42 -04:00
|
|
|
|
_logger.LogDebug("-Releasing OnTick for timer");
|
|
|
|
|
|
|
|
|
|
if (_onRunningTick.CurrentCount == 0)
|
|
|
|
|
{
|
|
|
|
|
_onRunningTick.Release(1);
|
|
|
|
|
}
|
2022-02-07 19:43:36 -05:00
|
|
|
|
|
|
|
|
|
if (_onDependentAction?.CurrentCount != 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-10-17 11:45:42 -04:00
|
|
|
|
|
2022-02-07 19:43:36 -05:00
|
|
|
|
_onDependentAction?.Release(1);
|
|
|
|
|
}
|
2022-10-17 11:45:42 -04:00
|
|
|
|
|
|
|
|
|
private async void OnTickInternal()
|
2022-02-07 19:43:36 -05:00
|
|
|
|
{
|
2022-10-17 11:45:42 -04:00
|
|
|
|
var previousTimerRunning = false;
|
2022-02-07 19:43:36 -05:00
|
|
|
|
try
|
|
|
|
|
{
|
2022-10-17 11:45:42 -04:00
|
|
|
|
if (_onRunningTick.CurrentCount == 0)
|
2022-02-07 19:43:36 -05:00
|
|
|
|
{
|
2022-02-09 15:45:28 -05:00
|
|
|
|
_logger.LogDebug("Previous {OnTick} is still running, so we are skipping this one",
|
2022-10-17 11:45:42 -04:00
|
|
|
|
nameof(OnTickInternal));
|
|
|
|
|
previousTimerRunning = true;
|
2022-02-07 19:43:36 -05:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-17 11:45:42 -04:00
|
|
|
|
await _onRunningTick.WaitAsync();
|
2022-02-07 19:43:36 -05:00
|
|
|
|
|
2022-10-17 11:45:42 -04:00
|
|
|
|
var tokenSource = new CancellationTokenSource();
|
|
|
|
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// the js engine is not thread safe so we need to ensure we're not executing OnTick and OnEventAsync simultaneously
|
|
|
|
|
if (_onDependentAction is not null)
|
|
|
|
|
{
|
|
|
|
|
await _onDependentAction.WaitAsync(tokenSource.Token);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogDebug("Dependent action did not release in allotted time so we are cancelling this tick");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-26 13:07:43 -04:00
|
|
|
|
_logger.LogDebug("+Running OnTick for timer");
|
2022-02-09 15:45:28 -05:00
|
|
|
|
var start = DateTime.Now;
|
2022-02-07 19:43:36 -05:00
|
|
|
|
_jsAction.DynamicInvoke(JsValue.Undefined, new[] { JsValue.Undefined });
|
2022-02-09 15:45:28 -05:00
|
|
|
|
_logger.LogDebug("OnTick took {Time}ms", (DateTime.Now - start).TotalMilliseconds);
|
2022-02-07 19:43:36 -05:00
|
|
|
|
}
|
2022-10-17 11:45:42 -04:00
|
|
|
|
catch (Exception ex) when (ex.InnerException is JavaScriptException jsx)
|
2022-02-07 19:43:36 -05:00
|
|
|
|
{
|
2022-10-17 11:45:42 -04:00
|
|
|
|
_logger.LogError(jsx,
|
2022-02-07 19:43:36 -05:00
|
|
|
|
"Could not execute timer tick for script action {ActionName} [@{LocationInfo}]", _actionName,
|
2022-10-17 11:45:42 -04:00
|
|
|
|
jsx.Location);
|
2022-02-07 19:43:36 -05:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Could not execute timer tick for script action {ActionName}", _actionName);
|
2022-10-17 11:45:42 -04:00
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
if (!previousTimerRunning)
|
|
|
|
|
{
|
|
|
|
|
ReleaseThreads();
|
|
|
|
|
}
|
2022-02-07 19:43:36 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetDependency(SemaphoreSlim dependentSemaphore)
|
|
|
|
|
{
|
|
|
|
|
_onDependentAction = dependentSemaphore;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsRunning { get; private set; }
|
|
|
|
|
}
|