Move to event loop, prettify warps, add delay to warp with damage counter
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@ -3,6 +3,7 @@ package commands
|
||||
import (
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"meow.tf/residentsleeper/commands"
|
||||
"meow.tf/residentsleeper/scripting/eventloop"
|
||||
)
|
||||
|
||||
func Loader(L *lua.LState) int {
|
||||
@ -20,15 +21,19 @@ func commandRegister(L *lua.LState) int {
|
||||
|
||||
handler := L.CheckFunction(2)
|
||||
|
||||
loop := eventloop.FromState(L)
|
||||
|
||||
cb := func(ctx *commands.CommandContext) {
|
||||
L.Push(handler)
|
||||
L.Push(lua.LString(ctx.User))
|
||||
loop.RunOnLoop(func(L *lua.LState) {
|
||||
L.Push(handler)
|
||||
L.Push(lua.LString(ctx.User))
|
||||
|
||||
for _, v := range ctx.Arguments {
|
||||
L.Push(lua.LString(v))
|
||||
}
|
||||
for _, v := range ctx.Arguments {
|
||||
L.Push(lua.LString(v))
|
||||
}
|
||||
|
||||
L.PCall(1+ctx.ArgumentCount, 0, nil)
|
||||
L.PCall(1+ctx.ArgumentCount, 0, nil)
|
||||
})
|
||||
}
|
||||
|
||||
commands.Register(name, cb)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
"meow.tf/residentsleeper/events"
|
||||
"meow.tf/residentsleeper/scripting/eventloop"
|
||||
)
|
||||
|
||||
func Loader(L *lua.LState) int {
|
||||
@ -23,14 +24,18 @@ func onFunc(L *lua.LState) int {
|
||||
name := L.CheckString(1)
|
||||
handler := L.CheckFunction(2)
|
||||
|
||||
loop := eventloop.FromState(L)
|
||||
|
||||
events.On(name, func(args ...interface{}) {
|
||||
L.Push(handler)
|
||||
loop.RunOnLoop(func(L *lua.LState) {
|
||||
L.Push(handler)
|
||||
|
||||
for _, arg := range args {
|
||||
L.Push(luar.New(L, arg))
|
||||
}
|
||||
for _, arg := range args {
|
||||
L.Push(luar.New(L, arg))
|
||||
}
|
||||
|
||||
L.PCall(len(args), 0, handler)
|
||||
L.PCall(len(args), 0, handler)
|
||||
})
|
||||
})
|
||||
|
||||
return 0
|
||||
|
372
scripting/eventloop/eventloop.go
Normal file
372
scripting/eventloop/eventloop.go
Normal file
@ -0,0 +1,372 @@
|
||||
package eventloop
|
||||
|
||||
import (
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
luar "layeh.com/gopher-luar"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type job struct {
|
||||
cancelled bool
|
||||
fn func()
|
||||
}
|
||||
|
||||
type Timer struct {
|
||||
job
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
type Interval struct {
|
||||
job
|
||||
ticker *time.Ticker
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
type EventLoop struct {
|
||||
vm *lua.LState
|
||||
jobChan chan func()
|
||||
jobCount int32
|
||||
canRun bool
|
||||
|
||||
auxJobs []func()
|
||||
auxJobsLock sync.Mutex
|
||||
wakeup chan struct{}
|
||||
|
||||
stopCond *sync.Cond
|
||||
running bool
|
||||
|
||||
enableConsole bool
|
||||
}
|
||||
|
||||
func NewEventLoop(opts ...Option) *EventLoop {
|
||||
vm := lua.NewState()
|
||||
|
||||
loop := &EventLoop{
|
||||
vm: vm,
|
||||
jobChan: make(chan func()),
|
||||
wakeup: make(chan struct{}, 1),
|
||||
stopCond: sync.NewCond(&sync.Mutex{}),
|
||||
enableConsole: true,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(loop)
|
||||
}
|
||||
|
||||
vm.SetGlobal("__loop", luar.New(vm, loop))
|
||||
|
||||
vm.PreloadModule("timers", loop.timerLoader)
|
||||
|
||||
return loop
|
||||
}
|
||||
|
||||
type Option func(*EventLoop)
|
||||
|
||||
// EnableConsole controls whether the "console" module is loaded into
|
||||
// the runtime used by the loop. By default, loops are created with
|
||||
// the "console" module loaded, pass EnableConsole(false) to
|
||||
// NewEventLoop to disable this behavior.
|
||||
func EnableConsole(enableConsole bool) Option {
|
||||
return func(loop *EventLoop) {
|
||||
loop.enableConsole = enableConsole
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) timerLoader(L *lua.LState) int {
|
||||
// register functions to the table
|
||||
mod := L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{
|
||||
"setInterval": loop.setInterval,
|
||||
"setTimeout": loop.setTimeout,
|
||||
"clearInterval": loop.clearIntervalBinding,
|
||||
})
|
||||
|
||||
// returns the module
|
||||
L.Push(mod)
|
||||
return 1
|
||||
}
|
||||
|
||||
func (loop *EventLoop) schedule(repeating bool) int {
|
||||
if fn, ok := loop.vm.Get(1).(*lua.LFunction); ok {
|
||||
delay := loop.vm.CheckInt(2)
|
||||
|
||||
log.Println("Delay:", delay)
|
||||
var args []lua.LValue
|
||||
if loop.vm.GetTop() > 2 {
|
||||
args = make([]lua.LValue, loop.vm.GetTop()-2)
|
||||
|
||||
for i := 3; i < loop.vm.GetTop(); i++ {
|
||||
args[i-3] = loop.vm.Get(i)
|
||||
}
|
||||
}
|
||||
f := func() {
|
||||
loop.vm.Push(fn)
|
||||
|
||||
for _, arg := range args {
|
||||
loop.vm.Push(arg)
|
||||
}
|
||||
|
||||
err := loop.vm.PCall(len(args), 0, fn)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln("Error calling function:", err)
|
||||
}
|
||||
}
|
||||
loop.jobCount++
|
||||
|
||||
var val interface{}
|
||||
|
||||
if repeating {
|
||||
val = loop.addInterval(f, time.Duration(delay)*time.Millisecond)
|
||||
} else {
|
||||
val = loop.addTimeout(f, time.Duration(delay)*time.Millisecond)
|
||||
}
|
||||
|
||||
loop.vm.Push(luar.New(loop.vm, val))
|
||||
return 1
|
||||
} else {
|
||||
log.Println("Unable to get function to call")
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (loop *EventLoop) setTimeout(_ *lua.LState) int {
|
||||
return loop.schedule(false)
|
||||
}
|
||||
|
||||
func (loop *EventLoop) setInterval(_ *lua.LState) int {
|
||||
return loop.schedule(true)
|
||||
}
|
||||
|
||||
func (loop *EventLoop) clearIntervalBinding(L *lua.LState) int {
|
||||
ud := L.CheckUserData(1)
|
||||
|
||||
if interval, ok := ud.Value.(*Interval); ok {
|
||||
loop.clearInterval(interval)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// SetTimeout schedules to run the specified function in the context
|
||||
// of the loop as soon as possible after the specified timeout period.
|
||||
// SetTimeout returns a Timer which can be passed to ClearTimeout.
|
||||
// The instance of goja.Runtime that is passed to the function and any Values derived
|
||||
// from it must not be used outside of the function. SetTimeout is
|
||||
// safe to call inside or outside of the loop.
|
||||
func (loop *EventLoop) SetTimeout(fn func(*lua.LState), timeout time.Duration) *Timer {
|
||||
t := loop.addTimeout(func() { fn(loop.vm) }, timeout)
|
||||
loop.addAuxJob(func() {
|
||||
loop.jobCount++
|
||||
})
|
||||
return t
|
||||
}
|
||||
|
||||
// ClearTimeout cancels a Timer returned by SetTimeout if it has not run yet.
|
||||
// ClearTimeout is safe to call inside or outside of the loop.
|
||||
func (loop *EventLoop) ClearTimeout(t *Timer) {
|
||||
loop.addAuxJob(func() {
|
||||
loop.clearTimeout(t)
|
||||
})
|
||||
}
|
||||
|
||||
// SetInterval schedules to repeatedly run the specified function in
|
||||
// the context of the loop as soon as possible after every specified
|
||||
// timeout period. SetInterval returns an Interval which can be
|
||||
// passed to ClearInterval. The instance of goja.Runtime that is passed to the
|
||||
// function and any Values derived from it must not be used outside of
|
||||
// the function. SetInterval is safe to call inside or outside of the
|
||||
// loop.
|
||||
func (loop *EventLoop) SetInterval(fn func(*lua.LState), timeout time.Duration) *Interval {
|
||||
i := loop.addInterval(func() { fn(loop.vm) }, timeout)
|
||||
loop.addAuxJob(func() {
|
||||
loop.jobCount++
|
||||
})
|
||||
return i
|
||||
}
|
||||
|
||||
// ClearInterval cancels an Interval returned by SetInterval.
|
||||
// ClearInterval is safe to call inside or outside of the loop.
|
||||
func (loop *EventLoop) ClearInterval(i *Interval) {
|
||||
loop.addAuxJob(func() {
|
||||
loop.clearInterval(i)
|
||||
})
|
||||
}
|
||||
|
||||
func (loop *EventLoop) setRunning() {
|
||||
loop.stopCond.L.Lock()
|
||||
if loop.running {
|
||||
panic("Loop is already started")
|
||||
}
|
||||
loop.running = true
|
||||
loop.stopCond.L.Unlock()
|
||||
}
|
||||
|
||||
// Run calls the specified function, starts the event loop and waits until there are no more delayed jobs to run
|
||||
// after which it stops the loop and returns.
|
||||
// The instance of goja.Runtime that is passed to the function and any Values derived from it must not be used outside
|
||||
// of the function.
|
||||
// Do NOT use this function while the loop is already running. Use RunOnLoop() instead.
|
||||
// If the loop is already started it will panic.
|
||||
func (loop *EventLoop) Run(fn func(*lua.LState)) {
|
||||
loop.setRunning()
|
||||
fn(loop.vm)
|
||||
loop.run(false)
|
||||
}
|
||||
|
||||
// Start the event loop in the background. The loop continues to run until Stop() is called.
|
||||
// If the loop is already started it will panic.
|
||||
func (loop *EventLoop) Start() {
|
||||
loop.setRunning()
|
||||
go loop.run(true)
|
||||
}
|
||||
|
||||
// Stop the loop that was started with Start(). After this function returns there will be no more jobs executed
|
||||
// by the loop. It is possible to call Start() or Run() again after this to resume the execution.
|
||||
// Note, it does not cancel active timeouts.
|
||||
// It is not allowed to run Start() and Stop() concurrently.
|
||||
// Calling Stop() on an already stopped loop or inside the loop will hang.
|
||||
func (loop *EventLoop) Stop() {
|
||||
loop.jobChan <- func() {
|
||||
loop.canRun = false
|
||||
}
|
||||
|
||||
loop.stopCond.L.Lock()
|
||||
for loop.running {
|
||||
loop.stopCond.Wait()
|
||||
}
|
||||
loop.stopCond.L.Unlock()
|
||||
}
|
||||
|
||||
// RunOnLoop schedules to run the specified function in the context of the loop as soon as possible.
|
||||
// The order of the runs is preserved (i.e. the functions will be called in the same order as calls to RunOnLoop())
|
||||
// The instance of goja.Runtime that is passed to the function and any Values derived from it must not be used outside
|
||||
// of the function. It is safe to call inside or outside of the loop.
|
||||
func (loop *EventLoop) RunOnLoop(fn func(*lua.LState)) {
|
||||
loop.addAuxJob(func() { fn(loop.vm) })
|
||||
}
|
||||
|
||||
func (loop *EventLoop) runAux() {
|
||||
loop.auxJobsLock.Lock()
|
||||
jobs := loop.auxJobs
|
||||
loop.auxJobs = nil
|
||||
loop.auxJobsLock.Unlock()
|
||||
for _, job := range jobs {
|
||||
job()
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) run(inBackground bool) {
|
||||
loop.canRun = true
|
||||
loop.runAux()
|
||||
|
||||
for loop.canRun && (inBackground || loop.jobCount > 0) {
|
||||
select {
|
||||
case job := <-loop.jobChan:
|
||||
job()
|
||||
if loop.canRun {
|
||||
select {
|
||||
case <-loop.wakeup:
|
||||
loop.runAux()
|
||||
default:
|
||||
}
|
||||
}
|
||||
case <-loop.wakeup:
|
||||
loop.runAux()
|
||||
}
|
||||
}
|
||||
loop.stopCond.L.Lock()
|
||||
loop.running = false
|
||||
loop.stopCond.L.Unlock()
|
||||
loop.stopCond.Broadcast()
|
||||
}
|
||||
|
||||
func (loop *EventLoop) addAuxJob(fn func()) {
|
||||
loop.auxJobsLock.Lock()
|
||||
loop.auxJobs = append(loop.auxJobs, fn)
|
||||
loop.auxJobsLock.Unlock()
|
||||
select {
|
||||
case loop.wakeup <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) addTimeout(f func(), timeout time.Duration) *Timer {
|
||||
t := &Timer{
|
||||
job: job{fn: f},
|
||||
}
|
||||
t.timer = time.AfterFunc(timeout, func() {
|
||||
loop.jobChan <- func() {
|
||||
loop.doTimeout(t)
|
||||
}
|
||||
})
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (loop *EventLoop) addInterval(f func(), timeout time.Duration) *Interval {
|
||||
i := &Interval{
|
||||
job: job{fn: f},
|
||||
ticker: time.NewTicker(timeout),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
go i.run(loop)
|
||||
return i
|
||||
}
|
||||
|
||||
func (loop *EventLoop) doTimeout(t *Timer) {
|
||||
if !t.cancelled {
|
||||
t.fn()
|
||||
t.cancelled = true
|
||||
loop.jobCount--
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) doInterval(i *Interval) {
|
||||
if !i.cancelled {
|
||||
i.fn()
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) clearTimeout(t *Timer) {
|
||||
if t != nil && !t.cancelled {
|
||||
t.timer.Stop()
|
||||
t.cancelled = true
|
||||
loop.jobCount--
|
||||
}
|
||||
}
|
||||
|
||||
func (loop *EventLoop) clearInterval(i *Interval) {
|
||||
if i != nil && !i.cancelled {
|
||||
i.cancelled = true
|
||||
close(i.stopChan)
|
||||
loop.jobCount--
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Interval) run(loop *EventLoop) {
|
||||
L:
|
||||
for {
|
||||
select {
|
||||
case <-i.stopChan:
|
||||
i.ticker.Stop()
|
||||
break L
|
||||
case <-i.ticker.C:
|
||||
loop.jobChan <- func() {
|
||||
loop.doInterval(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FromState(L *lua.LState) *EventLoop {
|
||||
loopGlobal := L.GetGlobal("__loop")
|
||||
|
||||
if ud, ok := loopGlobal.(*lua.LUserData); ok {
|
||||
return ud.Value.(*EventLoop)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user