Re: Player positions

From: Daniel A. Koepke (dkoepke@california.com)
Date: 01/15/00


On Sun, 16 Jan 2000, Ron Martin wrote:

> Both would require some recoding.  I am planning to redo all my
> mobiles anyway, so I may go with option 1.

Not speaking in an official capacity:

The formats of the database files use too many numbers for enumerated
constants, anyway.  E.g., 8 instead of "standing" -- why build the code
system to rely upon the position of the numbers, then restrain the coder
from changing those positions by adding an entire layer that relies upon
the positions as they are?  IHMO, this is such a large design flaw that it
runs into the domain of "bugs".  If CircleMUD is to be truly a clean-slate
for which programmers to build their own Mud from, why are we preventing
redesign and modification of the code with the stock world?

To answer my own question, in a more official capacity:

Backwards compability is important here and the 3.0 beta has gone on for 5
years now -- adding more time to that for the sake of properly expressing
enumerated constants in the database (especially when C still doesn't have
a proper way of expressing enumerated constants -- 'enum' works, but it
throws everything into a global namespace, which forces you to prefix
enumerated constants to avoid name collisions...Ugh) is ridiculous.

Just out of curiousity:

When is enough *enough*?  That is, when is beta software sufficiently
stable to where the developers say, "The other problems are with the
design, and as much as patching the design with kludges emulates progress,
real progress won't be made until we re-approach the problem from the
beginning?"

Anyway, I'm interested in what architecture people would really want from
4.0.  I have some ideas that I've yet to toss to the rest of the team, but
I might as well bring them up here for everyone to see.

First, the big question, what language is CircleMUD 4.0 written in?  My
response?  I dunno.  If 4.0 goes the route of 3.0 and has much of the game
mechanics and object handling in the kernel, then definitely C++ or
another object oriented language.  If, however, we have a sufficient
database backend where the mechanics of the objects can be held indepedent
of hard-coding (such as, say, would happen with Berkeley DB or SQL) and a
sufficient virtual machine and scripting language for implementing the
core mechanics, then perhaps ANSI C is the way to go.  I know George ran a
poll on this on the Ceramic Mouse some time back, but maybe someone will
have a fresh take on the matter?

What is more clear to me is that 4.0 should be multi-threaded.  A while
ago George posted (privately to the developers) a summary of various Event
Models for the networking code.  These included some threading models that
I (or he) had tried, some we had talked about, the current single-threaded
networking event model, and a SIGIO based model.  I have one additional
model that I think is superior to all those dealt with previously:

                               kernel
                              /  |   \
                            /    |     \
                   timed event   |   inactive thread
                    service      |    expiration service
                                 |
                             networking
                              server
                                 |
             +---+---+---+---+---+---+---+---+---+---+
             |   |   |   |   |   |   |   |   |   |   |
             +---+---+--- IO thread pool +---+---+---+
                                 |
                           ... --+-- ...
                      temporary IO thread pool

As you can see, there's three basic services running from this
hypothetical 4.0 kernel, the timed (or "user") event service, which allows
for the event-driven nature of the Mud; the inactive thread expiration
service, which sets up an event with the user event service to check all
the threads registered with it for activity (the registered threads should
occassionally call some function to update their status with the
expiration service -- not all threads need to be registered with this
service), and if it hasn't been active for a while, the kernel either
restarts the thread, kills it outright, or sends it a SIGUSR1 signal so
that the thread can try to restart itself.

Lastly, is the networking server service (:P).  Basically, a new thread is
spawned for the (blocking) TCP/IP server.  All it does, after creating and
binding the server socket, is call select() and wait indefinitely for some
IO event[1].  When it gets some event, it tries to signal a thread in its
IO thread pool that's waiting on the server's IO_event_condition.  When
the condition is signaled, one of the IO threads receives the information
for the socket that has the IO event and what type of event it is.  The IO
thread then handles the event, leaving the server to go back to sleep.
The IO event thread function probably looks something like this,

    void *
    netHandleSocketEvent (void *v)
    {
        netIOEvent_t *iov = (netIOEvent_t *) v;

        switch (iov->eventType)
        {
            case iovRead:
                helperPerformRead (iov->socket);
                break;
            case iovWrite:
                helperPerformWrite (iov->socket);
                break;
            case iovException:
                /*
                 * process OOB data
                 */
                break;
        }

        return (NULL);
    }                                                   ,

where helperPerformRead() would read from iov->socket->fd, doing the
appropriate line breaking (note that, unlike 3.0, it would not be in
charge of performing ! or ^a^b substitutions; that would be handled
higher-up, this would do just the base read and line-splitting).  Since
it's done in a separate thread, a blocking read is fine and it can call
the command interpreter or whatever immediately after.  You can determine
what helperPerformWrite() would do...

The temporary thread pool doesn't exist by default.  IO threads are added
if they're necessary because the default ("static") pool is filled.  This
pool is more aptly referred to as the "transient" IO thread pool, since
the threads here can be removed by the CM kernel's inactive thread
expiration service (whereas, the "static" pool is much more fixed in size,
with reductions to it being made only rarely when the number of threads in
the pool outnumbers the number of sockets we're handling, in which case
it's pruned down to a happy number to keep down system load when CM4 is
inactive).

Services not shown include the database service and the virtual machine.


(I remembered my footnotes for once!)

[1] Note that select() returns almost instanteously because most sockets
    are available for writing all of the time.  It might be better to
    have the server waiting on something like,

        .
        .
        .
        struct timeval timeout;

        timeout.tv_sec = 0;
        timeout.tv_usec = SOME_TIME_HERE;

        while (!activity)
        {
            activity = !(condWait (srv->outputCond, &timeout));
            if ((t = select (srv->topFD+1, &r, NULL, NULL, &notime)) < 0)
            {
                if (errno == EINTR) continue;
                logError ("select (%d)", srv->topFD+1);
                exit (1);
            }
            activity = (t > 0);
        }

        .
        .
        .

    Of course, the details are a bit sketchy, since I haven't done an
    implementation of this.  We might be just as well doing a non-blocking
    write to a socket rather than have buffered output.  <shrug>

-dak


     +------------------------------------------------------------+
     | Ensure that you have read the CircleMUD Mailing List FAQ:  |
     |  http://qsilver.queensu.ca/~fletchra/Circle/list-faq.html  |
     +------------------------------------------------------------+



This archive was generated by hypermail 2b30 : 04/10/01 PDT