Skip to content

v5 extensions and events design #1820

@akiraveliara

Description

@akiraveliara

Description

This is intended to be a bit of an open discussion issue about extensions and events in v5. Of all extensions, this primarily affects Commands and Interactivity, which heavily rely on both the extension and event systems, but when designing, we should keep other extensions in mind.

There are many flaws in the current event system, starting with being able to mutate the event handlers after the gateway starts, which leads to quite some overhead: https://github.com/DSharpPlus/DSharpPlus/blob/master/DSharpPlus/AsyncEvents/AsyncEvent%602.cs#L91-L105
This also includes legacy code such as https://github.com/DSharpPlus/DSharpPlus/blob/master/DSharpPlus/AsyncEvents/AsyncEventArgs.cs#L17 that we should remove, and of course the dreaded Task vs ValueTask issue.

We have also for a while now envisioned events participating in DI in some way or another, #1683 touches upon that topic and it comes up from time to time in support.


Enlightening the main client about DI now ties into extensions. First, we could see extensions participating in DI, both in their creation and in users being able to request the extension in their own services. The main use-case coming to mind here is naturally Interactivity, but users are only limited by their own creativity here; but this provides significant advantages in creating the extension objects too - from trivially customizing parts of how the extension works, such as ICommandExecutor in Commands to error handling using a default-implemented service users can shim individual methods of or replace entirely.

Generalizing this concept then begs the question of how to treat DiscordClient. On one hand, DiscordClient (and DiscordShardedClient) are the main entrypoints of any DSharpPlus bot, it is nearly impossible to interact with library code in any meaningful way without using one of those two, on the other hand, they would benefit from participating in DI in much the same ways as extensions, and exposing more components of the library as customizable services would bring a plethora of improvements - caching, ratelimits, the gateway transport layer (for example, supporting ETF as an extension to v5) come to mind.

This would lead us to changing the creation model, away from DiscordClient..ctor(...). There is plenty of design space here, and I'm entirely open to suggestions. I'll pitch the following API for discussion here:

  • static DiscordClient.Create(string token, DiscordIntents intents); for simplest use-cases
  • static DiscordClient.Create(DiscordConfiguration configuration); for slightly more advanced client-only use-cases (we can consider additional overloads)
  • static DiscordClient.Create(Action<IServiceCollection> configuration); exposing the underlying service collection, which would allow more complex cases including registering extensions, but wouldn't be particularly clean for complex setup
  • DSharpPlus.DiscordClientBuilder..ctor(); creating a new builder with an empty service collection,
  • DSharpPlus.DiscordClientBuilder..ctor(IServiceCollection); creating a new builder from a provided service collection,
  • and DSharpPlus.DiscordClientBuilder.Build(); creating a DiscordClient instance. We can then expose configuration on the builder, as well as letting extensions provide their own extension methods (for instance, setting the text command prefix could be something like DiscordClientBuilderExtensions.SetCommandPrefix(this DiscordClientBuilder builder, string prefix);)

Specify the libraries you want this feature request to affect

DSharpPlus, the core library, DSharpPlus.Commands, DSharpPlus.Interactivity

Other considerations

This is a fairly complex task and will most definitely take a while to implement, no matter what design we settle on. There are some preparatory tasks we can already work on, though, such as preparing the default logger to be used in more places and ways, migrating event dispatch to use ValueTask by default (optionally keeping compatibility for Task-based events, though the compiler error here should be obvious enough to let people migrate trivially) and to use immutable event handler collections.

As for design, there are many questions to solve here, starting with the elephant in many rooms - the deprecated extensions. Since they're deprecated, not removed, we'll have to update them to compile and ideally work. Do we care enough to update them to a completely new model or do we find some hacky minimum-effort solution, leaving them dangling between the old and new models? Do we fix the old extension classes up to be able to participate in DI?

I do think this is important to get done one of these days, because changing the extension model marks a huge breaking change for libraries such as Lavalink4NET to adapt to, and the sooner we can stabilize these things for the v5 release cycle, the more painless it will be to update.

As for events, what surface do we want to support? How do we want to provide event handlers with DI? Here, too, is a lot of open design space that will influence almost every bot and almost every extension targeting the library (note that 'almost' is a disclaimer in case there exists one that wouldn't be affected; I cannot provide any examples of either).


From here on, I will be aggregating feedback and the results of design discussions so we can move forward with implementing after we're confident in our design.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions