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.