The basic structure of components in UnterMUD is very strongly separated into "low level" and "high level" routines. The object is to make it feasible to program at a high level and rely on the lower level taking care of the nit-picky stuff, or to have a grand old time doing bit-fiddling in the lower-level routines while people worry and argue about the format (yawn) of the WHO list. Wherever possible, similar components that are built on top of eachother use (and should continue to use) similar interfaces, and preference should be given to making function calls that set static variables, rather than using globals. UnterMUD does use some globals but they have been chosen carefully - globals should only be used for parts of the code that are absolutely un-implementation-dependent. IE: variable names, types, etc. If you do not understand this, please do not insult the author's code by buggering it up. High-Level routines: -------------------- The highest level routines are the commands, all of which are implemented in the CMD directory, and are linked into the program ONLY through the master command table "cmd.c". The higher level routines may take advantage of lower-level calls such as cache_get() to get or cache_put() to put objects, without worrying about what is really happening. Low-Level routines: ------------------- Low level routines encompass list manipulation, setting object attributes, searching for object attributes, traversing lists, etc. Some of the lower-level routines are built on top of basic utility code that really has nothing to do with a MUD, such as the stretchy buffer libraries. A rough picture of the way things depend on eachother is: (descending from highest level to lowest level) ----------------------------------------------------- main program loop | V network I/O layer (generic interface) \ (say(), ioloop(), iodrop(), etc) | V command evaluation layer \ (run(), runline(), enargv()) [commands dispatched through table in cmd.c] | V INDIVIDUAL COMMANDS [may call upon a variety of lower level code] \ | \ V | object database access | | | V | cache layer (cache_get(), cache_put(), cache_check()) | [if cache layer cannot fulfill, may pass to db layer] | | | V | db layer (db-get(), db_put(), db_check()) | [calling interface to db layer is 100% same as cache] | | | V | OIF code - object interchange representation | [may or may not be present - handles object encoding] | | | V object attribute support (objgetattr(), objsetattr(), etc) | V object list support (lstadd(), lstnext(), etc) Typically commands will consist primarily of combinations of accessing objects from the cache, modifying them or examining them, sending messages to the user, and returning to the command dispatch loop. Ideally, adding extensions to the MUD will be a matter of figuring out what level you are trying to program into, and using existing support, rather than having to create it from scratch. The flow of control in the MUD resembles: ----------------------------------------- main entry point | V cache/database initialization | V network layer initialization | V game initialization/configuration | V MAIN LOOP (execute until shutdown) <-----\ | | V | call network layer | network layer multiplexes input | hands it off one line at a time | | | V | command dispatch/lookup | | | V | individual command | | | | | V | END OF MAIN LOOP | | | V | possible maintenance (cache sync, etc) | | | \-------------------------------------/ Throughout the run-cycle the level of programming moves from the higher level routines (EG: ioloop()) down to the lowest level routines (EG: adding a new object to a player's inventory in create.c) at the individual command level. Server state is maintained through exception flags - IE: the server is shutdown by flagging a shutdown flag that is checked in the main loop, not by calling a shutdown out of a low-level routine. Isolated Routines: ------------------ Certain routines are isolated from all the others deliberately to provide a high level of portability. One example is the cache functions, which take a limited set of commands that are as generic as possible. The cache layer, in turn, makes a set of calls to the database layer that is *exactly* plug-compatible to the cache commands in its call interface. Thus, the database layer can be easily snapped in and out, or rewritten completely to take advantage of a specific system. In fact, the cache layer can be completely thrown away, and the database layer can be "plugged" directly in instead, if the underlying O/S has a database that does efficient caching, or caching is not desired. [you will have to handle locking active objects, but that's another story] The network I/O routines are similarly isolated. The main program loop simply makes successive calls to the io_loop, and trusts that it will correctly multiplex input and make calls to the command execution code where appropriate. Nothing more is assumed. Programmers who decide that some form of "internal access" to the I/O subsystem's data structures (to, say, enhance the WHO list) had better ensure that they do not in any way alter the interface to the layer, or they will be reviled, cast out, and beaten with sticks. mjr. '91