Idea: signals for adv3lite

I was just wondering about the lack of signals in any IF authoring system I know of. It seems like an incredibly useful concept in my “normal” programming work (be it C++, C#, or whatever.) They’re a huge time saver and result in less code. It does seem like a good fit for an IF library.

The basic idea is this: an object sometimes needs to know when something happens to another object. To give a concrete example, say we have a box, a door and a window. When someone opens the box, the door and window need to close (for whatever reason; maybe an alarm system of sorts.)

Normally, you’d need to track down the box object in your code and then write the handling for this in whatever part of the box code deals with closing it. Needless to say, you also need to put a reference to the door and window in the box object.

But instead, Imagine if you could just do the following:

connect(box.opening, door.close); connect(box.opening, window.close);
Later on, maybe after someone disabled the alarm system, you don’t want this to happen anymore. So you’d just do:

disconnect(box.opening, door.close); disconnect(box.opening, window.close);
And this code would simply go into the alarm system object. Normally, you’d need to write that logic in the box object, and check there whether the alarm system is on or off. Way too much code and it’s spread all over the place.

But with signals (or events), those connect() and disconnect() calls are enough. No other code involved. The author does not need to insert any code to the box object at all. The library just allows for wiring signals to slots (or events to handlers; the terminology differs from language to language.)

I like this idea a lot – at least, in an abstract, conceptual way. But I don’t understand, “No other code involved.” For the connection to do anything, some other code would need to be written. If, by “door.close”, you simply mean “door.makeOpen(nil)”, then that mapping (of handler to in-game action) would have to be added somewhere.

Yes, as a practical matter it’s sometimes annoying to have to track down the box object and write:

makeOpen(stat) { inherited(stat); door.makeOpen(nil); "As you open the box, the door magically swings shut. "; }
Of course, there’s also a nasty potential for circularity in that code, because what if you carelessly change the door’s makeOpen(stat) code to do something to the box? But leaving aside that potential bug, I guess I’m not sure what problem (other than finding the code for the box object) would be solved by signaling.

It occurs to me that an object in an IF game (either an in-world simulation object or an abstract object such as an EventList) can change state in a variety of ways, some of which would be defined by the library while others are created by the author. Would it make sense, as a general feature of the library, to have every object send a notification to some central state-change handler indicating that its state – that is, the value of some property, any property at all – had just changed? If that functionality was operating continuously in the background, then the central state-change handler object could be set up so as to allow the author to pick out and respond to any state change as needed.

I think that may be more or less what you’re talking about … not sure. If I were a real programmer, I’m sure I’d understand what you’re talking about.

Yes. The point here though is that it gets added to the relevant object. And other objects can alter the wiring without having to add more code to the box.

As mentioned, it’s to avoid big chunks of switchboard-code in objects that actually don’t need to know anything about other objects that their signals affect. “I’m telling the world what’s happening to me, do what you want with that information, I don’t need to know anything about you.”

To make this a bit easier to understand, I’ll expand the example a bit. Initially, closing the box will close the door and the window. Now imagine there’s a puzzle where the player can prevent the window from closing by cutting a wire next to the window. In the code that handles the action of cutting that wire, you can insert:

disconnect(box.opening, window.close);

This code goes into the same place as the code that handles the cutting of the wire. It naturally belongs there. And neither the box nor the window need to know anything about the wire. Normally you’d need code in the box object that checks whether the wire is cut or not. But here, the signal is not emitted to begin with and thus the window won’t close.

This makes it easier to rework stuff in a project. Here you could just throw away the whole cut-the-wire puzzle by just deleting the wire object. Neither the box nor the window are affected by this removal. Of course it’s not always that easy; signals won’t magically eliminate object dependencies. But they can help quite a bit with keeping code more localized to where it belongs.

Now the exact syntax of this whole thing would most probably need to be adjusted in order for the receiver of a signal to get information about the emitter of the signal, if needed. The door.close() slot could be defined as:

close(sender)
{
    if (sender == box)
        // ...
    else if (sender == alarmButton)
        // ...
    else // ...
}

The box, the alarm button, or any other object (you could have dozens if you wanted) that has any of its signals connected to door.close(), do not need to have any code inserted into them to handle the closing of the door. Only the door itself needs to have that code.

This is how it’s done in libraries that provide signaling. There’s a central dispatch mechanism that receives all signals and dispatches them to objects that are connected. But it doesn’t need to run in the background. It can be done normally as part of the normal game loop.

Signaling all property changes would be overkill though. Normally, only interesting events should be defined. In the case of adv3lite, that would mean stuff like opening/closing, taking/dropping, entering/leaving, inserting/removing, etc. The author would define signals manually for less common stuff and emit them when needed. In the case of box.opening, the code that emits the signal (in this case by adv3lite itself) could look something like:

self.opening()

or:

emit self.opening

Abstract signals that have a higher level meaning and are not tied to a single property state change would also be possible. Like:

connect(diva.startsYelling, peter.handleNoise, cat.kill, microphone.handleNoise, window.handleNoise);

Again, the diva doesn’t need to know anything about the other objects when the yelling starts. The objects themselves handle the getting yelled at.

To me, the whole signal idea seems to generalize something that’s already in TADS. The dobjFor (and friends) methods for example, are basically events handlers. But they’re hardwired callbacks, you can’t rewire them easily. Signals would provide an easy way to change the plumbing that right now is hard-coded under the hood. With a full-on signaling mechanism, even the dobjFor could be just slots for signals emitted by player objects, and could be rewired at will. Though of course making everything signal-based like this would be a lot of work.

I like this idea. I’m musing about other possible examples:

connect(guard.asleep, cellDoor.openable); connect(electricity.flowing, stove.canHeat); connect(tire.inflated, bicycle.canTravel);
Each of the first-position properties (asleep, flowing, and inflated) could presumably be switched on and off, perhaps repeatedly, by other events. And the second-position properties are not things that happen – they’re potentiations. Things that can now happen (if the player tries the actions). What’s happening here, I think, would be equivalent to something like this:

stove: Fixture 'stove' // ... etc. canHeat = (electricity.flowing) ;
I think possibly (again, I’m not a real programmer) the point of what you’re suggesting is that if the author later decides that the electricity will be flowing whenever the giant hamster is running on the treadmill, there’s no need to edit the stove object. All that would be needed would be:

disconnect(electricity.flowing, stove.canHeat); connect(hamster.running, stove.canHeat);
But it’s not clear to me where that code would go. It couldn’t go in a method within the hamster object, because it wouldn’t run (changing the connections) unless a particular block of hamster code runs. If it’s global code (tucked away in showIntro, for example), then it’s not where it belongs in an OO sense; on the contrary, it’s going to make debugging harder. I suppose connect and disconnect could be “init properties” of some sort, declared within individual objects as needed, but created during preinit.

I don’t know. I’m just mumbling here in a confused way. Feel free to ignore me.

Before we all get carried away by too many flights of fancy, let me say that I’m not planning on any radical reworking of adv3Lite to work entirely by signals; nor do I want to risk changing it into adv3ExtremelyHeavy or adv3Quagmire by putting more mechanisms into the core library.

I should also point out that this kind of syntax cannot possibly me made to work in the TADS 3 language:

connect(guard.asleep, cellDoor.openable);
connect(electricity.flowing, stove.canHeat);
connect(tire.inflated, bicycle.canTravel);

What the first of the statements does in TADS 3 is to call the connect() function with the values of the guard.asleep and cellDoor.openable properties passed as arguments; so it would be like calling:

connect(nil, nil);

Which doesn’t establish any kind of relationship that you’d be looking for. Possibly what you had in mind was something like:

connect(guard, &asleep, cellDoor, &openable);

But the thought of trying to make that do anything useful for any arbitrary combination of objects and properties gives me nightmares, as well as bringing to mind:

connect(sledgehammer, crack, nut);

That said, this syntax reminds me of that used to establish relations in the new Relations extension that’s been added to the library for the new release (and is already available from GitHub):

relate(obj1, relation, obj2);
unrelate(obj1, relation, ob2);

It occurs to me further that the original suggestion is somewhat akin to establishing a particular kind of relation between objects, which makes me wonder in turn whether a Signals extension (which I might consider adding to the library) couldn’t be based on making a Signal a special kind of Relation. The basic framework might look something like this:

modify Thing
   emit(sig)
  {
      sig.emit(self);
  }

  handle(sender, sig)
  {
  }
;

class Signal: Relation
   emit(sender)
   {
        foreach(local obj in valToList(relTable(sender))
           obj.handle(sender, self);
   }
;

Then you could do stuff like:

modify Thing
  makeOpen(stat)
  {
      inherited(stat);
      if(stat)
        emit(openingSignal);
      else
        emit(closingSignal);
   }
;

openingSignal: Signal 'opening'
;

closingSignal: Signal 'closing'
;

// and in some method somewhere:
relate(magicBox, 'opening', dragon);
unrelate(cellDoor, 'closing', prisonGuard);

This is just a rough idea, of course, and it doesn’t cover what happens on the handle() side, but it seems to me to be a possible framework for something that might be feasible on the basis of what’s already in the adv3Lite library.

EDIT: A couple of further points.

On further reflection, I think it would be up to game code to deal with the handle() side of things. Any one-size-fits all solution in the library (or an extension) is likely to prove needlessly cumbersome. Moreover, it should presumably be entirely up to the receiver how it wants to handle any given signal from any given sender. I suspect in many, if not most cases, potential receivers will only be interested in one particular signal from one particular sender, so that a handle() method could be as simple as:

 handle(sender, signal)
 {
     if(sender == safe && signal == openingSignal)
        "The alarm starts ringing frantically. ";
  }

At the other extremene, if an object needed to handle a large number of combinations of different signals and senders, the best solution might be to use a Rulebook (as defined in the new rules extension added for the next release):

 handle(sender, signal)
 {
     alarmHandleRules.follow(signal, sender);
 }

...

alarmHandleRules: RuleBook
   initBook(signal, sender)
   {
      actor = sender;
   }
;

+ Rule
     who = safe
     matchObj = openingSignal

    follow(signal, sender)
    {                  
        "The alarm starts ringing frantically. ";
    }
;

+ Rule
   ...

One further point: the adv3Lite already issue a great many signals between objects, in the form of beforeAction, beforeTravel, afterAction and afterTravel notifications (plus a few others). There’s no need to duplicate that mechanism. The kind of signalling system outlined above would presumably only need to cater for cases existing mechanisms didn’t cover, which would mainly to be to send signals between objects that would not otherwise normally be connected by the standard notification system, and to provide a means of switching the sending of such signals on and off.

I’ve just knocked up an experimental extension along the lines I sketched above and uploaded it to GitHub (as signals.t under the extensions folder).
There’s no documentation as yet, apart from a few comments in the source code. If you want to try it, you’ll need the relations.t extension as well.

I’ve defined a few signals relating to common state changes and actions such as opening and taking. I’m not sure if many more would be needed in the extension; at some point game code would need to define and implement any more specialized signals it needed, and in any case there are limits to what the adv3Lite library can track. At this point, though, the extension is intended mainly as an illustration of the approach I indicated in my previous post and something for people to play around with to see if it might be of any use.

So far I’ve only tested it with the following extremely trivial game, which nevertheless illustrates how to use the thing:

gameMain: GameMainDef
    initialPlayerChar = me
    paraBrksBtwnSubcontents = nil
    
    showIntro()
    {
        relate(pen, 'take', box);
    }   
;


me: Actor 'me' @room
    "You don't look quite yourself. "
        
    person = 2
    
;


room: Room 'room'
    "The room. "  
    
;

+ box: OpenableContainer 'box'
    lockability = indirectLockable
    
    handle(sender, signal)
    {
        if(sender == pen && signal == takeSignal && isLocked)
        {
            makeLocked(nil);
            "As you lift the pen there's a mysterious click from the box. ";
        }
    }
;

++ gem: Thing 'gem'
;

+ pen: Thing 'pen'
;