package provide mylsn 1.0
package require Itcl
# ListenEvent
# =====================================================================
itcl::class ListenEvent {
public variable name
public variable src
public variable data
constructor {aname asrc {adata ""}} {
set name aname
set src asrc
set data adata
}
}
# DataListener
# =====================================================================
itcl::class DataListener {
# list of understandable providers' datatypes. Empty - all
public variable datatypes ""
protected variable _event {}; # ListenEvent
# all listeners
public common all {}
constructor {} { lappend all $this }
destructor {
set i [lsearch -exact $all $this]
if {$i >= 0} { set all [lreplace $all $i $i] }
}
# FSM state: LISTEN|SESSION|OFF. Means:
# LISTEN - ready for data from data provider
# SESSION - already reading some data (first data packet was received)
# OFF - reading of data is disabled
protected variable _lstate LISTEN
# values is list of values
protected method ondata {values} {}
# on run polling (listening session). columns is the list of columns names,
# units is the list of unit names
protected method onrun {columns units} {}
# on stop listening session
protected method onstop {} {}
# on add to data provider
protected method onadd {provider} {}
# on delete from data provider
protected method ondel {provider} {}
method event {ev src args} {
# generate event (call callback) for this listener.
# ev is ListenEvent object, src is the source of event.
# ev is one of run|stop|data|on|off|add|del:
# run - before first packet sent
# stop - after last packet sent
# data - packet is received
# on - enable listening
# off - disable listening
# add - connect with some data provider
# del - disconnect from some data provider
set _event [ListenEvent #auto $ev $src $args]
switch -- $_lstate {
LISTEN {
switch -- $ev {
run { set _lstate SESSION
catch { $this onrun {*}$args } }
off { set _lstate OFF }
add { catch { $this onadd {*}$args } }
del { catch { $this ondel {*}$args } }
}
}
SESSION {
switch -- $ev {
stop { set _lstate LISTEN
catch { $this onstop } }
data { catch { $this ondata {*}$args } }
}
}
OFF {
switch -- $ev {
on { set _lstate LISTEN }
add { catch { $this onadd {*}$args } }
del { catch { $this ondel {*}$args } }
}
}
}
}
# listen -on|-off
method listen {what} {
# listen -on -- turn-on listening
# listen -off -- turn-off listening
# event without src ("" - user call is event source)
switch -- $what {
-on { $this event on "" }
-off { $this event off "" }
default { error "listen can be -on or -off only" }
}
return
}
method listened {} {
# Is listening now?
return [expr {$_lstate ne "OFF"}]
}
# join columns and units with delimiter into new list
method join_columns_units {columns units {delim ","}} {
set res {}
foreach c $columns u $units {
lappend res "$c$delim$u"
}
return $res
}
}
# DataProvider
# =====================================================================
itcl::class DataProvider {
# static list of all providers
public common all {}
public variable datatype ""
protected variable _listeners
constructor {} { lappend all $this }
destructor {
set i [lsearch -exact $all $this]
if {$i >= 0} { set all [lreplace $all $i $i] }
}
# returns list (pair) of columns (list) and units - if they are
# fixed all the time (for any session)
method fixed_columns_units {} {}
# normalize name, need bcz user can use quilified name as
# ::a, not a.
# FIXME namespace which -variable does not work in Itcl, so
# i cut all :, but is possible to add/del only listeners on
# top-level namespace
protected method _normname {name} {
return [regsub -all ":" $name ""]
}
method get_listeners {} {
# Returns names of all listeners
return [array names _listeners]
}
method add_listener {listener} {
# Add some listener
set lsndts [$listener cget -datatypes]; # datatypes expected by listener
if {[llength $lsndts] && !($datatype in $lsndts)} {
# if listener datatypes not empty (expect some) and my datatype
# is not in it's datatypes, so I can't add this listener
error "Listener $listener does not understand $this provider"
}
set name [_normname $listener]
if {[itcl::is object -class DataListener $listener]} {
if {[array get _listeners $name] ne ""} {
error "Listener $name already exists"
}
set _listeners($name) $listener
$listener event add $this $this
} else {
error "listener should be DataListener object"
}
}
method del_listener {listener {stop 1}} {
# Deletes listener, sends before stop event if needed
set name [_normname $listener]
set listener [lindex [array get _listeners $name] 1]
if {$listener ne ""} {
if {$stop} { $listener event stop $this }
array unset _listeners $name
$listener event del $this $this
}
return $listener
}
# XXX not effective
method del_all_listeners {{stop 1}} {
# Deletes all listeners, send stop event before, if needed
foreach name [array names _listeners] {
del_listener $name $stop
}
}
method notify_all {ev args} {
# Notify all listeners with event ev and some args
foreach {name listener} [array get _listeners] {
$listener event $ev $this {*}$args
}
}
method number {} {
# Returns number of listeners
return [array size _listeners]
}
}
# ProxyListener - general object for process events without creating
# a special class
# =====================================================================
itcl::class ProxyListener {
inherit DataListener DataProvider
public variable onrunproc ""
public variable onstopproc ""
public variable ondataproc ""
public variable onaddproc ""
public variable ondelproc ""
protected method onrun {columns units} {
if {$onrunproc eq ""} {
notify_all run $columns $units
} else {
$onrunproc $columns $units
}
}
protected method onstop {} {
if {$onstopproc eq ""} {
notify_all stop
} else {
$onstopproc
}
}
protected method ondata {values} {
if {$ondataproc eq ""} {
notify_all data $values
} else {
$ondataproc $values
}
}
protected method onadd {provider} {
if {$onaddproc eq ""} {
notify_all add $provider
} else {
$onaddproc $provider
}
}
protected method ondel {provider} {
if {$ondelproc eq ""} {
notify_all del $provider
} else {
$ondelproc $provider
}
}
}
# DebugListener - repeater
# =====================================================================
itcl::class DebugListener {
inherit ProxyListener
public variable fixed_cu ""
constructor {} {
$this configure -datatype phys -datatypes {v s flt raw phys} \
-onrunproc [itcl::code $this onrunproc] \
-onstopproc [itcl::code $this onstopproc] \
-ondataproc [itcl::code $this ondataproc] \
}
method fixed_columns_units {} {
return $fixed_cu
}
method onrunproc {columns units} {
notify_all run $columns $units
puts "*DEBUG_${this}* onrun: $columns, $units"
}
method onstopproc {} {
notify_all stop
puts "*DEBUG_${this}* onstop"
}
method ondataproc {values} {
notify_all data $values
puts "*DEBUG_${this}* ondata: $values"
}
}
# procedures
# =====================================================================
proc debug_listen {d between0 between1} {
#DebugListener $this.#auto
$d configure -fixed_cu [$between0 fixed_columns_units]
listen $between0:$d
listen $d:$between1
}
proc listen args {
# Set who listen who:
# listen provider...: listener...
# or
# listen prov...: all
# or
# listen -- return list of lists {provider {listeners}}
# or
# listen -txt -- return formatted string (for user)
# or
# listen -p -- returns formatted string with providers and it's datatypes
# or
# listen -l -- returns formatted string with listeners and it's datatypes
if {[lsearch -exact $args "-txt"] != -1} {
# if there is "-txt" option, return formatted string
set res {}
foreach prov $DataProvider::all {
set nprov [regsub -all ":" $prov ""]
lappend res "$nprov: [join [$prov get_listeners] ,\ ]"
}
return [join $res "\n"]
} elseif {[lsearch -exact $args "-p"] != -1} {
set res {}
foreach prov $DataProvider::all {
set nprov [regsub -all ":" $prov ""]
set dt [$prov cget -datatype]
lappend res "$nprov: provides '$dt'"
}
return [join $res "\n"]
} elseif {[lsearch -exact $args "-l"] != -1} {
set res {}
foreach lsn $DataListener::all {
set nlsn [regsub -all ":" $lsn ""]
set dt [join [$lsn cget -datatypes] "','"]
lappend res "$nlsn: listens '$dt'"
}
return [join $res "\n"]
} elseif {$args eq ""} {
# if no args, returns listening table (all links)
set res {}
foreach prov $DataProvider::all {
lappend res [list $prov [$prov get_listeners]]
}
return $res
} else {
# normalize args (make ':' like arg, not part of another arg)
lassign [split [join $args " "] :] providers listeners
# if there is 'all' in listeners then it'll be all of them :)
if {[lsearch -exact $listeners all] != -1} {
set listeners $DataListener::all
}
# delete each listener from ALL known providers then attach
# to selected
foreach lsn $listeners {
foreach prov $DataProvider::all { $prov del_listener $lsn 1 }
foreach prov $providers { $prov add_listener $lsn }
}
}
}
There are classes:- ListenEvent
- DataListener (should be inherited)
- DataProvider (should be inherited)
- ProxyListener
- DebugListener
First 3 are clear. ProxyListener avoids creation of class - options-callbacks are used instead. Also, it's like bridge: listens and provides. DebugListener is proxy too, but for debug purpose (transparent bridge with console logging). Here is a scheme:
Procedure listen is used to link providers and listeners or to obtain all of providers/listeners in system, or to obtain current link scheme (with native or pretty text representation). debug_listen procedure is used to insert DebugListener between two nodes.

Комментариев нет:
Отправить комментарий
Thanks for your posting!