.. | ||
Commands | ||
Database | ||
EventHandlers | ||
Properties | ||
ThumbScrapper | ||
Utils | ||
.editorconfig | ||
CompatBot.csproj | ||
Config.cs | ||
GlobalSuppressions.cs | ||
Program.cs | ||
readme.md | ||
Watchdog.cs |
RPCS3 Compatibility Bot
Configuration
Currently all configurable tunables are stored in the Config static class that is initialized once on startup. This is mostly done like this because it's very easy to implement and is enough for current needs.
Some settings are grouped in additional static classes for easier use (e.g. Reactions
and Colors
).
Everything is initialized with default values that correspond to the main bot instance (except for sensitive tokens), and require overriding through configuration for any test instance.
Currently, configuration is possible through the $ dotnet user-secrets
command. Configuration through the environment variables was disabled as it had some unintended consequences between bot restarts (preserved values; require complete manual shutdown and restart to update configuration).
Be careful with input validation during configuration, as unhandled exceptions in static constructor will lead to
TypeInitializationException
and program termination that might be tricky to debug.
In addition to the configuration variables, Config
contains the Log
instance and the global CancellationTokenSource
.
We're using NLog for logging, configured to mimic the default Log4Net layout (mostly because I already have the syntax highlighter for Sublime Text 3 for it). It is also configured to ignore TaskCancelledException
s that occur when CancellationToken
s are being cancelled to reduce spam in logs.
Do log exceptions as an argument to log methods, instead of calling
.ToString()
on them
Global CancellationTokenSource
(Config.Cts
) is used to signal the program shutdown.
Do check it and abort whenever possible to reduce the restart time and prevent infinite code execution.
Program entry point
On startup we check for other instances. We wait a bit for their shutdown, or shutdown ourselves otherwise. This is done through spinning a separate thread and using global mutex. Unlike semaphores, mutex must be released in the same thread it was acquired, which is impossible to do in asynchronous code, thus the dedicated thread.
Note that
Config
initialization will happen on first call to the class.
Next we open the databases and run migrations to upgrade their structure if needed. Currently we have two of them:
- BotDb is used to store all the settings and custom data for the bot.
- ThumbsDb is used to store PSN metadata and game thumbnail links.
When databases are ready, we immediately restore bot runtime statistics.
Next we start all the background tasks that will run periodically while the bot is up and running. This includes thumbnail scraping, AppVeyor build history scraper, AMD Driver version updater, etc.
Next we configure the discord client. This includes registering all the commands and event handlers. We use the built-in help formatter and hook up the built-in discord client logging to our own logger.
Of particular note is the GuildAvailable
event, where we check and make sure the bot is running in the configured guild. There was one time when bot wasn't configured properly and someone quietly added it to their own private server, which caused crash on startup. That was fun.
We also fun backlog checks where it makes sense for moderation, in case something slipped past while the bot was unavailable.
On restart we try to gracefully wait for a bit to let any outstanding task to complete, but not too long. This is why it is important to check Config.Cts
cancellation status whenever possible.