Socket-Based Scripting

Most projects will work well if you choose one of the scripting methods just explained. Whether it's a custom-built language, something like Lua, or the JNI, we have covered a lot of ground that should help you in most scenarios. For improved flexibility, we have even taken a look at how a dynamically linked library scripting system might work.

However, there is an additional technique that can be useful when extreme flexibility and safety are required. We could choose to code our application's scripts using a DLL. But a DLL can contain malicious code that can make our application misbehave or even crash. Besides, DLLs (or whatever equivalent mechanism you end up using) are highly platform specific.

So, we will try to come up with a new solution that provides:

  • A separate running environment for the script, so it can be safely shut down if necessary

  • A platform-independent infrastructure

The solution is to implement your scripting modules by means of sockets. Sockets (explained in detail in Chapter 10, "Network Programming") are the standard mechanism to implement network connections using the TCP/IP protocol. They can be imagined as bidirectional queues. Both peers in a communication stream can write data to one end of the socket, and the other computer will then be able to retrieve it by reading from the opposite end.

As you might have already guessed, one end of the socket will belong to the main game module, and the other end will be at the script module. Because they will be two separate applications, a faulty script module will not necessarily affect the game. On the other hand, socket programming is largely standard. Sockets are supported by PCs, UNIX/Linux boxes, and Macintosh computers, and will probably be supported in Internet-ready game consoles. They can be coded from C, C++, Java, and a host of other programming languages.

But sockets have two unexpected benefits. First, the script module can be a compiled binary, which means performance will be better than in other scripting strategies (DLLs excluded). Second, the script does not need to be physically on the same machine as the main game code. Scripts are executed through Internet connections, which can be handy in certain scenarios.

Notice, however, that these benefits come at a cost. The scripting language will have to be your programming language of choice (C, C++, and so on). Custom routines can certainly be added to make life easier for script writers, but overall you get less control of the syntax (as opposed to a custom-built language). In addition, socket communications are not very well suited for time-critical tasks. Admittedly, networks are getting better all the time, but some projects will find sockets too limiting in terms of performance not for the script itself, but for the time required to pass arguments (and return results). Another issue you must be aware of is that sockets can be tricky to maintain if lots of scripts must be handled. In the extreme case, your game can begin to resemble a massively multiplayer title with scripts instead of gamers.

Let's examine the creation of a socket-based script system. The first step is to decide the taxonomy of the callable module. Clearly, we want maximum efficiency, so we will keep the module loaded in memory instead of executing it every time. Besides, by making it persistent, we can make modules that keep their own state variables and use them from one iteration to another.

Thus, modules will by started by the parent, and once booted, will enter an infinite loop that will remain until told by the caller to shut down. While on the loop, they will watch the socket for requests, process them, and optionally return any results to the calling process. Here is the coding idea for such a module:

 // open socket to main game module while (!end)       {       // read opcode from socket       switch (opcode)             {             case QUIT:                   end=true;                   break;             // opcode specific operations go here             }       } // close socket 

This algorithm is quite efficient both in terms of speed and CPU use. Concerning speed, keeping the module loaded and the socket open ensures that communication overhead is kept to a minimum. But keeping many scripts in memory can cause some problems, especially if they are CPU hungry. That's why we keep the socket open and read from it at the beginning of each iteration. As you will learn in Chapter 10, most sockets are usually blocking. This means that reading from an empty socket causes the process to halt until new data arrives. Thus, the "read socket" call somehow acts like a semaphore. It pauses the script process until a command arrives (so CPU use is virtually zero). Once a new request arrives, the process is resumed, the request is dealt with in one of the switch opcode-handling routines, results are returned using the same socket, and the process is put to sleep again until new commands are received. The complete process is showcased in Figure 9.3.

Figure 9.3. Socket-based scripting transparently integrates the network into the game engine.


Notice, however, that the problems we had with Java scripting are still present here. For example, it is hard for the script to access data structures or algorithms stored on the core game engine. When we were using JNI, we saw how making a call from the script to native C/C++ code was a little complex and had associated overhead. With sockets, the situation gets even worse. The script is simply designed to receive parameters through the socket, perform local calculations, and return a result.

There are two solutions to this problem. The first one is the poor man's solution: sending commands through the pipeline to the host application using a predefined protocol. This can work with simple use cases, but when lots of commands are present, will quickly become unmanageable. A more elegant solution to this is using the Remote Procedure Call (RPC) mechanism to ensure that we can make calls to our code from the script module. RPC allows developers to register functions at compile time so they are visible by both peers. A routine implemented in the core game engine can be exposed to the script and the other way around. This makes compilation a bit more complex because we need to use tools like RPCGen, but the benefit is obvious.

Core Techniques and Algorithms in Game Programming2003
Core Techniques and Algorithms in Game Programming2003
Year: 2004
Pages: 261 © 2008-2017.
If you may any questions please contact us: