The game has been threaded. Major changes have occurred. Emp_update, emp_login, emp_tm, and emp_player have been merged. Basically, there isn't anything else besides server. It's big. 60,000-lines of code big. The lib directory now contains nine subdirectories: common gen global as player commands subs update lwp The main directory contains five (code) directories: client lib server util h The server has seven basic threads: main: creates accept, killidle, and update scheduler. sets signals, opens files. accept: opens a socket, binds, listens, and accepts new players. creates a player thread for each new socket. player: negotaties the player's login, and then interprets the player's game commands. killidle: eyeballs the logged-in players every sixty seconds and closes the connections of those who have been idle for longer than 15 minutes. update scheduler: Sleeps until update is due to go off, then instructs all player threads currently running commands to abort them. It waits for a few seconds so that this can occur, and then creates the update thread, and schedules the next update. (This scheduler should produce single, accurate updates instead of double-updates and delayed updates) update: The standard update procedure. It runs at high priority and performs no network i/o, so the game effectively hangs while the update is in progress. select: This thread and its interface provides a mechanism for other threads to deschedule until either a file descriptor is read/write ready, or a particular amount of time has passed. When the select thread actually runs, the whole process blocks until the select system call returns. However, select runs at the lowest possible priority so other threads get to run to completion before the select gets executed. Overall Notes: Unit and sector files are kept in-core for Your Viewing Pleasure. (It was actually required in order to merge in emp_update) This means the server will use significant memory for the larger games. Per-player bigmap files have been merged into one EF_MAP file, with each player getting one record. This is also kept in-core. Estimated memory cost of a 64-player 256x256 world game with each player having 100 land units, 100 planes, 100 ships, and a bigmap comes to a little over eleven megabytes of space, including the 700k text segment. the "wai()" command doesn't work yet. Implementation Notes: My Opportunity to Spout Empire Technical Jargon Empire is now a miniature operating system, with all that entails. Threads are not simple to use. I know the threads package intimately, and I was confused several times -- probably because the threads interface I provide isn't all that straightforward, even though it seems like it. Hopefully a second iteration by someone who knows what they're doing will be better. I expect this will be much worse for everyone else who hasn't gone through my experience. Bottom line for all you part-time hackers out there: don't mess with the current process model, or you'll get yourself into all kinds of trouble. Thread scheduling and descheduling happens in the io_input and io_output calls. The higher level interfaces in the player thread to these are the getstarg/getstring and pr procedures. If your process ever has to wait for input or output, it will block, allowing other threads to run. Those other threads may well modify data out from under you, so be prepared to have your shared in-memory constructs (ships, planes, sectors, land units, etc) modified out from under you whenever you do i/o. There's a new player global context structure that is shared amongst all the player threads. When a given thread starts or restarts, it sets the global player variable to the appropriate value. Thus, part of the logic of a "context switch" is the setting of player. If you go and add calls to the lightweight process system, you *must* be sure to set the player variable as the io_output and io_input routines do. Otherwise, things will be extremely confused. (I'm not very happy with this global variable, but I didn't have the gumption to do anything more) Most routines that used to return pointers to static space no longer do so. Instead, you're expected to pass in a buffer which will be filled up by the routine. This hit a *lot* of routines, so check the new syntax before using an old and trusted routine blindly. Any files that are loaded into core (like the sector, map, nation, ship, plane, and land unit files) are shared between all the threads. That's good news. If your thread modifies another player's nat_tgms field, he'll see it next time through the command loop, without anyone having to read anything from disk! Furthermore, he'll have no delay in zeroing out that field, so there won't be the annoying double telegram reports which were caused by the delay induced by emp_tm. Unfortunately, modifications to entries to these mapped files must be followed by the appropriate "write record" command (like putship, putnat, etc) or else the changes won't be stored to disk permanently. Update is the exception to this rule, because it writes all records to disk using ef_flush when it's done. This is important, since if and when the players learn how to coredump the server *and* they've managed to build some object w/o their nation record having been updated, they essentially get the item without paying for it. There are two interfaces to the empire file code: the pointer interface exemplified by np = getnatp(cnum), and the copy interface shown by getsect(x, y, §)/putsect(§). Both still work fine. However, you have to be careful when using the pointer interface not to change things prematurely, because any changes to the pointer actually change the data itself -- including putvar, etc. Some commands use the copy interface now, change some variables, and then decide to bail out of the command when something goes wrong. Be careful if you decide to use pointers and then bail out early. Even if *you* don't write the pointer to disk, other subsequent activity probably *will*, resulting in a surprise for the players. Each player thread gets 64k of stack space + more depending on the size of WORLD_X*WORLD_Y. I hope that's enough. If not, we'll have to remove the larger stack variables and move them into static space. Adding new records to core-loaded (EFF_MEM) files is annoying now. Instead of simply writing out a new record, you have to call ef_extend(type, count) to enlarge the file by count records. Additionally, any outstanding pointers you obtained from that file are now invalid. This command results in a "close/open" for the file, requiring the entire contents to be read in from disk again. Luckily, the only instance I saw of this was the "build" command, and my call to ef_extend does it in groups of fifty. Dave