diff --git a/DisCatSharp.CommandsNext/CommandsNextConfiguration.cs b/DisCatSharp.CommandsNext/CommandsNextConfiguration.cs
index 09916871f..623c545c2 100644
--- a/DisCatSharp.CommandsNext/CommandsNextConfiguration.cs
+++ b/DisCatSharp.CommandsNext/CommandsNextConfiguration.cs
@@ -1,160 +1,160 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DisCatSharp.CommandsNext.Attributes;
using DisCatSharp.CommandsNext.Converters;
using DisCatSharp.Entities;
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.CommandsNext
{
///
/// Represents a delegate for a function that takes a message, and returns the position of the start of command invocation in the message. It has to return -1 if prefix is not present.
///
/// It is recommended that helper methods and
/// be used internally for checking. Their output can be passed through.
///
///
/// Message to check for prefix.
/// Position of the command invocation or -1 if not present.
public delegate Task PrefixResolverDelegate(DiscordMessage msg);
///
/// Represents a configuration for .
///
public sealed class CommandsNextConfiguration
{
///
/// Sets the string prefixes used for commands.
/// Defaults to no value (disabled).
///
public IEnumerable StringPrefixes { internal get; set; }
///
/// Sets the custom prefix resolver used for commands.
/// Defaults to none (disabled).
///
public PrefixResolverDelegate PrefixResolver { internal get; set; } = null;
///
/// Sets whether to allow mentioning the bot to be used as command prefix.
/// Defaults to true.
///
public bool EnableMentionPrefix { internal get; set; } = true;
///
/// Sets whether strings should be matched in a case-sensitive manner.
/// This switch affects the behaviour of default prefix resolver, command searching, and argument conversion.
/// Defaults to false.
///
public bool CaseSensitive { internal get; set; } = false;
///
/// Sets whether to enable default help command.
/// Disabling this will allow you to make your own help command.
///
- /// Modifying default help can be achieved via custom help formatters (see and for more details).
+ /// Modifying default help can be achieved via custom help formatters (see and for more details).
/// It is recommended to use help formatter instead of disabling help.
///
/// Defaults to true.
///
public bool EnableDefaultHelp { internal get; set; } = true;
///
/// Controls whether the default help will be sent via DMs or not.
/// Enabling this will make the bot respond with help via direct messages.
/// Defaults to false.
///
public bool DmHelp { internal get; set; } = false;
///
/// Sets the default pre-execution checks for the built-in help command.
/// Only applicable if default help is enabled.
/// Defaults to null.
///
public IEnumerable DefaultHelpChecks { internal get; set; } = null;
///
/// Sets whether commands sent via direct messages should be processed.
/// Defaults to true.
///
public bool EnableDms { internal get; set; } = true;
///
/// Sets the service provider for this CommandsNext instance.
/// Objects in this provider are used when instantiating command modules. This allows passing data around without resorting to static members.
/// Defaults to null.
///
public IServiceProvider ServiceProvider { internal get; set; } = new ServiceCollection().BuildServiceProvider(true);
///
/// Gets whether any extra arguments passed to commands should be ignored or not. If this is set to false, extra arguments will throw, otherwise they will be ignored.
/// Defaults to false.
///
public bool IgnoreExtraArguments { internal get; set; } = false;
///
/// Gets or sets whether to automatically enable handling commands.
/// If this is set to false, you will need to manually handle each incoming message and pass it to CommandsNext.
/// Defaults to true.
///
public bool UseDefaultCommandHandler { internal get; set; } = true;
///
/// Creates a new instance of .
///
public CommandsNextConfiguration() { }
///
/// Initializes a new instance of the class.
///
/// The service provider.
[ActivatorUtilitiesConstructor]
public CommandsNextConfiguration(IServiceProvider provider)
{
this.ServiceProvider = provider;
}
///
/// Creates a new instance of , copying the properties of another configuration.
///
/// Configuration the properties of which are to be copied.
public CommandsNextConfiguration(CommandsNextConfiguration other)
{
this.CaseSensitive = other.CaseSensitive;
this.PrefixResolver = other.PrefixResolver;
this.DefaultHelpChecks = other.DefaultHelpChecks;
this.EnableDefaultHelp = other.EnableDefaultHelp;
this.EnableDms = other.EnableDms;
this.EnableMentionPrefix = other.EnableMentionPrefix;
this.IgnoreExtraArguments = other.IgnoreExtraArguments;
this.UseDefaultCommandHandler = other.UseDefaultCommandHandler;
this.ServiceProvider = other.ServiceProvider;
this.StringPrefixes = other.StringPrefixes?.ToArray();
this.DmHelp = other.DmHelp;
}
}
}
diff --git a/DisCatSharp.CommandsNext/Converters/TimeConverters.cs b/DisCatSharp.CommandsNext/Converters/TimeConverters.cs
index c4675b2c1..7e7c77dfb 100644
--- a/DisCatSharp.CommandsNext/Converters/TimeConverters.cs
+++ b/DisCatSharp.CommandsNext/Converters/TimeConverters.cs
@@ -1,148 +1,148 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Text.RegularExpressions;
using System.Threading.Tasks;
using DisCatSharp.Entities;
namespace DisCatSharp.CommandsNext.Converters
{
///
/// Represents a date time converter.
///
public class DateTimeConverter : IArgumentConverter
{
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
return DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)
? Task.FromResult(new Optional(result))
: Task.FromResult(Optional.FromNoValue());
}
}
///
/// Represents a date time offset converter.
///
public class DateTimeOffsetConverter : IArgumentConverter
{
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
return DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)
? Task.FromResult(Optional.FromValue(result))
: Task.FromResult(Optional.FromNoValue());
}
}
///
/// Represents a time span converter.
///
public class TimeSpanConverter : IArgumentConverter
{
///
/// Gets or sets the time span regex.
///
private static Regex TimeSpanRegex { get; set; }
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
static TimeSpanConverter()
{
#if NETSTANDARD1_3
TimeSpanRegex = new Regex(@"^(?\d+d\s*)?(?\d{1,2}h\s*)?(?\d{1,2}m\s*)?(?\d{1,2}s\s*)?$", RegexOptions.ECMAScript);
#else
TimeSpanRegex = new Regex(@"^(?\d+d\s*)?(?\d{1,2}h\s*)?(?\d{1,2}m\s*)?(?\d{1,2}s\s*)?$", RegexOptions.ECMAScript | RegexOptions.Compiled);
#endif
}
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
if (value == "0")
return Task.FromResult(Optional.FromValue(TimeSpan.Zero));
if (int.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out _))
return Task.FromResult(Optional.FromNoValue());
if (!ctx.Config.CaseSensitive)
value = value.ToLowerInvariant();
if (TimeSpan.TryParse(value, CultureInfo.InvariantCulture, out var result))
return Task.FromResult(Optional.FromValue(result));
var gps = new string[] { "days", "hours", "minutes", "seconds" };
var mtc = TimeSpanRegex.Match(value);
if (!mtc.Success)
return Task.FromResult(Optional.FromNoValue());
var d = 0;
var h = 0;
var m = 0;
var s = 0;
foreach (var gp in gps)
{
var gpc = mtc.Groups[gp].Value;
if (string.IsNullOrWhiteSpace(gpc))
continue;
var gpt = gpc[gpc.Length - 1];
int.TryParse(gpc.Substring(0, gpc.Length - 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var val);
switch (gpt)
{
case 'd':
d = val;
break;
case 'h':
h = val;
break;
case 'm':
m = val;
break;
case 's':
s = val;
break;
}
}
result = new TimeSpan(d, h, m, s);
return Task.FromResult(Optional.FromValue(result));
}
}
}
diff --git a/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs b/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs
index 55fcd8beb..b3f04c0c4 100644
--- a/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs
+++ b/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs
@@ -1,133 +1,133 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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;
namespace DisCatSharp.Common.Serialization
{
///
- /// Defines the format for string-serialized and objects.
+ /// Defines the format for string-serialized and objects.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class DateTimeFormatAttribute : SerializationAttribute
{
///
/// Gets the ISO 8601 format string of "yyyy-MM-ddTHH:mm:ss.fffzzz".
///
public const string FormatISO8601 = "yyyy-MM-ddTHH:mm:ss.fffzzz";
///
/// Gets the RFC 1123 format string of "R".
///
public const string FormatRFC1123 = "R";
///
/// Gets the general long format.
///
public const string FormatLong = "G";
///
/// Gets the general short format.
///
public const string FormatShort = "g";
///
/// Gets the custom datetime format string to use.
///
public string Format { get; }
///
/// Gets the predefined datetime format kind.
///
public DateTimeFormatKind Kind { get; }
///
/// Specifies a predefined format to use.
///
/// Predefined format kind to use.
public DateTimeFormatAttribute(DateTimeFormatKind kind)
{
if (kind < 0 || kind > DateTimeFormatKind.InvariantLocaleShort)
throw new ArgumentOutOfRangeException(nameof(kind), "Specified format kind is not legal or supported.");
this.Kind = kind;
this.Format = null;
}
///
/// Specifies a custom format to use.
/// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings for more details.
///
/// Custom format string to use.
public DateTimeFormatAttribute(string format)
{
if (string.IsNullOrWhiteSpace(format))
throw new ArgumentNullException(nameof(format), "Specified format cannot be null or empty.");
this.Kind = DateTimeFormatKind.Custom;
this.Format = format;
}
}
///
- /// Defines which built-in format to use for for and serialization.
+ /// Defines which built-in format to use for for and serialization.
/// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings and https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings for more details.
///
public enum DateTimeFormatKind : int
{
///
/// Specifies ISO 8601 format, which is equivalent to .NET format string of "yyyy-MM-ddTHH:mm:ss.fffzzz".
///
ISO8601 = 0,
///
/// Specifies RFC 1123 format, which is equivalent to .NET format string of "R".
///
RFC1123 = 1,
///
- /// Specifies a format defined by , with a format string of "G". This format is not recommended for portability reasons.
+ /// Specifies a format defined by , with a format string of "G". This format is not recommended for portability reasons.
///
CurrentLocaleLong = 2,
///
- /// Specifies a format defined by , with a format string of "g". This format is not recommended for portability reasons.
+ /// Specifies a format defined by , with a format string of "g". This format is not recommended for portability reasons.
///
CurrentLocaleShort = 3,
///
- /// Specifies a format defined by , with a format string of "G".
+ /// Specifies a format defined by , with a format string of "G".
///
InvariantLocaleLong = 4,
///
- /// Specifies a format defined by , with a format string of "g".
+ /// Specifies a format defined by , with a format string of "g".
///
InvariantLocaleShort = 5,
///
/// Specifies a custom format. This value is not usable directly.
///
Custom = int.MaxValue
}
}
diff --git a/DisCatSharp.Common/Attributes/TimeSpanAttributes.cs b/DisCatSharp.Common/Attributes/TimeSpanAttributes.cs
index cacfe8d67..c97653667 100644
--- a/DisCatSharp.Common/Attributes/TimeSpanAttributes.cs
+++ b/DisCatSharp.Common/Attributes/TimeSpanAttributes.cs
@@ -1,42 +1,42 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Common.Serialization
{
///
- /// Specifies that this will be serialized as a number of whole seconds.
+ /// Specifies that this will be serialized as a number of whole seconds.
/// This value will always be serialized as a number.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class TimeSpanSecondsAttribute : SerializationAttribute
{ }
///
- /// Specifies that this will be serialized as a number of whole milliseconds.
+ /// Specifies that this will be serialized as a number of whole milliseconds.
/// This value will always be serialized as a number.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class TimeSpanMillisecondsAttribute : SerializationAttribute
{ }
}
diff --git a/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs b/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs
index e0df68803..78eeb6fc5 100644
--- a/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs
+++ b/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs
@@ -1,133 +1,133 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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;
namespace DisCatSharp.Common.Serialization
{
///
- /// Defines the format for string-serialized objects.
+ /// Defines the format for string-serialized objects.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class TimeSpanFormatAttribute : SerializationAttribute
{
///
/// Gets the ISO 8601 format string of @"ddThh\:mm\:ss\.fff".
///
public const string FormatISO8601 = @"ddThh\:mm\:ss\.fff";
///
/// Gets the constant format.
///
public const string FormatConstant = "c";
///
/// Gets the general long format.
///
public const string FormatLong = "G";
///
/// Gets the general short format.
///
public const string FormatShort = "g";
///
/// Gets the custom datetime format string to use.
///
public string Format { get; }
///
/// Gets the predefined datetime format kind.
///
public TimeSpanFormatKind Kind { get; }
///
/// Specifies a predefined format to use.
///
/// Predefined format kind to use.
public TimeSpanFormatAttribute(TimeSpanFormatKind kind)
{
if (kind < 0 || kind > TimeSpanFormatKind.InvariantLocaleShort)
throw new ArgumentOutOfRangeException(nameof(kind), "Specified format kind is not legal or supported.");
this.Kind = kind;
this.Format = null;
}
///
/// Specifies a custom format to use.
/// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-timespan-format-strings for more details.
///
/// Custom format string to use.
public TimeSpanFormatAttribute(string format)
{
if (string.IsNullOrWhiteSpace(format))
throw new ArgumentNullException(nameof(format), "Specified format cannot be null or empty.");
this.Kind = TimeSpanFormatKind.Custom;
this.Format = format;
}
}
///
- /// Defines which built-in format to use for serialization.
+ /// Defines which built-in format to use for serialization.
/// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-timespan-format-strings and https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-timespan-format-strings for more details.
///
public enum TimeSpanFormatKind : int
{
///
/// Specifies ISO 8601-like time format, which is equivalent to .NET format string of @"ddThh\:mm\:ss\.fff".
///
ISO8601 = 0,
///
- /// Specifies a format defined by , with a format string of "c".
+ /// Specifies a format defined by , with a format string of "c".
///
InvariantConstant = 1,
///
- /// Specifies a format defined by , with a format string of "G". This format is not recommended for portability reasons.
+ /// Specifies a format defined by , with a format string of "G". This format is not recommended for portability reasons.
///
CurrentLocaleLong = 2,
///
- /// Specifies a format defined by , with a format string of "g". This format is not recommended for portability reasons.
+ /// Specifies a format defined by , with a format string of "g". This format is not recommended for portability reasons.
///
CurrentLocaleShort = 3,
///
- /// Specifies a format defined by , with a format string of "G". This format is not recommended for portability reasons.
+ /// Specifies a format defined by , with a format string of "G". This format is not recommended for portability reasons.
///
InvariantLocaleLong = 4,
///
- /// Specifies a format defined by , with a format string of "g". This format is not recommended for portability reasons.
+ /// Specifies a format defined by , with a format string of "g". This format is not recommended for portability reasons.
///
InvariantLocaleShort = 5,
///
/// Specifies a custom format. This value is not usable directly.
///
Custom = int.MaxValue
}
}
diff --git a/DisCatSharp.Common/Attributes/UnixTimestampAttributes.cs b/DisCatSharp.Common/Attributes/UnixTimestampAttributes.cs
index 1579de6d5..aadb6bff6 100644
--- a/DisCatSharp.Common/Attributes/UnixTimestampAttributes.cs
+++ b/DisCatSharp.Common/Attributes/UnixTimestampAttributes.cs
@@ -1,42 +1,42 @@
-// This file is part of the DisCatSharp project.
+// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Common.Serialization
{
///
- /// Specifies that this or will be serialized as Unix timestamp seconds.
+ /// Specifies that this or will be serialized as Unix timestamp seconds.
/// This value will always be serialized as a number.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class UnixSecondsAttribute : SerializationAttribute
{ }
///
- /// Specifies that this or will be serialized as Unix timestamp milliseconds.
+ /// Specifies that this or will be serialized as Unix timestamp milliseconds.
/// This value will always be serialized as a number.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class UnixMillisecondsAttribute : SerializationAttribute
{ }
}
diff --git a/DisCatSharp.Common/Types/CharSpanLookupDictionary.cs b/DisCatSharp.Common/Types/CharSpanLookupDictionary.cs
index 06c75336e..40cc56b0a 100644
--- a/DisCatSharp.Common/Types/CharSpanLookupDictionary.cs
+++ b/DisCatSharp.Common/Types/CharSpanLookupDictionary.cs
@@ -1,830 +1,830 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace DisCatSharp.Common
{
///
- /// Represents collection of string keys and values, allowing the use of for dictionary operations.
+ /// Represents collection of string keys and values, allowing the use of for dictionary operations.
///
/// Type of items in this dictionary.
public sealed class CharSpanLookupDictionary :
IDictionary,
IReadOnlyDictionary,
IDictionary
{
///
/// Gets the collection of all keys present in this dictionary.
///
public IEnumerable Keys => this.GetKeysInternal();
///
/// Gets the keys.
///
ICollection IDictionary.Keys => this.GetKeysInternal();
///
/// Gets the keys.
///
ICollection IDictionary.Keys => this.GetKeysInternal();
///
/// Gets the collection of all values present in this dictionary.
///
public IEnumerable Values => this.GetValuesInternal();
///
/// Gets the values.
///
ICollection IDictionary.Values => this.GetValuesInternal();
///
/// Gets the values.
///
ICollection IDictionary.Values => this.GetValuesInternal();
///
/// Gets the total number of items in this dictionary.
///
public int Count { get; private set; } = 0;
///
/// Gets whether this dictionary is read-only.
///
public bool IsReadOnly => false;
///
/// Gets whether this dictionary has a fixed size.
///
public bool IsFixedSize => false;
///
/// Gets whether this dictionary is considered thread-safe.
///
public bool IsSynchronized => false;
///
/// Gets the object which allows synchronizing access to this dictionary.
///
public object SyncRoot { get; } = new object();
///
/// Gets or sets a value corresponding to given key in this dictionary.
///
/// Key to get or set the value for.
/// Value matching the supplied key, if applicable.
public TValue this[string key]
{
get
{
if (key == null)
throw new ArgumentNullException(nameof(key));
if (!this.TryRetrieveInternal(key.AsSpan(), out var value))
throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary.");
return value;
}
set
{
if (key == null)
throw new ArgumentNullException(nameof(key));
this.TryInsertInternal(key, value, true);
}
}
///
/// Gets or sets a value corresponding to given key in this dictionary.
///
/// Key to get or set the value for.
/// Value matching the supplied key, if applicable.
public TValue this[ReadOnlySpan key]
{
get
{
if (!this.TryRetrieveInternal(key, out var value))
throw new KeyNotFoundException($"The given key was not present in the dictionary.");
return value;
}
#if NETCOREAPP
set => this.TryInsertInternal(new string(key), value, true);
#else
set
{
unsafe
{
fixed (char* chars = &key.GetPinnableReference())
this.TryInsertInternal(new string(chars, 0, key.Length), value, true);
}
}
#endif
}
object IDictionary.this[object key]
{
get
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
if (!this.TryRetrieveInternal(tkey.AsSpan(), out var value))
throw new KeyNotFoundException($"The given key '{tkey}' was not present in the dictionary.");
return value;
}
set
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
if (!(value is TValue tvalue))
{
tvalue = default;
if (tvalue != null)
throw new ArgumentException($"Value needs to be an instance of {typeof(TValue)}.");
}
this.TryInsertInternal(tkey, tvalue, true);
}
}
///
/// Gets the internal buckets.
///
private Dictionary InternalBuckets { get; }
///
/// Creates a new, empty with string keys and items of type .
///
public CharSpanLookupDictionary()
{
this.InternalBuckets = new Dictionary();
}
///
/// Creates a new, empty with string keys and items of type and sets its initial capacity to specified value.
///
/// Initial capacity of the dictionary.
public CharSpanLookupDictionary(int initialCapacity)
{
this.InternalBuckets = new Dictionary(initialCapacity);
}
///
/// Creates a new with string keys and items of type and populates it with key-value pairs from supplied dictionary.
///
/// Dictionary containing items to populate this dictionary with.
public CharSpanLookupDictionary(IDictionary values)
: this(values.Count)
{
foreach (var (k, v) in values)
this.Add(k, v);
}
///
/// Creates a new with string keys and items of type and populates it with key-value pairs from supplied dictionary.
///
/// Dictionary containing items to populate this dictionary with.
public CharSpanLookupDictionary(IReadOnlyDictionary values)
: this(values.Count)
{
foreach (var (k, v) in values)
this.Add(k, v);
}
///
/// Creates a new with string keys and items of type and populates it with key-value pairs from supplied key-value collection.
///
/// Dictionary containing items to populate this dictionary with.
public CharSpanLookupDictionary(IEnumerable> values)
: this()
{
foreach (var (k, v) in values)
this.Add(k, v);
}
///
/// Inserts a specific key and corresponding value into this dictionary.
///
/// Key to insert.
/// Value corresponding to this key.
public void Add(string key, TValue value)
{
if (!this.TryInsertInternal(key, value, false))
throw new ArgumentException("Given key is already present in the dictionary.", nameof(key));
}
///
/// Inserts a specific key and corresponding value into this dictionary.
///
/// Key to insert.
/// Value corresponding to this key.
public void Add(ReadOnlySpan key, TValue value)
#if NETCOREAPP
{
if (!this.TryInsertInternal(new string(key), value, false))
throw new ArgumentException("Given key is already present in the dictionary.", nameof(key));
}
#else
{
unsafe
{
fixed (char* chars = &key.GetPinnableReference())
if (!this.TryInsertInternal(new string(chars, 0, key.Length), value, false))
throw new ArgumentException("Given key is already present in the dictionary.", nameof(key));
}
}
#endif
///
/// Attempts to insert a specific key and corresponding value into this dictionary.
///
/// Key to insert.
/// Value corresponding to this key.
/// Whether the operation was successful.
public bool TryAdd(string key, TValue value)
=> this.TryInsertInternal(key, value, false);
///
/// Attempts to insert a specific key and corresponding value into this dictionary.
///
/// Key to insert.
/// Value corresponding to this key.
/// Whether the operation was successful.
public bool TryAdd(ReadOnlySpan key, TValue value)
#if NETCOREAPP
=> this.TryInsertInternal(new string(key), value, false);
#else
{
unsafe
{
fixed (char* chars = &key.GetPinnableReference())
return this.TryInsertInternal(new string(chars, 0, key.Length), value, false);
}
}
#endif
///
/// Attempts to retrieve a value corresponding to the supplied key from this dictionary.
///
/// Key to retrieve the value for.
/// Retrieved value.
/// Whether the operation was successful.
public bool TryGetValue(string key, out TValue value)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
return this.TryRetrieveInternal(key.AsSpan(), out value);
}
///
/// Attempts to retrieve a value corresponding to the supplied key from this dictionary.
///
/// Key to retrieve the value for.
/// Retrieved value.
/// Whether the operation was successful.
public bool TryGetValue(ReadOnlySpan key, out TValue value)
=> this.TryRetrieveInternal(key, out value);
///
/// Attempts to remove a value corresponding to the supplied key from this dictionary.
///
/// Key to remove the value for.
/// Removed value.
/// Whether the operation was successful.
public bool TryRemove(string key, out TValue value)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
return this.TryRemoveInternal(key.AsSpan(), out value);
}
///
/// Attempts to remove a value corresponding to the supplied key from this dictionary.
///
/// Key to remove the value for.
/// Removed value.
/// Whether the operation was successful.
public bool TryRemove(ReadOnlySpan key, out TValue value)
=> this.TryRemoveInternal(key, out value);
///
/// Checks whether this dictionary contains the specified key.
///
/// Key to check for in this dictionary.
/// Whether the key was present in the dictionary.
public bool ContainsKey(string key)
=> this.ContainsKeyInternal(key.AsSpan());
///
/// Checks whether this dictionary contains the specified key.
///
/// Key to check for in this dictionary.
/// Whether the key was present in the dictionary.
public bool ContainsKey(ReadOnlySpan key)
=> this.ContainsKeyInternal(key);
///
/// Removes all items from this dictionary.
///
public void Clear()
{
this.InternalBuckets.Clear();
this.Count = 0;
}
///
/// Gets an enumerator over key-value pairs in this dictionary.
///
///
public IEnumerator> GetEnumerator()
=> new Enumerator(this);
///
/// Removes the.
///
/// The key.
/// A bool.
bool IDictionary.Remove(string key)
=> this.TryRemove(key.AsSpan(), out _);
///
/// Adds the.
///
/// The key.
/// The value.
void IDictionary.Add(object key, object value)
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
if (!(value is TValue tvalue))
{
tvalue = default;
if (tvalue != null)
throw new ArgumentException($"Value needs to be an instance of {typeof(TValue)}.");
}
this.Add(tkey, tvalue);
}
///
/// Removes the.
///
/// The key.
void IDictionary.Remove(object key)
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
this.TryRemove(tkey, out _);
}
///
/// Contains the.
///
/// The key.
/// A bool.
bool IDictionary.Contains(object key)
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
return this.ContainsKey(tkey);
}
///
/// Gets the enumerator.
///
/// An IDictionaryEnumerator.
IDictionaryEnumerator IDictionary.GetEnumerator()
=> new Enumerator(this);
///
/// Adds the.
///
/// The item.
void ICollection>.Add(KeyValuePair item)
=> this.Add(item.Key, item.Value);
///
/// Removes the.
///
/// The item.
/// A bool.
bool ICollection>.Remove(KeyValuePair item)
=> this.TryRemove(item.Key, out _);
///
/// Contains the.
///
/// The item.
/// A bool.
bool ICollection>.Contains(KeyValuePair item)
=> this.TryGetValue(item.Key, out var value) && EqualityComparer.Default.Equals(value, item.Value);
///
/// Copies the to.
///
/// The array.
/// The array index.
void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
{
if (array.Length - arrayIndex < this.Count)
throw new ArgumentException("Target array is too small.", nameof(array));
var i = arrayIndex;
foreach (var (k, v) in this.InternalBuckets)
{
var kdv = v;
while (kdv != null)
{
array[i++] = new KeyValuePair(kdv.Key, kdv.Value);
kdv = kdv.Next;
}
}
}
///
/// Copies the to.
///
/// The array.
/// The array index.
void ICollection.CopyTo(Array array, int arrayIndex)
{
if (array is KeyValuePair[] tarray)
{
(this as ICollection>).CopyTo(tarray, arrayIndex);
return;
}
if (array is not object[])
throw new ArgumentException($"Array needs to be an instance of {typeof(TValue[])} or object[].");
var i = arrayIndex;
foreach (var (k, v) in this.InternalBuckets)
{
var kdv = v;
while (kdv != null)
{
array.SetValue(new KeyValuePair(kdv.Key, kdv.Value), i++);
kdv = kdv.Next;
}
}
}
///
/// Gets the enumerator.
///
/// An IEnumerator.
IEnumerator IEnumerable.GetEnumerator()
=> this.GetEnumerator();
///
/// Tries the insert internal.
///
/// The key.
/// The value.
/// If true, replace.
/// A bool.
private bool TryInsertInternal(string key, TValue value, bool replace)
{
if (key == null)
throw new ArgumentNullException(nameof(key), "Key cannot be null.");
var hash = key.CalculateKnuthHash();
if (!this.InternalBuckets.ContainsKey(hash))
{
this.InternalBuckets.Add(hash, new KeyedValue(key, hash, value));
this.Count++;
return true;
}
var kdv = this.InternalBuckets[hash];
var kdvLast = kdv;
while (kdv != null)
{
if (kdv.Key == key)
{
if (!replace)
return false;
kdv.Value = value;
return true;
}
kdvLast = kdv;
kdv = kdv.Next;
}
kdvLast.Next = new KeyedValue(key, hash, value);
this.Count++;
return true;
}
///
/// Tries the retrieve internal.
///
/// The key.
/// The value.
/// A bool.
private bool TryRetrieveInternal(ReadOnlySpan key, out TValue value)
{
value = default;
var hash = key.CalculateKnuthHash();
if (!this.InternalBuckets.TryGetValue(hash, out var kdv))
return false;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
{
value = kdv.Value;
return true;
}
}
return false;
}
///
/// Tries the remove internal.
///
/// The key.
/// The value.
/// A bool.
private bool TryRemoveInternal(ReadOnlySpan key, out TValue value)
{
value = default;
var hash = key.CalculateKnuthHash();
if (!this.InternalBuckets.TryGetValue(hash, out var kdv))
return false;
if (kdv.Next == null && key.SequenceEqual(kdv.Key.AsSpan()))
{
// Only bucket under this hash and key matches, pop the entire bucket
value = kdv.Value;
this.InternalBuckets.Remove(hash);
this.Count--;
return true;
}
else if (kdv.Next == null)
{
// Only bucket under this hash and key does not match, cannot remove
return false;
}
else if (key.SequenceEqual(kdv.Key.AsSpan()))
{
// First key in the bucket matches, pop it and set its child as current bucket
value = kdv.Value;
this.InternalBuckets[hash] = kdv.Next;
this.Count--;
return true;
}
var kdvLast = kdv;
kdv = kdv.Next;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
{
// Key matched, remove this bucket from the chain
value = kdv.Value;
kdvLast.Next = kdv.Next;
this.Count--;
return true;
}
kdvLast = kdv;
kdv = kdv.Next;
}
return false;
}
///
/// Contains the key internal.
///
/// The key.
/// A bool.
private bool ContainsKeyInternal(ReadOnlySpan key)
{
var hash = key.CalculateKnuthHash();
if (!this.InternalBuckets.TryGetValue(hash, out var kdv))
return false;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
return true;
kdv = kdv.Next;
}
return false;
}
///
/// Gets the keys internal.
///
/// An ImmutableArray.
private ImmutableArray GetKeysInternal()
{
var builder = ImmutableArray.CreateBuilder(this.Count);
foreach (var value in this.InternalBuckets.Values)
{
var kdv = value;
while (kdv != null)
{
builder.Add(kdv.Key);
kdv = kdv.Next;
}
}
return builder.MoveToImmutable();
}
///
/// Gets the values internal.
///
/// An ImmutableArray.
private ImmutableArray GetValuesInternal()
{
var builder = ImmutableArray.CreateBuilder(this.Count);
foreach (var value in this.InternalBuckets.Values)
{
var kdv = value;
while (kdv != null)
{
builder.Add(kdv.Value);
kdv = kdv.Next;
}
}
return builder.MoveToImmutable();
}
///
/// The keyed value.
///
private class KeyedValue
{
///
/// Gets the key hash.
///
public ulong KeyHash { get; }
///
/// Gets the key.
///
public string Key { get; }
///
/// Gets or sets the value.
///
public TValue Value { get; set; }
///
/// Gets or sets the next.
///
public KeyedValue Next { get; set; }
///
/// Initializes a new instance of the class.
///
/// The key.
/// The key hash.
/// The value.
public KeyedValue(string key, ulong keyHash, TValue value)
{
this.KeyHash = keyHash;
this.Key = key;
this.Value = value;
}
}
///
/// The enumerator.
///
private class Enumerator :
IEnumerator>,
IDictionaryEnumerator
{
///
/// Gets the current.
///
public KeyValuePair Current { get; private set; }
///
/// Gets the current.
///
object IEnumerator.Current => this.Current;
///
/// Gets the key.
///
object IDictionaryEnumerator.Key => this.Current.Key;
///
/// Gets the value.
///
object IDictionaryEnumerator.Value => this.Current.Value;
///
/// Gets the entry.
///
DictionaryEntry IDictionaryEnumerator.Entry => new DictionaryEntry(this.Current.Key, this.Current.Value);
///
/// Gets the internal dictionary.
///
private CharSpanLookupDictionary InternalDictionary { get; }
///
/// Gets the internal enumerator.
///
private IEnumerator> InternalEnumerator { get; }
///
/// Gets or sets the current value.
///
private KeyedValue CurrentValue { get; set; } = null;
///
/// Initializes a new instance of the class.
///
/// The sp dict.
public Enumerator(CharSpanLookupDictionary spDict)
{
this.InternalDictionary = spDict;
this.InternalEnumerator = this.InternalDictionary.InternalBuckets.GetEnumerator();
}
///
/// Moves the next.
///
/// A bool.
public bool MoveNext()
{
var kdv = this.CurrentValue;
if (kdv == null)
{
if (!this.InternalEnumerator.MoveNext())
return false;
kdv = this.InternalEnumerator.Current.Value;
this.Current = new KeyValuePair(kdv.Key, kdv.Value);
this.CurrentValue = kdv.Next;
return true;
}
this.Current = new KeyValuePair(kdv.Key, kdv.Value);
this.CurrentValue = kdv.Next;
return true;
}
///
/// Resets the.
///
public void Reset()
{
this.InternalEnumerator.Reset();
this.Current = default;
this.CurrentValue = null;
}
///
/// Disposes the.
///
public void Dispose()
{
this.Reset();
}
}
}
}
diff --git a/DisCatSharp.Common/Types/CharSpanLookupReadOnlyDictionary.cs b/DisCatSharp.Common/Types/CharSpanLookupReadOnlyDictionary.cs
index 775141380..8f955eb5d 100644
--- a/DisCatSharp.Common/Types/CharSpanLookupReadOnlyDictionary.cs
+++ b/DisCatSharp.Common/Types/CharSpanLookupReadOnlyDictionary.cs
@@ -1,417 +1,417 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
namespace DisCatSharp.Common
{
///
- /// Represents collection of string keys and values, allowing the use of for dictionary operations.
+ /// Represents collection of string keys and values, allowing the use of for dictionary operations.
///
/// Type of items in this dictionary.
public sealed class CharSpanLookupReadOnlyDictionary : IReadOnlyDictionary
{
///
/// Gets the collection of all keys present in this dictionary.
///
public IEnumerable Keys => this.GetKeysInternal();
///
/// Gets the collection of all values present in this dictionary.
///
public IEnumerable Values => this.GetValuesInternal();
///
/// Gets the total number of items in this dictionary.
///
public int Count { get; }
///
/// Gets a value corresponding to given key in this dictionary.
///
/// Key to get or set the value for.
/// Value matching the supplied key, if applicable.
public TValue this[string key]
{
get
{
if (key == null)
throw new ArgumentNullException(nameof(key));
if (!this.TryRetrieveInternal(key.AsSpan(), out var value))
throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary.");
return value;
}
}
///
/// Gets a value corresponding to given key in this dictionary.
///
/// Key to get or set the value for.
/// Value matching the supplied key, if applicable.
public TValue this[ReadOnlySpan key]
{
get
{
if (!this.TryRetrieveInternal(key, out var value))
throw new KeyNotFoundException($"The given key was not present in the dictionary.");
return value;
}
}
///
/// Gets the internal buckets.
///
private IReadOnlyDictionary InternalBuckets { get; }
///
/// Creates a new with string keys and items of type and populates it with key-value pairs from supplied dictionary.
///
/// Dictionary containing items to populate this dictionary with.
public CharSpanLookupReadOnlyDictionary(IDictionary values)
: this(values as IEnumerable>)
{ }
///
/// Creates a new with string keys and items of type and populates it with key-value pairs from supplied dictionary.
///
/// Dictionary containing items to populate this dictionary with.
public CharSpanLookupReadOnlyDictionary(IReadOnlyDictionary values)
: this(values as IEnumerable>)
{ }
///
/// Creates a new with string keys and items of type and populates it with key-value pairs from supplied key-value collection.
///
/// Dictionary containing items to populate this dictionary with.
public CharSpanLookupReadOnlyDictionary(IEnumerable> values)
{
this.InternalBuckets = PrepareItems(values, out var count);
this.Count = count;
}
///
/// Attempts to retrieve a value corresponding to the supplied key from this dictionary.
///
/// Key to retrieve the value for.
/// Retrieved value.
/// Whether the operation was successful.
public bool TryGetValue(string key, out TValue value)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
return this.TryRetrieveInternal(key.AsSpan(), out value);
}
///
/// Attempts to retrieve a value corresponding to the supplied key from this dictionary.
///
/// Key to retrieve the value for.
/// Retrieved value.
/// Whether the operation was successful.
public bool TryGetValue(ReadOnlySpan key, out TValue value)
=> this.TryRetrieveInternal(key, out value);
///
/// Checks whether this dictionary contains the specified key.
///
/// Key to check for in this dictionary.
/// Whether the key was present in the dictionary.
public bool ContainsKey(string key)
=> this.ContainsKeyInternal(key.AsSpan());
///
/// Checks whether this dictionary contains the specified key.
///
/// Key to check for in this dictionary.
/// Whether the key was present in the dictionary.
public bool ContainsKey(ReadOnlySpan key)
=> this.ContainsKeyInternal(key);
///
/// Gets an enumerator over key-value pairs in this dictionary.
///
///
public IEnumerator> GetEnumerator()
=> new Enumerator(this);
///
/// Gets the enumerator.
///
/// An IEnumerator.
IEnumerator IEnumerable.GetEnumerator()
=> this.GetEnumerator();
///
/// Tries the retrieve internal.
///
/// The key.
/// The value.
/// A bool.
private bool TryRetrieveInternal(ReadOnlySpan key, out TValue value)
{
value = default;
var hash = key.CalculateKnuthHash();
if (!this.InternalBuckets.TryGetValue(hash, out var kdv))
return false;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
{
value = kdv.Value;
return true;
}
}
return false;
}
///
/// Contains the key internal.
///
/// The key.
/// A bool.
private bool ContainsKeyInternal(ReadOnlySpan key)
{
var hash = key.CalculateKnuthHash();
if (!this.InternalBuckets.TryGetValue(hash, out var kdv))
return false;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
return true;
kdv = kdv.Next;
}
return false;
}
///
/// Gets the keys internal.
///
/// An ImmutableArray.
private ImmutableArray GetKeysInternal()
{
var builder = ImmutableArray.CreateBuilder(this.Count);
foreach (var value in this.InternalBuckets.Values)
{
var kdv = value;
while (kdv != null)
{
builder.Add(kdv.Key);
kdv = kdv.Next;
}
}
return builder.MoveToImmutable();
}
///
/// Gets the values internal.
///
/// An ImmutableArray.
private ImmutableArray GetValuesInternal()
{
var builder = ImmutableArray.CreateBuilder(this.Count);
foreach (var value in this.InternalBuckets.Values)
{
var kdv = value;
while (kdv != null)
{
builder.Add(kdv.Value);
kdv = kdv.Next;
}
}
return builder.MoveToImmutable();
}
///
/// Prepares the items.
///
/// The items.
/// The count.
/// An IReadOnlyDictionary.
private static IReadOnlyDictionary PrepareItems(IEnumerable> items, out int count)
{
count = 0;
var dict = new Dictionary();
foreach (var (k, v) in items)
{
if (k == null)
throw new ArgumentException("Keys cannot be null.", nameof(items));
var hash = k.CalculateKnuthHash();
if (!dict.ContainsKey(hash))
{
dict.Add(hash, new KeyedValue(k, hash, v));
count++;
continue;
}
var kdv = dict[hash];
var kdvLast = kdv;
while (kdv != null)
{
if (kdv.Key == k)
throw new ArgumentException("Given key is already present in the dictionary.", nameof(items));
kdvLast = kdv;
kdv = kdv.Next;
}
kdvLast.Next = new KeyedValue(k, hash, v);
count++;
}
return new ReadOnlyDictionary(dict);
}
///
/// The keyed value.
///
private class KeyedValue
{
///
/// Gets the key hash.
///
public ulong KeyHash { get; }
///
/// Gets the key.
///
public string Key { get; }
///
/// Gets or sets the value.
///
public TValue Value { get; set; }
///
/// Gets or sets the next.
///
public KeyedValue Next { get; set; }
///
/// Initializes a new instance of the class.
///
/// The key.
/// The key hash.
/// The value.
public KeyedValue(string key, ulong keyHash, TValue value)
{
this.KeyHash = keyHash;
this.Key = key;
this.Value = value;
}
}
///
/// The enumerator.
///
private class Enumerator : IEnumerator>
{
///
/// Gets the current.
///
public KeyValuePair Current { get; private set; }
///
/// Gets the current.
///
object IEnumerator.Current => this.Current;
///
/// Gets the internal dictionary.
///
private CharSpanLookupReadOnlyDictionary InternalDictionary { get; }
///
/// Gets the internal enumerator.
///
private IEnumerator> InternalEnumerator { get; }
///
/// Gets or sets the current value.
///
private KeyedValue CurrentValue { get; set; } = null;
///
/// Initializes a new instance of the class.
///
/// The sp dict.
public Enumerator(CharSpanLookupReadOnlyDictionary spDict)
{
this.InternalDictionary = spDict;
this.InternalEnumerator = this.InternalDictionary.InternalBuckets.GetEnumerator();
}
///
/// Moves the next.
///
/// A bool.
public bool MoveNext()
{
var kdv = this.CurrentValue;
if (kdv == null)
{
if (!this.InternalEnumerator.MoveNext())
return false;
kdv = this.InternalEnumerator.Current.Value;
this.Current = new KeyValuePair(kdv.Key, kdv.Value);
this.CurrentValue = kdv.Next;
return true;
}
this.Current = new KeyValuePair(kdv.Key, kdv.Value);
this.CurrentValue = kdv.Next;
return true;
}
///
/// Resets the.
///
public void Reset()
{
this.InternalEnumerator.Reset();
this.Current = default;
this.CurrentValue = null;
}
///
/// Disposes the.
///
public void Dispose()
{
this.Reset();
}
}
}
}
diff --git a/DisCatSharp.Common/Types/ContinuousMemoryBuffer.cs b/DisCatSharp.Common/Types/ContinuousMemoryBuffer.cs
index e1736cd76..53a9cde41 100644
--- a/DisCatSharp.Common/Types/ContinuousMemoryBuffer.cs
+++ b/DisCatSharp.Common/Types/ContinuousMemoryBuffer.cs
@@ -1,274 +1,274 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace DisCatSharp.Common.Types
{
///
/// Provides a resizable memory buffer analogous to , using a single continuous memory region instead.
///
/// Type of item to hold in the buffer.
public sealed class ContinuousMemoryBuffer : IMemoryBuffer where T : unmanaged
{
///
public ulong Capacity => (ulong)this._buff.Length;
///
public ulong Length => (ulong)this._pos;
///
public ulong Count => (ulong)(this._pos / this._itemSize);
private readonly MemoryPool _pool;
private IMemoryOwner _buffOwner;
private Memory _buff;
private readonly bool _clear;
private int _pos;
private readonly int _itemSize;
private bool _isDisposed;
///
/// Creates a new buffer with a specified segment size, specified number of initially-allocated segments, and supplied memory pool.
///
/// Initial size of the buffer in bytes. Defaults to 64KiB.
- /// Memory pool to use for renting buffers. Defaults to .
+ /// Memory pool to use for renting buffers. Defaults to .
/// Determines whether the underlying buffers should be cleared on exit. If dealing with sensitive data, it might be a good idea to set this option to true.
public ContinuousMemoryBuffer(int initialSize = 65536, MemoryPool memPool = default, bool clearOnDispose = false)
{
this._itemSize = Unsafe.SizeOf();
this._pool = memPool ?? MemoryPool.Shared;
this._clear = clearOnDispose;
this._buffOwner = this._pool.Rent(initialSize);
this._buff = this._buffOwner.Memory;
this._isDisposed = false;
}
///
public void Write(ReadOnlySpan data)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var bytes = MemoryMarshal.AsBytes(data);
this.EnsureSize(this._pos + bytes.Length);
bytes.CopyTo(this._buff.Slice(this._pos).Span);
this._pos += bytes.Length;
}
///
public void Write(T[] data, int start, int count)
=> this.Write(data.AsSpan(start, count));
///
public void Write(ArraySegment data)
=> this.Write(data.AsSpan());
///
public void Write(Stream stream)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
if (stream.CanSeek)
this.WriteStreamSeekable(stream);
else
this.WriteStreamUnseekable(stream);
}
///
/// Writes the stream seekable.
///
/// The stream.
private void WriteStreamSeekable(Stream stream)
{
if (stream.Length > int.MaxValue)
throw new ArgumentException("Stream is too long.", nameof(stream));
this.EnsureSize(this._pos + (int)stream.Length);
#if HAS_SPAN_STREAM_OVERLOADS
stream.Read(this._buff.Slice(this._pos).Span);
#else
var memo = ArrayPool.Shared.Rent((int)stream.Length);
try
{
var br = stream.Read(memo, 0, memo.Length);
memo.AsSpan(0, br).CopyTo(this._buff.Slice(this._pos).Span);
}
finally
{
ArrayPool.Shared.Return(memo);
}
#endif
this._pos += (int)stream.Length;
}
///
/// Writes the stream unseekable.
///
/// The stream.
private void WriteStreamUnseekable(Stream stream)
{
#if HAS_SPAN_STREAM_OVERLOADS
var br = 0;
do
{
this.EnsureSize(this._pos + 4096);
br = stream.Read(this._buff.Slice(this._pos).Span);
this._pos += br;
}
while (br != 0);
#else
var memo = ArrayPool.Shared.Rent(4096);
try
{
var br = 0;
while ((br = stream.Read(memo, 0, memo.Length)) != 0)
{
this.EnsureSize(this._pos + br);
memo.AsSpan(0, br).CopyTo(this._buff.Slice(this._pos).Span);
this._pos += br;
}
}
finally
{
ArrayPool.Shared.Return(memo);
}
#endif
}
///
public bool Read(Span destination, ulong source, out int itemsWritten)
{
itemsWritten = 0;
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
source *= (ulong)this._itemSize;
if (source > this.Count)
throw new ArgumentOutOfRangeException(nameof(source), "Cannot copy data from beyond the buffer.");
var start = (int)source;
var sbuff = this._buff.Slice(start, this._pos - start).Span;
var dbuff = MemoryMarshal.AsBytes(destination);
if (sbuff.Length > dbuff.Length)
sbuff = sbuff.Slice(0, dbuff.Length);
itemsWritten = sbuff.Length / this._itemSize;
sbuff.CopyTo(dbuff);
return (this.Length - source) != (ulong)itemsWritten;
}
///
public bool Read(T[] data, int start, int count, ulong source, out int itemsWritten)
=> this.Read(data.AsSpan(start, count), source, out itemsWritten);
///
public bool Read(ArraySegment data, ulong source, out int itemsWritten)
=> this.Read(data.AsSpan(), source, out itemsWritten);
///
public T[] ToArray()
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
return MemoryMarshal.Cast(this._buff.Slice(0, this._pos).Span).ToArray();
}
///
public void CopyTo(Stream destination)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
#if HAS_SPAN_STREAM_OVERLOADS
destination.Write(this._buff.Slice(0, this._pos).Span);
#else
var buff = this._buff.Slice(0, this._pos).ToArray();
destination.Write(buff, 0, buff.Length);
#endif
}
///
public void Clear()
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
this._pos = 0;
}
///
/// Disposes of any resources claimed by this buffer.
///
public void Dispose()
{
if (this._isDisposed)
return;
this._isDisposed = true;
if (this._clear)
this._buff.Span.Clear();
this._buffOwner.Dispose();
this._buff = default;
}
///
/// Ensures the size.
///
/// The new capacity.
private void EnsureSize(int newCapacity)
{
var cap = this._buff.Length;
if (cap >= newCapacity)
return;
var factor = newCapacity / cap;
if (newCapacity % cap != 0)
++factor;
var newActualCapacity = cap * factor;
var newBuffOwner = this._pool.Rent(newActualCapacity);
var newBuff = newBuffOwner.Memory;
this._buff.Span.CopyTo(newBuff.Span);
if (this._clear)
this._buff.Span.Clear();
this._buffOwner.Dispose();
this._buffOwner = newBuffOwner;
this._buff = newBuff;
}
}
}
diff --git a/DisCatSharp.Common/Types/MemoryBuffer.cs b/DisCatSharp.Common/Types/MemoryBuffer.cs
index 935f7eefa..696c86215 100644
--- a/DisCatSharp.Common/Types/MemoryBuffer.cs
+++ b/DisCatSharp.Common/Types/MemoryBuffer.cs
@@ -1,356 +1,356 @@
-// This file is part of the DisCatSharp project.
+// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace DisCatSharp.Common.Types
{
///
/// Provides a resizable memory buffer, which can be read from and written to. It will automatically resize whenever required.
///
/// Type of item to hold in the buffer.
public sealed class MemoryBuffer : IMemoryBuffer where T : unmanaged
{
///
public ulong Capacity => this._segments.Aggregate(0UL, (a, x) => a + (ulong)x.Memory.Length); // .Sum() does only int
///
public ulong Length { get; private set; }
///
public ulong Count => this.Length / (ulong)this._itemSize;
private readonly MemoryPool _pool;
private readonly int _segmentSize;
private int _lastSegmentLength;
private int _segNo;
private readonly bool _clear;
private readonly List> _segments;
private readonly int _itemSize;
private bool _isDisposed;
///
/// Creates a new buffer with a specified segment size, specified number of initially-allocated segments, and supplied memory pool.
///
/// Byte size of an individual segment. Defaults to 64KiB.
/// Number of segments to allocate. Defaults to 0.
- /// Memory pool to use for renting buffers. Defaults to .
+ /// Memory pool to use for renting buffers. Defaults to .
/// Determines whether the underlying buffers should be cleared on exit. If dealing with sensitive data, it might be a good idea to set this option to true.
public MemoryBuffer(int segmentSize = 65536, int initialSegmentCount = 0, MemoryPool memPool = default, bool clearOnDispose = false)
{
this._itemSize = Unsafe.SizeOf();
if (segmentSize % this._itemSize != 0)
throw new ArgumentException("Segment size must match size of individual item.");
this._pool = memPool ?? MemoryPool.Shared;
this._segmentSize = segmentSize;
this._segNo = 0;
this._lastSegmentLength = 0;
this._clear = clearOnDispose;
this._segments = new List>(initialSegmentCount + 1);
for (var i = 0; i < initialSegmentCount; i++)
this._segments.Add(this._pool.Rent(this._segmentSize));
this.Length = 0;
this._isDisposed = false;
}
///
public void Write(ReadOnlySpan data)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var src = MemoryMarshal.AsBytes(data);
this.Grow(src.Length);
while (this._segNo < this._segments.Count && src.Length > 0)
{
var seg = this._segments[this._segNo];
var mem = seg.Memory;
var avs = mem.Length - this._lastSegmentLength;
avs = avs > src.Length
? src.Length
: avs;
var dmem = mem.Slice(this._lastSegmentLength);
src.Slice(0, avs).CopyTo(dmem.Span);
src = src.Slice(avs);
this.Length += (ulong)avs;
this._lastSegmentLength += avs;
if (this._lastSegmentLength == mem.Length)
{
this._segNo++;
this._lastSegmentLength = 0;
}
}
}
///
public void Write(T[] data, int start, int count)
=> this.Write(data.AsSpan(start, count));
///
public void Write(ArraySegment data)
=> this.Write(data.AsSpan());
///
public void Write(Stream stream)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
if (stream.CanSeek)
this.WriteStreamSeekable(stream);
else
this.WriteStreamUnseekable(stream);
}
///
/// Writes the stream seekable.
///
/// The stream.
private void WriteStreamSeekable(Stream stream)
{
var len = (int)(stream.Length - stream.Position);
this.Grow(len);
#if !HAS_SPAN_STREAM_OVERLOADS
var buff = new byte[this._segmentSize];
#endif
while (this._segNo < this._segments.Count && len > 0)
{
var seg = this._segments[this._segNo];
var mem = seg.Memory;
var avs = mem.Length - this._lastSegmentLength;
avs = avs > len
? len
: avs;
var dmem = mem.Slice(this._lastSegmentLength);
#if HAS_SPAN_STREAM_OVERLOADS
stream.Read(dmem.Span);
#else
var lsl = this._lastSegmentLength;
var slen = dmem.Span.Length - lsl;
stream.Read(buff, 0, slen);
buff.AsSpan(0, slen).CopyTo(dmem.Span);
#endif
len -= dmem.Span.Length;
this.Length += (ulong)avs;
this._lastSegmentLength += avs;
if (this._lastSegmentLength == mem.Length)
{
this._segNo++;
this._lastSegmentLength = 0;
}
}
}
///
/// Writes the stream unseekable.
///
/// The stream.
private void WriteStreamUnseekable(Stream stream)
{
var read = 0;
#if HAS_SPAN_STREAM_OVERLOADS
Span buffs = stackalloc byte[this._segmentSize];
while ((read = stream.Read(buffs)) != 0)
#else
var buff = new byte[this._segmentSize];
var buffs = buff.AsSpan();
while ((read = stream.Read(buff, 0, buff.Length - this._lastSegmentLength)) != 0)
#endif
this.Write(MemoryMarshal.Cast(buffs.Slice(0, read)));
}
///
public bool Read(Span destination, ulong source, out int itemsWritten)
{
itemsWritten = 0;
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
source *= (ulong)this._itemSize;
if (source > this.Count)
throw new ArgumentOutOfRangeException(nameof(source), "Cannot copy data from beyond the buffer.");
// Find where to begin
var i = 0;
for (; i < this._segments.Count; i++)
{
var seg = this._segments[i];
var mem = seg.Memory;
if ((ulong)mem.Length > source)
break;
source -= (ulong)mem.Length;
}
// Do actual copy
var dl = (int)(this.Length - source);
var sri = (int)source;
var dst = MemoryMarshal.AsBytes(destination);
for (; i < this._segments.Count && dst.Length > 0; i++)
{
var seg = this._segments[i];
var mem = seg.Memory;
var src = mem.Span;
if (sri != 0)
{
src = src.Slice(sri);
sri = 0;
}
if (itemsWritten + src.Length > dl)
src = src.Slice(0, dl - itemsWritten);
if (src.Length > dst.Length)
src = src.Slice(0, dst.Length);
src.CopyTo(dst);
dst = dst.Slice(src.Length);
itemsWritten += src.Length;
}
itemsWritten /= this._itemSize;
return (this.Length - source) != (ulong)itemsWritten;
}
///
public bool Read(T[] data, int start, int count, ulong source, out int itemsWritten)
=> this.Read(data.AsSpan(start, count), source, out itemsWritten);
///
public bool Read(ArraySegment data, ulong source, out int itemsWritten)
=> this.Read(data.AsSpan(), source, out itemsWritten);
///
public T[] ToArray()
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var bytes = new T[this.Count];
this.Read(bytes, 0, out _);
return bytes;
}
///
public void CopyTo(Stream destination)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
#if HAS_SPAN_STREAM_OVERLOADS
foreach (var seg in this._segments)
destination.Write(seg.Memory.Span);
#else
var longest = this._segments.Max(x => x.Memory.Length);
var buff = new byte[longest];
foreach (var seg in this._segments)
{
var mem = seg.Memory.Span;
var spn = buff.AsSpan(0, mem.Length);
mem.CopyTo(spn);
destination.Write(buff, 0, spn.Length);
}
#endif
}
///
public void Clear()
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
this._segNo = 0;
this._lastSegmentLength = 0;
this.Length = 0;
}
///
/// Disposes of any resources claimed by this buffer.
///
public void Dispose()
{
if (this._isDisposed)
return;
this._isDisposed = true;
foreach (var segment in this._segments)
{
if (this._clear)
segment.Memory.Span.Clear();
segment.Dispose();
}
}
///
/// Grows the.
///
/// The min amount.
private void Grow(int minAmount)
{
var capacity = this.Capacity;
var length = this.Length;
var totalAmt = (length + (ulong)minAmount);
if (capacity >= totalAmt)
return; // we're good
var amt = (int)(totalAmt - capacity);
var segCount = amt / this._segmentSize;
if (amt % this._segmentSize != 0)
segCount++;
// Basically List.EnsureCapacity
// Default grow behaviour is minimum current*2
var segCap = this._segments.Count + segCount;
if (segCap > this._segments.Capacity)
this._segments.Capacity = segCap < this._segments.Capacity * 2
? this._segments.Capacity * 2
: segCap;
for (var i = 0; i < segCount; i++)
this._segments.Add(this._pool.Rent(this._segmentSize));
}
}
}
diff --git a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEvent.cs b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEvent.cs
index 94bf639fb..755d9fa04 100644
--- a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEvent.cs
+++ b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEvent.cs
@@ -1,204 +1,204 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
namespace DisCatSharp.Common.Utilities
{
///
/// ABC for , allowing for using instances thereof without knowing the underlying instance's type parameters.
///
public abstract class AsyncEvent
{
///
/// Gets the name of this event.
///
public string Name { get; }
///
/// Prevents a default instance of the class from being created.
///
/// The name.
private protected AsyncEvent(string name)
{
this.Name = name;
}
}
///
/// Implementation of asynchronous event. The handlers of such events are executed asynchronously, but sequentially.
///
/// Type of the object that dispatches this event.
/// Type of event argument object passed to this event's handlers.
public sealed class AsyncEvent : AsyncEvent
where TArgs : AsyncEventArgs
{
///
/// Gets the maximum alloted execution time for all handlers. Any event which causes the handler to time out
/// will raise a non-fatal .
///
public TimeSpan MaximumExecutionTime { get; }
private readonly object _lock = new object();
private ImmutableArray> _handlers;
private readonly AsyncEventExceptionHandler _exceptionHandler;
///
/// Creates a new asynchronous event with specified name and exception handler.
///
/// Name of this event.
- /// Maximum handler execution time. A value of means infinite.
+ /// Maximum handler execution time. A value of means infinite.
/// Delegate which handles exceptions caused by this event.
public AsyncEvent(string name, TimeSpan maxExecutionTime, AsyncEventExceptionHandler exceptionHandler)
: base(name)
{
this._handlers = ImmutableArray>.Empty;
this._exceptionHandler = exceptionHandler;
this.MaximumExecutionTime = maxExecutionTime;
}
///
/// Registers a new handler for this event.
///
/// Handler to register for this event.
public void Register(AsyncEventHandler handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this._lock)
this._handlers = this._handlers.Add(handler);
}
///
/// Unregisters an existing handler from this event.
///
/// Handler to unregister from the event.
public void Unregister(AsyncEventHandler handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this._lock)
this._handlers = this._handlers.Remove(handler);
}
///
/// Unregisters all existing handlers from this event.
///
public void UnregisterAll()
{
this._handlers = ImmutableArray>.Empty;
}
///
/// Raises this event by invoking all of its registered handlers, in order of registration.
/// All exceptions throw during invocation will be handled by the event's registered exception handler.
///
/// Object which raised this event.
/// Arguments for this event.
/// Defines what to do with exceptions caught from handlers.
///
public async Task InvokeAsync(TSender sender, TArgs e, AsyncEventExceptionMode exceptionMode = AsyncEventExceptionMode.Default)
{
var handlers = this._handlers;
if (handlers.Length == 0)
return;
// Collect exceptions
List exceptions = null;
if ((exceptionMode & AsyncEventExceptionMode.ThrowAll) != 0)
exceptions = new List(handlers.Length * 2 /* timeout + regular */);
// If we have a timeout configured, start the timeout task
var timeout = this.MaximumExecutionTime > TimeSpan.Zero ? Task.Delay(this.MaximumExecutionTime) : null;
for (var i = 0; i < handlers.Length; i++)
{
var handler = handlers[i];
try
{
// Start the handler execution
var handlerTask = handler(sender, e);
if (handlerTask != null && timeout != null)
{
// If timeout is configured, wait for any task to finish
// If the timeout task finishes first, the handler is causing a timeout
var result = await Task.WhenAny(timeout, handlerTask).ConfigureAwait(false);
if (result == timeout)
{
timeout = null;
var timeoutEx = new AsyncEventTimeoutException(this, handler);
// Notify about the timeout and complete execution
if ((exceptionMode & AsyncEventExceptionMode.HandleNonFatal) == AsyncEventExceptionMode.HandleNonFatal)
this.HandleException(timeoutEx, handler, sender, e);
if ((exceptionMode & AsyncEventExceptionMode.ThrowNonFatal) == AsyncEventExceptionMode.ThrowNonFatal)
exceptions.Add(timeoutEx);
await handlerTask.ConfigureAwait(false);
}
}
else if (handlerTask != null)
{
// No timeout is configured, or timeout already expired, proceed as usual
await handlerTask.ConfigureAwait(false);
}
if (e.Handled)
break;
}
catch (Exception ex)
{
e.Handled = false;
if ((exceptionMode & AsyncEventExceptionMode.HandleFatal) == AsyncEventExceptionMode.HandleFatal)
this.HandleException(ex, handler, sender, e);
if ((exceptionMode & AsyncEventExceptionMode.ThrowFatal) == AsyncEventExceptionMode.ThrowFatal)
exceptions.Add(ex);
}
}
if ((exceptionMode & AsyncEventExceptionMode.ThrowAll) != 0 && exceptions.Count > 0)
throw new AggregateException("Exceptions were thrown during execution of the event's handlers.", exceptions);
}
///
/// Handles the exception.
///
/// The ex.
/// The handler.
/// The sender.
/// The args.
private void HandleException(Exception ex, AsyncEventHandler handler, TSender sender, TArgs args)
{
if (this._exceptionHandler != null)
this._exceptionHandler(this, ex, handler, sender, args);
}
}
}
diff --git a/DisCatSharp.Common/Utilities/AsyncManualResetEvent.cs b/DisCatSharp.Common/Utilities/AsyncManualResetEvent.cs
index 665c2c837..3ea58eb38 100644
--- a/DisCatSharp.Common/Utilities/AsyncManualResetEvent.cs
+++ b/DisCatSharp.Common/Utilities/AsyncManualResetEvent.cs
@@ -1,81 +1,81 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Threading;
using System.Threading.Tasks;
namespace DisCatSharp.Common.Utilities
{
///
- /// Represents a thread synchronization event that, when signaled, must be reset manually. Unlike , this event is asynchronous.
+ /// Represents a thread synchronization event that, when signaled, must be reset manually. Unlike , this event is asynchronous.
///
public sealed class AsyncManualResetEvent
{
///
/// Gets whether this event has been signaled.
///
public bool IsSet => this._resetTcs?.Task?.IsCompleted == true;
private volatile TaskCompletionSource _resetTcs;
///
/// Creates a new asynchronous synchronization event with initial state.
///
/// Initial state of this event.
public AsyncManualResetEvent(bool initialState)
{
this._resetTcs = new TaskCompletionSource();
if (initialState)
this._resetTcs.TrySetResult(initialState);
}
// Spawn a threadpool thread instead of making a task
// Maybe overkill, but I am less unsure of this than awaits and
// potentially cross-scheduler interactions
///
/// Asynchronously signal this event.
///
///
public Task SetAsync()
=> Task.Run(() => this._resetTcs.TrySetResult(true));
///
/// Asynchronously wait for this event to be signaled.
///
///
public Task WaitAsync()
=> this._resetTcs.Task;
///
/// Reset this event's signal state to unsignaled.
///
public void Reset()
{
while (true)
{
var tcs = this._resetTcs;
if (!tcs.Task.IsCompleted || Interlocked.CompareExchange(ref this._resetTcs, new TaskCompletionSource(), tcs) == tcs)
return;
}
}
}
}
diff --git a/DisCatSharp.Common/Utilities/Extensions.cs b/DisCatSharp.Common/Utilities/Extensions.cs
index 13b5a06f9..31b54a8fb 100644
--- a/DisCatSharp.Common/Utilities/Extensions.cs
+++ b/DisCatSharp.Common/Utilities/Extensions.cs
@@ -1,490 +1,490 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Collections.Generic;
using System.Runtime.CompilerServices;
namespace DisCatSharp.Common
{
///
/// Assortment of various extension and utility methods, designed to make working with various types a little easier.
///
public static class Extensions
{
///
- /// Deconstructs a key-value pair item () into 2 separate variables.
- /// This allows for enumerating over dictionaries in foreach blocks by using a (k, v) tuple as the enumerator variable, instead of having to use a directly.
+ /// Deconstructs a key-value pair item () into 2 separate variables.
+ /// This allows for enumerating over dictionaries in foreach blocks by using a (k, v) tuple as the enumerator variable, instead of having to use a directly.
///
/// Type of dictionary item key.
/// Type of dictionary item value.
/// Key-value pair to deconstruct.
/// Deconstructed key.
/// Deconstructed value.
public static void Deconstruct(this KeyValuePair kvp, out TKey key, out TValue value)
{
key = kvp.Key;
value = kvp.Value;
}
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated number length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this sbyte num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(num == sbyte.MinValue ? num + 1 : num))) + (num < 0 ? 2 /* include sign */ : 1);
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated nuembr length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this byte num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(num)) + 1;
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated number length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this short num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(num == short.MinValue ? num + 1 : num))) + (num < 0 ? 2 /* include sign */ : 1);
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated nuembr length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this ushort num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(num)) + 1;
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated number length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this int num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(num == int.MinValue ? num + 1 : num))) + (num < 0 ? 2 /* include sign */ : 1);
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated nuembr length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this uint num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(num)) + 1;
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated number length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this long num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(num == long.MinValue ? num + 1 : num))) + (num < 0 ? 2 /* include sign */ : 1);
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated nuembr length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this ulong num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(num)) + 1;
///
/// Tests wheter given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this sbyte num, sbyte min, sbyte max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? (num >= min && num <= max) : (num > min && num < max);
}
///
/// Tests wheter given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this byte num, byte min, byte max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? (num >= min && num <= max) : (num > min && num < max);
}
///
/// Tests wheter given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this short num, short min, short max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? (num >= min && num <= max) : (num > min && num < max);
}
///
/// Tests wheter given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this ushort num, ushort min, ushort max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? (num >= min && num <= max) : (num > min && num < max);
}
///
/// Tests wheter given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this int num, int min, int max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? (num >= min && num <= max) : (num > min && num < max);
}
///
/// Tests wheter given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this uint num, uint min, uint max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? (num >= min && num <= max) : (num > min && num < max);
}
///
/// Tests wheter given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this long num, long min, long max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? (num >= min && num <= max) : (num > min && num < max);
}
///
/// Tests wheter given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this ulong num, ulong min, ulong max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? (num >= min && num <= max) : (num > min && num < max);
}
///
/// Tests wheter given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this float num, float min, float max, bool inclusive = true)
{
if (min > max)
return false;
return inclusive ? (num >= min && num <= max) : (num > min && num < max);
}
///
/// Tests wheter given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this double num, double min, double max, bool inclusive = true)
{
if (min > max)
return false;
return inclusive ? (num >= min && num <= max) : (num > min && num < max);
}
///
/// Returns whether supplied character is in any of the following ranges: a-z, A-Z, 0-9.
///
/// Character to test.
/// Whether the character is in basic alphanumeric character range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsBasicAlphanumeric(this char c)
=> (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
///
/// Returns whether supplied character is in the 0-9 range.
///
/// Character to test.
/// Whether the character is in basic numeric digit character range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsBasicDigit(this char c)
=> c >= '0' && c <= '9';
///
/// Returns whether supplied character is in the a-z or A-Z range.
///
/// Character to test.
/// Whether the character is in basic letter character range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsBasicLetter(this char c)
=> (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
///
/// Tests whether given string ends with given character.
///
/// String to test.
/// Character to test for.
/// Whether the supplied string ends with supplied character.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EndsWithCharacter(this string s, char c)
=> s.Length >= 1 && s[s.Length - 1] == c;
///
/// Tests whether given string starts with given character.
///
/// String to test.
/// Character to test for.
/// Whether the supplied string starts with supplied character.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool StartsWithCharacter(this string s, char c)
=> s.Length >= 1 && s[0] == c;
// https://stackoverflow.com/questions/9545619/a-fast-hash-function-for-string-in-c-sharp
// Calls are inlined to call the underlying method directly
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this ReadOnlySpan chars)
=> Knuth(chars);
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this Span chars)
=> Knuth(chars);
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this ReadOnlyMemory chars)
=> Knuth(chars.Span);
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this Memory chars)
=> Knuth(chars.Span);
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this ArraySegment chars)
=> Knuth(chars.AsSpan());
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this char[] chars)
=> Knuth(chars.AsSpan());
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Offset in the array to start calculating from.
/// Number of characters to compute the hash from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this char[] chars, int start, int count)
=> Knuth(chars.AsSpan(start, count));
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this string chars)
=> Knuth(chars.AsSpan());
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Offset in the array to start calculating from.
/// Number of characters to compute the hash from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this string chars, int start, int count)
=> Knuth(chars.AsSpan(start, count));
///
/// Firsts the two or default.
///
/// The enumerable.
/// A (T first, T second) .
internal static (T first, T second) FirstTwoOrDefault(this IEnumerable enumerable)
{
using var enumerator = enumerable.GetEnumerator();
if (!enumerator.MoveNext())
return (default, default);
var first = enumerator.Current;
if (!enumerator.MoveNext())
return (first, default);
return (first, enumerator.Current);
}
///
/// Knuths the.
///
/// The chars.
/// An ulong.
private static ulong Knuth(ReadOnlySpan chars)
{
var hash = 3074457345618258791ul;
for (var i = 0; i < chars.Length; i++)
hash = (hash + chars[i]) * 3074457345618258799ul;
return hash;
}
}
}
diff --git a/DisCatSharp.Docs/filter_config.yml b/DisCatSharp.Docs/filter_config.yml
index 0dee89b81..2dc66c164 100644
--- a/DisCatSharp.Docs/filter_config.yml
+++ b/DisCatSharp.Docs/filter_config.yml
@@ -1,83 +1,92 @@
apiRules:
- exclude:
uidRegex: ^System\.Collections\.Immutable$
- exclude:
uidRegex: ^System\.Runtime\.CompilerServices\.Unsafe$
- exclude:
uidRegex: ^System\.Runtime\.CompilerServices$
- exclude:
uidRegex: ^System\..*$
- exclude:
uidRegex: ^DisCatSharp\.Hosting\.Tests$
- exclude:
uidRegex: ^DisCatSharp\.Configuration\.Tests$
attributeRules:
- exclude:
uidRegex: ^System\.Collections\.Immutable$
type: Namespace
+- exclude:
+ uidRegex: ^Microsoft\.Extensions\.Logging$
+ type: Namespace
+- exclude:
+ uidRegex: ^Microsoft\.Extensions\.Hosting\.BackgroundService$
+ type: Namespace
+- exclude:
+ uidRegex: ^Newtonsoft\.Json$
+ type: Namespace
- exclude:
uidRegex: ^System\.Runtime\.CompilerServices\.Unsafe$
type: Namespace
- exclude:
uidRegex: ^System\.Runtime\.CompilerServices$
type: Namespace
- exclude:
uidRegex: ^System\.ComponentModel\.Design$
type: Namespace
- exclude:
uidRegex: ^System\.ComponentModel\.Design\.Serialization$
type: Namespace
- exclude:
uidRegex: ^System\.Xml\.Serialization$
type: Namespace
- exclude:
uidRegex: ^System\.Web\.Compilation$
type: Namespace
- exclude:
uidRegex: ^System\.Runtime\.Versioning$
type: Namespace
- exclude:
uidRegex: ^System\.Runtime\.ConstrainedExecution$
type: Namespace
- exclude:
uidRegex: ^System\.EnterpriseServices$
type: Namespace
- exclude:
uidRegex: ^System\.Diagnostics\.CodeAnalysis$
type: Namespace
- include:
uidRegex: ^System\.Diagnostics\.(ConditionalAttribute|EventLogPermissionAttribute|PerformanceCounterPermissionAttribute)$
type: Type
- exclude:
uidRegex: '^System\.Diagnostics\.[^.]+$'
type: Type
- include:
uidRegex: ^System\.ComponentModel\.(BindableAttribute|BrowsableAttribute|ComplexBindingPropertiesAttribute|DataObjectAttribute|DefaultBindingPropertyAttribute|ListBindableAttribute|LookupBindingPropertiesAttribute|SettingsBindableAttribute|TypeConverterAttribute)$
type: Type
- exclude:
uidRegex: '^System\.ComponentModel\.[^.]+$'
type: Type
- exclude:
uidRegex: ^System\.Reflection\.DefaultMemberAttribute$
type: Type
- exclude:
uidRegex: ^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$
type: Type
- exclude:
uidRegex: '^System\.Runtime\.CompilerServices\.[^.]+$'
type: Type
- exclude:
uidRegex: '^System\.Runtime\.InteropServices\.[^.]+$'
type: Type
- include:
uidRegex: ^System\.Security\.(SecurityCriticalAttribute|SecurityTreatAsSafeAttribute|AllowPartiallyTrustedCallersAttribute)$
type: Type
- exclude:
uidRegex: '^System\.Security\.[^.]+$'
type: Type
- exclude:
uidRegex: '^System\.Web\.UI\.[^.]+$'
type: Type
- exclude:
uidRegex: '^System\.Windows\.Markup\.[^.]+$'
type: Type
diff --git a/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs b/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs
index 2c1f9397f..e66096ef9 100644
--- a/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs
+++ b/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs
@@ -1,243 +1,243 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.EventArgs;
using DisCatSharp.Interactivity.Enums;
using DisCatSharp.Interactivity.EventHandling;
namespace DisCatSharp.Interactivity.Extensions
{
///
/// Interactivity extension methods for .
///
public static class MessageExtensions
{
///
/// Waits for the next message that has the same author and channel as this message.
///
/// Original message.
/// Overrides the timeout set in
public static Task> GetNextMessageAsync(this DiscordMessage message, TimeSpan? timeoutOverride = null)
=> message.Channel.GetNextMessageAsync(message.Author, timeoutOverride);
///
/// Waits for the next message with the same author and channel as this message, which also satisfies a predicate.
///
/// Original message.
/// A predicate that should return if a message matches.
/// Overrides the timeout set in
public static Task> GetNextMessageAsync(this DiscordMessage message, Func predicate, TimeSpan? timeoutOverride = null)
=> message.Channel.GetNextMessageAsync(msg => msg.Author.Id == message.Author.Id && message.ChannelId == msg.ChannelId && predicate(msg), timeoutOverride);
///
/// Waits for any button to be pressed on the specified message.
///
/// The message to wait on.
public static Task> WaitForButtonAsync(this DiscordMessage message)
=> GetInteractivity(message).WaitForButtonAsync(message);
///
/// Waits for any button to be pressed on the specified message.
///
/// The message to wait on.
/// Overrides the timeout set in
public static Task> WaitForButtonAsync(this DiscordMessage message, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForButtonAsync(message, timeoutOverride);
///
/// Waits for any button to be pressed on the specified message.
///
/// The message to wait on.
/// A custom cancellation token that can be cancelled at any point.
public static Task> WaitForButtonAsync(this DiscordMessage message, CancellationToken token)
=> GetInteractivity(message).WaitForButtonAsync(message, token);
///
/// Waits for a button with the specified Id to be pressed on the specified message.
///
/// The message to wait on.
/// The Id of the button to wait for.
/// Overrides the timeout set in
public static Task> WaitForButtonAsync(this DiscordMessage message, string id, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForButtonAsync(message, id, timeoutOverride);
///
/// Waits for a button with the specified Id to be pressed on the specified message.
///
/// The message to wait on.
/// The Id of the button to wait for.
/// A custom cancellation token that can be cancelled at any point.
public static Task> WaitForButtonAsync(this DiscordMessage message, string id, CancellationToken token)
=> GetInteractivity(message).WaitForButtonAsync(message, id, token);
///
/// Waits for any button to be pressed on the specified message by the specified user.
///
/// The message to wait on.
/// The user to wait for button input from.
/// Overrides the timeout set in
public static Task> WaitForButtonAsync(this DiscordMessage message, DiscordUser user, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForButtonAsync(message, user, timeoutOverride);
///
/// Waits for any button to be pressed on the specified message by the specified user.
///
/// The message to wait on.
/// The user to wait for button input from.
/// A custom cancellation token that can be cancelled at any point.
public static Task> WaitForButtonAsync(this DiscordMessage message, DiscordUser user, CancellationToken token)
=> GetInteractivity(message).WaitForButtonAsync(message, user, token);
///
/// Waits for any button to be interacted with.
///
/// The message to wait on.
/// The predicate to filter interactions by.
/// Override the timeout specified in
public static Task> WaitForButtonAsync(this DiscordMessage message, Func predicate, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForButtonAsync(message, predicate, timeoutOverride);
///
/// Waits for any button to be interacted with.
///
/// The message to wait on.
/// The predicate to filter interactions by.
- /// A token to cancel interactivity with at any time. Pass to wait indefinitely.
+ /// A token to cancel interactivity with at any time. Pass to wait indefinitely.
public static Task> WaitForButtonAsync(this DiscordMessage message, Func predicate, CancellationToken token)
=> GetInteractivity(message).WaitForButtonAsync(message, predicate, token);
///
/// Waits for any dropdown to be interacted with.
///
/// The message to wait for.
/// A filter predicate.
/// Override the timeout period specified in .
/// Thrown when the message doesn't contain any dropdowns
public static Task> WaitForSelectAsync(this DiscordMessage message, Func predicate, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForSelectAsync(message, predicate, timeoutOverride);
///
/// Waits for any dropdown to be interacted with.
///
/// The message to wait for.
/// A filter predicate.
- /// A token that can be used to cancel interactivity. Pass to wait indefinitely.
+ /// A token that can be used to cancel interactivity. Pass to wait indefinitely.
/// Thrown when the message doesn't contain any dropdowns
public static Task> WaitForSelectAsync(this DiscordMessage message, Func predicate, CancellationToken token)
=> GetInteractivity(message).WaitForSelectAsync(message, predicate, token);
///
/// Waits for a dropdown to be interacted with.
///
/// The message to wait on.
/// The Id of the dropdown to wait for.
/// Overrides the timeout set in
public static Task> WaitForSelectAsync(this DiscordMessage message, string id, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForSelectAsync(message, id, timeoutOverride);
///
/// Waits for a dropdown to be interacted with.
///
/// The message to wait on.
/// The Id of the dropdown to wait for.
/// A custom cancellation token that can be cancelled at any point.
public static Task> WaitForSelectAsync(this DiscordMessage message, string id, CancellationToken token)
=> GetInteractivity(message).WaitForSelectAsync(message, id, token);
///
/// Waits for a dropdown to be interacted with by the specified user.
///
/// The message to wait on.
/// The user to wait for.
/// The Id of the dropdown to wait for.
///
public static Task> WaitForSelectAsync(this DiscordMessage message, DiscordUser user, string id, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForSelectAsync(message, user, id, timeoutOverride);
///
/// Waits for a dropdown to be interacted with by the specified user.
///
/// The message to wait on.
/// The user to wait for.
/// The Id of the dropdown to wait for.
/// A custom cancellation token that can be cancelled at any point.
public static Task> WaitForSelectAsync(this DiscordMessage message, DiscordUser user, string id, CancellationToken token)
=> GetInteractivity(message).WaitForSelectAsync(message, user, id, token);
///
/// Waits for a reaction on this message from a specific user.
///
/// Target message.
/// The target user.
/// Overrides the timeout set in
/// Thrown if interactivity is not enabled for the client associated with the message.
public static Task> WaitForReactionAsync(this DiscordMessage message, DiscordUser user, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForReactionAsync(message, user, timeoutOverride);
///
/// Waits for a specific reaction on this message from the specified user.
///
/// Target message.
/// The target user.
/// The target emoji.
/// Overrides the timeout set in
/// Thrown if interactivity is not enabled for the client associated with the message.
public static Task> WaitForReactionAsync(this DiscordMessage message, DiscordUser user, DiscordEmoji emoji, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForReactionAsync(e => e.Emoji == emoji, message, user, timeoutOverride);
///
/// Collects all reactions on this message within the timeout duration.
///
/// The message to collect reactions from.
/// Overrides the timeout set in
/// Thrown if interactivity is not enabled for the client associated with the message.
public static Task> CollectReactionsAsync(this DiscordMessage message, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).CollectReactionsAsync(message, timeoutOverride);
///
/// Begins a poll using this message.
///
/// Target message.
/// Options for this poll.
/// Overrides the action set in
/// Overrides the timeout set in
/// Thrown if interactivity is not enabled for the client associated with the message.
public static Task> DoPollAsync(this DiscordMessage message, IEnumerable emojis, PollBehaviour? behaviorOverride = null, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).DoPollAsync(message, emojis, behaviorOverride, timeoutOverride);
///
/// Retrieves an interactivity instance from a message instance.
///
internal static InteractivityExtension GetInteractivity(DiscordMessage message)
{
var client = (DiscordClient)message.Discord;
var interactivity = client.GetInteractivity();
return interactivity ?? throw new InvalidOperationException($"Interactivity is not enabled for this {(client._isShard ? "shard" : "client")}.");
}
}
}
diff --git a/DisCatSharp.Interactivity/InteractivityExtension.cs b/DisCatSharp.Interactivity/InteractivityExtension.cs
index 460a3836e..fc93fcfaf 100644
--- a/DisCatSharp.Interactivity/InteractivityExtension.cs
+++ b/DisCatSharp.Interactivity/InteractivityExtension.cs
@@ -1,930 +1,930 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.EventArgs;
using DisCatSharp.Interactivity.Enums;
using DisCatSharp.Interactivity.EventHandling;
using DisCatSharp.Common.Utilities;
using DisCatSharp.Enums;
using System.Threading;
using System.Globalization;
namespace DisCatSharp.Interactivity
{
///
/// Extension class for DisCatSharp.Interactivity
///
public class InteractivityExtension : BaseExtension
{
#pragma warning disable IDE1006 // Naming Styles
///
/// Gets the config.
///
internal InteractivityConfiguration Config { get; }
private EventWaiter MessageCreatedWaiter;
private EventWaiter MessageReactionAddWaiter;
private EventWaiter TypingStartWaiter;
private EventWaiter ComponentInteractionWaiter;
private ComponentEventWaiter ComponentEventWaiter;
private ReactionCollector ReactionCollector;
private Poller Poller;
private Paginator Paginator;
private ComponentPaginator _compPaginator;
#pragma warning restore IDE1006 // Naming Styles
///
/// Initializes a new instance of the class.
///
/// The configuration.
internal InteractivityExtension(InteractivityConfiguration cfg)
{
this.Config = new InteractivityConfiguration(cfg);
}
///
/// Setups the Interactivity Extension.
///
/// Discord client.
protected internal override void Setup(DiscordClient client)
{
this.Client = client;
this.MessageCreatedWaiter = new EventWaiter(this.Client);
this.MessageReactionAddWaiter = new EventWaiter(this.Client);
this.ComponentInteractionWaiter = new EventWaiter(this.Client);
this.TypingStartWaiter = new EventWaiter(this.Client);
this.Poller = new Poller(this.Client);
this.ReactionCollector = new ReactionCollector(this.Client);
this.Paginator = new Paginator(this.Client);
this._compPaginator = new(this.Client, this.Config);
this.ComponentEventWaiter = new(this.Client, this.Config);
}
///
/// Makes a poll and returns poll results.
///
/// Message to create poll on.
/// Emojis to use for this poll.
/// What to do when the poll ends.
/// override timeout period.
///
public async Task> DoPollAsync(DiscordMessage m, IEnumerable emojis, PollBehaviour? behaviour = default, TimeSpan? timeout = null)
{
if (!Utilities.HasReactionIntents(this.Client.Configuration.Intents))
throw new InvalidOperationException("No reaction intents are enabled.");
if (!emojis.Any())
throw new ArgumentException("You need to provide at least one emoji for a poll!");
foreach (var em in emojis)
await m.CreateReactionAsync(em).ConfigureAwait(false);
var res = await this.Poller.DoPollAsync(new PollRequest(m, timeout ?? this.Config.Timeout, emojis)).ConfigureAwait(false);
var pollbehaviour = behaviour ?? this.Config.PollBehaviour;
var thismember = await m.Channel.Guild.GetMemberAsync(this.Client.CurrentUser.Id).ConfigureAwait(false);
if (pollbehaviour == PollBehaviour.DeleteEmojis && m.Channel.PermissionsFor(thismember).HasPermission(Permissions.ManageMessages))
await m.DeleteAllReactionsAsync().ConfigureAwait(false);
return new ReadOnlyCollection(res.ToList());
}
///
/// Waits for any button in the specified collection to be pressed.
///
/// The message to wait on.
/// A collection of buttons to listen for.
/// Override the timeout period in .
/// A with the result of button that was pressed, if any.
/// Thrown when attempting to wait for a message that is not authored by the current user.
/// Thrown when the message does not contain a button with the specified Id, or any buttons at all.
public Task> WaitForButtonAsync(DiscordMessage message, IEnumerable buttons, TimeSpan? timeoutOverride = null)
=> this.WaitForButtonAsync(message, buttons, this.GetCancellationToken(timeoutOverride));
///
/// Waits for any button in the specified collection to be pressed.
///
/// The message to wait on.
/// A collection of buttons to listen for.
/// A custom cancellation token that can be cancelled at any point.
/// A with the result of button that was pressed, if any.
/// Thrown when attempting to wait for a message that is not authored by the current user.
/// Thrown when the message does not contain a button with the specified Id, or any buttons at all.
public async Task> WaitForButtonAsync(DiscordMessage message, IEnumerable buttons, CancellationToken token)
{
if (message.Author != this.Client.CurrentUser)
throw new InvalidOperationException("Interaction events are only sent to the application that created them.");
if (!buttons.Any())
throw new ArgumentException("You must specify at least one button to listen for.");
if (!message.Components.Any())
throw new ArgumentException("Provided message does not contain any components.");
if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button))
throw new ArgumentException("Provided Message does not contain any button components.");
var res = await this.ComponentEventWaiter
.WaitForMatchAsync(new(message,
c =>
c.Interaction.Data.ComponentType == ComponentType.Button &&
buttons.Any(b => b.CustomId == c.Id), token)).ConfigureAwait(false);
return new(res is null, res);
}
///
/// Waits for any button on the specified message to be pressed.
///
/// The message to wait for the button on.
/// Override the timeout period specified in .
/// A with the result of button that was pressed, if any.
/// Thrown when attempting to wait for a message that is not authored by the current user.
/// Thrown when the message does not contain a button with the specified Id, or any buttons at all.
public Task> WaitForButtonAsync(DiscordMessage message, TimeSpan? timeoutOverride = null)
=> this.WaitForButtonAsync(message, this.GetCancellationToken(timeoutOverride));
///
/// Waits for any button on the specified message to be pressed.
///
/// The message to wait for the button on.
/// A custom cancellation token that can be cancelled at any point.
/// A with the result of button that was pressed, if any.
/// Thrown when attempting to wait for a message that is not authored by the current user.
/// Thrown when the message does not contain a button with the specified Id, or any buttons at all.
public async Task> WaitForButtonAsync(DiscordMessage message, CancellationToken token)
{
if (message.Author != this.Client.CurrentUser)
throw new InvalidOperationException("Interaction events are only sent to the application that created them.");
if (!message.Components.Any())
throw new ArgumentException("Provided message does not contain any components.");
if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button))
throw new ArgumentException("Message does not contain any button components.");
var ids = message.Components.SelectMany(m => m.Components).Select(c => c.CustomId);
var result =
await this
.ComponentEventWaiter
.WaitForMatchAsync(new(message, c => c.Interaction.Data.ComponentType == ComponentType.Button && ids.Contains(c.Id), token))
.ConfigureAwait(false);
return new(result is null, result);
}
///
/// Waits for any button on the specified message to be pressed by the specified user.
///
/// The message to wait for the button on.
/// The user to wait for the button press from.
/// Override the timeout period specified in .
/// A with the result of button that was pressed, if any.
/// Thrown when attempting to wait for a message that is not authored by the current user.
/// Thrown when the message does not contain a button with the specified Id, or any buttons at all.
public Task> WaitForButtonAsync(DiscordMessage message, DiscordUser user, TimeSpan? timeoutOverride = null)
=> this.WaitForButtonAsync(message, user, this.GetCancellationToken(timeoutOverride));
///
/// Waits for any button on the specified message to be pressed by the specified user.
///
/// The message to wait for the button on.
/// The user to wait for the button press from.
/// A custom cancellation token that can be cancelled at any point.
/// A with the result of button that was pressed, if any.
/// Thrown when attempting to wait for a message that is not authored by the current user.
/// Thrown when the message does not contain a button with the specified Id, or any buttons at all.
public async Task> WaitForButtonAsync(DiscordMessage message, DiscordUser user, CancellationToken token)
{
if (message.Author != this.Client.CurrentUser)
throw new InvalidOperationException("Interaction events are only sent to the application that created them.");
if (!message.Components.Any())
throw new ArgumentException("Provided message does not contain any components.");
if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button))
throw new ArgumentException("Message does not contain any button components.");
var result = await this
.ComponentEventWaiter
.WaitForMatchAsync(new(message, (c) => c.Interaction.Data.ComponentType is ComponentType.Button && c.User == user, token))
.ConfigureAwait(false);
return new(result is null, result);
}
///
/// Waits for a button with the specified Id to be pressed.
///
/// The message to wait for the button on.
/// The Id of the button to wait for.
/// Override the timeout period specified in .
/// A with the result of the operation.
/// Thrown when attempting to wait for a message that is not authored by the current user.
/// Thrown when the message does not contain a button with the specified Id, or any buttons at all.
public Task> WaitForButtonAsync(DiscordMessage message, string id, TimeSpan? timeoutOverride = null)
=> this.WaitForButtonAsync(message, id, this.GetCancellationToken(timeoutOverride));
///
/// Waits for a button with the specified Id to be pressed.
///
/// The message to wait for the button on.
/// The Id of the button to wait for.
/// Override the timeout period specified in .
/// A with the result of the operation.
/// Thrown when attempting to wait for a message that is not authored by the current user.
/// Thrown when the message does not contain a button with the specified Id, or any buttons at all.
public async Task> WaitForButtonAsync(DiscordMessage message, string id, CancellationToken token)
{
if (message.Author != this.Client.CurrentUser)
throw new InvalidOperationException("Interaction events are only sent to the application that created them.");
if (!message.Components.Any())
throw new ArgumentException("Provided message does not contain any components.");
if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button))
throw new ArgumentException("Message does not contain any button components.");
if (!message.Components.SelectMany(c => c.Components).OfType().Any(c => c.CustomId == id))
throw new ArgumentException($"Message does not contain button with Id of '{id}'.");
var result = await this
.ComponentEventWaiter
.WaitForMatchAsync(new(message, (c) => c.Interaction.Data.ComponentType is ComponentType.Button && c.Id == id, token))
.ConfigureAwait(false);
return new(result is null, result);
}
///
/// Waits for any button to be interacted with.
///
/// The message to wait on.
/// The predicate to filter interactions by.
/// Override the timeout specified in
public Task> WaitForButtonAsync(DiscordMessage message, Func predicate, TimeSpan? timeoutOverride = null)
=> this.WaitForButtonAsync(message, predicate, this.GetCancellationToken(timeoutOverride));
///
/// Waits for any button to be interacted with.
///
/// The message to wait on.
/// The predicate to filter interactions by.
- /// A token to cancel interactivity with at any time. Pass to wait indefinitely.
+ /// A token to cancel interactivity with at any time. Pass to wait indefinitely.
public async Task> WaitForButtonAsync(DiscordMessage message, Func predicate, CancellationToken token)
{
if (message.Author != this.Client.CurrentUser)
throw new InvalidOperationException("Interaction events are only sent to the application that created them.");
if (!message.Components.Any())
throw new ArgumentException("Provided message does not contain any components.");
if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button))
throw new ArgumentException("Message does not contain any button components.");
var result = await this
.ComponentEventWaiter
.WaitForMatchAsync(new(message, c => c.Interaction.Data.ComponentType is ComponentType.Button && predicate(c), token))
.ConfigureAwait(false);
return new(result is null, result);
}
///
/// Waits for any dropdown to be interacted with.
///
/// The message to wait for.
/// A filter predicate.
/// Override the timeout period specified in .
/// Thrown when the Provided message does not contain any dropdowns
public Task> WaitForSelectAsync(DiscordMessage message, Func predicate, TimeSpan? timeoutOverride = null)
=> this.WaitForSelectAsync(message, predicate, this.GetCancellationToken(timeoutOverride));
///
/// Waits for any dropdown to be interacted with.
///
/// The message to wait for.
/// A filter predicate.
- /// A token that can be used to cancel interactivity. Pass to wait indefinitely.
+ /// A token that can be used to cancel interactivity. Pass to wait indefinitely.
/// Thrown when the Provided message does not contain any dropdowns
public async Task> WaitForSelectAsync(DiscordMessage message, Func predicate, CancellationToken token)
{
if (message.Author != this.Client.CurrentUser)
throw new InvalidOperationException("Interaction events are only sent to the application that created them.");
if (!message.Components.Any())
throw new ArgumentException("Provided message does not contain any components.");
if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Select))
throw new ArgumentException("Message does not contain any select components.");
var result = await this
.ComponentEventWaiter
.WaitForMatchAsync(new(message, c => c.Interaction.Data.ComponentType is ComponentType.Select && predicate(c), token))
.ConfigureAwait(false);
return new(result is null, result);
}
///
/// Waits for a dropdown to be interacted with.
///
/// This is here for backwards-compatibility and will internally create a cancellation token.
/// The message to wait on.
/// The Id of the dropdown to wait on.
/// Override the timeout period specified in .
/// Thrown when the message does not have any dropdowns or any dropdown with the specified Id.
public Task> WaitForSelectAsync(DiscordMessage message, string id, TimeSpan? timeoutOverride = null)
=> this.WaitForSelectAsync(message, id, this.GetCancellationToken(timeoutOverride));
///
/// Waits for a dropdown to be interacted with.
///
/// The message to wait on.
/// The Id of the dropdown to wait on.
/// A custom cancellation token that can be cancelled at any point.
/// Thrown when the message does not have any dropdowns or any dropdown with the specified Id.
public async Task> WaitForSelectAsync(DiscordMessage message, string id, CancellationToken token)
{
if (message.Author != this.Client.CurrentUser)
throw new InvalidOperationException("Interaction events are only sent to the application that created them.");
if (!message.Components.Any())
throw new ArgumentException("Provided message does not contain any components.");
if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Select))
throw new ArgumentException("Message does not contain any select components.");
if (message.Components.SelectMany(c => c.Components).OfType().All(c => c.CustomId != id))
throw new ArgumentException($"Message does not contain select component with Id of '{id}'.");
var result = await this
.ComponentEventWaiter
.WaitForMatchAsync(new(message, (c) => c.Interaction.Data.ComponentType is ComponentType.Select && c.Id == id, token))
.ConfigureAwait(false);
return new(result is null, result);
}
///
/// Waits for a dropdown to be interacted with by a specific user.
///
/// The message to wait on.
/// The user to wait on.
/// The Id of the dropdown to wait on.
/// Override the timeout period specified in .
/// Thrown when the message does not have any dropdowns or any dropdown with the specified Id.
public Task> WaitForSelectAsync(DiscordMessage message, DiscordUser user, string id, TimeSpan? timeoutOverride = null)
=> this.WaitForSelectAsync(message, user, id, this.GetCancellationToken(timeoutOverride));
///
/// Waits for a dropdown to be interacted with by a specific user.
///
/// The message to wait on.
/// The user to wait on.
/// The Id of the dropdown to wait on.
/// A custom cancellation token that can be cancelled at any point.
/// Thrown when the message does not have any dropdowns or any dropdown with the specified Id.
public async Task> WaitForSelectAsync(DiscordMessage message, DiscordUser user, string id, CancellationToken token)
{
if (message.Author != this.Client.CurrentUser)
throw new InvalidOperationException("Interaction events are only sent to the application that created them.");
if (!message.Components.Any())
throw new ArgumentException("Provided message does not contain any components.");
if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Select))
throw new ArgumentException("Message does not contain any select components.");
if (message.Components.SelectMany(c => c.Components).OfType().All(c => c.CustomId != id))
throw new ArgumentException($"Message does not contain select with Id of '{id}'.");
var result = await this
.ComponentEventWaiter
.WaitForMatchAsync(new(message, (c) => c.Id == id && c.User == user, token)).ConfigureAwait(false);
return new(result is null, result);
}
///
/// Waits for a specific message.
///
/// Predicate to match.
/// override timeout period.
public async Task> WaitForMessageAsync(Func predicate,
TimeSpan? timeoutoverride = null)
{
if (!Utilities.HasMessageIntents(this.Client.Configuration.Intents))
throw new InvalidOperationException("No message intents are enabled.");
var timeout = timeoutoverride ?? this.Config.Timeout;
var returns = await this.MessageCreatedWaiter.WaitForMatchAsync(new MatchRequest(x => predicate(x.Message), timeout)).ConfigureAwait(false);
return new InteractivityResult(returns == null, returns?.Message);
}
///
/// Wait for a specific reaction.
///
/// Predicate to match.
/// override timeout period.
public async Task> WaitForReactionAsync(Func predicate,
TimeSpan? timeoutoverride = null)
{
if (!Utilities.HasReactionIntents(this.Client.Configuration.Intents))
throw new InvalidOperationException("No reaction intents are enabled.");
var timeout = timeoutoverride ?? this.Config.Timeout;
var returns = await this.MessageReactionAddWaiter.WaitForMatchAsync(new MatchRequest(predicate, timeout)).ConfigureAwait(false);
return new InteractivityResult(returns == null, returns);
}
///
/// Wait for a specific reaction.
/// For this Event you need the intent specified in
///
/// Message reaction was added to.
/// User that made the reaction.
/// override timeout period.
public async Task> WaitForReactionAsync(DiscordMessage message, DiscordUser user,
TimeSpan? timeoutoverride = null)
=> await this.WaitForReactionAsync(x => x.User.Id == user.Id && x.Message.Id == message.Id, timeoutoverride).ConfigureAwait(false);
///
/// Waits for a specific reaction.
/// For this Event you need the intent specified in
///
/// Predicate to match.
/// Message reaction was added to.
/// User that made the reaction.
/// override timeout period.
public async Task> WaitForReactionAsync(Func predicate,
DiscordMessage message, DiscordUser user, TimeSpan? timeoutoverride = null)
=> await this.WaitForReactionAsync(x => predicate(x) && x.User.Id == user.Id && x.Message.Id == message.Id, timeoutoverride).ConfigureAwait(false);
///
/// Waits for a specific reaction.
/// For this Event you need the intent specified in
///
/// predicate to match.
/// User that made the reaction.
/// Override timeout period.
public async Task> WaitForReactionAsync(Func predicate,
DiscordUser user, TimeSpan? timeoutoverride = null)
=> await this.WaitForReactionAsync(x => predicate(x) && x.User.Id == user.Id, timeoutoverride).ConfigureAwait(false);
///
/// Waits for a user to start typing.
///
/// User that starts typing.
/// Channel the user is typing in.
/// Override timeout period.
public async Task> WaitForUserTypingAsync(DiscordUser user,
DiscordChannel channel, TimeSpan? timeoutoverride = null)
{
if (!Utilities.HasTypingIntents(this.Client.Configuration.Intents))
throw new InvalidOperationException("No typing intents are enabled.");
var timeout = timeoutoverride ?? this.Config.Timeout;
var returns = await this.TypingStartWaiter.WaitForMatchAsync(
new MatchRequest(x => x.User.Id == user.Id && x.Channel.Id == channel.Id, timeout))
.ConfigureAwait(false);
return new InteractivityResult(returns == null, returns);
}
///
/// Waits for a user to start typing.
///
/// User that starts typing.
/// Override timeout period.
public async Task> WaitForUserTypingAsync(DiscordUser user, TimeSpan? timeoutoverride = null)
{
if (!Utilities.HasTypingIntents(this.Client.Configuration.Intents))
throw new InvalidOperationException("No typing intents are enabled.");
var timeout = timeoutoverride ?? this.Config.Timeout;
var returns = await this.TypingStartWaiter.WaitForMatchAsync(
new MatchRequest(x => x.User.Id == user.Id, timeout))
.ConfigureAwait(false);
return new InteractivityResult(returns == null, returns);
}
///
/// Waits for any user to start typing.
///
/// Channel to type in.
/// Override timeout period.
public async Task> WaitForTypingAsync(DiscordChannel channel, TimeSpan? timeoutoverride = null)
{
if (!Utilities.HasTypingIntents(this.Client.Configuration.Intents))
throw new InvalidOperationException("No typing intents are enabled.");
var timeout = timeoutoverride ?? this.Config.Timeout;
var returns = await this.TypingStartWaiter.WaitForMatchAsync(
new MatchRequest(x => x.Channel.Id == channel.Id, timeout))
.ConfigureAwait(false);
return new InteractivityResult(returns == null, returns);
}
///
/// Collects reactions on a specific message.
///
/// Message to collect reactions on.
/// Override timeout period.
public async Task> CollectReactionsAsync(DiscordMessage m, TimeSpan? timeoutoverride = null)
{
if (!Utilities.HasReactionIntents(this.Client.Configuration.Intents))
throw new InvalidOperationException("No reaction intents are enabled.");
var timeout = timeoutoverride ?? this.Config.Timeout;
var collection = await this.ReactionCollector.CollectAsync(new ReactionCollectRequest(m, timeout)).ConfigureAwait(false);
return collection;
}
///
/// Waits for specific event args to be received. Make sure the appropriate are registered, if needed.
///
///
/// The predicate.
/// Override timeout period.
public async Task> WaitForEventArgsAsync(Func predicate, TimeSpan? timeoutoverride = null) where T : AsyncEventArgs
{
var timeout = timeoutoverride ?? this.Config.Timeout;
using var waiter = new EventWaiter(this.Client);
var res = await waiter.WaitForMatchAsync(new MatchRequest(predicate, timeout)).ConfigureAwait(false);
return new InteractivityResult