mirror of https://github.com/Hilbis/Hilbish
feat: add timer pool and api (closes #135)
adds a map (but lets call it a pool) of all running timers. this makes us able to keep track of all running intervals and timeouts. it also means hilbish can wait for them to be done before exiting (it only waits when non interactive). this introduces the `hilbish.timers` interface, documented by `doc timers`. the `hilbish.interval` and `hilbish.timeout` functions return a timer object now.ctrl-delete
parent
c342f4f6f5
commit
c95ff42dee
44
api.go
44
api.go
|
@ -131,6 +131,11 @@ Check out the {blue}{bold}guide{reset} command to get started.
|
|||
jobModule := jobs.loader(rtm)
|
||||
util.Document(jobModule, "(Background) job interface.")
|
||||
mod.Set(rt.StringValue("jobs"), rt.TableValue(jobModule))
|
||||
|
||||
timers = newTimerHandler()
|
||||
timerModule := timers.loader(rtm)
|
||||
util.Document(timerModule, "Timer interface, for control of all intervals and timeouts.")
|
||||
mod.Set(rt.StringValue("timers"), rt.TableValue(timerModule))
|
||||
|
||||
return rt.TableValue(mod), nil
|
||||
}
|
||||
|
@ -481,15 +486,11 @@ func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
timeout := time.Duration(ms) * time.Millisecond
|
||||
time.Sleep(timeout)
|
||||
|
||||
_, err = rt.Call1(l.MainThread(), rt.FunctionValue(cb))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
interval := time.Duration(ms) * time.Millisecond
|
||||
timer := timers.create(timerTimeout, interval, cb)
|
||||
timer.start()
|
||||
|
||||
return c.PushingNext1(t.Runtime, timer.lua()), nil
|
||||
}
|
||||
|
||||
// interval(cb, time)
|
||||
|
@ -508,29 +509,12 @@ func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
interval := time.Duration(ms) * time.Millisecond
|
||||
timer := timers.create(timerInterval, interval, cb)
|
||||
timer.start()
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
stop := make(chan rt.Value)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
_, err := rt.Call1(l.MainThread(), rt.FunctionValue(cb))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error in interval function:\n\n", err)
|
||||
stop <- rt.BoolValue(true) // stop the interval
|
||||
}
|
||||
case <-stop:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO: return channel
|
||||
return c.Next(), nil
|
||||
return c.PushingNext1(t.Runtime, timer.lua()), nil
|
||||
}
|
||||
|
||||
// complete(scope, cb)
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
If you ever want to run a piece of code on a timed interval, or want to wait
|
||||
a few seconds, you don't have to rely on timing tricks, as Hilbish has a
|
||||
timer API to set intervals and timeouts.
|
||||
|
||||
These are the simple functions `hilbish.interval` and `hilbish.timeout` (doc
|
||||
accessible with `doc hilbish`). But if you want slightly more control over
|
||||
them, there is the `hilbish.timers` interface. It allows you to get
|
||||
a timer via ID.
|
||||
|
||||
# Timer Interface
|
||||
## Functions
|
||||
- `get(id)` -> timer: get a timer via its id
|
||||
- `create(type, ms, callback)` -> timer: creates a timer, adding it to the timer pool.
|
||||
`type` is the type of timer it will be. 0 is an interval, 1 is a timeout.
|
||||
`ms` is the time it will run for in seconds. callback is the function called
|
||||
when the timer is triggered.
|
||||
|
||||
# Timer Object
|
||||
Those previously mentioned functions return a `timer` object, to which you can
|
||||
stop and start a timer again. The functions of the timers interface also
|
||||
return a timer object.
|
||||
|
||||
## Properties
|
||||
- `duration`: amount of time the timer runs for in milliseconds
|
||||
- `running`: whether the timer is running or not
|
||||
- `type`: the type of timer (0 is interval, 1 is timeout)
|
||||
|
||||
## Functions
|
||||
- `stop()`: stops the timer. returns an error if it's already stopped
|
||||
- `start()`: starts the timer. returns an error if it's already started
|
14
main.go
14
main.go
|
@ -145,6 +145,7 @@ func main() {
|
|||
text := scanner.Text()
|
||||
runInput(text, true)
|
||||
}
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if *cmdflag != "" {
|
||||
|
@ -161,9 +162,9 @@ func main() {
|
|||
err := util.DoFile(l, getopt.Arg(0))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}
|
||||
|
||||
initialized = true
|
||||
|
@ -299,3 +300,12 @@ func contains(s []string, e string) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func exit(code int) {
|
||||
// wait for all timers to finish before exiting
|
||||
for {
|
||||
if timers.running == 0 {
|
||||
os.Exit(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
type timerType int64
|
||||
const (
|
||||
timerInterval timerType = iota
|
||||
timerTimeout
|
||||
)
|
||||
|
||||
type timer struct{
|
||||
id int
|
||||
typ timerType
|
||||
running bool
|
||||
dur time.Duration
|
||||
fun *rt.Closure
|
||||
th *timerHandler
|
||||
ticker *time.Ticker
|
||||
channel chan bool
|
||||
}
|
||||
|
||||
func (t *timer) start() error {
|
||||
if t.running {
|
||||
return errors.New("timer is already running")
|
||||
}
|
||||
|
||||
t.running = true
|
||||
t.th.running++
|
||||
t.ticker = time.NewTicker(t.dur)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-t.ticker.C:
|
||||
_, err := rt.Call1(l.MainThread(), rt.FunctionValue(t.fun))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error in function:\n", err)
|
||||
t.stop()
|
||||
}
|
||||
// only run one for timeout
|
||||
if t.typ == timerTimeout {
|
||||
t.stop()
|
||||
}
|
||||
case <-t.channel:
|
||||
t.ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *timer) stop() error {
|
||||
if !t.running {
|
||||
return errors.New("timer not running")
|
||||
}
|
||||
|
||||
t.channel <- true
|
||||
t.running = false
|
||||
t.th.running--
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *timer) luaStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
err := t.start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
func (t *timer) luaStop(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
err := t.stop()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
func (t *timer) lua() rt.Value {
|
||||
tExports := map[string]util.LuaExport{
|
||||
"start": {t.luaStart, 0, false},
|
||||
"stop": {t.luaStop, 0, false},
|
||||
}
|
||||
luaTimer := rt.NewTable()
|
||||
util.SetExports(l, luaTimer, tExports)
|
||||
|
||||
luaTimer.Set(rt.StringValue("type"), rt.IntValue(int64(t.typ)))
|
||||
luaTimer.Set(rt.StringValue("running"), rt.BoolValue(t.running))
|
||||
luaTimer.Set(rt.StringValue("duration"), rt.IntValue(int64(t.dur / time.Millisecond)))
|
||||
|
||||
return rt.TableValue(luaTimer)
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
var timers *timerHandler
|
||||
type timerHandler struct {
|
||||
mu *sync.RWMutex
|
||||
timers map[int]*timer
|
||||
latestID int
|
||||
running int
|
||||
}
|
||||
|
||||
func newTimerHandler() *timerHandler {
|
||||
return &timerHandler{
|
||||
timers: make(map[int]*timer),
|
||||
latestID: 0,
|
||||
mu: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (th *timerHandler) create(typ timerType, dur time.Duration, fun *rt.Closure) *timer {
|
||||
th.mu.Lock()
|
||||
defer th.mu.Unlock()
|
||||
|
||||
th.latestID++
|
||||
t := &timer{
|
||||
typ: typ,
|
||||
fun: fun,
|
||||
dur: dur,
|
||||
channel: make(chan bool, 1),
|
||||
th: th,
|
||||
id: th.latestID,
|
||||
}
|
||||
th.timers[th.latestID] = t
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (th *timerHandler) get(id int) *timer {
|
||||
th.mu.RLock()
|
||||
defer th.mu.RUnlock()
|
||||
|
||||
return th.timers[id]
|
||||
}
|
||||
|
||||
func (th *timerHandler) luaCreate(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
timerTypInt, err := c.IntArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ms, err := c.IntArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cb, err := c.ClosureArg(2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timerTyp := timerType(timerTypInt)
|
||||
tmr := th.create(timerTyp, time.Duration(ms) * time.Millisecond, cb)
|
||||
return c.PushingNext1(t.Runtime, tmr.lua()), nil
|
||||
}
|
||||
|
||||
func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, err := c.IntArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := th.get(int(id))
|
||||
if t != nil {
|
||||
return c.PushingNext1(thr.Runtime, t.lua()), nil
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
func (th *timerHandler) loader(rtm *rt.Runtime) *rt.Table {
|
||||
thExports := map[string]util.LuaExport{
|
||||
"create": {th.luaCreate, 3, false},
|
||||
"get": {th.luaGet, 1, false},
|
||||
}
|
||||
|
||||
luaTh := rt.NewTable()
|
||||
util.SetExports(rtm, luaTh, thExports)
|
||||
|
||||
return luaTh
|
||||
}
|
Loading…
Reference in New Issue