Embed out in coroutine+YieldTo+Yielder interface+Sleeper

This commit is contained in:
bloeys
2022-11-05 01:11:13 +04:00
parent 1d4451dac2
commit b40ae1fe3d
7 changed files with 293 additions and 16 deletions

View File

@ -1 +1,5 @@
# cogo # cogo
Cogo is a **Coroutine** implementation for Go!
## What are Coroutines?

View File

@ -2,32 +2,67 @@ package cogo
import "fmt" import "fmt"
type CoroutineFunc[InT, OutT any] func(c *Coroutine[InT, OutT]) (out OutT) type CoroutineFunc[InT, OutT any] func(c *Coroutine[InT, OutT])
var _ Yielder = &Coroutine[int, int]{}
type Coroutine[InT, OutT any] struct { type Coroutine[InT, OutT any] struct {
State int32 State int32
SubState int32 SubState int32
In InT In InT
Out OutT
Func CoroutineFunc[InT, OutT] Func CoroutineFunc[InT, OutT]
Yielder Yielder
} }
func (c *Coroutine[InT, OutT]) Begin() { func (c *Coroutine[InT, OutT]) Begin() {
} }
func (c *Coroutine[InT, OutT]) Tick() (out OutT, done bool) { func (c *Coroutine[InT, OutT]) Tick() (done bool) {
if c.State == -1 { if c.State == -1 {
return out, true return true
} }
out = c.Func(c) if c.Yielder != nil {
return out, c.State == -1 if !c.Yielder.Tick() {
return false
}
c.Yielder = nil
}
oldYielder := c.Yielder
c.Func(c)
// On YieldTo() we want to always tick once before returning, so here we check do that.
// Also, if the yielder was done after one tick we nil it
if c.Yielder != oldYielder {
if c.Yielder.Tick() {
c.Yielder = nil
} else {
return false
}
}
return c.State == -1
} }
func (c *Coroutine[InT, OutT]) Yield(out OutT) { func (c *Coroutine[InT, OutT]) Yield(out OutT) {
panic(fmt.Sprintf("Yield got called at runtime, which means the code generator was not run, you used cogo incorrectly, or cogo has a bug. Yield should NOT get called at runtime. coroutine: %+v;;; yield value: %+v;;;", c, out)) panic(fmt.Sprintf("Yield got called at runtime, which means the code generator was not run, you used cogo incorrectly, or cogo has a bug. Yield should NOT get called at runtime. coroutine: %+v;;; yield value: %+v;;;", c, out))
} }
func (c *Coroutine[InT, OutT]) YieldTo(y Yielder) {
panic(fmt.Sprintf("YieldTo got called at runtime, which means the code generator was not run, you used cogo incorrectly, or cogo has a bug. Yield should NOT get called at runtime. coroutine: %+v;;; yielder value: %+v;;;", c, y))
}
func HasGen() bool { func HasGen() bool {
return true return true
} }
func New[InT, OutT any](coro CoroutineFunc[InT, OutT], input InT) (c *Coroutine[InT, OutT]) {
return &Coroutine[InT, OutT]{
Func: coro,
In: input,
}
}

20
cogo/sleeper.go Executable file
View File

@ -0,0 +1,20 @@
package cogo
import "time"
var _ Yielder = &Sleeper{}
type Sleeper struct {
wakeupTime time.Time
}
func (s *Sleeper) Tick() bool {
return !time.Now().Before(s.wakeupTime)
}
// NewSleeper returns a sleeper that is done after at least sleepDuration time has passed
func NewSleeper(sleepDuration time.Duration) *Sleeper {
return &Sleeper{
wakeupTime: time.Now().Add(sleepDuration),
}
}

5
cogo/yielder.go Executable file
View File

@ -0,0 +1,5 @@
package cogo
type Yielder interface {
Tick() (done bool)
}

94
demo.cogo.go Executable file
View File

@ -0,0 +1,94 @@
// Code generated by 'cogo'; DO NOT EDIT.
package main
import (
"time"
"github.com/bloeys/cogo/cogo"
)
func test_cogo(c *cogo.Coroutine[int, int]) {
switch c.State {
case 0:
switch c.SubState {
default:
}
println("test yield:", 1)
c.State++
c.SubState = -1
c.Out = 1
return
case 1:
switch c.SubState {
default:
}
c.State++
c.SubState = -1
c.Yielder = cogo.NewSleeper(100 * time.Millisecond)
return
case 2:
switch c.SubState {
default:
}
c.State++
c.SubState = -1
c.Yielder = cogo.New(test2, 0)
return
case 3:
switch c.SubState {
default:
}
println("test yield:", 2)
c.State++
c.SubState = -1
c.Out = 2
return
case 4:
switch c.SubState {
default:
}
c.State = -1
c.SubState = -1
default:
c.State = -1
c.SubState = -1
return
}
}
func test2_cogo(c *cogo.Coroutine[int, int]) {
switch c.State {
case 0:
switch c.SubState {
default:
}
println("test2222 yield:", 1)
c.State++
c.SubState = -1
c.Out = 1
return
case 1:
switch c.SubState {
default:
}
println("test2222 yield:", 2)
c.State++
c.SubState = -1
c.Out = 2
return
case 2:
switch c.SubState {
default:
}
c.State = -1
c.SubState = -1
default:
c.State = -1
c.SubState = -1
return
}
}

54
demo.go Executable file
View File

@ -0,0 +1,54 @@
//go:generate cogo
package main
import (
"time"
"github.com/bloeys/cogo/cogo"
)
func runDemo() {
c := cogo.New(test, 0)
ticks := 1
for done := c.Tick(); !done; done = c.Tick() {
println("Ticks done:", ticks, "; Output:", c.Out, "\n")
ticks++
time.Sleep(1 * time.Millisecond)
}
}
func test(c *cogo.Coroutine[int, int]) {
if cogo.HasGen() {
test_cogo(c)
return
}
c.Begin()
println("test yield:", 1)
c.Yield(1)
c.YieldTo(cogo.NewSleeper(100 * time.Millisecond))
c.YieldTo(cogo.New(test2, 0))
println("test yield:", 2)
c.Yield(2)
}
func test2(c *cogo.Coroutine[int, int]) {
if cogo.HasGen() {
test2_cogo(c)
return
}
c.Begin()
println("test2222 yield:", 1)
c.Yield(1)
println("test2222 yield:", 2)
c.Yield(2)
}

85
main.go
View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"go/ast" "go/ast"
"go/format" "go/format"
@ -15,8 +16,19 @@ import (
"golang.org/x/tools/imports" "golang.org/x/tools/imports"
) )
var (
demo = flag.Bool("demo", false, "")
)
func main() { func main() {
flag.Parse()
if *demo {
runDemo()
return
}
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {
panic(err) panic(err)
@ -276,7 +288,7 @@ func (p *processor) genCogoFuncsNodeProcessor(c *astutil.Cursor) bool {
// Add everything from the last begin/yield until this yield into a case // Add everything from the last begin/yield until this yield into a case
stmtsSinceLastCogo := funcDecl.Body.List[lastCaseEndBodyListIndex+1 : i] stmtsSinceLastCogo := funcDecl.Body.List[lastCaseEndBodyListIndex+1 : i]
caseStmts := make([]ast.Stmt, 0, len(stmtsSinceLastCogo)+2) caseStmts := make([]ast.Stmt, 0, len(stmtsSinceLastCogo)+4)
caseStmts = append(caseStmts, subSwitchStmt) caseStmts = append(caseStmts, subSwitchStmt)
subSwitchStmt = &ast.SwitchStmt{ subSwitchStmt = &ast.SwitchStmt{
@ -299,9 +311,12 @@ func (p *processor) genCogoFuncsNodeProcessor(c *astutil.Cursor) bool {
Tok: token.ASSIGN, Tok: token.ASSIGN,
Rhs: []ast.Expr{ast.NewIdent("-1")}, Rhs: []ast.Expr{ast.NewIdent("-1")},
}, },
&ast.ReturnStmt{ &ast.AssignStmt{
Results: callExpr.Args, Lhs: []ast.Expr{ast.NewIdent(coroutineParamName + ".Out")},
Tok: token.ASSIGN,
Rhs: callExpr.Args,
}, },
&ast.ReturnStmt{},
) )
switchStmt.Body.List = append(switchStmt.Body.List, switchStmt.Body.List = append(switchStmt.Body.List,
@ -313,6 +328,50 @@ func (p *processor) genCogoFuncsNodeProcessor(c *astutil.Cursor) bool {
lastCaseEndBodyListIndex = i lastCaseEndBodyListIndex = i
} else if cogoFuncSelExpr.Sel.Name == "YieldTo" {
// Add everything from the last begin/yield until this yield into a case
stmtsSinceLastCogo := funcDecl.Body.List[lastCaseEndBodyListIndex+1 : i]
caseStmts := make([]ast.Stmt, 0, len(stmtsSinceLastCogo)+5)
caseStmts = append(caseStmts, subSwitchStmt)
subSwitchStmt = &ast.SwitchStmt{
Tag: ast.NewIdent(coroutineParamName + ".SubState"),
Body: &ast.BlockStmt{
List: []ast.Stmt{
getCaseWithStmts(nil, []ast.Stmt{}),
},
},
}
caseStmts = append(caseStmts, stmtsSinceLastCogo...)
caseStmts = append(caseStmts,
&ast.IncDecStmt{
Tok: token.INC,
X: ast.NewIdent(coroutineParamName + ".State"),
},
&ast.AssignStmt{
Lhs: []ast.Expr{ast.NewIdent(coroutineParamName + ".SubState")},
Tok: token.ASSIGN,
Rhs: []ast.Expr{ast.NewIdent("-1")},
},
&ast.AssignStmt{
Lhs: []ast.Expr{ast.NewIdent(coroutineParamName + ".Yielder")},
Tok: token.ASSIGN,
Rhs: callExpr.Args,
},
&ast.ReturnStmt{},
)
switchStmt.Body.List = append(switchStmt.Body.List,
getCaseWithStmts(
[]ast.Expr{ast.NewIdent(fmt.Sprint(len(switchStmt.Body.List)))},
caseStmts,
),
)
lastCaseEndBodyListIndex = i
} }
} }
@ -435,9 +494,12 @@ func (p *processor) genCogoForStmt(forStmt *ast.ForStmt, coroutineParamName stri
Tok: token.ASSIGN, Tok: token.ASSIGN,
Rhs: []ast.Expr{ast.NewIdent(fmt.Sprint(postInitLblNum))}, Rhs: []ast.Expr{ast.NewIdent(fmt.Sprint(postInitLblNum))},
}, },
&ast.ReturnStmt{ &ast.AssignStmt{
Results: selExprArgs, Lhs: []ast.Expr{ast.NewIdent(coroutineParamName + ".Out")},
Tok: token.ASSIGN,
Rhs: selExprArgs,
}, },
&ast.ReturnStmt{},
}, },
} }
} }
@ -472,9 +534,12 @@ func (p *processor) genCogoBlockStmt(blockStmt *ast.BlockStmt, coroutineParamNam
Tok: token.ASSIGN, Tok: token.ASSIGN,
Rhs: []ast.Expr{ast.NewIdent(fmt.Sprint(newSubStateNum))}, Rhs: []ast.Expr{ast.NewIdent(fmt.Sprint(newSubStateNum))},
}, },
&ast.ReturnStmt{ &ast.AssignStmt{
Results: selExprArgs, Lhs: []ast.Expr{ast.NewIdent(coroutineParamName + ".Out")},
Tok: token.ASSIGN,
Rhs: selExprArgs,
}, },
&ast.ReturnStmt{},
}, },
} }
} }
@ -566,12 +631,12 @@ func createHasGenIfStmt(funcDecl *ast.FuncDecl, coroutineParamName string) *ast.
Cond: createStmtFromSelFuncCall("cogo", "HasGen").(*ast.ExprStmt).X, Cond: createStmtFromSelFuncCall("cogo", "HasGen").(*ast.ExprStmt).X,
Body: &ast.BlockStmt{ Body: &ast.BlockStmt{
List: []ast.Stmt{ List: []ast.Stmt{
&ast.ReturnStmt{ &ast.ExprStmt{
Results: []ast.Expr{&ast.CallExpr{ X: &ast.CallExpr{
Fun: ast.NewIdent(funcDecl.Name.Name + "_cogo"), Fun: ast.NewIdent(funcDecl.Name.Name + "_cogo"),
Args: []ast.Expr{ast.NewIdent(coroutineParamName)}, Args: []ast.Expr{ast.NewIdent(coroutineParamName)},
}}, }},
}, &ast.ReturnStmt{},
}, },
}, },
} }