Version 0 of coro dispatch for TclOO

Updated 2012-02-01 02:16:31 by CMcC

I often want to instantiate an object then call it via coroutine - it's useful for lightweight threading (green threads)

Here's an easy way to do it.

# coclass.tcl - classes which are also coros

namespace eval tcl::unsupported namespace export yieldm
namespace import tcl::unsupported::yieldm

if {[info commands ::oo::define::classmethod] eq ""} {
    proc ::oo::define::classmethod {name {args {}} {body {}}} {
        set class [lindex [info level -1] 1]
        set classmy [info object namespace $class]::my
        if {[llength [info level 0]] == 4} {
            uplevel 1 [list self method $name $args $body]
        }
        uplevel 1 [list forward $name $classmy $name]
    }
}

oo::class create oo::coclass {
    # objgone - the object has died - kill the coro too
    classmethod objgone {obj coro} {
        trace delete command $coro delete [list ::oo::coclass corogone $obj $coro]
        rename $coro {}
    }

    # corogone - the coro has gone - restart it if the object is still there.
    classmethod corogone {obj coro} {
        if {[info commands $obj] ne ""} {
            # (re)create a coro attachment to $obj
            coroutine $coro $obj __dispatch__        ;# create our coro with the object's name
            trace add command $coro delete [list ::oo::coclass corogone $obj $coro]
        } else {
            # both the object and the coro have gone.
        }
    }

    # intervene - add a __dispatch__ method to the object
    # __dispatch__ shims between the fake object (coro) and the actual object.
    method intervene {obj} {
        rename $obj ${obj}_        ;# move the object out of the way
        trace add command ${obj}_ delete [list ::oo::coclass::objgone ${obj}_ $obj]
        my corogone ${obj}_ $obj
        return $obj
    }

    # new - create a new instance of this class,
    # shimmed so it's actually being invoked as a coro
    method new {args} {
        tailcall my intervene [next {*}$args]
    }

    # new - create a new instance of this class,
    # shimmed so it's actually being invoked as a coro
    method create {name args} {
        tailcall my intervene [next $name {*}$args]
    }

    superclass ::oo::class
    constructor {args} {
        next {*}$args

        # add a __dispatch__ method to the class to shim between coro interface and object instance
        oo::define [self] {
            method __dispatch__ {} {
                set self [info coroutine]        ;# this is the coroutine
                set obj [self]                        ;# the original obj being forwarded to

                set result {}                ;# method invocation result
                catch {
                    while {1} {
                        set result [my {*}[::yieldm $result]]        ;# invoke whatever's passed in
                    }
                } e eo

                # The method invocation errored
                # this will kill the coro if we return it, but it must be returned to caller
                # So we must make another identically named coro and then return the error
                # from this inevitably doomed coro.
                rename $self ${self}_dead_
                if {[catch {::oo::coclass corogone $obj $self} e1 eo1]} {
                    # we failed to make the replacement coro - here's why
                    puts stderr "corogone fail: $e1 ($eo1)"
                }

                return -options $eo $e        ;# return the error result to the caller
                # this instance of the coro goes away now
            }
            export __dispatch__
        }

        return [self]
    }
}

oo::coclass create Fred {
    method Var {var} {
        variable $var
        return [set $var]
    }
    method error {} {
        error "ERROR"
    }

    constructor {args} {
        variable {*}$args
    }
}

if {1} {
    set fred [Fred new a 1 b 2 c 3]
    puts [$fred Var a]
    if {[catch {$fred error}]} {
        puts stderr "CAUGHT"
    }
    puts [$fred Var b]
    $fred destroy
}