Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8141e10
preliminary sharding design
akiraveliara Jun 4, 2024
fbb988b
we do need to disconnect too :P
akiraveliara Jun 4, 2024
1c3ffff
implement a rudimentary transport service
akiraveliara Jun 5, 2024
a36db14
reduce lifetime allocations of a DSharpPlus bot by up to one
akiraveliara Jun 6, 2024
ee57672
support decompression in TransportService
akiraveliara Jun 7, 2024
fe41712
Merge branch 'master' into aki/sharding
akiraveliara Jun 7, 2024
95d9f31
commit to ask for feedback
akiraveliara Jun 10, 2024
e8f03d0
slightly more state handling effort + options
akiraveliara Jun 13, 2024
8312085
alert GatewayClient of errors
akiraveliara Jun 22, 2024
e680aaa
able to start a single shard (in theory)
akiraveliara Jun 22, 2024
25412dc
theoretically multi-shard orchestration works too now
akiraveliara Jun 22, 2024
e1f9ae6
delete DiscordClient.WebSocket.cs
akiraveliara Jun 22, 2024
ce0a2dd
make sharding clients possible to create
akiraveliara Jun 22, 2024
3b49528
delete the legacy websocketclient
akiraveliara Jun 22, 2024
ede92d4
merge
akiraveliara Jun 22, 2024
ee038f8
add support for getting latency and connection state
akiraveliara Jun 28, 2024
74bbc2f
DiscordClient may receive events, as a treat
akiraveliara Jun 28, 2024
58dc796
Merge branch 'master' into aki/sharding
akiraveliara Jun 30, 2024
0d825d9
expose zombied to the user
akiraveliara Jun 30, 2024
1d791e9
remove the sharded client
akiraveliara Jun 30, 2024
2f25fc2
implement sending payloads and reconnecting
akiraveliara Jun 30, 2024
a91f1ee
remove more legacy ShardedClient code
akiraveliara Jun 30, 2024
882e6db
make the shard count default to whatever Discord tells us
akiraveliara Jul 1, 2024
57f8672
move reconnecting logic into GatewayClient
akiraveliara Jul 2, 2024
c697a5b
build
akiraveliara Jul 2, 2024
560a666
convenience methods
akiraveliara Jul 2, 2024
0399daf
docs
akiraveliara Jul 2, 2024
cf4eee8
xmldocs for IGatewayClient
akiraveliara Jul 2, 2024
fa406f5
works for single shards
akiraveliara Jul 3, 2024
1059d52
don't send opcode 11 to dispatch
akiraveliara Jul 3, 2024
0c2758e
improve logging between shards
akiraveliara Jul 4, 2024
4a40fa9
implement the 5s concurrency limit
akiraveliara Jul 5, 2024
039166a
accursed
akiraveliara Jul 5, 2024
398c587
don't explode on reconnecting
akiraveliara Jul 6, 2024
cedcf46
retry resuming if the connection cuts out
akiraveliara Jul 7, 2024
853000b
remove unused using
akiraveliara Jul 7, 2024
fafb0e2
check whether all shards have connected before firing GDC
akiraveliara Jul 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions DSharpPlus.Commands/ExtensionMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,6 @@ public static CommandsExtension UseCommands(this DiscordClient client, CommandsC
return extension;
}

/// <summary>
/// Registers the extension with all the shards on the <see cref="DiscordShardedClient"/>.
/// </summary>
/// <param name="shardedClient">The client to register the extension with.</param>
/// <param name="configuration">The configuration to use for the extension.</param>
public static async Task<IReadOnlyDictionary<int, CommandsExtension>> UseCommandsAsync(this DiscordShardedClient shardedClient, CommandsConfiguration? configuration = null)
{
ArgumentNullException.ThrowIfNull(shardedClient);

await shardedClient.InitializeShardsAsync();
configuration ??= new();

Dictionary<int, CommandsExtension> extensions = [];
foreach (DiscordClient shard in shardedClient.ShardClients.Values)
{
extensions[shard.ShardId] = shard.GetExtension<CommandsExtension>() ?? shard.UseCommands(configuration);
}

return extensions.AsReadOnly();
}

/// <summary>
/// Retrieves the <see cref="CommandsExtension"/> from the <see cref="DiscordClient"/>.
/// </summary>
Expand All @@ -62,27 +41,6 @@ public static async Task<IReadOnlyDictionary<int, CommandsExtension>> UseCommand
? throw new ArgumentNullException(nameof(client))
: client.GetExtension<CommandsExtension>();

/// <summary>
/// Retrieves the <see cref="CommandsExtension"/> from all of the shards on <see cref="DiscordShardedClient"/>.
/// </summary>
/// <param name="shardedClient">The client to retrieve the extension from.</param>
public static IReadOnlyDictionary<int, CommandsExtension> GetCommandsExtensions(this DiscordShardedClient shardedClient)
{
ArgumentNullException.ThrowIfNull(shardedClient);

Dictionary<int, CommandsExtension> extensions = [];
foreach (DiscordClient shard in shardedClient.ShardClients.Values)
{
CommandsExtension? extension = shard.GetExtension<CommandsExtension>();
if (extension is not null)
{
extensions[shard.ShardId] = extension;
}
}

return extensions.AsReadOnly();
}

/// <inheritdoc cref="Array.IndexOf{T}(T[], T)"/>
internal static int IndexOf<T>(this IEnumerable<T> array, T? value) where T : IEquatable<T>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,7 @@ public override async ValueTask ConfigureAsync(CommandsExtension extension)
{
this.configured = true;
extension.Client.InteractionCreated += ExecuteInteractionAsync;
extension.Client.GuildDownloadCompleted += async (client, eventArgs) =>
{
if (client.ShardId == 0)
{
await RegisterSlashCommandsAsync(extension);
}
};
extension.Client.GuildDownloadCompleted += async (client, eventArgs) => await RegisterSlashCommandsAsync(extension);
}
}

Expand Down
42 changes: 2 additions & 40 deletions DSharpPlus.CommandsNext/ExtensionMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ public static CommandsNextExtension UseCommandsNext(this DiscordClient client, C
throw new InvalidOperationException("CommandsNext is already enabled for that client.");
}

if (!Utilities.HasMessageIntents(client.Configuration.Intents))
if (!Utilities.HasMessageIntents(client.Intents))
{
client.Logger.LogCritical(CommandsNextEvents.Intents, "The CommandsNext extension is registered but there are no message intents enabled. It is highly recommended to enable them.");
}

if (!client.Configuration.Intents.HasIntent(DiscordIntents.Guilds))
if (!client.Intents.HasIntent(DiscordIntents.Guilds))
{
client.Logger.LogCritical(CommandsNextEvents.Intents, "The CommandsNext extension is registered but the guilds intent is not enabled. It is highly recommended to enable it.");
}
Expand All @@ -43,26 +43,6 @@ public static CommandsNextExtension UseCommandsNext(this DiscordClient client, C
return cnext;
}

/// <summary>
/// Enables CommandsNext module on all shards in this <see cref="DiscordShardedClient"/>.
/// </summary>
/// <param name="client">Client to enable CommandsNext for.</param>
/// <param name="cfg">CommandsNext configuration to use.</param>
/// <returns>A dictionary of created <see cref="CommandsNextExtension"/>, indexed by shard id.</returns>
public static async Task<IReadOnlyDictionary<int, CommandsNextExtension>> UseCommandsNextAsync(this DiscordShardedClient client, CommandsNextConfiguration cfg)
{
Dictionary<int, CommandsNextExtension> modules = [];
await client.InitializeShardsAsync();

foreach (DiscordClient? shard in client.ShardClients.Select(xkvp => xkvp.Value))
{
CommandsNextExtension? cnext = shard.GetExtension<CommandsNextExtension>() ?? shard.UseCommandsNext(cfg);
modules[shard.ShardId] = cnext;
}

return new ReadOnlyDictionary<int, CommandsNextExtension>(modules);
}

/// <summary>
/// Gets the active CommandsNext module for this client.
/// </summary>
Expand All @@ -71,24 +51,6 @@ public static async Task<IReadOnlyDictionary<int, CommandsNextExtension>> UseCom
public static CommandsNextExtension GetCommandsNext(this DiscordClient client)
=> client.GetExtension<CommandsNextExtension>();

/// <summary>
/// Gets the active CommandsNext modules for all shards in this client.
/// </summary>
/// <param name="client">Client to get CommandsNext instances from.</param>
/// <returns>A dictionary of the modules, indexed by shard id.</returns>
public static async Task<IReadOnlyDictionary<int, CommandsNextExtension>> GetCommandsNextAsync(this DiscordShardedClient client)
{
await client.InitializeShardsAsync();
Dictionary<int, CommandsNextExtension> extensions = [];

foreach (DiscordClient? shard in client.ShardClients.Select(xkvp => xkvp.Value))
{
extensions.Add(shard.ShardId, shard.GetExtension<CommandsNextExtension>());
}

return new ReadOnlyDictionary<int, CommandsNextExtension>(extensions);
}

/// <summary>
/// Registers all commands from a given assembly. The command classes need to be public to be considered for registration.
/// </summary>
Expand Down
44 changes: 1 addition & 43 deletions DSharpPlus.Interactivity/Extensions/ClientExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;

namespace DSharpPlus.Interactivity.Extensions;

/// <summary>
/// Interactivity extension methods for <see cref="DiscordClient"/> and <see cref="DiscordShardedClient"/>.
/// Interactivity extension methods for <see cref="DiscordClient"/>.
/// </summary>
public static class ClientExtensions
{
Expand All @@ -32,49 +28,11 @@ public static InteractivityExtension UseInteractivity(this DiscordClient client,
return extension;
}

/// <summary>
/// Enables interactivity for each shard.
/// </summary>
/// <param name="client">The shard client to enable interactivity for.</param>
/// <param name="configuration">Configuration to use for all shards. If one isn't provided, default configuration values will be used.</param>
/// <returns>A dictionary containing new <see cref="InteractivityExtension"/> instances for each shard.</returns>
public static async Task<IReadOnlyDictionary<int, InteractivityExtension>> UseInteractivityAsync(this DiscordShardedClient client, InteractivityConfiguration configuration = null)
{
Dictionary<int, InteractivityExtension> extensions = [];
await client.InitializeShardsAsync();

foreach (DiscordClient? shard in client.ShardClients.Select(xkvp => xkvp.Value))
{
InteractivityExtension extension = shard.GetExtension<InteractivityExtension>() ?? shard.UseInteractivity(configuration);
extensions.Add(shard.ShardId, extension);
}

return new ReadOnlyDictionary<int, InteractivityExtension>(extensions);
}

/// <summary>
/// Retrieves the registered <see cref="InteractivityExtension"/> instance for this client.
/// </summary>
/// <param name="client">The client to retrieve an <see cref="InteractivityExtension"/> instance from.</param>
/// <returns>An existing <see cref="InteractivityExtension"/> instance, or <see langword="null"/> if interactivity is not enabled for the <see cref="DiscordClient"/> instance.</returns>
public static InteractivityExtension GetInteractivity(this DiscordClient client)
=> client.GetExtension<InteractivityExtension>();

/// <summary>
/// Retrieves a <see cref="InteractivityExtension"/> instance for each shard.
/// </summary>
/// <param name="client">The shard client to retrieve interactivity instances from.</param>
/// <returns>A dictionary containing <see cref="InteractivityExtension"/> instances for each shard.</returns>
public static async Task<ReadOnlyDictionary<int, InteractivityExtension>> GetInteractivityAsync(this DiscordShardedClient client)
{
await client.InitializeShardsAsync();
Dictionary<int, InteractivityExtension> extensions = [];

foreach (DiscordClient? shard in client.ShardClients.Select(xkvp => xkvp.Value))
{
extensions.Add(shard.ShardId, shard.GetExtension<InteractivityExtension>());
}

return new ReadOnlyDictionary<int, InteractivityExtension>(extensions);
}
}
22 changes: 11 additions & 11 deletions DSharpPlus.Interactivity/InteractivityExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ protected internal override void Setup(DiscordClient client)
/// <returns></returns>
public async Task<ReadOnlyCollection<PollEmoji>> DoPollAsync(DiscordMessage m, IEnumerable<DiscordEmoji> emojis, PollBehaviour? behaviour = default, TimeSpan? timeout = null)
{
if (!Utilities.HasReactionIntents(this.Client.Configuration.Intents))
if (!Utilities.HasReactionIntents(this.Client.Intents))
{
throw new InvalidOperationException("No reaction intents are enabled.");
}
Expand Down Expand Up @@ -559,7 +559,7 @@ public async Task<InteractivityResult<ComponentInteractionCreatedEventArgs>> Wai
public async Task<InteractivityResult<DiscordMessage>> WaitForMessageAsync(Func<DiscordMessage, bool> predicate,
TimeSpan? timeoutoverride = null)
{
if (!Utilities.HasMessageIntents(this.Client.Configuration.Intents))
if (!Utilities.HasMessageIntents(this.Client.Intents))
{
throw new InvalidOperationException("No message intents are enabled.");
}
Expand All @@ -579,7 +579,7 @@ public async Task<InteractivityResult<DiscordMessage>> WaitForMessageAsync(Func<
public async Task<InteractivityResult<MessageReactionAddedEventArgs>> WaitForReactionAsync(Func<MessageReactionAddedEventArgs, bool> predicate,
TimeSpan? timeoutoverride = null)
{
if (!Utilities.HasReactionIntents(this.Client.Configuration.Intents))
if (!Utilities.HasReactionIntents(this.Client.Intents))
{
throw new InvalidOperationException("No reaction intents are enabled.");
}
Expand All @@ -592,7 +592,7 @@ public async Task<InteractivityResult<MessageReactionAddedEventArgs>> WaitForRea

/// <summary>
/// Wait for a specific reaction.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <see cref="BaseDiscordClient.Intents"/>
/// </summary>
/// <param name="message">Message reaction was added to.</param>
/// <param name="user">User that made the reaction.</param>
Expand All @@ -604,7 +604,7 @@ public async Task<InteractivityResult<MessageReactionAddedEventArgs>> WaitForRea

/// <summary>
/// Waits for a specific reaction.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <see cref="BaseDiscordClient.Intents"/>
/// </summary>
/// <param name="predicate">Predicate to match.</param>
/// <param name="message">Message reaction was added to.</param>
Expand All @@ -617,7 +617,7 @@ public async Task<InteractivityResult<MessageReactionAddedEventArgs>> WaitForRea

/// <summary>
/// Waits for a specific reaction.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <see cref="BaseDiscordClient.Intents"/>
/// </summary>
/// <param name="predicate">predicate to match.</param>
/// <param name="user">User that made the reaction.</param>
Expand All @@ -637,7 +637,7 @@ public async Task<InteractivityResult<MessageReactionAddedEventArgs>> WaitForRea
public async Task<InteractivityResult<TypingStartedEventArgs>> WaitForUserTypingAsync(DiscordUser user,
DiscordChannel channel, TimeSpan? timeoutoverride = null)
{
if (!Utilities.HasTypingIntents(this.Client.Configuration.Intents))
if (!Utilities.HasTypingIntents(this.Client.Intents))
{
throw new InvalidOperationException("No typing intents are enabled.");
}
Expand All @@ -658,7 +658,7 @@ public async Task<InteractivityResult<TypingStartedEventArgs>> WaitForUserTyping
/// <returns></returns>
public async Task<InteractivityResult<TypingStartedEventArgs>> WaitForUserTypingAsync(DiscordUser user, TimeSpan? timeoutoverride = null)
{
if (!Utilities.HasTypingIntents(this.Client.Configuration.Intents))
if (!Utilities.HasTypingIntents(this.Client.Intents))
{
throw new InvalidOperationException("No typing intents are enabled.");
}
Expand All @@ -679,7 +679,7 @@ public async Task<InteractivityResult<TypingStartedEventArgs>> WaitForUserTyping
/// <returns></returns>
public async Task<InteractivityResult<TypingStartedEventArgs>> WaitForTypingAsync(DiscordChannel channel, TimeSpan? timeoutoverride = null)
{
if (!Utilities.HasTypingIntents(this.Client.Configuration.Intents))
if (!Utilities.HasTypingIntents(this.Client.Intents))
{
throw new InvalidOperationException("No typing intents are enabled.");
}
Expand All @@ -700,7 +700,7 @@ public async Task<InteractivityResult<TypingStartedEventArgs>> WaitForTypingAsyn
/// <returns></returns>
public async Task<ReadOnlyCollection<Reaction>> CollectReactionsAsync(DiscordMessage m, TimeSpan? timeoutoverride = null)
{
if (!Utilities.HasReactionIntents(this.Client.Configuration.Intents))
if (!Utilities.HasReactionIntents(this.Client.Intents))
{
throw new InvalidOperationException("No reaction intents are enabled.");
}
Expand Down Expand Up @@ -822,7 +822,7 @@ public Task SendPaginatedMessageAsync(DiscordChannel channel, DiscordUser user,

/// <summary>
/// Sends a paginated message.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <see cref="BaseDiscordClient.Intents"/>
/// </summary>
/// <param name="channel">Channel to send paginated message in.</param>
/// <param name="user">User to give control.</param>
Expand Down
18 changes: 10 additions & 8 deletions DSharpPlus.Rest/DiscordRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,22 @@ public override IReadOnlyDictionary<ulong, DiscordGuild> Guilds
internal Dictionary<ulong, DiscordGuild> guilds = [];
private bool disposedValue;

public DiscordRestClient(DiscordConfiguration config) : base()
public string Token { get; }

public TokenType TokenType { get; }

public DiscordRestClient(DiscordConfiguration config, string token, TokenType tokenType) : base()
{
this.ApiClient = new(new
(
new(),
TimeSpan.FromSeconds(10),
NullLogger.Instance,
config.MaximumRatelimitRetries,
config.RatelimitRetryDelayFallback,
config.TimeoutForInitialApiRequest,
config.MaximumRestRequestsPerSecond
NullLogger.Instance
));

this.ApiClient.SetClient(this);
this.Token = token;
this.TokenType = tokenType;
}

/// <summary>
Expand Down Expand Up @@ -1015,7 +1017,7 @@ public async Task UnpinMessageAsync(ulong channelId, ulong messageId)
/// <param name="nickname">DM nickname</param>
/// <returns></returns>
public async Task JoinGroupDmAsync(ulong channelId, string nickname)
=> await this.ApiClient.AddGroupDmRecipientAsync(channelId, this.CurrentUser.Id, this.Configuration.Token, nickname);
=> await this.ApiClient.AddGroupDmRecipientAsync(channelId, this.CurrentUser.Id, this.Token, nickname);

/// <summary>
/// Adds a member to a group DM
Expand Down Expand Up @@ -1063,7 +1065,7 @@ public async Task<DiscordDmChannel> CreateGroupDmAsync(IEnumerable<string> acces
public async Task<DiscordDmChannel> CreateGroupDmWithCurrentUserAsync(IEnumerable<string> accessTokens, IDictionary<ulong, string> nicks)
{
List<string> a = accessTokens.ToList();
a.Add(this.Configuration.Token);
a.Add(this.Token);
return await this.ApiClient.CreateGroupDmAsync(a, nicks);
}

Expand Down
Loading