Go to the first, previous, next, last section, table of contents.


Server Commands and Database Assumptions

This chapter describes all of the commands that are built into the server and every property and verb in the database specifically accessed by the server. Aside from what is listed here, no assumptions are made by the server concerning the contents of the database.

Built-in Commands

As was mentioned in the chapter on command parsing, there are five commands whose interpretation is fixed by the server: PREFIX, OUTPUTPREFIX, SUFFIX, OUTPUTSUFFIX, and .program. The first four of these are intended for use by programs that connect to the MOO, so-called `client' programs. The .program command is used by programmers to associate a MOO program with a particular verb. The server can, in addition, recognize a sixth special command on any or all connections, the flush command.

The server also performs special processing on command lines that begin with certain punctuation characters.

This section discusses these built-in pieces of the command-interpretation process.

Command-Output Delimiters

Every MOO network connection has associated with it two strings, the output prefix and the output suffix. Just before executing a command typed on that connection, the server prints the output prefix, if any, to the player. Similarly, just after finishing the command, the output suffix, if any, is printed to the player. Initially, these strings are not defined, so no extra printing takes place.

The PREFIX and SUFFIX commands are used to set and clear these strings. They have the following simple syntax:

PREFIX  output-prefix
SUFFIX  output-suffix

That is, all text after the command name and any following spaces is used as the new value of the appropriate string. If there is no non-blank text after the command string, then the corresponding string is cleared. For compatibility with some general MUD client programs, the server also recognizes OUTPUTPREFIX as a synonym for PREFIX and OUTPUTSUFFIX as a synonym for SUFFIX.

These commands are intended for use by programs connected to the MOO, so that they can issue MOO commands and reliably determine the beginning and end of the resulting output. For example, one editor-based client program sends this sequence of commands on occasion:

PREFIX >>MOO-Prefix<<
SUFFIX >>MOO-Suffix<<
@list object:verb without numbers
PREFIX
SUFFIX

The effect of which, in a LambdaCore-derived database, is to print out the code for the named verb preceded by a line containing only `>>MOO-Prefix<<' and followed by a line containing only `>>MOO-Suffix<<'. This enables the editor to reliably extract the program text from the MOO output and show it to the user in a separate editor window. There are many other possible uses.

The built-in function output_delimiters() can be used by MOO code to find out the output prefix and suffix currently in effect on a particular network connection.

Programming

The .program command is a common way for programmers to associate a particular MOO-code program with a particular verb. It has the following syntax:

.program object:verb
...several lines of MOO code...
.

That is, after typing the .program command, then all lines of input from the player are considered to be a part of the MOO program being defined. This ends as soon as the player types a line containing only a dot (`.'). When that line is received, the accumulated MOO program is checked for proper MOO syntax and, if correct, associated with the named verb.

If, at the time the line containing only a dot is processed, (a) the player is not a programmer, (b) the player does not have write permission on the named verb, or (c) the property $server_options.protect_set_verb_code exists and has a true value and the player is not a wizard, then an error message is printed and the named verb's program is not changed.

In the .program command, object may have one of three forms:

Flushing Unprocessed Input

It sometimes happens that a user changes their mind about having typed one or more lines of input and would like to `untype' them before the server actually gets around to processing them. If they react quickly enough, they can type their connection's defined flush command; when the server first reads that command from the network, it immediately and completely flushes any as-yet unprocessed input from that user, printing a message to the user describing just which lines of input were discarded, if any.

Fine point: The flush command is handled very early in the server's processing of a line of input, before the line is entered into the task queue for the connection and well before it is parsed into words like other commands. For this reason, it must be typed exactly as it was defined, alone on the line, without quotation marks, and without any spaces before or after it.

When a connection is first accepted by the server, it is given an initial flush command setting taken from the current default. This initial setting can be changed later using the set_connection_option() command.

By default, each connection is initially given `.flush' as its flush command. If the property $server_options.default_flush_command exists, then its value overrides this default. If $server_options.default_flush_command is a non-empty string, then that string is the flush command for all new connections; otherwise, new connections are initially given no flush command at all.

Initial Punctuation in Commands

The server interprets command lines that begin with any of the following characters specially:

"        :        ;

Before processing the command, the initial punctuation character is replaced by the corresponding word below, followed by a space:

say      emote    eval

For example, the command line

"Hello, there.

is transformed into

say Hello, there.

before parsing.

Server Assumptions About the Database

There are a small number of circumstances under which the server directly and specifically accesses a particular verb or property in the database. This section gives a complete list of such circumstances.

Server Options Set in the Database

Many optional behaviors of the server can be controlled from within the database by creating the property #0.server_options (also known as $server_options), assigning as its value a valid object number, and then defining various properties on that object. At a number of times, the server checks for whether the property $server_options exists and has an object number as its value. If so, then the server looks for a variety of other properties on that $server_options object and, if they exist, uses their values to control how the server operates.

The specific properties searched for are each described in the appropriate section below, but here is a brief list of all of the relevant properties for ease of reference:

bg_seconds
The number of seconds allotted to background tasks.
bg_ticks
The number of ticks allotted to background tasks.
connect_timeout
The maximum number of seconds to allow an un-logged-in in-bound connection to remain open.
default_flush_command
The initial setting of each new connection's flush command.
fg_seconds
The number of seconds allotted to foreground tasks.
fg_ticks
The number of ticks allotted to foreground tasks.
max_stack_depth
The maximum number of levels of nested verb calls.
name_lookup_timeout
The maximum number of seconds to wait for a network hostname/address lookup.
outbound_connect_timeout
The maximum number of seconds to wait for an outbound network connection to successfully open.
protect_property
Restrict reading of built-in property to wizards.
protect_function
Restrict use of built-in function to wizards.
support_numeric_verbname_strings
Enables use of an obsolete verb-naming mechanism.

Server Messages Set in the Database

There are a number of circumstances under which the server itself generates messages on network connections. Most of these can be customized or even eliminated from within the database. In each such case, a property on $server_options is checked at the time the message would be printed. If the property does not exist, a default message is printed. If the property exists and its value is not a string or a list containing strings, then no message is printed at all. Otherwise, the string(s) are printed in place of the default message, one string per line. None of these messages are ever printed on an outbound network connection created by the function open_network_connection().

The following list covers all of the customizable messages, showing for each the name of the relevant property on $server_options, the default message, and the circumstances under which the message is printed:

boot_msg = "*** Disconnected ***"
The function boot_player() was called on this connection.
connect_msg = "*** Connected ***"
The user object that just logged in on this connection existed before $do_login_command() was called.
create_msg = "*** Created ***"
The user object that just logged in on this connection did not exist before $do_login_command() was called.
recycle_msg = "*** Recycled ***"
The logged-in user of this connection has been recycled or renumbered (via the renumber() function).
redirect_from_msg = "*** Redirecting connection to new port ***"
The logged-in user of this connection has just logged in on some other connection.
redirect_to_msg = "*** Redirecting old connection to this port ***"
The user who just logged in on this connection was already logged in on some other connection.
server_full_msg
Default:
*** Sorry, but the server cannot accept any more connections right now.
*** Please try again later.
This connection arrived when the server really couldn't accept any more connections, due to running out of a critical operating system resource.
timeout_msg = "*** Timed-out waiting for login. ***"
This in-bound network connection was idle and un-logged-in for at least CONNECT_TIMEOUT seconds (as defined in the file `options.h' when the server was compiled).

Fine point: If the network connection in question was received at a listening point (established by the `listen()' function) handled by an object obj other than #0, then system messages for that connection are looked for on obj.server_options; if that property does not exist, then $server_options is used instead.

Checkpointing the Database

The server maintains the entire MOO database in main memory, not on disk. It is therefore necessary for it to dump the database to disk if it is to persist beyond the lifetime of any particular server execution. The server is careful to dump the database just before shutting down, of course, but it is also prudent for it to do so at regular intervals, just in case something untoward happens.

To determine how often to make these checkpoints of the database, the server consults the value of #0.dump_interval. If it exists and its value is an integer greater than or equal to 60, then it is taken as the number of seconds to wait between checkpoints; otherwise, the server makes a new checkpoint every 3600 seconds (one hour). If the value of #0.dump_interval implies that the next checkpoint should be scheduled at a time after 3:14:07 a.m. on Tuesday, January 19, 2038, then the server instead uses the default value of 3600 seconds in the future.

The decision about how long to wait between checkpoints is made again immediately after each one begins. Thus, changes to #0.dump_interval will take effect after the next checkpoint happens.

Whenever the server begins to make a checkpoint, it makes the following verb call:

$checkpoint_started()

When the checkpointing process is complete, the server makes the following verb call:

$checkpoint_finished(success)

where success is true if and only if the checkpoint was successfully written on the disk. Checkpointing can fail for a number of reasons, usually due to exhaustion of various operating system resources such as virtual memory or disk space. It is not an error if either of these verbs does not exist; the corresponding call is simply skipped.

Accepting and Initiating Network Connections

When the server first accepts a new, incoming network connection, it is given the low-level network address of computer on the other end. It immediately attempts to convert this address into the human-readable host name that will be entered in the server log and returned by the connection_name() function. This conversion can, for the TCP/IP networking configurations, involve a certain amount of communication with remote name servers, which can take quite a long time and/or fail entirely. While the server is doing this conversion, it is not doing anything else at all; in particular, it it not responding to user commands or executing MOO tasks.

By default, the server will wait no more than 5 seconds for such a name lookup to succeed; after that, it behaves as if the conversion had failed, using instead a printable representation of the low-level address. If the property name_lookup_timeout exists on $server_options and has an integer as its value, that integer is used instead as the timeout interval.

When the open_network_connection() function is used, the server must again do a conversion, this time from the host name given as an argument into the low-level address necessary for actually opening the connection. This conversion is subject to the same timeout as in the in-bound case; if the conversion does not succeed before the timeout expires, the connection attempt is aborted and open_network_connection() raises E_QUOTA.

After a successful conversion, though, the server must still wait for the actual connection to be accepted by the remote computer. As before, this can take a long time during which the server is again doing nothing else. Also as before, the server will by default wait no more than 5 seconds for the connection attempt to succeed; if the timeout expires, open_network_connection() again raises E_QUOTA. This default timeout interval can also be overridden from within the database, by defining the property outbound_connect_timeout on $server_options with an integer as its value.

Associating Network Connections with Players

When a network connection is first made to the MOO, it is identified by a unique, negative object number. Such a connection is said to be un-logged-in and is not yet associated with any MOO player object.

Each line of input on an un-logged-in connection is first parsed into words in the usual way (see the chapter on command parsing for details) and then these words are passed as the arguments in a call to the verb $do_login_command(). For example, the input line

connect Munchkin frebblebit

would result in the following call being made:

$do_login_command("connect", "Munchkin", "frebblebit")

In that call, the variable player will have as its value the negative object number associated with the appropriate network connection. The functions notify() and boot_player() can be used with such object numbers to send output to and disconnect un-logged-in connections. Also, the variable argstr will have as its value the unparsed command line as received on the network connection.

If $do_login_command() returns a valid player object and the connection is still open, then the connection is considered to have logged into that player. The server then makes one of the following verbs calls, depending on the player object that was returned:

$user_created(player)
$user_connected(player)
$user_reconnected(player)

The first of these is used if the returned object number is greater than the value returned by the max_object() function before $do_login_command() was invoked, that is, it is called if the returned object appears to have been freshly created. If this is not the case, then one of the other two verb calls is used. The $user_connected() call is used if there was no existing active connection for the returned player object. Otherwise, the $user_reconnected() call is used instead.

Fine point: If a user reconnects and the user's old and new connections are on two different listening points being handled by different objects (see the description of the listen() function for more details), then user_client_disconnected is called for the old connection and user_connected for the new one.

If an in-bound network connection does not successfully log in within a certain period of time, the server will automatically shut down the connection, thereby freeing up the resources associated with maintaining it. Let L be the object handling the listening point on which the connection was received (or #0 if the connection came in on the initial listening point). To discover the timeout period, the server checks on L.server_options or, if it doesn't exist, on $server_options for a connect_timeout property. If one is found and its value is a positive integer, then that's the number of seconds the server will use for the timeout period. If the connect_timeout property exists but its value isn't a positive integer, then there is no timeout at all. If the property doesn't exist, then the default timeout is 300 seconds.

When any network connection (even an un-logged-in or outbound one) is terminated, by either the server or the client, then one of the following two verb calls is made:

$user_disconnected(player)
$user_client_disconnected(player)

The first is used if the disconnection is due to actions taken by the server (e.g., a use of the boot_player() function or the un-logged-in timeout described above) and the second if the disconnection was initiated by the client side.

It is not an error if any of these five verbs do not exist; the corresponding call is simply skipped.

Note: Only one network connection can be controlling a given player object at a given time; should a second connection attempt to log in as that player, the first connection is unceremoniously closed (and $user_reconnected() called, as described above). This makes it easy to recover from various kinds of network problems that leave connections open but inaccessible.

When the network connection is first established, the null command is automatically entered by the server, resulting in an initial call to $do_login_command() with no arguments. This signal can be used by the verb to print out a welcome message, for example.

Warning: If there is no $do_login_command() verb defined, then lines of input from un-logged-in connections are simply discarded. Thus, it is necessary for any database to include a suitable definition for this verb.

Out-of-Band Commands

It is possible to compile the server with an option defining an out-of-band prefix for commands. This is a string that the server will check for at the beginning of every line of input from players, regardless of whether or not those players are logged in and regardless of whether or not reading tasks are waiting for input from those players. If a given line of input begins with the defined out-of-band prefix (leading spaces, if any, are not stripped before testing), then it is not treated as a normal command or as input to any reading task. Instead, the line is parsed into a list of words in the usual way and those words are given as the arguments in a call to $do_out_of_band_command(). For example, if the out-of-band prefix were defined to be `#$#', then the line of input

#$# client-type fancy

would result in the following call being made in a new server task:

$do_out_of_band_command("#$#", "client-type", "fancy")

During the call to $do_out_of_band_command(), the variable player is set to the object number representing the player associated with the connection from which the input line came. Of course, if that connection has not yet logged in, the object number will be negative. Also, the variable argstr will have as its value the unparsed input line as received on the network connection.

Out-of-band commands are intended for use by fancy client programs that may generate asynchronous events of which the server must be notified. Since the client cannot, in general, know the state of the player's connection (logged-in or not, reading task or not), out-of-band commands provide the only reliable client-to-server communications channel.

The First Tasks Run By the Server

Whenever the server is booted, there are a few tasks it runs right at the beginning, before accepting connections or getting the value of #0.dump_interval to schedule the first checkpoint (see below for more information on checkpoint scheduling).

First, the server calls $user_disconnected() once for each user who was connected at the time the database file was written; this allows for any cleaning up that's usually done when users disconnect (e.g., moving their player objects back to some `home' location, etc.).

Next, it checks for the existence of the verb $server_started(). If there is such a verb, then the server runs a task invoking that verb with no arguments and with player equal to #-1. This is useful for carefully scheduling checkpoints and for re-initializing any state that is not properly represented in the database file (e.g., re-opening certain outbound network connections, clearing out certain tables, etc.).

Controlling the Execution of Tasks

As described earlier, in the section describing MOO tasks, the server places limits on the number of seconds for which any task may run continuously and the number of "ticks," or low-level operations, any task may execute in one unbroken period. By default, foreground tasks may use 30,000 ticks and five seconds, and background tasks may use 15,000 ticks and three seconds. These defaults can be overridden from within the database by defining any or all of the following properties on $server_options and giving them integer values:

bg_seconds
The number of seconds allotted to background tasks.
bg_ticks
The number of ticks allotted to background tasks.
fg_seconds
The number of seconds allotted to foreground tasks.
fg_ticks
The number of ticks allotted to foreground tasks.

The server ignores the values of fg_ticks and bg_ticks if they are less than 100 and similarly ignores fg_seconds and bg_seconds if their values are less than 1. This may help prevent utter disaster should you accidentally give them uselessly-small values.

Recall that command tasks and server tasks are deemed foreground tasks, while forked, suspended, and reading tasks are defined as background tasks. The settings of these variables take effect only at the beginning of execution or upon resumption of execution after suspending or reading.

The server also places a limit on the number of levels of nested verb calls, raising E_MAXREC from a verb-call expression if the limit is exceeded. The limit is 50 levels by default, but this can be increased from within the database by defining the max_stack_depth property on $server_options and giving it an integer value greater than 50. The maximum stack depth for any task is set at the time that task is created and cannot be changed thereafter. This implies that suspended tasks, even after being saved in and restored from the DB, are not affected by later changes to $server_options.max_stack_depth.

Finally, the server can place a limit on the number of forked or suspended tasks any player can have queued at a given time. Each time a fork statement or a call to suspend() is executed in some verb, the server checks for a property named queued_task_limit on the programmer. If that property exists and its value is a non-negative integer, then that integer is the limit. Otherwise, if $server_options.queued_task_limit exists and its value is a non-negative integer, then that's the limit. Otherwise, there is no limit. If the programmer already has a number of queued tasks that is greater than or equal to the limit, E_QUOTA is raised instead of either forking or suspending. Reading tasks are affected by the queued-task limit.

Controlling the Handling of Aborted Tasks

The server will abort the execution of tasks for either of two reasons:

  1. an error was raised within the task but not caught, or
  2. the task exceeded the limits on ticks and/or seconds.

In each case, after aborting the task, the server attempts to call a particular handler verb within the database to allow code there to handle this mishap in some appropriate way. If this verb call suspends or returns a true value, then it is considered to have handled the situation completely and no further processing will be done by the server. On the other hand, if the handler verb does not exist, or if the call either returns a false value without suspending or itself is aborted, the server takes matters into its own hands.

First, an error message and a MOO verb-call stack traceback are printed to the player who typed the command that created the original aborted task, explaining why the task was aborted and where in the task the problem occurred. Then, if the call to the handler verb was itself aborted, a second error message and traceback are printed, describing that problem as well. Note that if the handler-verb call itself is aborted, no further `nested' handler calls are made; this policy prevents what might otherwise be quite a vicious little cycle.

The specific handler verb, and the set of arguments it is passed, differs for the two causes of aborted tasks.

If an error is raised and not caught, then the verb-call

$handle_uncaught_error(code, msg, value, traceback, formatted)

is made, where code, msg, value, and traceback are the values that would have been passed to a handler in a try-except statement and formatted is a list of strings being the lines of error and traceback output that will be printed to the player if $handle_uncaught_error returns false without suspending.

If a task runs out of ticks or seconds, then the verb-call

$handle_task_timeout(resource, traceback, formatted)

is made, where resource is the appropriate one of the strings "ticks" or "seconds", and traceback and formatted are as above.

Matching in Command Parsing

In the process of matching the direct and indirect object strings in a command to actual objects, the server uses the value of the aliases property, if any, on each object in the contents of the player and the player's location. For complete details, see the chapter on command parsing.

Restricting Access to Built-in Properties and Functions

Whenever verb code attempts to read the value of a built-in property prop on any object, the server checks to see if the property $server_options.protect_prop exists and has a true value. If so, then E_PERM is raised if the programmer is not a wizard.

Whenever verb code calls a built-in function func() and the caller is not the object #0, the server checks to see if the property $server_options.protect_func exists and has a true value. If so, then the server next checks to see if the verb $bf_func() exists; if that verb exists, then the server calls it instead of the built-in function, returning or raising whatever that verb returns or raises. If the $bf_func() does not exist and the programmer is not a wizard, then the server immediately raises E_PERM, without actually calling the function. Otherwise (if the caller is #0, if $server_options.protect_func either doesn't exist or has a false value, or if $bf_func() exists but the programmer is a wizard), then the built-in function is called normally.

Creating and Recycling Objects

Whenever the create() function is used to create a new object, that object's initialize verb, if any, is called with no arguments. The call is simply skipped if no such verb is defined on the object.

Symmetrically, just before the recycle() function actually destroys an object, the object's recycle verb, if any, is called with no arguments. Again, the call is simply skipped if no such verb is defined on the object.

Both create() and recycle() check for the existence of an ownership_quota property on the owner of the newly-created or -destroyed object. If such a property exists and its value is an integer, then it is treated as a quota on object ownership. Otherwise, the following two paragraphs do not apply.

The create() function checks whether or not the quota is positive; if so, it is reduced by one and stored back into the ownership_quota property on the owner. If the quota is zero or negative, the quota is considered to be exhausted and create() raises E_QUOTA.

The recycle() function increases the quota by one and stores it back into the ownership_quota property on the owner.

Object Movement

During evaluation of a call to the move() function, the server can make calls on the accept and enterfunc verbs defined on the destination of the move and on the exitfunc verb defined on the source. The rules and circumstances are somewhat complicated and are given in detail in the description of the move() function.

Temporarily Enabling Obsolete Server Features

If the property $server_options.support_numeric_verbname_strings exists and has a true value, then the server supports a obsolete mechanism for less ambiguously referring to specific verbs in various built-in functions. For more details, see the discussion given just following the description of the verbs() function.


Go to the first, previous, next, last section, table of contents.