161 lines
6.8 KiB
Text
161 lines
6.8 KiB
Text
|
|
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 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
|