# Coronet - a simple skeleton for coroutine networking
# CMcC and MS 2009-01-14
namespace eval Coronet {
# if you are adventurous, you can redefine yield in terms of ::yield
# proc yield {{retval ""}} {...}
variable maxline 1024
# provide access to variables defined in top level of coroutine
proc corovars {args} {
foreach n $args {lappend v $n $n}
uplevel 1 [list upvar #1 {*}$v]
}
# terminate coroutine
proc terminate {args} {
return -level [info level] $args ;# terminate consumer
}
# coroutine-enabled gets
proc get {socket {reason ""}} {
variable maxline
set result [yield]
set line ""
while {[chan gets $socket line] == -1 && ![chan eof $socket]} {
set result [yield]
if {$maxline && [chan pending input $socket] > $maxline} {
error "line length greater than $maxline"
}
}
if {[chan eof $socket]} {
terminate $reason ;# check the socket for closure
}
# return the line
return $line
}
# coroutine-enabled read
proc read {socket size} {
# read a chunk of $size bytes
set chunk ""
while {$size && ![chan eof $socket]} {
set result [yield]
set chunklet [chan read $socket $size]
incr size [expr {-[string length $chunklet]}]
append chunk $chunklet
}
if {[chan eof $socket]} {
terminate entity ;# check the socket for closure
}
# return the chunk
return $chunk
}
proc do {args} {
set x 1 ;# x is available via [corovars x]
.... this is where your network code goes
}
proc init {name socket args} {
set coro [coroutine $name ::Coronet::do {*}$args]
fileevent $socket readable [list $coro INCOMING]
return $coro
}
namespace export -clear *
namespace ensemble create -subcommands {}
}Note this uses the very important facility corovars.
AMG: A greatly simplified version of the above code appears in Wibble.
A slightly different form by kbk, snatched from the pastebin
proc cgets {chan args} {
if {[llength $args] == 1} {
upvar 1 [lindex $args 0] line
} elseif {[llength $args] > 1} {
return -code error "wrong \# args, should be \"[lindex [info level 0] 0] channel ?variable?\""
}
while {1} {
set blocking [fconfigure $chan -blocking]
fconfigure $chan -blocking 0
set status [catch {gets $chan line} result opts]
if {$status} {
fconfigure $chan -blocking $blocking
return -code $status -options $opts
} elseif {[fblocked $chan]} {
fileevent $chan readable [list [info coroutine]]
yield
fileevent $chan readable {}
} else {
fconfigure $chan -blocking $blocking
if {[llength $args] == 1} {
return $result
} else {
return $line
}
}
}
}
coroutine foo apply {{} {
while {[cgets stdin line] >= 0} {
puts $line
}
set ::finished 1
}}
vwait finished