A while back, I came accross a game called Frozen Bubble, which is a Snood clone written in Perl using the OpenGL bindings. I was rather offended by the whole concept, and said something like "Arg! Thats almost as bad as writing a 3D game in Bash!". So, this December during winter break, when I was trying to think of something to do for Mike's Abuse of Technology #6....
"Oh, God! No!!" | <-- (common actual quote) |
But here it is! Bwa-Ha-Ha! All the normal languages have OpenGL support, and some scripting languages like Perl, Python, and PHP have OpenGL bindings, and now Bash does too!
If you have to ask why, you are not a member of the intended audience. Please
go on about your business and accept my apologies for this distraction.
(tribute to Bob Zimbinski [1])
Right. So I gave it an interface just like all the others it uses: a simple command that can be given parameters and fed commands on standatd input.
When invoked, CmdlineGL starts a server which creates an OpenGL window, and then either reads input from a file or from standard-in. To deliver commands to it, a script can pipe standard-out to the program, or write to a fifo, or other creative methods. The script sends lines of text to the server that tell it to execute any of the supported OpenGL, GLU, or GLUT API calls. CmdlineGL supports textures, display lists, quadrics, and just about all of the usual gl calls.I'd argue for Bad and Right. Keep reading.
Its definately slower than games written in C, but might not be as slow as you think: CmdlineGL is written in straight C with as little overhead as possible, to the point of doing all text operations on the same character buffer filled by the call to 'read', and sacraficing reusability for performance (and wow, was it ever liberating!). It also uses a statically defined hash table to map command names to function pointers. To top things off, it uses a Red/Black Tree to manage the names of objects like display lists, quadrics, textures, and fonts.
Because of the support for display lists, any part of your game that doesn't move can be compiled into the OpenGL end and replayed as a single line of text. And, in a 3D application, most of the time is spent drawing pixels and texturing them, and this is probably done by the graphics card, so there's plenty of CPU time left over for running data through a pipe and a socket, and running string-to-float conversions, and repeatedly forking hundreds of times per frame per second, especially with the speed of today's computers ;-)
Well, mostly just demos, but a game engine of sorts, yes.
(I never finished the flight simulator)
Lets review some of the obstacles involved with a real-time game, and our old friend/nemesis Bash:
The only natural way bash has to receive feedback from the user is a text stream. One other possibility is for it to call a special program which would contact the server, get some information, and give it back to bash in the form of a integer result code, but this seemed particularly slow. So, I came up with the idea that the server could simply write user actions to stdout, and this could be piped to the stdin of the script.
I formed this cyclic stream using a fifo:
And then the rest was simple: script reads user input from stdin, script generates frame of output commands to stdout, and repeat.
The next problem of course, was that if the user doesn't type anything, a call to "read" will block. Read actually does have a timeout feature, but in granularity of seconds, and a timeout of 0 doesn't seem to work. So, I just changed my design a little to ensure that there is always a line to read from stdin. Thus the existance of the "cglEcho" command. At the beginning of the script, and after reading user input on each frame, I write "cglEcho _____". CmdlineGL sees the echo command, and echoes the argument back to me. Then, next time I read through stdin I stop after seeing "_____" because it means I've processed a whole frame's worth of user input.
The cglEcho command actually became obsolete, with the arrival of cglGetTime, as you will see in my next point. I left cglEcho as it was, though.
One of the more classic video game problems is making sure that the game runs the same speed on all machines. When playing a video, you need to keep the frame rate steady, and when running a game engine you need to make sure that the framerate doesn't affect the speed of the gameplay.
Since Bash doesn't really have a way of keeping time (other than 'date', haha) I decided to put frame-limiting and time counting on the CmdlineGL side. When CmdlineGL starts it marks the current time, and thereafter can subtract that from the current time to get elapsed game-time. There are two ways to take advantage of game-time: The first is using "cglSync T" which tells CmdlineGL to wait until time T before processing any more commands. The second is "cglGetTime", which causes CmdlineGL to write the current time (in milliseconds) to stdout. The script can then process time differences to adjust the progress it displays in each frame.
Additionally, there is a cglSleep function, which causes CmdlineGL to sleep for a number of milliseconds. This would be useful in animated text movies to put a pause at certian points regardless of the frame rate.
So, one of the first major obstacles I hit when porting my walking robot demo from C to bash was the lack of floating point support. It seemed obvious that calling bc for all my math operations would be a bit slow. At first, I tried writing functions that would string-manipulate a floating point number to fixed point, perform the calculation, and then string-manipulate back. This involved a lot of back-quote substitutions, and was slow. So, I needed a way to use integers. Why not just use glScale? Well, mainly because that won't scale the angles of the glRotate command, and the robot was looking a little jerky with whole degree rotations.
So... against my instincts for good design, I added a 'feature' to CmdlineGL that causes it to look for fixed-point numbers any place it would normally expect floating point. Implementing this was rather simple, though: I made a "cglFixedPt <x>" function that takes a parameter X, calculates 1/X, and multiplies all future floating point numbers by this amount. The fact that the script is sending integers is then irrelevant.
Less time than I wasted on World of Warcraft
If you were thinking along the lines of thanks: bug fixes, implementing more of the OpenGL commands, bug reports, money, pizza, sending Morrowgrain to Zellin on Earthen Ring (if you don't know what that means, don't worry about it), etc.
If you were thinking more along the lines of revenge, you could try using CmdlineGL to write a multiplayer racing game (like a Mario Cart clone or something) in Prolog.
The Windows version is the same as the Linux version. You'll just need to edit the source code a little before you can run it. ;-)
At the moment, the unix-specific things in this code are the asynchronous IO, the fifo's, and the timing. If you replace all the 'open' and 'read' with CreateFile and ReadFile, comment out the fifo stuff, and replace gettimeofday and usleep with GetCurrentTime and Sleep (remember that it'll change microseconds to milliseconds), it just might work on Windows. Then all you need is bash, which can be gotten from Cygwin. You will still need some way of creating the stdin/stdout loop between the script and CmdlineGL, and I'm not going to even investigate that.
I really can't see any reason why you'd want to do this on Windows (unless perhaps your graphics card isn't accelerated under X). One alternative is to install Cygwin and then export the display of your script to the windows box. That only works of you have two machines, of course. Anyway, it's your time, but if you get a windows version working I'll gladly host it.