diff --git a/DisCatSharp.ApplicationCommands/Entities/CooldownBucket.cs b/DisCatSharp.ApplicationCommands/Entities/CooldownBucket.cs index 4c296d023..0ac202de0 100644 --- a/DisCatSharp.ApplicationCommands/Entities/CooldownBucket.cs +++ b/DisCatSharp.ApplicationCommands/Entities/CooldownBucket.cs @@ -1,154 +1,185 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021-2022 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; namespace DisCatSharp.ApplicationCommands.Entities; public class CooldownBucket : IBucket, IEquatable { + /// + /// The user id for this bucket. + /// public ulong UserId { get; } + + /// + /// The channel id for this bucket. + /// public ulong ChannelId { get; } + + /// + /// The guild id for this bucket. + /// public ulong GuildId { get; } + + /// + /// The id for this bucket. + /// public string BucketId { get; } + + /// + /// The remaining uses for this bucket. + /// public int RemainingUses => Volatile.Read(ref this._remainingUses); + + /// + /// The max uses for this bucket. + /// public int MaxUses { get; } + + /// + /// The datetime offset when this bucket resets. + /// public DateTimeOffset ResetsAt { get; internal set; } + + /// + /// The timespan when this bucket resets. + /// public TimeSpan Reset { get; internal set; } /// /// Gets the semaphore used to lock the use value. /// private readonly SemaphoreSlim _usageSemaphore; private int _remainingUses; /// /// Creates a new command cooldown bucket. /// /// Maximum number of uses for this bucket. /// Time after which this bucket resets. /// ID of the user with which this cooldown is associated. /// ID of the channel with which this cooldown is associated. /// ID of the guild with which this cooldown is associated. internal CooldownBucket(int maxUses, TimeSpan resetAfter, ulong userId = 0, ulong channelId = 0, ulong guildId = 0) { this._remainingUses = maxUses; this.MaxUses = maxUses; this.ResetsAt = DateTimeOffset.UtcNow + resetAfter; this.Reset = resetAfter; this.UserId = userId; this.ChannelId = channelId; this.GuildId = guildId; this.BucketId = MakeId(userId, channelId, guildId); this._usageSemaphore = new(1, 1); } /// /// Decrements the remaining use counter. /// /// Whether decrement succeeded or not. internal async Task DecrementUseAsync() { await this._usageSemaphore.WaitAsync().ConfigureAwait(false); // if we're past reset time... var now = DateTimeOffset.UtcNow; if (now >= this.ResetsAt) { // ...do the reset and set a new reset time Interlocked.Exchange(ref this._remainingUses, this.MaxUses); this.ResetsAt = now + this.Reset; } // check if we have any uses left, if we do... var success = false; if (this.RemainingUses > 0) { // ...decrement, and return success... Interlocked.Decrement(ref this._remainingUses); success = true; } // ...otherwise just fail this._usageSemaphore.Release(); return success; } - // + /// /// Checks whether this is equal to another object. /// /// Object to compare to. /// Whether the object is equal to this . public override bool Equals(object obj) => this.Equals(obj as CooldownBucket); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(CooldownBucket other) => other is not null && (ReferenceEquals(this, other) || (this.UserId == other.UserId && this.ChannelId == other.ChannelId && this.GuildId == other.GuildId)); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() => HashCode.Combine(this.UserId, this.ChannelId, this.GuildId); /// /// Gets whether the two objects are equal. /// /// First bucket to compare. /// Second bucket to compare. /// Whether the two buckets are equal. public static bool operator ==(CooldownBucket bucket1, CooldownBucket bucket2) { var null1 = bucket1 is null; var null2 = bucket2 is null; return (null1 && null2) || (null1 == null2 && null1.Equals(null2)); } /// /// Gets whether the two objects are not equal. /// /// First bucket to compare. /// Second bucket to compare. /// Whether the two buckets are not equal. public static bool operator !=(CooldownBucket bucket1, CooldownBucket bucket2) => !(bucket1 == bucket2); /// /// Creates a bucket ID from given bucket parameters. /// /// ID of the user with which this cooldown is associated. /// ID of the channel with which this cooldown is associated. /// ID of the guild with which this cooldown is associated. /// Generated bucket ID. public static string MakeId(ulong userId = 0, ulong channelId = 0, ulong guildId = 0) => $"{userId.ToString(CultureInfo.InvariantCulture)}:{channelId.ToString(CultureInfo.InvariantCulture)}:{guildId.ToString(CultureInfo.InvariantCulture)}"; } diff --git a/DisCatSharp.ApplicationCommands/Entities/IBucket.cs b/DisCatSharp.ApplicationCommands/Entities/IBucket.cs index 62a4e7f58..5ef9aee60 100644 --- a/DisCatSharp.ApplicationCommands/Entities/IBucket.cs +++ b/DisCatSharp.ApplicationCommands/Entities/IBucket.cs @@ -1,73 +1,71 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021-2022 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; namespace DisCatSharp.ApplicationCommands.Entities; /// /// Defines the standard contract for bucket feature /// public interface IBucket { /// /// Gets the ID of the user whom this cooldown is associated /// ulong UserId { get; } /// /// Gets the ID of the channel with which this cooldown is associated /// ulong ChannelId { get; } /// /// Gets the ID of the guild with which this cooldown is associated /// ulong GuildId { get; } /// /// Gets the ID of the bucket. This is used to distinguish between cooldown buckets /// string BucketId { get; } /// /// Gets the remaining number of uses before the cooldown is triggered /// int RemainingUses { get; } /// /// Gets the maximum number of times this command can be used in a given timespan /// int MaxUses { get; } /// /// Gets the date and time at which the cooldown resets /// DateTimeOffset ResetsAt { get; } /// /// Get the time after which this cooldown resets /// TimeSpan Reset { get; } - - }