yduJ's MOO Lore Pamphlet|
-- -- -- lag
yduJ's Theory Of Lag
What causes lag? This is a perennial question on LambdaMOO. There two basic
types of lag: Net lag, and CPU lag. Net lag is lag you experience between
your local computer (whatever's connected to the keyboard) and the remote
computer (LambdaMOO), and can have lots of causes.
Net lag is usually different for different players, depending on their
distance from the MOO. Distance isn't necessarily in miles---someone 10 miles
from the MOO machine behind a 14.4kbaud SLIP line may have higher lag than
someone in New York sitting on a machine with a T1 line. People on other
continents than the MOO typically have higher netlag than those in North
America, because their bits have to get funneled through some narrow channel
under an ocean, competing with the propagation of
alt.binaries.pictures.erotica, or have to go up to a satellite and back down
again, which takes a while.
LambdaMOO, however, primarily suffers from CPU lag. This means the source of
the problem is on the computer that's running the MOO itself, and no amount of
closeness to the MOO is going to help fend it off. There are several things
which can cause CPU lag, and lambdamoo suffers from them all.
When a program takes more memory than the machine has physical memory, it uses
the disk to make memory look bigger, and does what's called "paging" to switch
stuff back and forth. This makes stuff way slower. LambdaMOO's physical
memory is 256 meg (that's bigger than most people's hard drives), but the
program is about 330 meg, so it doesn't fit. Surprisingly, measurements have
shown that LambdaMOO's machine isn't paging very much, though it is paging
But the main thing that's going on with LambdaMOO is that there is just too
much work for the poor little thing to do. There are frequently 250 people
logged in at once, and if they're all typing, in order to execute within 1
second lag, it has to do 250 commands per second. This is a lot, because to
run the MOO language, the computer has to do a fair amount of work. To make
matters worse, there are usually about 350 queued "forked tasks" competing
with the 250 logged in users. Adding this all up makes for a very busy
computer and very bad lag.
So what's my theory of what makes lag worse? My theory is that it takes a lot
more time to create a forked task than you think. (Note: suspending creates a
task too, but fork is the real killer.) My theory is that if one is doing a
lot of work, and suspending at regular intervals, you should use up your whole
quantum of ticks, and call :suspend_if_needed(60), giving the machine a whole
minute to ignore you and run someone else's stuff. It needs the break by then!
You should also be very careful when writing loops that you're not computing
the same data twice. Store data in properties, and update the properties
incrementally, rather than recomputing whole reams of stuff for different
If you're doing something that's periodic, emitting a message into a room, for
example, make it with a really long period. Nobody cares to see the sky
change color every 10 seconds; every 5 minutes is good enough. And make sure
there's someone to *see* your text. Don't write tasks that sit in the
background and change the color of the sky, even every 5 minutes, if there's
nobody there to watch. Use :enterfunc to start tasks, and check on :exitfunc
whether you should be stopping them. If you have something that changes its
description based on a time factor, don't make a task to change the
description. Instead, when the object is looked at, have it quickly compute
what *would have happened* had there been a task changing it. You may think
"but my task only goes once every five minutes!" Yes, that's true. But
you're sharing the machine with 8,000 other people, and once every five
minutes is way more than your share. Stamp out periodic tasks!
If you're triggering on something like :tell, if you should fork() (a definite
no-no!), make sure you are running only one task at a time. Record the
currently running task in a property on the object. In the next iteration,
check $code_utils:task_valid and don't fork if it's valid.
-- -- -- permissions
Permissions are problematical in MOO. Generally you don't need to be
concerned with permissions unless you are trying allow others to create
children of your generic objects. However, most serious MOO programmers end
up doing this, so it's worth knowing how the permissions work.
Let's use as example the Generic Wind-up Toy from our previous tutorial, only
this time we'll have a different user, Eiram, create the child object, Wind-up
@create #12221 named Wind-Up Sushi,Sushi
You now have Wind-Up Sushi (aka Sushi) with object number #15666 and
parent Generic Wind-Up Toy (#12221).
The default for creating a child object is that the new owner owns not only
the object, but all of the properties as well. For example, let's look at
.wind_down_msg, on the generic, and on our new child:
Owner: yduJ (#68)
Owner: Eiram (#40068)
Value: "spasms once and stops spinning."
We see that yduJ, who owns the generic, owns the property. But on the child,
the owner of that object owns the property.
Here's the tricky part. Only the owner of a property may change that
property. This changing owner on a property makes a lot of sense for things
like messages. You wouldn't want yduJ to be able to change Eiram's sushi's
messages, just because she owns the generic.
However, we'll recall that the windup toy has a property .wound that it sets
and decrements as it runs.
The way that verb permissions work is that they run as though the person who
wrote the verb was doing all the work (with the author's permissions), not as
though the person who issued the verb call was doing the work, or as the
person who owns the object that initiated the verb. You could imagine any of
these three possibilities. The second one would be more like Unix. In unix,
for example, it matters who is typing "cat file", not who wrote the cat
program. This allows for trojan horses to be installed in the cat program by
people with questionable moral systems. The MOO system is somewhat less
flexible, but more secure. Since it matters who wrote the verb, players know
that their own properties and objects are safe from would-be crackers. (Unix
folks can think of verbs as always running "setuid".)
However, this means that if the .wound property were treated the same as the
.wind_down_msg property, then the :wind verb (which was written by yduJ, and
thus can only change properties owned by yduJ) wouldn't be able to change the
property on Eiram's sushi. Thus, you'd wind and wind, but it wouldn't ever
get wound up. Similarly, the verb that winds down wouldn't be able to
decrement the .wound property, so the sushi would run forever! (Really, in
both cases it would instead crash with a permissions error, but still, neither
fate is acceptable.)
Let's talk about that Permissions: line in the @show above. Property
permissions are stored as a string of characters, one or zero each from the
set "rwc". R means readable; any verb can just do local =
#15666.wind_down_msg and have the value assigned into the string. If the "r"
bit is not set, only the owner of the property can look at it. W means
writable; any verb can *change* the property. This is extremely rare, because
of the possibility for abuse. C means "change in children", and is the one
responsible for causing Eiram to have become the owner of the .wind_down_msg
when he did @create. If you simply remove the "c" bit from a property, the
copies in children don't get changed to the owner of the child object, but
stay as the person who defined them on the generic. So, before letting Eiram
make a child object, yduJ did:
@chmod toy.wound !c
Property permissions set to r.
And now when we use @show:
Owner: yduJ (#68)
Owner: yduJ (#68)
We see that yduJ is the owner of this property. Note that this means Eiram
can't change that property, despite owning the object it is on! Read the entry
on Security in order to learn how to deal with this problem.
-- -- -- primitives
You can't really program on LambdaMOO just from reading the manual. Reading
the manual gives you excellent background on how the underlying server works,
but really, in the LambdaCORE, there are some verbs which take the place of
(really, enhance the features of) the primitives defined by the server.
notify(person,message) => person:tell(message);
move(object,place) => object:moveto(place);
read() => $command_utils:read() or $command_utils:read_lines()
In addition to direct replacements defined for all objects, there are an
enormous number of utility verbs designed to make programming tasks easier.
'Help Utilities' gives you a starting point to the documentation on the
utilities; there are a lot of them!
Proselytizing here a bit:
create() => #4455:_create()
recycle() => #4455:_recycle()
Read recycling in #23456 for more information.
-- -- -- scheduler
How does the scheduler work?
The idea is there is a round robin queue of player tasks. That means that each
player gets no more and no less CPU than any other. It just takes them one at
a time, in order.
Within that timeslice, each player gets eir own round robin of forked tasks,
including the interactive task that e may be typing at. (E may be logged out,
and still have tasks, so it isn't necessary that there be an interactive task
in the list.)
I think of it as a two dimensional array, or maybe a list each element of
which is a bunch of lists. You go around the big list, taking the top item
from the sublists each time. The sublists get added to by fork. You get a new
interactive task for each command, so that adds to it, but also subtracts from
it. Suspend moves the tasks to the end of your little list. The big list gets
added to only when a player not currently in the list has a task started (e.g.
with an automatic trigger in a room, or by connecting to the MOO).
So by having a large number of tasks ready to run, a player spams eirself,
giving eirself a noticeable lag, which supposedly are not felt by players with
only one task. Large numbers of cpu-intensive tasks can spam the entire MOO,
though, impacting even those players who are being socially responsible with
There's another wrinkle: If a player does a lot of computation even if e has
few forks, that amount of computation is remembered, and when e comes up again
for round robin scheduling, e may be skipped based on that large amount of
computation, until someone else has built up a similar quantity, or there
aren't any other tasks waiting. (I don't understand the exact algorithm here;
the short of it is if you see "out of seconds", go get coffee---you're
blackballed for a while---maybe several minutes.)
-- -- -- security
Read the entry on Permissions to understand how property permissions work.
In many cases, both the property owner (verb/generic author) and the owner of
the child object will need to be able to change a property. MOO does not have
"multiple ownership", or "permission groups", or any of those sorts of things.
The typical solution to this problem is for the generic owner to provide a
callable verb :set_whatever. Sometimes it is appropriate to also provide a
command line verb such as @set-whatever, depending on the application. These
setting verbs then generally need to have security in them, to make sure that
nobody but the child owner and generic owner actually make any changes. So
here's some blathering on what are the available options:
The variable "caller" is like "this" from the calling verb. If you check for
caller == this, you can be sure that the calling verb was defined either on
this object or on one of its parent hierarchy. For verbs which are pass()ed
to, caller == this is true. If there wasn't a caller because this is the
first verb executed in the command, then caller is the same as player. You
might use caller == this as your security check if you want to make sure that
the :set_whatever verbs are only called by the child objects, or your own
verbs. caller doesn't let you distinguish between being called by a verb
defined on the generic or a verb defined on the child.
caller_perms() gives you the permissions of the calling verb (usually the
person who wrote the calling verb). Note that permissions are represented by
player objects, so when I say "the verb was called with my permissions" that
means caller_perms() == #68. Only a wizard-authored verb can change the
permissions that a verb runs with; see documentation on the function
set_task_perms() if interested. Basically, you can use caller_perms() to see
if someone is trying to spoof your verb. Check caller_perms() == this.owner
to make sure only the proper person is calling the verb. Caller_perms() is
#-1 if your verb was invoked directly from the command line; sometimes you
want to have a verb that's callable by another verb, *or* straight from the
command lines, and still be able to do security checks. So, you use:
valid(caller_perms()) ? caller_perms() | player
where you'd have just used caller_perms() in a verb not command line callable.
This idiom can also be used in a wizardly set_task_perms() call.
Wizards think it's nice if your verbs that do various permissions checkings
allow wizards to use your verbs, even if they don't own the object. You can
use $perm_utils:controls(person,object) instead of using a == this.owner type
check... Wizards can bash your object anyway, it's just more convenient for
them if you don't get in their way. :-) Seriously, on LambdaMOO, a lot of
people make errors in their code and their object needs to be rescued; it's
nice if you make these janitorial jobs easier.
callers() is another way you can do security checking. It tells you a lot of
information about exactly how this verb got called. Do help callers() for
more info... I always forget how it works, so whenever I'm writing a verb
that uses callers(), I get the help, and then I write the verb with a
so I can see just what I'm getting... I recommend this sort of experimental
data gathering. Some people hate callers(). They think that for every use of
callers() there is one verb that's doing two jobs, and it should be separated
out into two verbs. In most cases they're right. If you're using callers()
to determine how your verb should behave, rather than as a security thing,
think about restructuring your code.
One sweeping method of security is to make your verb !x. This means it can
*only* be invoked from the command line. However, this also means it can
neither be called from a program nor be pass()ed to, which is sad, because
someone might want to customize the behavior of this command line verb.
Remember, use args of "this none this" to prevent a verb from being invoked
via the command line. A !x "this none this" verb can't be called in any way
(and thus is totally pointless!) A +x verb with a normal arglist can be
called from anywhere. Read about arglists in the programmer's manual.
You'll note that I've never used "player" except in the caller_perms() case
where I knew the verb had been invoked from the command line. This is because
player is basically completely useless, from a security point of view. Player
is the person who typed the original command which set in motion the thread of
control which eventually lead to your verb getting called. This doesn't mean
"player called your verb".
I'm going to describe how to spoof player in a :tell. I wrestled with myself
for a long time about whether I would do this in this document. So, here it
is. This type of spoofing is considered at least an order of magnitude more
rude than the usual sort. You've been warned.
pass(@args); /* do the normal stuff */
if (player == )
dobj = player;
So, everytime my enemy makes a noise in my presence, e bonks eirself with the
carrot, because it's really too hard to eschew the use of "player" in VR
settings such as the bonker. 99% of the time it's correct; indeed, it's only
incorrect in cases where someone is trying some funny business. This
demonstration shows how using "player" for security checking is totally
useless; the whole point of security checking is to prevent people from doing
things they shouldn't, which means that you have to assume these people will
be trying tricks such as the above. For example, if the name setting code
didn't have caller/caller_perms() security checks, I could have called
player:set_name("IamAnIdiot") instead of merely being a pain with the bonker.