Objects (Instances)

To actually use the public properties defined in a class, that class must be instantiated in an object. This done in one of two ways: statically or dynamically. Static objects have a lifetime which extends over the entire life of the program (it is not possible to destroy a static object). Dynamic objects are allocated on the fly and are destroyed when the runtime detects that no more references to the object exist. The syntax by which static objects and dynamic objects are created is slightly different. Note that the create() method and {} completion operator of a static object are called before the main() procedure begins to run, whereas those of a dynamic object are called at the point in the code where the new dynamic object is created and initialized.

Static Objects

A static object is created in one of two ways: as a named static object, or as an unnamed static object which is assigned to a constant, a variable, or used in an expression. The syntax for creating a static object is nearly identical in both cases; the object name is simply omitted for the unnamed static object. Both static and dynamic objects may be created in one of two ways. The first way is this:

classname objname( create_args );

where classname is the name of a previously declared class, objname is the desired name of the object, and create_args are the arguments to be passed to the class's create() method. The objname and terminating semicolon are omitted for unnamed static objects.

To initialize any of the public variables, use the syntax:

classname objname( create_args ) {
    assignments
}

where assignments is a list of items of the form:

name = expr

Each name must be the name of a public variable in the class, and each expr must be a constant expression. For example, here's an adventure-language like construct:

room room1 {
    Lit = 1
    LDesc = proc() {say("You are in room 1.\n");}
    SDesc = proc() {say("Room 1");}
}

The object name is omitted for unnamed static objects. The named static object creation results in exactly the same behavior as using an unnamed static object as the initializer for a constant; for example, the following two statements result in identical behavior:

myClass myObj { myVar = 1; }
const myObj = myClass { myVar = 1; }

The following two statements are NOT identical to each other, since the compiler thinks the second one is just creating a constant with the same value as the given class:

myClass myObj {}
const myObj = myClass; // Does not create a static object!

If the create() method does not require arguments, then the parenthesis on the class name are optional. Forward references to static objects may be created by specifying the class name and the object name without any create() arguments or initializers; for example:

myClass myObj;

myObj is a forward reference to the object, and does not actually initialize the object (if a program with unresolved forward references is run a warning is generated).

Not all the properties of a static object need to be specified at the same point in the source file; it is often useful to split out similar functionality implemented in many objects into separate source regions (for example, in an adventure, it is convenient to have all of the room descriptions in one source file, and all of the room actions in another source file). To accomplish this, use the ellipsis ... syntax:

location room1 {
    lit = TRUE
    ...
}
location room2 {
    lit = FALSE
    ...
}

/* some time much later in the input file */
... room1 {
    sdesc = proc() {"Room 1";}
    ...
}
... room2 {
    sdesc = proc() {"Room 2";}
    ...
}

/* much later still in the input file */
... room1 {
    ldesc = proc() {"You are in room 1.\n";}
} // Since we did not use ..., the room is complete at this point
... room2 {
    ldesc = proc() {"You are in room 2.\n";}
} // Since we did not use ..., the room is complete at this point

If the last token before the closing brace of an object definition is the ellipsis, that indicates that the object will be continued. If the token before an object name is an ellipsis, it indicates that this is a continuing definition (and, if the object was not previously continued, an error is printed). Both the pre-ellipsis and post-ellipsis may be present, as shown by the middle example above.

Dynamic Objects

Dynamic objects do not have a lifetime which extends over the life of the program; instead, they are destroyed when OADL detects that there are no more existing references to them. They also do not have a name (the objname() intrinsic will return nil if called with a dynamic object).

To create a dynamic object, use the new operator, passing arguments to the create() method of the class, optionally specifying the value of public variables, and store the result in a variable:

var = new classname( create_args ) { public_assigns };

Here's the same example as above, but coded as a dynamic object:

var room1;
proc main()
{
    room1 = new room() {
        Lit = 1
        LDesc = proc() {"You are in room 1.\n";}
        SDesc = proc() {"Room 1.\n";}
    };
}

Zombie Objects

During object destruction with a destroy method, all references to non-constant dynamic values in the object are replaced with nil. The object itself is made read-only. A temporary "deleted" property of the object is set. Objects in this state are known as "zombie" objects. Zombie objects may be resurrected if a new visible reference to them is created during execution of the destroy method:

    z = nil
    resurrect = true

    class foo {
        public var a, b;
        public proc create(x) {
            a = @x; // a will be a non-static
            b = x;  // b may be static if x is static
            "foo.create - returning ", self, '\n';
        }
        public proc destroy() {
            "", self, ".destroy() {\n";
            "  self.readonly() = ", self.readonly(), '\n';
            "  oadl::deleted(self) = ", oadl::deleted(self), '\n';
            "  dynamic copy ", a, " vs static ref ", b, '\n';
            "}\n";
            if (resurrect) z = self;
        }
    }

    a = new foo("foo")
foo.create - returning #OBJ(1)
    a = nil

    // The deleted() intrinsic prints "true" for the zombie object.
    // Zombie objects are read-only.
    oadl::gc()
#OBJ(1).destroy() {
  self.readonly = true
  oadl::deleted(self) = true
  dynamic copy nil vs static ref foo
}
    // Note that, since resurrect is "true", the destroy
    // method stashed a copy of that zombie object in the
    // global variable z

    // Set z.b to a dynamic copy of the string - it will get deleted
    // next time around
    z.b = @z.b

    // Examinine resurrected zombie z
    "z = ", z, "; z.b = ", z.b, '\n'
z = #OBJ(1); z.b = foo

    "oadl::deleted(z) = ", oadl::deleted(z), '\n'
oadl::deleted(z) = false

    // Now, without resurrection, the objects really get deleted
    resurrect = false
    z = nil
    oadl::gc()
#OBJ(1).destroy() {
  self.readonly = true
  oadl::deleted(self) = true
  dynamic copy nil vs static ref nil
}

The order in which destroy methods is called is non- deterministic. Back to Classes

Continue to Expressions

Return to Introduction