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