All your hours are wings that beat through space from self to self.
A DM program is ultimately a black box that takes in and spits out information. On the inside of this black box are a bunch of little compartments where it holds the information that it is working on. These are called variables and each one has a little label on it carved in cuneiform script by the hand of an ancient Babylonian black box engineer.
So far, you have seen object variables and argument variables. There are two other places where variables may reside. One is inside a procedure and the other is inside of nothing at all--a so-called global variable.
A global variable would generally be used to hold some information that is an attribute of the entire world. For example, you could store the state of the weather there.
var/weather = "Looks like another beautiful day!" mob/verb/look_up() usr << weather mob/DM/verb/set_weather(txt as text) weather = txt
This example has three parts: a global weather variable, a verb for players to check the weather, and a verb for the DM to set the weather. The chief point of interest is the variable definition. It goes under a var node at the root of the program (which is what makes it global). In this example, the weather variable was assigned an initial value (so the DM doesn't have to remember to do it). The initial value is an optional part of the variable definition.
The position in which a variable is defined determines its scope. A variable defined at the root (top level) of the code is therefore globally applicable. (Note that when we say top level of the code we mean the root of the code tree. That doesn't mean it has to be at the top of your file (though global things often are).) A variable defined inside of an object definition only applies to that object and its descendants.
You have already been using object variables like name, and icon, but they were already defined for you. You can add your own variables for purposes not covered by the built-in ones. For example, you could have a variable for the monetary value of an object.
obj var/value stone value = 1 ruby value = 50 diamond value = 100
When the variable is first declared, it is put under a var node. After that, when its initial value is overridden, it is simply assigned without a re-declaration. This is similar to the way verbs are declared and then overridden. The syntax serves the same purpose of preventing mistakes from slipping past the compiler.
In a procedure, it is possible to access object variables. We have already seen this for the case of verbs that modify properties of their source. For example:
obj/verb/set_value(v as num) set src in view() value = v
However, what if we wanted only the DM to have the ability to set the value? The verb just defined gives everyone the ability to do so. Instead of attaching the verb to the obj, we really want it attached to the DM. However, then we would have to access the value of the obj from inside the DM's verb. That requires defining a variable type. Here is how it is done:
mob/DM/verb/set_value(obj/O,v as num) O.value = v
The first variable is declared as obj/O
, which says that O
is an
obj and therefore has all of the variables of an obj. To access the
variable, the dot operator is used. The variable O
goes on the left
and the name of O
's variable that we want to access goes on the
right. The dot operator allows us to access the object variables belonging
to O
.
Look again at the syntax for the first argument. We could have written it
obj/O as obj in view()
, but since the variable is declared to be of
type obj, the rest is assumed by default. The long version is good to
understand, though. The first obj in it is a variable type. The second obj
is an input type. The DM language does not require that these be
identical. In the future you will see how that can be used to your
advantage; most of the time, however, it is convenient that the input type
defaults to match the variable type.
In DM, a variable you define can be assigned any type of value. The same variable could hold a text string, a number, or some type of object. If it is an object, and if the programmer needs to access that object's variables, only then is it necessary to inform the compiler what type of object the variable represents.
This is accomplished in the definition by placing the type path in front of
the variable name. It could be obj/O or something longer like
obj/scroll/O. In general, one would specify as much of the type as
necessary to get down to the level of the desired variables. For example,
obj/O would allow one to access O.name
, O.icon
,
and any other basic obj variables. A variable defined only for scrolls, say
O.duration
, would require a more specific type definition like
obj/scroll/O.
Two variables that you have already seen can be used with the dot operator
because their type is automatically defined for you. The variable
usr is declared as var/mob/usr
since it always refers to the
mob who is executing a verb. The variable src has the same type as
the object containing the verb (obviously).
Since accessing the variables of src is such a common task, you can do it
directly without the dot operator. We have been doing that all along. For
example, you can just use value
in place of src.value
.
The two are equivalent. (C/C++ and Java programmers will recognize
that src is similar to what they know of as this.)
The usr variable, on the other hand, is useful when the src and usr are different objects. For example, one could make a disguise object that changes the wearer's appearance.
obj/disguise verb/wear() usr.icon = icon //zing!
To allow the user to remove the disguise, you would need to store the original icon in a variable. You could do it like this:
obj/disguise var/old_icon verb wear() old_icon = usr.icon usr.icon = icon remove() usr.icon = old_icon
Here is an interesting thought. What if somebody finds a discarded disguise and removes it without wearing it?! That is the kind of freaky stuff players like to try. Never trust them! Wait until the next chapter for the tools to stop such funny business.
Verb arguments are a special type of procedure-level variable. The built-in variables usr and src also exist at that level. You can define your own variables inside of a procedure using the same syntax for defining variables as elsewhere.
Suppose you wanted two objects to exchange appearances--a slightly different effect from the disguise object. In this example, possession of the magic scroll gives one the ability to pose as the scroll while it appears like you. (Don't try this in the lavatory or somebody is bound to make a terrible mistake.)
obj/mirror_scroll verb/cast() var/usr_icon = usr.icon usr.icon = icon icon = usr_icon
The intermediate variable usr_icon
accomplishes the exchange of
images. We could have assigned it in a separate statement, but initializing
it in the variable definition was easier.
Variables can be defined at the top level, inside an object, and inside a procedure. These three different locations (or scopes) determine the range of access and life span of a variable.
Global variables are initialized at the beginning of time and exist until the end of it (for their world that is). Object variables are initialized when an object is created and exist until it is destroyed. Every object has its own copy of each variable. That is different from global variables which exist once and for all. At the very lowest level, procedure variables come into existence when the procedure is executed and cease to exist when it stops. These are often called local variables.
The scope of a variable determines its order of precedence. Consider, as an example, a case in which a procedure variable has the same name as an object variable.
mob/verb/call_me(name as text) name = name //obviously not what you want src.name = name //this is what you want
We defined a procedure variable (actually an argument) called name
,
which is the same as a mob variable. The procedure level variable takes
precedence over the object variable. In order to access the object
variable, we had to explicitly use src.name
rather than just
name
.
Similarly, global variables take a lower order of precedence than object or
local variables. In this case, a conflict can be resolved by using
global.name
. It is safest, however, to avoid such name conflicts
altogether since mistakes made in these cases can be difficult to see.
In the interests of avoiding name conflicts, it is sometimes desirable to have something that behaves like a global variable but which is defined at a lower level. For example, the variable may only be of interest to a small portion of the code but one may still want there to be a single permanent copy of it. The best thing to do in this case is to flag the variable as global but define it at the level where it is applicable. (Many languages use the term `static' instead of `global' to define a variable with limited scope but global existence. It is the same thing.)
As an example, you could make some magic papers such that when you write on one of them, the same writing appears on all the others.
obj/magic_paper var/global/msg verb write(txt as text) msg = txt read() usr << "[src] says, '[msg]'"
By defining the message variable inside magic_paper
, we avoided cluttering
up the global name space with something that only applied to the code in
this object. The global flag is simply inserted after var
in the variable definition. This prevents each piece of magic paper from
having a separate, independent copy of the variable holding the message. It
also frees up the variable name msg
to be used elsewhere in the code
without conflicting with this one.
Another flag that can be applied to a variable is const. This marks the variable as one that can be initialized but never modified. We call this a constant variable (which is a bit of an oxymoron).
The purpose of const is to keep your code from getting too cluttered with so-called magic numbers and other such values that are used repeatedly and which may need to be adjusted in the future. You have already seen another way to handle global constants using #define macros. However, the advantage of using a constant variable instead is that it does not necessarily have to be global in scope. It is best to reserve the #define command for situations which cannot be handled with const.
For example, you could make a sort of doppelganger object--the reverse of a disguise.
obj/doppelganger var/const/init_icon = 'doppel.dmi' icon = init_icon verb clone() set src in view() icon = usr.icon revert() set src in view() icon = init_icon
Of course we could have just used 'doppel.dmi'
everywhere in place of
init_icon
, but then if we decided to use a different icon file at some
point in the future, it would be more complicated to do (and more likely to
get messed up). (In this particular example, it
would also be possible to use the expression initial(icon)
for
finding the original compile-time value of a variable.)
While on the subject of memory, some of you may be curious about how information is actually stored by a DM program. For example, when we assign icons around from one variable to another, is the actual data of the icon file getting copied and duplicated? Fortunately, the answer is no, or many operations would be a lot less efficient.
In DM, variables actually only contain two types of data: numbers and references. Numbers are simple. When you assign a number to a variable, a copy of the number gets put in the variable. If you modify that variable, nothing else changes--just the number inside it gets altered.
All other types of data are handled through references, which point to where the data is actually stored. (C programmers will recognize that DM references are pointers.) In that way, several variables can contain references to the same piece of data, be it an icon, a mob, a text string, or anything else. When the contents of such variables are copied from one to another, only the reference is copied. The data itself is independent of such operations.
Programmers who are used to managing memory allocation for data like text strings will appreciate the fact that DM takes care of garbage collection. That means when a piece of data (like a message entered into the `say' verb) is no longer referenced by any variables, it is automatically deleted from memory to make room for new data. This makes working in a multi-media environment with text, icons, sounds, and so forth a lot easier. You simply don't have to worry about it.