Subsystems
While thinking about refactoring the input I realized we had no global “subsystem” concept. It would really clean the code if we had a consistent way of initializing parts of the engine in a clean manner. For audio, we already have OpenAL and DirectSound, but their support is hardcoded in the code (see aalInit()).
List of Subsystems
- Audio
- Input
- Graphics
- Windowing
- ...
Subsystems selection
cfg.ini ?
[subsystems_linux] Audio = OpenALAudio Input = SDLInput Graphics = OpenGLGraphics Windowing = X11Windowing [subsystems_windows] Audio = DSoundAudio Input = DInput Graphics = DX9Graphics Windowing = Win32Windowing
Some thoughts: many unix(-like) systems (linux, BSD, mac) will have similar values for most backends, but maybe not all - maybe subsystem_unix who'se values can be overriden are useful?
Scripting the engine init
I’ve come across another engine that had its whole startup sequence scripted. I found it very efficient to work with since you could tweak the init at any time without recompiling anything. It was also possible to use different script for each platform used. The same result could be achieved with ini files or with command line switches, but I think this kind of script is more elegant and flexible.
In the case of arx, a script file could look like this (a dumb example, but you get the point): arx_startup.script
LoadSubsystem Audio OpenALAudio LoadSubsystem Graphics OpenGL # Hey I really don’t need that splash screen every time I want to debug #ShowSplashScreen # Same for the intro cinematic #ShowIntroCinematic # Show main menu!... #ShowMainMenu # I just want to load a level directly LoadLevel 10
Commands can be registered easily in the code:
REGISTER_COMMAND(LoadSubsystem); bool LoadSubsystem(const std::string& args) { // Just do it }
Ideas / TODOs:
- How would a portable implementation of something line REGISTER_COMMAND look like (also applies to DECLARE_CLASS in the next section)? Remember that C++ allows compilers to lazy initialize global variables: The variable doesn't need to be initialized until a function / method from the same translation unit is called. I wonder how different compilers behave here?
- Scripting the intro sequence is definitely useful, but what advantage does scripting the subsystem setup have over the ini-based approach? Some subsystems will probably need to be initialized in a specific oder and may depend on specific implementations for other subsystems (IE: SDL input will probably depend on SDL windowing, ...)
- If subsystems are configured in scripts, how would an in-game config GUI save selected subsystems without clobbering custom script modifications?
- If we are going to use scripts we should probably re-use the existing .asl script syntax and interpreter (which is due to be cleaned up for the case sensitive work)
Class Reflexion
Would be a nice addition to allow all this generic init to work. Something like:
std::string strSubsystem = get from cfg Class* pSubsystemClass = Class::GetClassByName(strSubsystem); Subsystem* pSubsystemInstance = Cast<Subsystem>(pSubsystemClass->AllocateNew());
All that is needed for this to work for any class is simple macros inserted in the class body / cpp
DECLARE_CLASS(clsName, clsParentName); IMPLEMENT_CLASS(clsName);
Pushing it further
Having the option to store individual subsystems as .so/.dll and load them dynamically would be neat too!
All of this is really inspired by my engine / engines i’ve used in the past (UE3 for example) that worked like this... In my case I have a simple config file per platform listing all the subsystems that should be used. At one point I even had a dialog box that would pop up to allow the user to choose the subsystems the first time the engine was started :)