Modular Backends
Introduction
We are considering to change our backends to use a more modular approach to things. This would be reflect in both the implementations and also in the interface (i.e. OSystem).
The idea is that a more modular approach makes it easier to reuse code in backends. It also makes it possible to have generic default implementations of certain features, while still allowing backends to customize them if needed. This is also what makes it so interesting for the Small Devices Backend project.
Examples
Instead of talking at length about hypothetical advantages of this, let me give you some specific examples I hope to see eventually.
X11 backend
The X11 backend directly uses the X11 API to implement graphics output for ScummVM on any system that supports X11. But of course, being a backend, it also needs to provide a way to output audio. X11 doesn't offer an API for that (AFAIK, at least), so it has to use something else. Sadly, this limits its portability. Right now, it has code for any system that supports sys/soundcard.h
resp. linux/soundcard.h
(I think that means OSS,
With the modular system approach, we'd split it into a "graphics & input" module which was implemented based on X11, and a "sound" module implemented based on OSS.
Then if somebody wants to use it on a system which doesn't have OSS, they could either choose to use the "null" ("dummy" ? "empty" ?) sound module, or write their own sound module, based on whatever sound API is available on their platform.
And if somebody decides to write an fbcon graphics/input module at some point, they could still reuse the OSS module.
Timers
Another typical candidate for modularization is the Timer code. Currently, most backends are happy using our default Time class, which is based on OSystem::setTimerCallback
method. But the Morphos backend actually needs a custom implementation. The results in a rather nasty hack.
If we were doing things in a somewhat more modularized fashion, then this would become much simpler: Essentially, we'd have a "timer" module, and a default implementation of that which would be 99% identical to our current timer code. The difference being that we make it explicitly possible to replace it. Most backends would simple keep using the default implementation by providing the optional OSystem::setTimerCallback method (probably in a different way, but let's not get too specific here). But porters could also add an alternate implementation if desired.
The higher level code would simply ask the backend once for the "timer manager" via a new OSystem::getTimerManager
method (similar to the existing OSystem::getSavefileManager
method). The result is virtually identical for the client code (only a bit in main.cpp has to be changed), and also virtually identical for porters
Requirements
Modularization must be done in such a way that it doesn't hurt porters -- after all it's meant to *help* them. But many ports are rather monolithic, and actually wouldn't benefit much from being modularized (e.g. the PSP, PS2, Dreamcast, etc. ports) compared to the current situation. I.e. it should be relatively pain free to transit to the new system, and not force anybody to reinvent the wheel.
In particular, we want to avoid bloat. Implementing new backends should becomes *easier* with the module approach, not harder. So we have to try to avoid over design, and also should avoid breaking things down into too small bits, because then it becomes harder to maintain them. In particular, we probably still want to allow relatively "monolithic" implementations, by not forcing porters to split everything into multiple files (but maybe encouraging it).
It's usually a good idea to look at how SDL manages its backends & modules. For example, graphics, input and event code are usually all found in src/video/FOO
.
Potential modules
Possible modules include
- Graphics
- Input (events) -> possibly have graphics, input & event code all in a single type of module, just like SDL does -- after all they are usually tightly tied together anyway.
- Sound (possibly allow for custom mixer implementations. Maybe even make it possible to use custom MP3 playback implementations and such things, for platforms that offere optimized implementations. Of course, this is tricky, and may not be possible at all)
- Timers (the current code would be left as default implementation, but e.g. Morphos port could override it easily)
- Mutexes (only used when timers & threads are involved, so could be part of that)
- Virtual keyboard
- Filesystem code (already modular, see backends/fs/)
- SaveFileManager (already modular to an extent, but should be moved to backends/saves/)
- Native MIDI drivers (already modular, backends/midi/)
- Scaler code (see common/scaler*) -> we already share some scaler code, but that could probably be done in a better way, and maybe some more generic scalers could be added (e.g. a good & fast downscaler).
Source layout
Regarding the source layout: I imagine we will add subdirs to backends based on functionality, while retaining some / all of the current platform specific dirs (though we might be move the latter into a new subdir called "platform" or so). I.e. I envision the source hierarchy to eventually look something like this:
backends/fs/posix
backends/fs/windows
backends/fs/..
backends/saves/default
backends/saves/psp
backends/timer/default
backends/timer/morphos
backends/video/x11
backends/sound/oss
backends/midi
...
backends/platform/sdl
backends/platform/dc
I don't want to be too strict on how far we use subdirs or not. E.g. backends/midi
currently does not use subdirs. And the subdirs in backends/fs
are not really needed either, since each implementation so far consists of a single source file only, so we might want to flatten it. Those are fine details we still can decide (and change) latter.
The point of keeping a "platforms" directory is to allow backends to stay monolithic if they prefer: If, for example, it's easier for the DC backend to stay in a single file, then it shall be able to do so. Adding subdirs to the backends/MODULE/ dirs is mostly interesting if those are likely to be reused (but of course porters are welcome to do this separation anyway if they like to).