Phantasmal MUD Lib for DGD

Phantasmal Site > DGD > DGD Reference Manual > LPC > Functions

Functions

Function declarations

A function's declaration must state what kind of data type it returns, if any. A function name is a label much like a variable name, consisting of 32 characters or less and starting with a letter. No special characters other than _ should be used to separate words. Use function names that clearly reflect on what they do. A function declaration looks like this:

/*
 * Function name: <Function name>
 * Description:   <What it does>
 * Arguments:     <a list of the arguments and what they contain>
 * Returns:       <What the function returns>
 */
<return type>
<function name>(<argument list>)
{
   <code expressions>
}

The beginning comment, while optional, is highly recommended. Even if you don't use precisely this form of comment, you should state what the function does and what it expects to be given as input. Here is an example function:

/*
 * Function name: compute_diam
 * Description:   Compute the diameter of a circle given the 
 *                circumference.
 * Variables:     circumference - the circle's circumference
 *                pi - a famous irrational constant
 * Returns:       The circle's diameter
 */
float compute_diam(float circumference, float pi)
{
    float rval;

    /* Circumference = pi * diameter, so
       diameter = circumference / pi */

    rval = circumference / pi;

    return rval;
}

The argument list is a comma-separated list of data types. It specifies what kinds of data will be sent to the function and assigns names to this data for later use. The data recieved will only be usable inside the function, unless you explicitly send it elsewhere. The function's argument names are valid only within that function itself.

(In order to save space and improve on legibility in the text, I won't put a header on every example function).

A function that doesn't return anything should be declared as void. For instance:

void write_stuff(string mess)
{
    write_file("/usr/Bob/myfile.txt", mess);
}

Function calls

There are two kinds of function calls, internal and external. We've only discussed internal calls so far.

Making object-internal function calls

Making an internal function call is as simple as writing the function name and putting any arguments within parentheses afterwards. The argument list is simply a list of expressions, or nothing. A function call is an expression if it returns a value. A function call followed by a ; is a statement.

<function>(<argument list>);
e.g.
    pi = atan(1.0) * 4;
    

Making single object-external function calls

An external call is a call from one object into another. In order to do that you need an object reference to the object you want to call. We haven't discussed exactly how you acquire an object reference yet, but assume for the moment that it already is done for you. Object references (the same thing as object pointers) can be stored in variables of type object.

mixed <object reference/object path>-><function>(<argument list>);
mixed call_other(<ob ref/ob path>, "<function>", <arg list>);
e.g.
    /*
     * Assume that I want to call the function 'compute_pi' in the
     * object "/d/Mydom/thewiz/math_ob", and that I also have the
     * proper object pointer to it stored in the variable 'math_ob'
     */
    pi = math_ob->compute_pi(1.0);
    pi = "/d/Mydom/thewiz/math_ob"->compute_pi(1.0);
    pi = call_other(math_ob, "compute_pi", 1.0);
    pi = call_other("/d/Mydom/thewiz/math_ob", "compute_pi", 1.0);
    

Note that you can either use the call_other function or just use the -> notation. One is syntactic sugar for the other. The behavior is identical either way. Note that if you use the call_other() form, you need to put quotes around the function name since you're actually supplying any string -- which can be a variable, or the result of a computation:

pi = call_other(math_ob, "computer" + "_pi", 1.0);
    

If the object you call hasn't been loaded into memory yet, it will be. If it has a create() function that hasn't been called, that function will be called (note that the create() function may have a different name if DGD is configured differently). If an external call is made to a function that doesn't exist in the object you call, nil will be silently returned without any error messages.

If you use an object path (a string) instead of an object reference, the master object will be called. The master object can contain data just like any of the clones, and is often a good place to store a central copy of information that all clones want to use without each one needing its own copy.

Call-by-value versus Call-by-reference

In LPC, most values are passed by value. What that means is that when you pass, say, an integer variable with a value of 3 into a function and you change that variable in the function, the variable doesn't change outside the function . So the following code doesn't do what it looks like it's supposed to. In fact, it does nothing:

/* Swap two integers */
void swap(int a, int b) {
  int tmp;
  tmp = b;
  b = a;
  a = b;
}
    

The reason it doesn't work is because LPC makes the function's parameters new variables and copies the values in. You're swapping the value of the parameters, but those variables are going to disappear at the end of the function. This is called call-by-value because only the value gets passed in -- the original variable is copied so the function never sees it. In C, we could use pointers to pass references. In other languages, we could do what's called call-by-reference, and the swap routine above could actually swap the values of the two variables once the function was finished. LPC passes arrays by reference. That means that if you change an element in the array, it's changed everywhere, not just in that one function.

LPC passes several types by reference: arrays, mappings and objects. A mixed type is passed either by value or reference depending on what type it "really" is underneath.

This has security implications. For instance, let's say a function on your MUD keeps an array of all users. When queried, it passes the array of users to a calling function. But a wizard on your MUD would be able call that function and remove other users from the array. That's a bad thing. So instead, you just copy the array before returning it. If you just pass the array back as an array or mixed variable, it's call-by-reference and the wizard can change your copy. If you copy it first, it doesn't matter how it's passed because he's looking at a different copy.