Custom event notification

lexfiend 13 Dec 2005: After noticing at least one request along the lines of "[event generate] without Tk", I decided to hack the following, which is almost-but-not-quite-like trigger:

  namespace eval notify {
    namespace export register deregister trigger
    array set narray {}

    proc register {name proc} {
      variable narray
      set narray(${name}_proc) $proc
      trace add variable narray(${name}_args) write ::notify::process_trigger
    }

    proc deregister {name} {
      variable narray
      if {[info exists narray(${name}_proc)]} {
        trace remove variable narray(${name}_args) write ::notify::process_trigger
        unset narray(${name}_proc)
        unset narray(${name}_args)
      }
    }

    proc process_trigger {narray args_key op} {
      set proc_key [regsub {_args$} $args_key _proc]
      after 1 [list eval $::notify::narray($proc_key) $::notify::narray($args_key)]
    }

    proc trigger {name args} {
      variable narray
      set narray(${name}_args) $args
    }
  }

  proc test_print {arg1 args} {
    puts stderr "arg1 = $arg1"
    puts stderr "args = $args"
  }
  proc test_notify {} {
    notify::register !test1 test_print
    notify::trigger !test1 "This is" a test
    notify::trigger !test1 "This is" another test
    notify::deregister !test1
    notify::trigger !test1 "This should" not fire
  }
  after idle test_notify
  after 1000 {set ::forever 1}
  vwait forever

No warranties, and I'm sure it can be improved.

(As luck would have it, after I hacked the above up, I found The observer design pattern. Marco's implementation is certainly more elegant and can handle multiple registered procedures per trigger, but doesn't actually use the event queue as such.)

AMG: I improved it a tiny bit. Now you can have events whose names end in _proc. Before if you had an event named foo and another named foo_proc, things would break. lexfiend: True. I never expected anyone to name events with a _proc suffix, but better safe than sorry.

Hey, is there a difference between [after 1 $script] and [after 0 $script]? lexfiend: Not that I've ever been able to notice. Both should work just as well -- I prefer after 1 as the mental equivalent of "take a very short break, then get back to work". 8-)

One design issue I often run into is multiple "objects" both interested in and able to generate some given event. When Object A generates the event, Object B and Object C should be notified. Should Object A be notified as well? Generally I say no, but how is this notification inhibited? Also what about notifications triggered by responses to other notifications? (I'll get to that in a bit.)

Let's say the event is the modification of an [entry] box: A, B, and C each have one, and their values are all functions of some piece of shared data. A's [entry] is modified. B and C are notified and update their [entry]s. If A is also notified, it redundantly (and incorrectly, in the case of many possible [entry] contents mapping to any given shared data value; e.g. .5 == 0.5 == 5e-1) updates its own [entry], possibly squashing whatever the user was trying to type.

lexfiend: I'd say that could be handled in one of two ways. Either:

  • Prepend the event originator identifier (in the above example, A, B or C) to the event data
  • Notify everybody
  • Ensure the registered event handlers all check the event originator arg and not take action if originator == me

or take a page from the GroupKit book and implement separate trigger_all and trigger_others methods. Personally, I'd prefer the latter method; it's all too easy to forget about checking the originator, so the ability to explicitly say tell everybody in one place and tell everybody except myself in another is a Good Thing to have.

AMG: Next, what if the event is raised by a write trace on the variable "backing" an [entry]? (Is this legal?) Without careful (i.e. kludgy) coding, now receiving an event can trigger that same event! Such loops are also possible but harder to spot with circular chains of events.

lexfiend: I'd say that Really Bad Design. 8-)

AMG: I have some ideas about how to handle this, but this lab room is so friggin' cold I can't think or type anymore.