to show a short description of each plugin, instead of just the long list of names.
4.7 KiB
Software architecture
Limnoria abstracts its internal architecture away from plugins through its plugin API, which is enough most of the time. However, you may need want to understand its internal architecture, either to debug complex problems, provide advanced features that hook into the internals, contribute to the core, or simply out of curiosity.
This guide will try to walk you through this, assuming you are
already familiar with using the bot and writing plugins (if not, see the
capabilities
documentation, the plugin-tutorial
, and events
,
You should also be somewhat familiar with the IRC protocol <https://modern.ircdocs.horse/>.
Note
This document is a work in progress and is still incomplete. As usual, feel free to ask any questions in #limnoria @ freenode.
Main loop and drivers
The main event loop is a very classic synchronous loop. It is defined
in scripts/supybot
,
and essentially just this:
while world.ircs:
try:
drivers.run()
except KeyboardInterrupt:
# Handle Ctrl-C (trigger shutdown)
except:
# Handle other unhandled errors
Where drivers.run
does this:
for (name, driver) in _drivers.items():
if name not in _deadDrivers:
driver.run()
for name in _deadDrivers:
# Remove the driver
while _newDrivers:
(name, driver) = _newDrivers.pop()
# add the new driver
Drivers are the sources of events in the main thread. In a normal
Limnoria setup, there are two types of drivers: the Socket driver (which
connects synchronously to IRC) and the schedule driver <supybot-schedule>
(which runs
functions periodically, like cron). Historically, there was an
alternative driver to connect to IRC, based on Twisted. It was
deprecated, then removed in 2019, because Python's socket
module became as powerful as Twisted as it gained support for
select()
and TLS.
Network drivers have a reference to a irclib.Irc
object, and do three things in their
run()
method:
- check the connection is still alive (and schedule a reconnect if not)
- get new messages from their
irclib.Irc
instance (usingirclib.Irc.takeMsg
) and send them to the network - get new messages from the network and pass them to their
irclib.Irc
(usingirclib.Irc.feedMsg
)
The actual implementation of the current Socket
driver
is actually a little more complex than this, as all Socket
driver instances cooperate to use select()
together, but
this is the rough idea. See src/drivers/Socket.py
for details.
irclib
As we saw above, network drivers pass their messages to a class
defined in irclib
,
which is where most of the IRC protocol implementation is.
Unlike most event-driven software (especially IRC implementation),
Limnoria does not have hooks that are registered to call a function when
a specific event/IRC command is received. Instead, event listeners
receive all events, and inherit on supybot.irclib.IrcCommandDispatcher
, which calls a
specific method based on the IRC command name. For example, it calls the
doTopic
method when receiving a TOPIC
message.
This dispatching is used both in the main IRC handling (supybot.irclib.Irc
) and
plugins (via supybot.callbacks.PluginMixin
, which inherits supybot.irclib.IrcCommandDispatcher
).
We saw above that the supybot.irclib.Irc
object receives messages directly
from the driver. It's also in charge of keeping track of other callbacks
(ie. plugins) via supybot.irclib.Irc.addCallback
and passing every
message to their __call__
method (which then does the
dispatching on its own again, as it inherits supybot.irclib.IrcCommandDispatcher
).
As there are few callbacks (under a hundred plugins), this simple architecture is efficient enough.
Additionally, when receiving a message and before sending one, it
iterates through the list of plugins and calls their
inFilter
and outFilter
methods (respectively),
if any.
If you look at the code of supybot.irclib.Irc
and supybot.irclib.IrcState
,
you see they are mostly made of doXxx
methods, which
exhaustively implement every known IRC command, update some state, and
optionally react to it by queuing messages.
Commands
Next is the callbacks system, mostly implemented in `supybot.callbacks
`. This is
where all the magic happens to make plugins so easy to write; it's also
the most complex part of Limnoria and the hardest to understand, because
everything is tightly interleaved.
TODO
Registry
TODO
Auto-documentation
TODO