static poly morphism for multi platform api
Suppose if we are developing games for multiple platforms with the same code base, often we need to use some amount of poly-morphism to capture the platform-generic nature of components. Let us say that we are developing for the PC and PS2 platform. It is quite natural for us to have a base class called Texture, and derived classes called PCTexture and PS2Texture. The derived classes would contain the platform specific implementation of the Texture component. But in a single build configuration, we would be having only one kind of these derived classes. If the build configuration is PS2_Debug or PS2_Release, then the compiled code would not contain any instance of the PCTexture class. In fact, at compile time, we make the decision of which of the derived class is going to be used. So, the kind of runtime poly-morphism or dynamic poly-morphism using base-derived classes and v-tables is not a necessity.
This is a simple technique to express the same kind of polymorphic behavior of platform abstraction, using static poly-morphism.
An illustrative example can be downloaded here. Here is another viewable version of the code.
dynamic poly-morphism
The following code shows an implementation of the use of dynamic poly-morphism.
//This is widget.h #if defined(PC_PLATFORM) #define KGPLATFORM_PC 1 #define KGPLATFORM_PS2 0 #elif defined(PS2_PLATFORM) #define KGPLATFORM_PC 0 #define KGPLATFORM_PS2 1 #endif class Widget { public: Widget(); virtual ~Widget(); void CommonMethod(); virtual void PlatformSpecificMethod()=0; };
//This is widget.cpp //Which holds the implementation of //non-virtual methods of the base class. Widget::Widget() { //platform-generic widget implementation } Widget:~Widget() { //platform-generic widget implementation } void Widget::CommonMethod() { //platform generic implementation of common method }
Now the platform specific implementation for the PC platform would be
//This is pc_widget.h class PCWidget :public Widget { public: PCWidget(); ~PCWidget(); void PlatformSpecificMethod(); };
//This is pc_widget.cpp PCWidget:: PCWidget() { //platform specific contrcuctor } PCWidget::~PCWidget() { //platform specific destructor } void PCWidget:: PlatformSpecificMethod() { //platform specific implementation }
The main file that would be calling widget methods would be,
//This is main.cpp or some .cpp file that uses widget #include "widget.h" #if KGPLATFORM_PC #include "pc_widget.h" #elif KGPLATFORM_PS2 #include "ps2_widget.h" #endif int main( int argc, char **argv) { Widget *pw = NULL; #if KGPLATFORM_PC pw = new PCWidget(); #elif KGPLATFORM_PS2 pw = new PS2Widget(); #endif pw->CommonMethod(); pw->PlatformSpecificMethod(); delete pw; return 0; }
static poly-morphism
Now let us implement the same functionality using static poly-morphism.
//this is widget.h #if defined(PC_PLATFORM) #define KGPLATFORM_PC 1 #define KGPLATFORM_PS2 0 #elif defined(PS2_PLATFORM) #define KGPLATFORM_PC 0 #define KGPLATFORM_PS2 1 #endif template< int iPlat> class Widget { public: Widget(); ~Widget(); void CommonMethod() { //platform generic method } void PlatformSpecificMethod(); };
And the platform specific implementation for PC would be
//this is pcWidget.cpp template<> Widget<KGPLATFORM_PC>::Widget() { // platform specific implementation of Widget } template<> Widget<KGPLATFORM_PC>::~Widget(); { //platform specific implementatin of ~Widget } template<> void Widget<KGPLATFORM_PC>:: PlatformSpecificMethod() { //platform specific method }
The widget methods can be called as follows.
#include "widget.h" int main( int argc, char **argv) { //Always the current platform to which the //code has ben compiled for will have the //template parameter 1 Widget<1> *pw = new Widget<1>(); pw->CommonMethod(); pw->PlatformSpecificMethod(); delete pw; return 0; }
This is much simpler and cleaner and more efficient too, since we are getting rid of runtime dispatching using v-tables.
The code can be seen here.
issues
- code bloat: One of the problems with using templates in c++ language is that upon compiling, each template instantiation, tends to generate its own copy of the code. However this is not an issue here, since we don't instantiate anything other than the code for the current platform for each build configuration. Eg: PS2_Debug configuration, when compiled wont be having any Widget<KGPLATFORM_PC> instantiations.
- Readability: Templates often result in unreadable code. But in this case, we could use typedef for easy readable synonyms which doesnt have the template construct. typedef Widget<1> CurrentWidget
- Link issues involving compilers: Although C++ standard doesn't have any such requirement, some compilers require that, bodies of template functions or member functions must be in the header file. Otherwise the translation units (.cpp files) using the template instantiations, wont find them and cause link errors. In this case, any such generic code (Eg: Widget::CommonMethod), should indeed be completely defined in the header file. But at least for visual studio compilers, the specialization, which is were the platform specific code resides, can be defined in the .cpp files.
- Tool Build Configurations: In game development, usually the runtime engine have platform specific build configurations (Eg: PS2_Debug) and for asset building and authoring purposes the tools are built with a different configuration, which would be mostly equivalent to PC_Debug or PC_Release. For such configurations, there maybe a need where, all of the different platform specific instantiations may occur in the same configuration. Imagine a texture utility tool, which based upon a runtime parameter makes either PS2TExture or PCTexture in runtime. We suggest using enumeration template parameters for a tool build configuration and using the platform specific instantiations, based on that.
#if defined(TOOL_BUILD) #define KGPLATFORM_PC 1 #define KGPLATFORM_PS2 2 #define KGPLATFORM_X360 3 #define KGPLATFORM_PSP 4 #endif
</ul>
blog comments powered by Disqus