Another way of supporting multiplatform

Whether you're a newbie or an experienced programmer, any questions, help, or just talk of any language will be welcomed here.

Moderator: Coders of Rage

Post Reply
User avatar
shirobon
Chaos Rift Newbie
Chaos Rift Newbie
Posts: 2
Joined: Wed Aug 31, 2016 2:37 am

Another way of supporting multiplatform

Post by shirobon »

Hello, I've recently been getting ready to work on my first multiplatform project for DC and PC, and I'm not too sure of which way I should support multiplatform.

The obvious and maybe naive way would just be a bunch of preprocessor directives on all the function definitions and whatnot for conditional compilation, but this easily gets messy very quickly and is probably the worst way of doing it.
Another way I could think about it is creating separate static libraries for each platform, so the main project code should be platform agnostic because the implementations are handled by linking with the correct library. The drawback I see is that when you're developing, you're likely going to have to update the library very often, so you'll have to keep rebuilding the library and then rebuild the code with the new libraries. And you kind of have two codebases, one for the library implementations and then one for the actual project.

I thought of another method, but I'm not sure if it's a great solution.

First you would have a general platform.h file #defining the platform that is being built for.

Then you'll have your generic, platform independent main project code like

Code: Select all

/* graphics.c , platform independent code */
#ifdef _PLATFORM_DC_
#include "graphics_dc.c"
#else
#include "graphics_pc.c"
#endif

void drawString(const char* str, short x, short y)
{
	/* Theoretically this should be optimized using tail recursion, or will be inlined */
	drawString_impl(str,x,y);
}
And then you'll have

Code: Select all

/* graphics_dc.c , DC specific implementation */

static void drawString_impl(const char* str, short x, short y)
{
	/* ... */
}
and finally

Code: Select all

/* graphics_pc.c , PC specific implementation */

static void drawString_impl(const char* str, short x, short y)
{
	/* ... */
}
You're still using the preprocessor, but it's only once per file at the beginning to include the correct implementation.
And since you're using static functions in the implementations, there are no naming conflicts, and based off which implementation was included, you'll call the same function which will resolve to different implementation.
So essentially the platform independent functions are just wrappers for the implementation.

The possible drawbacks I see is that there is no guarantee they will be optimized with tail recursion, so the extra function call will set a new stack frame and obviously it will bring in overhead and will be slower. There is no guarantee it will be inlined either.

Thoughts?
User avatar
dandymcgee
ES Beta Backer
ES Beta Backer
Posts: 4709
Joined: Tue Apr 29, 2008 3:24 pm
Current Project: https://github.com/dbechrd/RicoTech
Favorite Gaming Platforms: NES, Sega Genesis, PS2, PC
Programming Language of Choice: C
Location: San Francisco
Contact:

Re: Another way of supporting multiplatform

Post by dandymcgee »

The first is a complete no-no. *Do not* litter your code with platform-specific logic unless it's literally in one or two lines of code specifically.

The second is the best if your goal is to create something reusable for the future, and well isolated. This is best for large teams or dedicated research. There are not many individuals who will pull this off without severely hindering the forward progress of their product.

The third is perfectly viable for single-purpose systems. I would recommend this approach before the other two if your goal is to finish, release, and support a commercially-viable product in a reasonable time frame with a team of 5 or less people.
Falco Girgis wrote:It is imperative that I can broadcast my narcissistic commit strings to the Twitter! Tweet Tweet, bitches! :twisted:
User avatar
YourNerdyJoe
Chaos Rift Cool Newbie
Chaos Rift Cool Newbie
Posts: 79
Joined: Sun Oct 02, 2011 3:28 pm
Current Project: Top secret (not really) Top-Down Shooter for GBA
Favorite Gaming Platforms: GBA, Gamecube, PC, 3DS
Programming Language of Choice: C/C++
Contact:

Re: Another way of supporting multiplatform

Post by YourNerdyJoe »

Generally what I do i set up separate source directories for each platform so the directory tree looks like this:

Code: Select all

project/
  include/
    graphics.h
  common/
    graphics_common.c
    main.c
  pc/
    graphics_pc.c
  dc/
    graphics_dc.c
Then you just set which directories to use in your ide or makefile. So there're no #ifdefs required.
And of course if you wanted to, you could just put the contents of include and common in the project directory if it makes more sense to you.
See that?.....
Exactly
https://yournerdyjoe.github.io/
User avatar
shirobon
Chaos Rift Newbie
Chaos Rift Newbie
Posts: 2
Joined: Wed Aug 31, 2016 2:37 am

Re: Another way of supporting multiplatform

Post by shirobon »

Thanks for the replies guys.

The design I ultimately ended up pursuing goes as follows. Took inspiration from one of Falco's posts.

First I have the platform.h which contains a bunch of #defines based off what you are targetting.

Code: Select all

#ifndef PLATFORM_H
#define PLATFORM_H
	
	#define JSD_VERSION		0.0.1

	#define JSD_PLATFORM_PC 0
	#define JSD_PLATFORM_DC 1

	/* This single line dictates whether EngineJSD is built for PC/SDL/OpenGL */
	/* Or Dreamcast/KallistiOS/PowerVR */
	/* The main project code is platform agnostic; All calls will be resolved */
	/* To their platform specific implementation transparently */
	#define	JSD_PLATFORM JSD_PLATFORM_PC

	/* Set up conditional includes based off target platform */
	/* Will resolve to different platform specific implementations */
	/* for PC/SDL/OpenGL  or  Dreamcast/KallistiOS/PowerVR */
	#if		JSD_PLATFORM == JSD_PLATFORM_PC
		#define JSD_API_INCLUDE "../PC/API_pc.h"
		#define JSD_STD_LIBRARY_INCLUDE "../PC/std_include_pc.h"
		#define JSD_VIDEO_INCLUDE "../PC/video_pc.h"
		#define JSD_VIDEO_SOURCE "../PC/video_pc.c"

	#elif	JSD_PLATFORM == JSD_PLATFORM_DC
		#define JSD_API_INCLUDE "../DC/API_dc.h"
		#define JSD_STD_LIBRARY_INCLUDE "../DC/std_include_dc.h"
		#define JSD_VIDEO_INCLUDE "../DC/video_dc.h"
		#define JSD_VIDEO_SOURCE "../DC/video_dc.c"
	
	#endif
	
#endif
Over here based off the target, different paths are defined. So from my common code, I am just including one of those defines which are resolving to different paths and code.

Then I have my platform specific code (not yet actually, I was just testing the foundation that I am about to build on with printf)

Code: Select all

/* ../PC/video_pc.c */
#include "../Common/platform.h"
#include JSD_API_INCLUDE
#include JSD_STD_LIBRARY_INCLUDE
#include JSD_VIDEO_INCLUDE

static void doStuff(void)
{
	printf("Printing from the PC specific implementation!!\n");
}

Code: Select all

/* ../DC/video_dc.c */
#include "../Common/platform.h"
#include JSD_API_INCLUDE
#include JSD_STD_LIBRARY_INCLUDE
#include JSD_VIDEO_INCLUDE

static void doStuff(void)
{
	printf("Printing from the Dreamcast specific implementation!!\n");
}
And finally I have the common code

Code: Select all

/* ../Common/video_common.c */
#include "../Common/platform.h"
#include "../Common/video_common.h"

#include JSD_API_INCLUDE
#include JSD_STD_LIBRARY_INCLUDE

#include JSD_VIDEO_INCLUDE
/* Contains declaration and definition of static functions for platform specific implementations */
#include JSD_VIDEO_SOURCE

void indirectFunc(void)
{
	doStuff(); /* Resolves to PC or DC depending on specified target */
}
I felt a little unsure about including .c files since tons of people say it's bad practice and has very specific use cases, but I justified this as one of those :lol:
It's not like the same code is being generated in separate object files and bloating the executable though, since the only files I am linking are the common object files. I just didn't want to put the actual definitions in the header files since that's usually a universal no-no, at least in C :roll:

And here is the ...drumroll... fruit of my efforts lol :bow:
Image

All in all, I think this framework will serve me well, and maybe this information be of use to somebody :bow:
Post Reply