Chapter 5

Variables

All your hours are wings that beat through space from self to self.
--Kahlil Gibran, The Prophet

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.

1. Global Variables

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.

2. Object Variables

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.

2.1 Defining An Object Variable

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.

2.2 Accessing An Object Variable

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.

2.2.1 Declaring Variable Types

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.

2.3 The usr and src Variables

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.

3. Procedure Variables

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.

4. The Life of a Variable

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.

5. Constants

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.)

6. Memory and Variables

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.