🔒 Locked
Writing portable code
Advertisement
Original Post
Hi everyone, I am currently in the process of redesigning my 2D game library. I recently bought a MacBook Pro laptop, so now I have both Windows XP and Mac OS X to play around with. I should be able to experiment with Linux too if I can figure out how to get a distro to play nice with my desktop hardware. Since I have multiple OSes to work with it makes sense to try my hand at making my library and games cross-platform. I work with SDL and OpenGL, so there theoretically should not be that much of a problem there. My main concern is that I have never really tried writing portable code for any sizable application. I am unsure about how I should go about it, or what common gotchas I need to look out for. For example I know vaguely about endian problems between different architectures, although I do not know if that has been complicated with Macs switching to Intel chips. And I suspect loading and saving files may be complicated. Are there any good beginner guides on writing portable game code, or just some tips you could provide? Edit: I'm using C++. Whoops, forgot to state that! Thanks in advance! [Edited by - Trapper Zoid on March 15, 2007 3:56:37 PM]
You might want to look at the book "Cross Platform Game Development" by Steven Goodwin. You can search and browse (and buy) it on Amazon.
http://www.amazon.com/Cross-Platform-Game-Programming-Development/dp/1584503793
From practical experience - good encapsulation of platform specific features is very important. But really there are a lot of potential pitfalls and good practice is to simply check every day or so that the code still works on the other platforms. Some kind of unit testing is useful there. Or at least a "smoke test".
http://www.amazon.com/Cross-Platform-Game-Programming-Development/dp/1584503793
From practical experience - good encapsulation of platform specific features is very important. But really there are a lot of potential pitfalls and good practice is to simply check every day or so that the code still works on the other platforms. Some kind of unit testing is useful there. Or at least a "smoke test".
Design wise i'd recommend if you find any platform specific code like file reading inherit from common classes. Even have an abstract class which is common code, then each platform has to derive from it to implement its own version of it. This is generally how most places handle cross-platform.
Oh and your right about endianess, its a pain :) If you use a debugger a lot then that different too, because strings aren't readable, because it starts at the last char often so you cant read it.
With the file reading stuff, i'd say its easiest to have some macros (#defines) like READENDIANINT and READENDIANWORD, which is then implemented in the platform part of the headers.
Oh and your right about endianess, its a pain :) If you use a debugger a lot then that different too, because strings aren't readable, because it starts at the last char often so you cant read it.
With the file reading stuff, i'd say its easiest to have some macros (#defines) like READENDIANINT and READENDIANWORD, which is then implemented in the platform part of the headers.
Set your compiler to the strictest settings it allows: ANSI conformance, use the most stringent errors/warnings your compiler provides, any settings that detect 64-bit portablility issues, etc.
presuming that you don't want to target PPC macs, the intel switch actually makes things easier since 99% of consumer PCs are now little-endian and its x86 across the board. No more endian-ness conversions, no more SSE/altivec code paths to maintain, no more x86/ppc assembler to maintain... Of course, the consoles are now all big-endian, PowerPC across the board - but I don't think that concerns you.
So, basically you're dealing with one architecture (unless you plan to support the more exotic linux hardware options as well) so you want to abstract the OS interfaces you're dealing with, rather than hardware differences. You've already been working with cross-platform libs, so you're good there.
You want to pay attention to things like:
OS interaction
OS controlled hardware resources
OS filesystems
differences in user privileges.
presuming that you don't want to target PPC macs, the intel switch actually makes things easier since 99% of consumer PCs are now little-endian and its x86 across the board. No more endian-ness conversions, no more SSE/altivec code paths to maintain, no more x86/ppc assembler to maintain... Of course, the consoles are now all big-endian, PowerPC across the board - but I don't think that concerns you.
So, basically you're dealing with one architecture (unless you plan to support the more exotic linux hardware options as well) so you want to abstract the OS interfaces you're dealing with, rather than hardware differences. You've already been working with cross-platform libs, so you're good there.
You want to pay attention to things like:
OS interaction
OS controlled hardware resources
OS filesystems
differences in user privileges.
Quote:
Original post by owl
I'll assume you're talking about C++.
Yes I am, sorry about not making that clear. I've added that to the original post.
I usually try to switch on all warnings for my compilers anyway, as I've learnt that it's never a good idea to ignore any warning, no matter how obscure. I'll make sure I continue to do so.
With the endian problems that was the bane of cross-platform coding between PCs and PPC Macs, what exactly did it effect? Was it only a problem if you were doing bitwise operations or reading individual bytes from files?
With abstracting away OS calls, is that just as simple as making functions for anything that deals with the OS (such as reading from files) and making platform specific code for each of those functions? Probably a very newbie question, but I am pretty new to this [smile].
The standard library is a pretty good template for abstracting away OS-level services, actually; look at the fstream classes for example. File IO is highly and notoriously platform dependent, but fstream is guaranteed to work the same on any platform - it's provided a good, solid abstraction.
In general there are four things you will need to abstract yourself which the standard library has not already wrapped for you: graphics, GUI (console UI is taken care of already with cin/cout), audio, and threading. Anything threading-related (including and especially timing/synchronization between threads) will be highly platform-dependent and need to be wrapped. Any GUI, including message dialogs and such, will need to be wrapped.
Honestly, if you throw OpenAL or something into your mix and don't do any multithreading, you're taken care of.
Endianness becomes a problem any time you have data that occurs in blocks larger than 1 byte. (Single-byte ANSI character strings are one exception, but strings are a crazy beast anyways so I'll ignore them for now.) This means any short, int, etc. in your code is potentially susceptible.
Mainly you'll encounter problems any time you look for a particular bit pattern or change the size of the bit block you're looking at. For instance, if you want to truncate an int to 16 bits on a little-endian platform, you take the first 16 bits in memory. On a big-endian platform, however, you need the last 16 bits - which means you will have to do different things based on the size of the data you're truncating.
Endian issues are actually not that common; I heard a lot of anecdotes during the PPC-to-Intel switch in the Mac world that many apps only had to change two or three lines of code, which was certainly my own experience. The main thing is being aware that the issues can and do arise, and knowing how to identify when they have.
In general there are four things you will need to abstract yourself which the standard library has not already wrapped for you: graphics, GUI (console UI is taken care of already with cin/cout), audio, and threading. Anything threading-related (including and especially timing/synchronization between threads) will be highly platform-dependent and need to be wrapped. Any GUI, including message dialogs and such, will need to be wrapped.
Honestly, if you throw OpenAL or something into your mix and don't do any multithreading, you're taken care of.
Endianness becomes a problem any time you have data that occurs in blocks larger than 1 byte. (Single-byte ANSI character strings are one exception, but strings are a crazy beast anyways so I'll ignore them for now.) This means any short, int, etc. in your code is potentially susceptible.
Mainly you'll encounter problems any time you look for a particular bit pattern or change the size of the bit block you're looking at. For instance, if you want to truncate an int to 16 bits on a little-endian platform, you take the first 16 bits in memory. On a big-endian platform, however, you need the last 16 bits - which means you will have to do different things based on the size of the data you're truncating.
Endian issues are actually not that common; I heard a lot of anecdotes during the PPC-to-Intel switch in the Mac world that many apps only had to change two or three lines of code, which was certainly my own experience. The main thing is being aware that the issues can and do arise, and knowing how to identify when they have.
Audio, video and input is the easy part. Using SDL, OpenGL (no platform specific extensions, like text) and OpenAL or SDL audio everything should be fine. Multithreading is not a problem either (like proposed above), SDL's threading is pretty neat.
I have written several small cross platform games and most problems I have faced have been about filesystems, networking, endianness and word size issues.
One problem with filesystems is that in windows, your data is typically installed in the game directory. In Linux, it's installed typically somewhere in the /usr directory. I don't know about Mac OS X. Writing to files is another issue, usually you can only write to the user's home directory in Linux, where as most windows users play as administrators. This problem requires you to hack with your build system. Actual file IO is not a problem. You could also take a look at PhysicsFS, which abstracts some of this stuff, but it isn't very handy with C++.
Networking is not a major issue. All you need is BSD sockets/winsock and a few #ifdef's. But it's a little bit of added trouble. Endianness is also important with network code.
Endianness and word sizes are something that you should be careful with.
-Riku
I have written several small cross platform games and most problems I have faced have been about filesystems, networking, endianness and word size issues.
One problem with filesystems is that in windows, your data is typically installed in the game directory. In Linux, it's installed typically somewhere in the /usr directory. I don't know about Mac OS X. Writing to files is another issue, usually you can only write to the user's home directory in Linux, where as most windows users play as administrators. This problem requires you to hack with your build system. Actual file IO is not a problem. You could also take a look at PhysicsFS, which abstracts some of this stuff, but it isn't very handy with C++.
Networking is not a major issue. All you need is BSD sockets/winsock and a few #ifdef's. But it's a little bit of added trouble. Endianness is also important with network code.
Endianness and word sizes are something that you should be careful with.
-Riku
When it comes to code which is pretty platform dependant instead of #ifdef'ing everywhere you might be better served to seperate it out into it's own per-platform file.
For example, when it comes to networking you'd have a header file which defined all the interface and a .cpp file which handles all 'common' functionality, you could then have a .cpp for windows, .cpp for linux and a .cpp for OS X as required to take care of platform specific issues.
For example, when it comes to networking you'd have a header file which defined all the interface and a .cpp file which handles all 'common' functionality, you could then have a .cpp for windows, .cpp for linux and a .cpp for OS X as required to take care of platform specific issues.
As far as I know, SDL abstracts every aspect of the platform layer you need, so as long as you use SDL in every instance you are concerned with the platform you should be good.
Also using data types like:
int32_t to guarantee the size on every platform.
Also using data types like:
int32_t to guarantee the size on every platform.
Thanks for all the replies!
If my understanding is correct, the main areas to look out are for the highly OS dependent operation, such as graphics, audio, networking, threading, file IO. Also I need to look out for anything that assumes the internal structure of a base data type.
If I am to use SDL, OpenGL and a cross-platform audio library, then that should deal with most of those issues for me. The only concern may be the location of files that I need to save.
Regardless of what I use, I should regularly test the code on the platforms I wish to support with full compiler warnings to ensure that porting bugs do not seep into the code base.
Is that an appropriate summary thus far?
I may look into getting that book at some stage too. Thanks for that suggestion.
If my understanding is correct, the main areas to look out are for the highly OS dependent operation, such as graphics, audio, networking, threading, file IO. Also I need to look out for anything that assumes the internal structure of a base data type.
If I am to use SDL, OpenGL and a cross-platform audio library, then that should deal with most of those issues for me. The only concern may be the location of files that I need to save.
Regardless of what I use, I should regularly test the code on the platforms I wish to support with full compiler warnings to ensure that porting bugs do not seep into the code base.
Is that an appropriate summary thus far?
I may look into getting that book at some stage too. Thanks for that suggestion.
also have a look at boost.filesystem
Check out poshlib, and Write Portable Code (book), by Brian Hook (of Id software fame).
Great, I'll have a look at POSH. If anyone has read both books, if I were to get just one book would recommend Hook's Write Portable Code or Goodwin's Cross Platform Game Development?
Numbered in order of importance:
1. Test, test, test.
2. Use portable libraries. Read the documentation. Many "portable" libraries are only "mostly portable"; some features don't work on all systems. Worse are features which work on all systems but not quite the same way. Make sure you know which are which.
2. Use standard, well-defined, C++.
Testing can help you avoid most issues. If you can arrange to run automated unit tests on your XP and OS X systems automatically, rather than when you remember to switch systems and test, then many portability problems will be diagnosed right away. If you only have two computers, running User-Mode Linux on Windows or normal Linux in VMware will eliminate the time you need to spend rebooting into another OS. VMware will also allow you to test your program on (what appears to be) quite primitive hardware, should that be of interest to you.
1. Test, test, test.
2. Use portable libraries. Read the documentation. Many "portable" libraries are only "mostly portable"; some features don't work on all systems. Worse are features which work on all systems but not quite the same way. Make sure you know which are which.
2. Use standard, well-defined, C++.
Testing can help you avoid most issues. If you can arrange to run automated unit tests on your XP and OS X systems automatically, rather than when you remember to switch systems and test, then many portability problems will be diagnosed right away. If you only have two computers, running User-Mode Linux on Windows or normal Linux in VMware will eliminate the time you need to spend rebooting into another OS. VMware will also allow you to test your program on (what appears to be) quite primitive hardware, should that be of interest to you.
Quote:
Original post by Nathan Baum
Testing can help you avoid most issues. If you can arrange to run automated unit tests on your XP and OS X systems automatically, rather than when you remember to switch systems and test, then many portability problems will be diagnosed right away. If you only have two computers, running User-Mode Linux on Windows or normal Linux in VMware will eliminate the time you need to spend rebooting into another OS. VMware will also allow you to test your program on (what appears to be) quite primitive hardware, should that be of interest to you.
Thanks - that makes sense. Plus that VMware functionality looks pretty useful. I'll try out the free VMware Player to see what it can do.
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement