carrot

package module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 2, 2023 License: MIT Imports: 9 Imported by: 4

README

carrot

Go Reference

A go coroutine library, designed to run inside game loops. It is library to be primarily used for creating asynchronous state machines.

Features

  • subjectively simple API
  • arbitrarily compose any coroutines
  • cancelablle and awaitable sub-coroutines
  • concurrent-safe without any further explicit locking, no coroutines will be running at the same time

Definitions

Coroutine - a function that takes a Control argument

Control - an object used to control the coroutine, with operations such yielding or cancelling

Script - a group of running related coroutines

Frame - equivalent to one call to Update(). Alternatively, it is one iteration in the game loop

Installation

go get github.com/nvlled/carrot

Documentation

API reference can be found here. A working example program can be found here:TODO.

Quick example code

count := 0
script := carrot.Start(func(ctrl carrot.Control) {
    println("Started!")
    for i := 0; i < 10; i++ {
        ctrl.Yield()
        count++
    }
})

for !script.IsDone() {
    script.Update()
    println(count)
}
script := carrot.Start(func(ctrl carrot.Control) {
  // synchronously call a coroutine
  subCoroutine(ctrl)

  // will not proceed here until subCoroutine is done

  // asynchronously start coroutines
  otherCtrl := ctrl.StartAsync(otherCoroutine)
  moreCtrl := ctrl.StartAsync(moreCoroutine)
  someCtrl := ctrl.StartAsync(someCoroutine)

  // proceed here immediately without waiting,
  // all sub-coroutines will run in the background

  done := false
  for !done {
    done = doSomething()
    ctrl.Yield()
  }

  // cancel moreCoroutine
  moreCtrl.Cancel()

  // waits until otherCoroutine is done
  ctrl.YieldUntil(otherCtrl.IsDone)

  // someCoroutine will be cancelled automatically
  // when this main coroutine ends.
})

// ... update script somewhere else

For more examples, see the coroutine test file. For actual usage, see the example platformer game, or the screenshot tool. TODO: Fix link

Troubleshooting

  • My program froze or hung up

    Check if you have any loops that doesn't have a yield in it. Consider the following:

    for {
      input := getInput()
      state := doSomething(input)
      updateState(state)
      ctrl.Yield() // forgetting this will freeze the program
    }
    

    If not, then it might be a bug.

Development

If you plan on making changes for yourself, regularly run the tests at least 10 times after some changes, especially anything inside loopRunner or Update methods. It's all too easy to cause a deadlock, or a bug that occurs rarely when you least expect it.

Prior art

Took some inspiration from a C# library AwaitableCoroutine. Notable difference is that carrot doesn't use shared global state, and async sub-coroutines can be cancelled without affecting parent coroutines.

Documentation

Overview

Package carrot is a coroutine library using channels

Index

Constants

This section is empty.

Variables

View Source
var ErrCancelled = errors.New("coroutine has been cancelled")

The error that is thrown while waiting on methods like Yield(), Sleep() and Delay(). This is used to prevent a coroutine from continuing when cancelled. No need to explicitly handle and recover from this error inside a coroutine.

Functions

func PreAllocCoroutines added in v0.7.0

func PreAllocCoroutines(count int)

Pre-allocate a number of coroutine.

func SetLogging added in v0.6.0

func SetLogging(enable bool)

Types

type Control added in v0.7.0

type Control struct {
	// ID of invoker. Mainly used for debugging.
	ID int64
	// contains filtered or unexported fields
}

An Control is used to direct the program flow of a coroutine.

Note: Methods are all concurrent-safe.

Note: Methods may block for one or more several frames,
except for those labeled with Async.

Note: Control methods should be only called within a coroutine
since yield methods will panic with ErrCancelled when cancelled.
This error will automatically be handled inside a coroutine,
no need to try to recover from this.

func NewControl added in v0.7.0

func NewControl() *Control

func (*Control) Abyss added in v0.7.0

func (ctrl *Control) Abyss()

Causes the coroutine to block indefinitely and spiral downwards the endless depths of nothingness, never again to return from the utter blackness of empty void.

func (*Control) Cancel added in v0.7.0

func (ctrl *Control) Cancel()

Cancels the coroutine. Also cancels all child coroutines created with StartAsync. This does not affect parent coroutines.

Note: Cancel() won't immediately take effect.
Actual cancellation will be done on next Update().

func (*Control) Delay added in v0.7.0

func (ctrl *Control) Delay(count int)

Delay waits for a number of calls to Update(). Panics when cancelled.

func (*Control) IsDone added in v0.7.0

func (ctrl *Control) IsDone() bool

Returns true it's not IsRunning() and is not flagged for Restart().

func (*Control) IsRunning added in v0.7.0

func (ctrl *Control) IsRunning() bool

Returns true if the coroutine is still running, meaning the coroutine function hasn't returned.

func (*Control) Logf added in v0.7.0

func (ctrl *Control) Logf(format string, args ...any)

Use for debugging. Call SetLogging(true) to enable.

func (*Control) Restart added in v0.7.0

func (ctrl *Control) Restart()

Restarts the coroutine. If the coroutine still running, it is cancelled first.

Note: Restart() won't immediately take effect.
Actual restart will be done on next Update().

func (*Control) Sleep added in v0.7.0

func (ctrl *Control) Sleep(sleepDuration time.Duration)

Sleep blocks and waits for the given duration.

Note: Actual sleep duration might be off by several milliseconds,
depending on your update FPS. Minimum sleep duration will be
the frame duration.

func (*Control) StartAsync added in v0.7.0

func (ctrl *Control) StartAsync(coroutine Coroutine) SubControl

Starts a new child coroutine asynchronously. The child coroutine will be automatically cancelled when the current coroutine ends and is no longer IsRunning(). To explicitly wait for the child coroutine to finish, use any preferred synchronization method, or do something like

ctrl.YieldUntil(childIn.IsDone)

See also the test functions TestAsync* for a more thorough example.

func (*Control) String added in v0.7.0

func (ctrl *Control) String() string

func (*Control) Transition added in v0.7.0

func (ctrl *Control) Transition(newCoroutine Coroutine)

Changes the current coroutine to a new one. If there is a current coroutine running, it is cancelled first. This is conceptually equivalent to transitions in finite state machines.

func (*Control) Yield added in v0.7.0

func (ctrl *Control) Yield()

Yield waits until the next call to Update(). In other words, Yield() waits for one frame. Panics when cancelled.

func (*Control) YieldUntil added in v0.7.0

func (ctrl *Control) YieldUntil(fn func() bool)

Repeatedly yields, and stops when fn returns true. Similar to WhileFunc(), but with the condition negated.

func (*Control) YieldUntilVar added in v0.7.0

func (ctrl *Control) YieldUntilVar(value *bool)

Repeatedly yields, and stops when *value is true. Similar to While(), but with the condition negated.

func (*Control) YieldWhile added in v0.7.0

func (ctrl *Control) YieldWhile(fn func() bool)

Repeatedly yields, and stops when fn returns false.

func (*Control) YieldWhileVar added in v0.7.0

func (ctrl *Control) YieldWhileVar(value *bool)

Repeatedly yields, and stops when *value is false or nil.

type Coroutine

type Coroutine = func(*Control)

A Coroutine is function that only takes an *Control argument.

type Script

type Script struct {
	// contains filtered or unexported fields
}

A Script is an instance of related coroutines running.

func Create added in v0.4.0

func Create() *Script

Creates an inactive coroutine script. To be used with script.Transition(otherCoroutine).

func Start

func Start(coroutine Coroutine) *Script

Creates a new coroutine script. Coroutine will only start on the first call to Update().

func (*Script) Cancel

func (script *Script) Cancel()

Cancels the coroutine. All coroutines started inside the script will be cancelled.

Note: cancellation will be done in the next Update()

func (*Script) IsDone

func (script *Script) IsDone() bool

Returns true if the coroutine finishes running and is not restarting.

func (*Script) Logf added in v0.6.0

func (script *Script) Logf(format string, args ...any)

Use for debugging. Call SetLogging(true) to enable.

func (*Script) Restart

func (script *Script) Restart()

Restarts the coroutine. If the coroutine is still running, it is Cancel()'ed first, then the coroutine is started again.

Note: restart will be done in the next Update()

func (*Script) Transition added in v0.4.0

func (script *Script) Transition(newCoroutine Coroutine)

Changes the current coroutine function to a new one. The old one is cancelled first before running the new coroutine. This is conceptually equivalent to transitions in finite state machines.

func (*Script) Update

func (script *Script) Update()

Update causes blocking calls to Yield(), Delay(), DelayAsync() and RunOnUpdate() to advance one step. Update is normally called repeatedly inside a loop, for instance a game loop, or any application loop in the main thread.

Note: Update is blocking, and will not return until
a Yield() is called inside the coroutine.

type SubControl added in v0.7.0

type SubControl interface {
	Cancel()
	Restart()
	Transition(Coroutine)
	IsRunning() bool
	IsDone() bool
}

A SubControl is a limited Control that is returned from the StartAsync method.

Note: You can cast SubControl to *Control if you
need to use Yield*() methods from a parent
coroutine, but this can lead to deadlocks or
unexpected behaviour is misused.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL