I'd like to share a few experiences about porting code and writing 
portable code. Scroll down, if you just want my thoughts on how portable 
the Source Engine is.

Recently I've been porting my in-development digital distribution 
platform to GNU/Linux for the fun of it. Naturally, most of my code 
didn't work right out of the box. But it is worth that several 
subsystems actually worked at the first attempt, or with an edit or two. 
For instance, my string system and parser classes/functions compiled 
right away.

However, stuff like accessing the filesystem, multithreading, user 
interfaces, networking, and so on didn't work because it relied on the 
Windows API. The interesting part here is that POSIX does things 
differently; but almost in the same manner as Windows. That means for 
each Windows API call you use, there is often one or more POSIX calls 
that does the same thing (if you add a little abstraction, that is).

Now, some of you heavily suggested the use of #ifdefs all around the 
code. You should not use #ifdefs each time you rely on platform specific 
behavior, but only in shared function calls or in headers. For instance, 
if you have to open a file. On Windows you can call the CreateFile 
function, while POSIX supports the open function. That means for each 
file opening, you need to write something like.

#ifdef linux
int FileHandle = open(Path, Flags);
#elif defined(WIN32)
HANDLE FileName = CreateFile(...)
#endif

Naturally, this isn't very pretty. And if this was used all over the 
Source Engine you would spend a lot of time writing #ifdefs and checking 
platform specific documentation. However, I am not saying #ifdefs are a 
bad idea. But instead of using them all over your code, you should move 
them to a shared class or function that simply implements all this once. 
In my code, I declared an abstract baseclass called MaxsiFileSystem that 
implements all the common functions to access the local filesystem. So 
now when I wish to open a file for reading, I would call:

MaxsiHandle FileHandle = FileSystem()->OpenFile(Path, MAXSI_FILE_READ | 
MAXSI_FILE_SEQUENTIAL);

This additional layer of abstraction makes it very easy to add support 
for new platforms as you just have to define a new child of the abstract 
baseclass. I have also added such a layer for my Window System. This 
means I call my own APIs in my actual code, and then it redirects it to 
the Windows API or GTK+ depending on your platform.

You might also have noticed I implemented a FileSystem() function, in 
the same manner I have implemented a WindowSystem() function that 
returns the window system in use by the current function/class. This 
makes it easy to simply swap the window system on the fly. For instance, 
my source mod links against my distribution platform (LGPL) and my mod 
then implements some of these interfaces. It could implement the 
MaxsiWindowSystem class using VGUI and then my programs could be 
natively drawn ingame with mininal work.

Other porting issues includes how the VS compiler breaks a lot of the 
C99 standard. To counter this, I have simply declared a lot of macros in 
my header files that replaces platform specific behavior. #defines are 
very powerful for this. For example, to declare a thread-specific 
variable, I would use this header define:

#ifdef __GNUC__
#define MAXSI_THREADED_VARIABLE __thread
#else
#define MAXSI_THREADED_VARIABLE __declspec( thread )
#endif

And then use the MAXSI_THREADED_VARIABLE macro to declare each threaded 
variable. My experience is also that the GNU Compilers throw much more 
errors and warnings than the Visual Studio compiler - and it is often 
right to do so. Visual Studio teaches you to write bad 
standards-breaking code, even if you just compile using MinGW you will 
get to fix a lot of issues that makes your code rather non-portable. 
(Like avoiding Microsoft-specific extensions to the C Library, in some 
cases.) But Microsoft did break the standard enough that you might need 
to use some of the above methods for porting, just to get your code 
compiling using MinGW.


Now to return to the Source Engine. In my experience a lot of stuff in 
the SDK code is already defined using interfaces, classes, and such. 
That means the actual porting issues have been outsourced to the Engine. 
This, in turn, means that the SDK code will be rather easy to port 
compared to the Engine. Fortunately, as the Source Engine already is 
highly modular using interfaces, it is easy to just swap a DX renderer 
with OpenGL. As such, they already have the framework to make their code 
work on new platforms - all they have to do is implement their 
interfaces using the local system calls. If you start to do this on the 
low-level interfaces and move upward, then soon your program starts 
working in all its glory.

As for a Steam Client for GNU/Linux. It exists. I lost the link, but it 
seems that Valve uploads nightly builds of their Steam Client, and each 
day it works just a bit better. Last I heard, the Steam Client actually 
logged on and the actual UI was partially drawn. I am not sure why Valve 
is so silent about this - perhaps it's just experimental, or they they 
to make a big deal about it, like they did with the Mac. Seriously, when 
are they gonna shut up about it? Last I saw was that they made a funny 
TF2 comic about it.


Porting programs to Linux hasn't been very hard for me, though it is a 
lot of work, if you want to do it properly. It seems that the Source 
Engine is already highly portable and GNU/Linux build doesn't seem too 
difficult, as it seems from the nightly builds. There is no doubt about 
whether we need a client for GNU/Linux, it is just a matter of time 
before they announce and release it.

_______________________________________________
To unsubscribe, edit your list preferences, or view the list archives, please 
visit:
http://list.valvesoftware.com/mailman/listinfo/hlcoders

Reply via email to