-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
717 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package co | ||
|
||
import ( | ||
"slices" | ||
) | ||
|
||
const routineCancelled = "coroutine cancelled" | ||
|
||
type Yield func() | ||
|
||
func Create(f func(Yield)) *Routine { | ||
r := &Routine{ // 1 alloc | ||
resumed: make(chan struct{}), // 1 alloc | ||
done: make(chan struct{}), // 1 alloc | ||
status: Suspended, | ||
} | ||
go r.start(f) // 3 allocs | ||
|
||
return r | ||
} | ||
|
||
type Routine struct { | ||
done chan struct{} | ||
resumed chan struct{} | ||
status Status | ||
} | ||
|
||
func (r *Routine) start(f func(Yield)) { // 1 alloc | ||
defer r.recoverAndDestroy() | ||
|
||
_, ok := <-r.resumed // 2 allocs | ||
if !ok { | ||
panic(routineCancelled) | ||
} | ||
|
||
r.status = Running | ||
f(r.yield) | ||
} | ||
|
||
func (r *Routine) yield() { | ||
r.done <- struct{}{} | ||
r.status = Suspended | ||
if _, ok := <-r.resumed; !ok { | ||
panic(routineCancelled) | ||
} | ||
} | ||
|
||
func (r *Routine) recoverAndDestroy() { | ||
p := recover() | ||
if p != nil && p != routineCancelled { | ||
panic("coroutine panicked") | ||
} | ||
r.status = Dead | ||
close(r.done) | ||
} | ||
|
||
func (r *Routine) Resume() { | ||
if r.status == Dead { | ||
return | ||
} | ||
|
||
r.resumed <- struct{}{} | ||
<-r.done | ||
} | ||
|
||
func (r *Routine) Status() Status { | ||
return r.status | ||
} | ||
|
||
func (r *Routine) Cancel() { | ||
if r.status == Dead { | ||
return | ||
} | ||
|
||
close(r.resumed) | ||
<-r.done | ||
} | ||
|
||
type Status string | ||
|
||
const ( | ||
// Normal Status = "normal" // This coroutine is currently waiting in coresume for another coroutine. (Either for the running coroutine, or for another normal coroutine) | ||
Running Status = "running" // This is the coroutine that's currently running - aka the one that just called costatus. | ||
Suspended Status = "suspended" // This coroutine is not running - either it has yielded or has never been resumed yet. | ||
Dead Status = "dead" // This coroutine has either returned or died due to an error. | ||
) | ||
|
||
type Routines []*Routine | ||
|
||
func (r Routines) ResumeAll() Routines { | ||
for _, rout := range r { | ||
rout.Resume() | ||
} | ||
return slices.DeleteFunc(r, func(r *Routine) bool { | ||
return r.Status() == Dead | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package co_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/elgopher/pi/co" | ||
) | ||
|
||
func BenchmarkCreate(b *testing.B) { | ||
b.ReportAllocs() | ||
|
||
var r *co.Routine | ||
|
||
for i := 0; i < b.N; i++ { | ||
r = co.Create(f) // 6 allocs :( 12us :( | ||
} | ||
|
||
_ = r | ||
} | ||
|
||
func BenchmarkResume(b *testing.B) { | ||
b.ReportAllocs() | ||
|
||
var r *co.Routine | ||
|
||
for i := 0; i < b.N; i++ { | ||
r = co.Create(f) // 6 allocs | ||
r.Resume() // 1 alloc, 0.8us :( | ||
} | ||
_ = r | ||
} | ||
|
||
func BenchmarkResumeUntilFinish(b *testing.B) { | ||
b.ReportAllocs() | ||
|
||
var r *co.Routine | ||
|
||
for i := 0; i < b.N; i++ { | ||
r = co.Create(f) // 6 allocs | ||
r.Resume() // 1 alloc, 0.8us :( | ||
r.Resume() // 1 alloc, 0.8us :( | ||
} | ||
_ = r | ||
} | ||
|
||
func BenchmarkCancel(b *testing.B) { | ||
b.ReportAllocs() | ||
|
||
var r *co.Routine | ||
|
||
for i := 0; i < b.N; i++ { | ||
r = co.Create(f) // 6 allocs | ||
r.Cancel() // -2 alloc???? | ||
} | ||
_ = r | ||
} | ||
|
||
//go:noinline | ||
func f(yield co.Yield) { | ||
yield() | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package main | ||
|
||
import ( | ||
"math/rand" | ||
|
||
"github.com/elgopher/pi" | ||
"github.com/elgopher/pi/co" | ||
"github.com/elgopher/pi/ebitengine" | ||
) | ||
|
||
var coroutines co.Routines | ||
|
||
func main() { | ||
pi.Update = func() { | ||
if pi.MouseBtnp(pi.MouseLeft) { | ||
//r := movePixel(pi.MousePos) | ||
for j := 0; j < 8192; j++ { | ||
coroutines = append(coroutines, co.Create(complexIterator())) | ||
} | ||
} | ||
} | ||
|
||
pi.Draw = func() { | ||
pi.Cls() | ||
coroutines = coroutines.ResumeAll() | ||
//devtools.Export("coroutines", coroutines) | ||
} | ||
|
||
ebitengine.Run() | ||
} | ||
|
||
func movePixel(pos pi.Position) func(yield co.Yield) { | ||
return func(yield co.Yield) { | ||
for i := 0; i < 64; i++ { | ||
pi.Set(pos.X+i, pos.Y+i, byte(rand.Intn(16))) | ||
yield() | ||
yield() | ||
} | ||
} | ||
} | ||
|
||
func moveHero(startX, stopX, minSpeed, maxSpeed int) func(yield co.Yield) { | ||
anim := randomMove(startX, stopX, minSpeed, maxSpeed) | ||
|
||
return func(yield co.Yield) { | ||
for { | ||
x, hasNext := anim() | ||
pi.Set(x, 20, 7) | ||
if hasNext { | ||
yield() | ||
} else { | ||
return | ||
} | ||
|
||
} | ||
} | ||
} | ||
|
||
// Reusable iterator which returns int. TODO THIS IS UGLY. I CANT USE co.Yield because it does not accept parameter | ||
func randomMove(start, stop, minSpeed, maxSpeed int) func() (int, bool) { | ||
pos := start | ||
|
||
return func() (int, bool) { | ||
speed := rand.Intn(maxSpeed - minSpeed) | ||
if stop > start { | ||
pos = pi.MinInt(stop, pos+speed) // move pos in stop direction by random speed | ||
} else { | ||
pos = pi.MaxInt(stop, pos-speed) | ||
} | ||
|
||
return pos, pos != stop | ||
} | ||
} | ||
|
||
func complexIterator() func(yield co.Yield) { | ||
return func(yield co.Yield) { | ||
sleep(10)(yield) | ||
moveHero(10, 120, 5, 10)(yield) | ||
sleep(20)(yield) | ||
moveHero(120, 10, 2, 10)(yield) | ||
} | ||
} | ||
|
||
func sleep(iterations int) func(yield co.Yield) { | ||
return func(yield co.Yield) { | ||
for i := 0; i < iterations; i++ { | ||
yield() | ||
} | ||
} | ||
} |
Oops, something went wrong.