Using the +[object] syntax to declare relationships between bespoke non-Thing objects

Just posting a little hint/tip/whatever here, because I couldn’t find existing documentation covering it and it took a little work to figure out from source diving.

If you have, for example, a Room, an Actor, and a Thing and you want to declare that they contain each other, you can of course do something like:

startRoom: Room 'Void' "This is a featureless void. ";
+me: Person;
++pebble: Thing 'small round pebble' 'pebble' "A small, round pebble. ";

…and this will make a room that contains the player, who is holding a pebble.

What I found myself wanting to do was declare similar (but not identical) relationships between non-Thing objects. But…

class Foo: object;
class FooState: object;
foo: Foo;
+FooState;

Doesn’t work the same way; object doesn’t have a contents property that’ll get things added to itself automagically during preinit/init.

Anyway, there are other non-Thing objects in adv3 that use this syntax (MenuItem, for example) and so figuring this out just involved a bit of source reading. For the benefit of future readers (including, probably, me in a couple months) here’s a simple worked example of rolling your own objects using this syntax:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

// A non-Thing object class.
class Foo: object
        // List to hold our "states"
        fooStates = static []

        // Method to add a state to our list.
        addFooState(obj) { fooStates += obj; }
;

// A "state" class for Foo instances.
class FooState: object
        // The only property in our toy class is an ID.
        id = nil

        // Hook for our pre-init object to handle setting things up.
        initializeFoozle() {
                if(location != nil)
                        location.addFooState(self);
        }
;

// Template to make declaring things easier.
FooState template 'id';

// Simple anonymous preinit object that calls the initialization method on
// all our state instances.
PreinitObject
        execute() {
                forEachInstance(FooState, { obj: obj.initializeFoozle() });
        }
;

// Our test case.
foo: Foo;
+FooState 'foo';
+FooState 'bar';
+FooState 'baz';

versionInfo: GameID;
gameMain: GameMainDef
        // All our "game" does is output the "states" on the object foo.
        newGame() {
                "Foo states:\n ";
                foo.fooStates.forEach(function(o) {
                        "\t<<o.id>>\n ";
                });
        }
;

Transcript:

Foo states:
   foo
   bar
   baz

The “trick” here is that the +[declaration] syntax causes the location property on the object to be set (to be the parent object). This appears to be at least a little heterodox, because location appears to be a property declared on Thing, not object. But in any case it works.

So if you did nothing else, then foo: object; +bar: object; would create two object instances, with bar.location == foo, but foo itself would contain no reference to bar.

So then you just need to define a PreinitObject to walk through all the instances of the class(es) you want to twiddle, calling an init method on each. The init method just needs to check if the object has a location, and if so, ping some method on it that causes the parent object to do whatever bookkeeping it needs to do in order to keep track of all the objects it “contains”. This happens automagically with Things because Thing.initializeLocation() takes care of it, but you have to roll your own with non-Thing objects.

Anyway, I don’t know if this is explained somewhere else. But if it is, I couldn’t find it. And so now it’s explained here.

3 Likes

Yeah, I tripped over this recently pawing through Menu stuff, belatedly realizing location was a property despite not appearing in the documentation. Also seems to be true that myMenu.addToContents(newTopic) does not set newTopic.location, and that needs to be done as an extra step.

1 Like