[MUD-Dev] TECH DGN: a few mud server design questions (long)

J C Lawrence claw at 2wire.com
Wed Jul 25 19:34:47 New Zealand Standard Time 2001


On Mon, 23 Jul 2001 09:57:00 -0500 (CDT) 
Robert Zubek <rob at cs.northwestern.edu> wrote:

For most of this I'm going to be writing about what I do or have
done rather than the more interesting general case.

BTW: I'd much prefer it if you lost the e e cummings bit and used
proper capitalisation.

> assuming that each event in the world gets parsed down to a
> standard representation, such as: { action: HIT, agent: PLAYER123,
> patient: NPC456, modifiers: ... } (not necessarily as a data
> structure, but in that the relevant information gets extracted on
> what the action is, who performs the action, and what/who the
> action is performed on.)

Not continuing the original perversion of the definition of "event"
that I ignorantly popularised on this list some years ago, I define
event^H^H^H^H^H^Hmessage as a structure that contains a target
address (who the message is to be delivered to), a message
body/data, and a time at which a delivery attempt is to be
attempted.

> now, how should these events get handled? presumably there's a big
> switch statement somewhere (or better yet, a data-driven dispatch
> table) directing the messages to subroutines that handle them -
> but what would be a good first-cut division of how these messages
> should be handled? 

You might like to search the archives for the discussion surrounding
my dispatchor/executor/thread_pool model.  Briefly:

  Messages are generated by various sources, encluding player
  actions and the execution/delivery of other messages.

  Messages are received by the dispatchor and stored on a priority
  queue ordered by time and sub-ordered by priority.  When a message
  expires the dispatchor pops it from the queue and sends it to the
  executor.

  The executor adds it to a local pool of pending messages (this is
  actually slightly complex, but the details are beyond this
  conversation) which it then hands off as possible to a ready
  member of the a thread pool which delivers/executes the message.

The guts are actually minorly messy and somewhat intricate,
especially below the executor.  If you're interested hit the
archives and look into the whole business of the Compare&Commit
model and event rescheduling, etc, or if you're really interested,
ask.

As to how to determine the target address of an event, I actually do
that in one of two ways:

  For parse events (eg player messages which need to be parsed) I
  generally log a parse event to the dispatchor with the command as
  body.  That event executes and parses the command into an
  objectified form and then logs that back into the dispatchor as a
  new message.  

  Messages which are generated by other message deliveries generally
  don't need the pre-parse step, and so are pre-objectified and thus
  ready for ultimate delivery.

As for how the look up is done, how message-to-XXXX is translated
into either f(XXX) or XXX(), well, that's pretty simple: function
pointers are your friend and tables of function pointers are even
better.  

All message targets devolve to object methods references, which
gives my a simple tuple of ObjectID, MethodName, and
MethodArguments.  Each object carries a hash table of mappings from
MethodNames to entry points so a simple hash dereference gives me
the entrance point to the method code to start the soft code
interpreter on.

> ie., should the agent who performs the action have the code that
> actually performs the action? should the recipient (ie. patient)
> handle the action it receives? or maybe all actions should be
> handled by global handlers? perhaps a mixture of all three?

I've been (was mostly) a fan of contextual binding and so restricted
all object methods to only being able to mutate their parent object
(I transparently exposed exported object attributes via hidden
accessors).  As such everything else devolved to message passing
among objects.  I did this explicitly so as to provide a clean
abstraction layer for database distribution, modeling off CoolMUD's
YO protocol.

> for the first example, we may wish to hang event handlers off of
> the patient of the action - so that when i hit another player,
> their handler will subtract the appropriate amount of hit
> points. this makes it very easy to add new objects that support
> novel kinds of actions (eg. adding a beverage to drink), but
> breaks in case of actions that have no patients (such as emotes:
> wave, say, etc.)

<nod>

Notice that I explicitly do not define event handlers.  Events are
messages.  Messages are purely an encapsulated form of an object
method invocation and the arguments to be provided to that method.
There are no event handlers.  There are messages and first class (ie
exposed/public) methods on objects.  That's all.

> having event handlers hooked to the agent will solve the problem
> of patient-less actions, but complicates the object model - now
> when i add a new target object with a novel action (such as a
> beverage), i also have to go and change all the classes that can
> use it. 

Object inheritance denoted method inheritance.  I used multiple
inheritance with a depth-first dynamically built method tree.
Certainly not the way I would do it now, but it works (now I'd go
for something akin to Java's interface concept or at least a much
more lightly defined object model).

> in addition to imposing unnecessary code dependencies, this also
> encourages me to cut up the object hierarchy according to the
> differences of which objects can perform what actions - and i'm
> not convinced that's a good way of organizing an ontology.

Bingo.  There are quite good arguments for all object actually being
of the same compound type and only differing by internal attribute
state.

> finally, selecting the event handler based on just the action is
> probably the simplest, but i would imagine has the worst effect on
> extensibility - since now we have to change all actions when we
> add new types of possible agents or patients. perhaps some sort of
> mix is in order - global actions for certain general types of
> events (such as combat), and then action handlers local to the
> agent (or the patient) for the more specialized actions.

Given an inheritance model, or at least a uniform object aggregation
and namespace derivation model I don't see much reason to worry
about this.  You end up implementing an indirection table to refer
to non-locally defined methods, and then running all lookups against
the set of that and the locally defined table.  Its really just a
question of namespace definition and namespace determination.  No?

> which of these gets used most often in actual muds? my suspicion
> is that it would be the last one (mix of global and local actions)
> - but wouldn't it also complicate extending the world?

Have a look into the various runtime morphic servers, in particular
Cold and LambdaMOO.  It might be particularly useful to read up on
the various times that base objects in both servers were edited,
critically crippling the object hierarchy, as well as their methods
of recovery.

> 2. how to handle long-lasting events

Do an archive search for "digging the panama canal".  Historically
that was our base scenario for discussing long-lived player-based
in-processes.  Most of your concerns and questions have been
addressed there.

> in this architecture, how does one elegantly handle temporally
> extended events - for example, a spell that takes several minutes
> to cast? the handler obviously can't block while waiting for the
> event to finish.

I took the multi-threaded way out as I considered that easier and
more elegant than either going for cooperative yield models, or an
in-system pre-emptive scheduler with all the related concerns
surrounding context push/pops, re-entrancy, etc.

> one could imagine a number of solutions inspired by operating
> systems research - perhaps implementing a fake
> continuation-passing style (as a kind of non-preemptive
> multitasking: a temporally-extended action would be expected to
> initialize itself, do a fraction of the necessary processing, then
> put a new fake event on the queue saying "continue the spell
> event", and exit). 

Which is what I did.  There are second order questions which arise
there in regard to what operations may continue in parallel with
such a long lived operation, and when and how conflicts are both
detected and addressed.

> or, if threads were cheap on that system, one might consider
> actually spawning a new thread for each call to the handler, and
> let those that take a while just run quietly in the background
> until they finish.

Thread fork times are rarely cheap.  Its easier to build and
maintain a thread pool that you fork once.  

More interesting, and likely far more complex once you go the thread
route is the transactional model.  How do you guarantee logical and
data consistency across multiple threads competing for r/w access to
shared objects?

It was in solving that latter problem that I adopted the C&C model.

> i'm also curious what are some standard ways of propagating
> messages in a mud engine. for example, when the player performs an
> action, the engine processes the event, returning some result
> value. at that point the involved parties (agent, patient) will
> need to be informed of the result of the action and then, if
> applicable, others in the area may be informed of what happened as
> well.

<nod>

> but once the handler figures out the return value for the event,
> what are some standard ways of propagating this value? does the
> handler send the result to the closest binding container (such as
> current room) that propagates it among its elements? does it
> return it to the agent and the patient, who propagate it
> themselves? or would the handler actually manually figure out the
> list of proper recipients for this message, and hand it to them
> individually?

For me every method call has the potential for generating two or
more messages:

  1) to the parent object.
  2) To the object that sent the message
  3) To any objects in the parent object's containment lists (there
  are multiple forms of containment and so there may be many
  messages here)
 
And as a second order effect, upon process compleation, via the
watchers (state-change monitors) to any objects that are interested
in state changes in the local object.

> each has a drawback: for the first (container passing the
> message to its elements), there are cases when these messages
> should be propagated further down to elements-of-elements
> (eg. people in a cage in a room should be able to see what the
> people in the room around them are doing), but it could grind the
> server to a halt if all of the contents of everyone's pockets
> would be getting all the messages about what's going on in the
> room. 

Not really.  A message gets sent to the room.  The room sends a
message to the cage.  The cage sends a message to the prisoner.  The
prisoner sends a message to his pocket.  The pocket sends a message
to the coin, the whistle, and the small stone.  Each one of the
layers can me executed as a separate message independently scheduled
through the dispatchor and executor (which is what I do, along with
the appropriate short circuit logic).  

The interesting case is not the multi-level case, but the case of
10,000 players all in a single room, all moving about and talking
loudly.  

> for the second one (agent doing the propagation) and the third one
> (handler manually propagating the result to relevant parties),
> it's a matter of maintaining abstraction boundaries - the agent
> and the event handler really shouldn't be worrying about making
> sure everybody saw what happened, it should be handled by some
> separate component.

I send the messages.  Each recipient is responsible for determining
whether or not they are interested.  Filter objects (which are
really just mostly-transparent containers and a fairly simple
variation on spoofs) intercept those messages which the target
object should never receive for whatever reason.

--
J C Lawrence                                    )\._.,--....,'``.	    
---------(*)                                   /,   _.. \   _\  ;`._ ,.
claw at kanga.nu                                 `._.-(,_..'--(,_..'`-.;.'
http://www.kanga.nu/~claw/                     Oh Freddled Gruntbuggly
_______________________________________________
MUD-Dev mailing list
MUD-Dev at kanga.nu
https://www.kanga.nu/lists/listinfo/mud-dev



More information about the MUD-Dev mailing list