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.
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.
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:
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:
The first variable is declared as
Look again at the syntax for the first argument. We could have written it
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
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
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
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.
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:
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.)
The intermediate 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.
We defined a procedure variable (actually an argument) called
Similarly, global variables take a lower order of precedence than object or
local variables. In this case, a conflict can be resolved by using
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.
By defining the message variable inside
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.
Of course we could have just used
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.
1. Global Variables
var/weather = "Looks like another beautiful day!"
mob/verb/look_up()
usr << weather
mob/DM/verb/set_weather(txt as text)
weather = txt
2. Object Variables
2.1 Defining An Object Variable
obj
var/value
stone
value = 1
ruby
value = 50
diamond
value = 100
2.2 Accessing An Object Variable
obj/verb/set_value(v as num)
set src in view()
value = v
mob/DM/verb/set_value(obj/O,v as num)
O.value = v
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
.
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
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
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).
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.)
obj/disguise
verb/wear()
usr.icon = icon //zing!
obj/disguise
var/old_icon
verb
wear()
old_icon = usr.icon
usr.icon = icon
remove()
usr.icon = old_icon
3. Procedure Variables
obj/mirror_scroll
verb/cast()
var/usr_icon = usr.icon
usr.icon = icon
icon = usr_icon
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
mob/verb/call_me(name as text)
name = name //obviously not what you want
src.name = name //this is what you want
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
.
global.name
. It is safest, however, to avoid such name conflicts
altogether since mistakes made in these cases can be difficult to see.
obj/magic_paper
var/global/msg
verb
write(txt as text)
msg = txt
read()
usr << "[src] says, '[msg]'"
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
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
'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