empserver/doc/threads

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, &sect)/putsect(&sect). 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