Originally by Danny Dulai
http://www.ishiboo.com/~nirva/Projects/phasemachines/ . I've found phasemachines to be very useful. --
dnm #
# Commands added while inside phase machine:
#
# phase_go_cb <num>
# Returns a callback to the phase given by the number in the
# argument. The first phase is 0, the second is 1, etc...
#
# phase_prev_cb
# Just like phase_go_cb, but it always returns the callback
# to the previous phase
#
# phase_same_cb
# Just like phase_go_cb, but it always returns the callback
# to the current phase
#
# phase_next_cb
# Just like phase_go_cb, but it always returns the callback
# to the next phase
#
# phase_go <num> <args>
# equiv of doing: eval [phase_go_cb <num>] <args>
#
# phase_next <args>
# equiv of doing: eval [phase_next_cb] <args>
#
# phase_exit
# cleanup resources used by phase machine
#
#------------------------------------------------------------
#
# Simple Example:
#
# proc foo { name nextphase } {
# puts helu
# after 1000 "$nextphase yay$name"
# }
#
# phasemachine test { name } {
# foo $name [phase_next_cb]
# } { str } {
# puts $str
# phase_exit
# }
#
# test hrm
# vwait done
#
# Looping example:
#
# proc foo { name nextphase } {
# puts helu
# after 1000 "$nextphase yay$name"
# }
#
# phasemachine test { name num } {
# puts --inphase0:$name:$num
# foo $name [phase_next_cb]
#
# } { str } {
# puts --inphase1:$str
# foo $str [phase_next_cb]
#
# } { str } {
# puts --inphase2:$str
# puts helu
# after 1000
# phase_go 0 bar [expr $num + 1]
# }
#
#test hrm 0
#vwait done
#
#
#------------------------------------------------------------------------------
namespace eval ::phasemachine { set _id 0 }
proc ::phasemachine::fixvar { v } {
regsub {([^(]*)\(.*\)} $v {\1} var
return $var
}
proc ::phasemachine::__set { id arg } {
upvar ::phasemachine::${id}::funcs funcs
upvar ::phasemachine::${id}::state state
if {[llength $arg] < 1 || [llength $arg] > 2} {
error "wrong # args: should be \"set varName ?newValue?\""
return
}
namespace eval ::phasemachine::$id [list variable [fixvar [lindex $arg 0]]]
if {[llength $arg] == 1} {
namespace eval ::phasemachine::$id [list ::set [lindex $arg 0]]
} else {
namespace eval ::phasemachine::$id [list ::set [lindex $arg 0] [lindex $arg 1]]
}
}
proc ::phasemachine::__go_phase { id num args } {
upvar ::phasemachine::${id}::funcs funcs
upvar ::phasemachine::${id}::state state
set state $num
set num [expr $num * 2]
set a [lindex $funcs $num]
incr num
set f [lindex $funcs $num]
set i 0
foreach a2 $a {
set def 0
if {[llength $a2] != 1} {
set def 1
set a3 [lindex $a2 1]
set a2 [lindex $a2 0]
}
namespace eval ::phasemachine::$id [list variable [fixvar $a2]]
if {$def && $i >= [llength $args]} {
namespace eval ::phasemachine::$id [list ::set $a2 $a3]
} else {
namespace eval ::phasemachine::$id [list ::set $a2 [lindex $args $i]]
}
incr i
}
namespace eval ::phasemachine::$id [list eval $f]
}
proc phasemachine { funcname args } {
if {[llength $args] < 2 || [expr [llength $args] % 2] != 0} {
error {wrong # args: should be "phasemachine name args body [args body..]"}
}
proc $funcname args "
upvar ::phasemachine::_id id
incr id
namespace eval ::phasemachine::\$id {set state -1}
namespace eval ::phasemachine::\$id {set funcs [list $args]}
proc ::phasemachine::\${id}::phase_get_current { } \"return \\\$::phasemachine::\${id}::state\"
proc ::phasemachine::\${id}::phase_go { num args } \"eval \\\[phase_go_cb \\\$num\\\] \\\$args\"
proc ::phasemachine::\${id}::phase_next { args } \"eval \\\[phase_next_cb\\\] \\\$args\"
proc ::phasemachine::\${id}::phase_go_cb { num } \"return \\\[list ::phasemachine::__go_phase \$id \\\$num\\\]\"
proc ::phasemachine::\${id}::phase_prev_cb { } \"return \\\[list ::phasemachine::__go_phase \$id \\\[expr \\\$::phasemachine::\${id}::state - 1\\\]\\\]\"
proc ::phasemachine::\${id}::phase_same_cb { } \"return \\\[list ::phasemachine::__go_phase \$id \\\$::phasemachine::\${id}::state\\\]\"
proc ::phasemachine::\${id}::phase_next_cb { } \"return \\\[list ::phasemachine::__go_phase \$id \\\[expr \\\$::phasemachine::\${id}::state + 1\\\]\\\]\"
proc ::phasemachine::\${id}::set { args } \"return \\\[::phasemachine::__set \$id \\\$args\\\]\"
proc ::phasemachine::\${id}::phase_exit { } \"namespace delete ::phasemachine::\$id\"
eval \[::phasemachine::\${id}::phase_next_cb\] \$args
"
}