Skip to content

Commit

Permalink
Refactor PluginContext and SaveOptions methods
Browse files Browse the repository at this point in the history
This commit makes the following changes:

- Change the PluginContext from a readonly record struct to a sealed record class
- Add a CancellationTokenChanger struct that implements IDisposable and temporarily changes the cancellation token of the PluginContext instance
- Add a TemporaryChangeCancellationToken method that creates an instance of the CancellationTokenChanger struct
- Change the SaveOptions method to return a string and use the TemporaryChangeCancellationToken method
- Change the CollectGamesOnClock method to use the TemporaryChangeCancellationToken method
- Add XML documentation comments to the PluginContext and its members
  • Loading branch information
maxisoft committed Oct 31, 2023
1 parent 3e9ee50 commit 887f095
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 16 deletions.
48 changes: 33 additions & 15 deletions ASFFreeGames/ASFFreeGamesPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ internal static PluginContext Context {
public ASFFreeGamesPlugin() {
CommandDispatcher = new CommandDispatcher(Options);
CollectIntervalManager = new CollectIntervalManager(this);
_context.Value = new PluginContext(Bots, BotContextRegistry, Options, LoggerFilter, new Lazy<CancellationToken>(() => CancellationTokenSourceLazy.Value.Token));
_context.Value = new PluginContext(Bots, BotContextRegistry, Options, LoggerFilter) { CancellationTokenLazy = new Lazy<CancellationToken>(() => CancellationTokenSourceLazy.Value.Token) };
}

public async Task OnASFInit(IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
Expand Down Expand Up @@ -110,26 +110,29 @@ public async void CollectGamesOnClock(object? source) {
return;
}

Bot[] reorderedBots;
IContextRegistry botContexts = Context.BotContexts;
// ReSharper disable once AccessToDisposedClosure
using (Context.TemporaryChangeCancellationToken(() => cts.Token)) {
Bot[] reorderedBots;
IContextRegistry botContexts = Context.BotContexts;

lock (botContexts) {
long orderByRunKeySelector(Bot bot) => botContexts.GetBotContext(bot)?.RunElapsedMilli ?? long.MaxValue;
int comparison(Bot x, Bot y) => orderByRunKeySelector(y).CompareTo(orderByRunKeySelector(x)); // sort in descending order
reorderedBots = Bots.ToArray();
Array.Sort(reorderedBots, comparison);
}
lock (botContexts) {
long orderByRunKeySelector(Bot bot) => botContexts.GetBotContext(bot)?.RunElapsedMilli ?? long.MaxValue;
int comparison(Bot x, Bot y) => orderByRunKeySelector(y).CompareTo(orderByRunKeySelector(x)); // sort in descending order
reorderedBots = Bots.ToArray();
Array.Sort(reorderedBots, comparison);
}

if (!cts.IsCancellationRequested) {
string cmd = $"FREEGAMES {FreeGamesCommand.CollectInternalCommandString} " + string.Join(' ', reorderedBots.Select(static bot => bot.BotName));
await OnBotCommand(null!, EAccess.None, cmd, cmd.Split()).ConfigureAwait(false);
if (!cts.IsCancellationRequested) {
string cmd = $"FREEGAMES {FreeGamesCommand.CollectInternalCommandString} " + string.Join(' ', reorderedBots.Select(static bot => bot.BotName));
await OnBotCommand(null!, EAccess.None, cmd, cmd.Split()).ConfigureAwait(false);
}
}
}

/// <summary>
/// Creates a new PluginContext instance and assigns it to the Context property.
/// </summary>
private void CreateContext() => Context = new PluginContext(Bots, BotContextRegistry, Options, LoggerFilter, new Lazy<CancellationToken>(() => CancellationTokenSourceLazy.Value.Token), true);
private void CreateContext() => Context = new PluginContext(Bots, BotContextRegistry, Options, LoggerFilter, true) { CancellationTokenLazy = new Lazy<CancellationToken>(() => CancellationTokenSourceLazy.Value.Token) };

private async Task RegisterBot(Bot bot) {
Bots.Add(bot);
Expand Down Expand Up @@ -166,11 +169,26 @@ private async Task RemoveBot(Bot bot) {
LoggerFilter.RemoveFilters(bot);
}

private async Task SaveOptions(CancellationToken cancellationToken) {
private async Task<string?> SaveOptions(CancellationToken cancellationToken) {
if (!cancellationToken.IsCancellationRequested) {
const string cmd = $"FREEGAMES {FreeGamesCommand.SaveOptionsInternalCommandString}";
await OnBotCommand(Bots.FirstOrDefault()!, EAccess.None, cmd, cmd.Split()).ConfigureAwait(false);
async Task<string?> continuation() => await OnBotCommand(Bots.FirstOrDefault()!, EAccess.None, cmd, cmd.Split()).ConfigureAwait(false);

string? result;

if (Context.Valid) {
using (Context.TemporaryChangeCancellationToken(() => cancellationToken)) {
result = await continuation().ConfigureAwait(false);
}
}
else {
result = await continuation().ConfigureAwait(false);
}

return result;
}

return null;
}

private void StartTimerIfNeeded() => CollectIntervalManager.StartTimerIfNeeded();
Expand Down
39 changes: 38 additions & 1 deletion ASFFreeGames/PluginContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,43 @@

namespace Maxisoft.ASF;

internal readonly record struct PluginContext(IReadOnlyCollection<Bot> Bots, IContextRegistry BotContexts, ASFFreeGamesOptions Options, LoggerFilter LoggerFilter, Lazy<CancellationToken> CancellationTokenLazy, bool Valid = false) {
internal sealed record PluginContext(IReadOnlyCollection<Bot> Bots, IContextRegistry BotContexts, ASFFreeGamesOptions Options, LoggerFilter LoggerFilter, bool Valid = false) {
/// <summary>
/// Gets the cancellation token associated with this context.
/// </summary>
public CancellationToken CancellationToken => CancellationTokenLazy.Value;

internal Lazy<CancellationToken> CancellationTokenLazy { private get; set; } = new(default(CancellationToken));

/// <summary>
/// A struct that implements IDisposable and temporarily changes the cancellation token of the PluginContext instance.
/// </summary>
public readonly struct CancellationTokenChanger : IDisposable {
private readonly PluginContext Context;
private readonly Lazy<CancellationToken> Original;

/// <summary>
/// Initializes a new instance of the <see cref="CancellationTokenChanger"/> struct with the specified context and factory.
/// </summary>
/// <param name="context">The PluginContext instance to change.</param>
/// <param name="factory">The function that creates a new cancellation token.</param>
public CancellationTokenChanger(PluginContext context, Func<CancellationToken> factory) {
Context = context;
Original = context.CancellationTokenLazy;
context.CancellationTokenLazy = new Lazy<CancellationToken>(factory);
}

/// <inheritdoc />
/// <summary>
/// Restores the original cancellation token to the PluginContext instance.
/// </summary>
public void Dispose() => Context.CancellationTokenLazy = Original;
}

/// <summary>
/// Creates a new instance of the <see cref="CancellationTokenChanger"/> struct with the specified factory.
/// </summary>
/// <param name="factory">The function that creates a new cancellation token.</param>
/// <returns>A new instance of the <see cref="CancellationTokenChanger"/> struct.</returns>
public CancellationTokenChanger TemporaryChangeCancellationToken(Func<CancellationToken> factory) => new(this, factory);
}

0 comments on commit 887f095

Please sign in to comment.