alchemy
alchemy copied to clipboard
[feature | breaking] Support for handling many bots at once
Right now, alchemy has a global architecture intended to support a single bot account. But there are many cases where a user would want to load and interface with many bots at a time, all in the same application instance.
Prerequisites
- In any case where named processes are used, replace it with a composite key using the client.
- For every stateful API method that alchemy provides, accept a client reference.
Problems
- Right now, both commands and events are implemented with
__using__
and the developer issuing ause
in their startup logic. This is inherently a global operation, so we would have to replace the method of registering these callbacks.
Cogs
- Current usage
defmodule MyCommands do
use Alchemy.Cogs
Cogs.def hello(name) do
Cogs.say "Hello " <> name
end
Cogs.def hello do
:noop
end
end
defmodule MyBot do
use Application
def start(_type, _args) do
run = Client.start("token")
use MyCommands
run
end
end
- Suggested usage (also moving away from a magical Cogs)
defmodule MyCommands do
def hello(message, [name], _context = %{client: client}) do
Alchemy.Client.reply(client, message, "Hello " <> name)
end
def hello(_message, _args, _context) do
:noop
end
def register_commands(client) do
Alchemy.Commands.add_command(client, "hello", &Commands.hello/3)
end
end
defmodule MyBot do
use Application
alias Alchemy.Client
def start(_type, _args) do
with {:ok, client} <- Client.start("token") do
# if we need custom keys in context
Alchemy.Commands.put_context(client, :some_key, :some_value)
MyCommands.register_commands(client)
{:ok, client}
end
end
end
Events
- Current usage
defmodule MyEvents do
use Alchemy.Events
Events.on_message(:on_message)
def on_message(message = %{channel_id: channel_id, content: content}) do
Alchemy.Client.send_message(channel_id, "Received: " <> content)
end
end
defmodule MyBot do
use Application
def start(_type, _args) do
run = Client.start("token")
use MyEvents
run
end
end
- Suggested usage (also with proposed events usage from #71)
defmodule MyEvents do
def on_message(_event = %MessageCreateEvent{channel_id: channel_id, message: message}, _context = %{client: client}) do
%{content: content} = message
Alchemy.Client.send_message(client, channel_id, "Received: " <> content)
end
def register_handlers(client) do
Alchemy.Events.add_handler(client, :message_create, &on_message/2)
end
end
defmodule MyBot do
use Application
def start(_type, _args) do
with {:ok, client} <- Client.start("token") do
# if we need custom keys in context
Alchemy.Events.put_context(client, :some_key, :some_value)
MyEvents.register_handlers(client)
{:ok, client}
end
end
end
These changes would make more sense to make after #71 and Cogs is changed. It's not clear whether or not Cache
would be affected by this change since we may want Client
s to be able to share a Cache
. With three bots, Alchemy would be receiving the same events three times (if the bots were in the same servers), which I think is an argument to make caches client-specific.
Yeah, this is probably the most longstanding flaw in the library, and something I've always wondered how to address in a clean way.
I remember kind of realizing that passing around the current client was pretty much unavoidable, or at the very least, you need some kind of way of knowing which API key to be using at any time, since you've got multiple floating around.
Maybe this redesign would be a candidate for a 1.0?
But yeah, this seems like a good idea, and a bit cleaner too.
I think you can design the Cache with this in mind, and have much problems. All requests would flow through the same infrastructure, just tagged with the API key, so you could use a universal cache for requests.
For guild events in theory you can share a cache, although one issue might be that one bot could see things in the cache that it would otherwise not have permission to see if it asked for them globally. This would give non-deterministic results at that point.
One solution to that problem is to simply separate things out into multiple caches, then you would never have problems of that sort.
I think that's one big thing that would be need to modified in the cache, as I think if you just used the current cache with multiple bots you could get into weird states where a bot can sometimes see things it's not supposed to have access to. But you could easily just dynamically spin up multiple caches, and route to the correct one based on which bot you're dealing with.