natsbot

NATS bot
git clone https://git.instinctive.eu/natsbot.git
Log | Files | Refs | README | LICENSE

natsbot.go (18145B)


      1 /*
      2  * Copyright (c) 2025, Natacha Porté
      3  *
      4  * Permission to use, copy, modify, and distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 package natsbot
     18 
     19 import (
     20 	"errors"
     21 	"fmt"
     22 	"log"
     23 	"strings"
     24 	"time"
     25 
     26 	"github.com/nats-io/nats.go"
     27 	"github.com/yuin/gopher-lua"
     28 )
     29 
     30 type NatsBot interface {
     31 	Setup(L *lua.LState)
     32 	Teardown(L *lua.LState)
     33 }
     34 
     35 type internalEvent struct {
     36 	name string
     37 	nc   *nats.Conn
     38 	subs *nats.Subscription
     39 	err  error
     40 }
     41 
     42 func Loop(cb NatsBot, mainScript string, capacity int) {
     43 	evtChan := make(chan *internalEvent, capacity)
     44 	msgChan := make(chan *nats.Msg, capacity)
     45 	toClean := make(map[*nats.Subscription]bool)
     46 
     47 	L := lua.NewState()
     48 	defer L.Close()
     49 
     50 	cb.Setup(L)
     51 	defer cb.Teardown(L)
     52 
     53 	registerConnType(L)
     54 	registerTimerType(L)
     55 	registerState(L, evtChan, msgChan)
     56 
     57 	if err := L.DoFile(mainScript); err != nil {
     58 		panic(err)
     59 	}
     60 
     61 	timer := time.NewTimer(0)
     62 	defer timer.Stop()
     63 
     64 	log.Println("natsbot started")
     65 
     66 	for {
     67 		select {
     68 		case evt, ok := <-evtChan:
     69 			if !ok {
     70 				log.Println("evtChan is closed")
     71 				break
     72 			}
     73 
     74 			processEvt(L, evt)
     75 
     76 		case msg, ok := <-msgChan:
     77 
     78 			if !ok {
     79 				log.Println("msgChan is closed")
     80 				break
     81 			}
     82 
     83 			processMsg(L, msg)
     84 
     85 			if !msg.Sub.IsValid() {
     86 				toClean[msg.Sub] = true
     87 			}
     88 
     89 		case <-timer.C:
     90 		}
     91 
     92 		runTimers(L, timer)
     93 
     94 		if len(msgChan) == 0 {
     95 			for s := range toClean {
     96 				if !s.IsValid() {
     97 					log.Printf("Pruning subscription %q", s.Subject)
     98 					tbl, idx := stateSubsTable(L)
     99 					L.RawSetInt(tbl, idx[s], lua.LNil)
    100 					delete(idx, s)
    101 				} else {
    102 					log.Printf("Subscription %q is still valid", s.Subject)
    103 				}
    104 			}
    105 			toClean = make(map[*nats.Subscription]bool)
    106 		}
    107 
    108 		if tableWithIndexIsEmpty(stateSubsTable(L)) && tableIsEmpty(stateTimerTable(L)) {
    109 			break
    110 		}
    111 	}
    112 
    113 	log.Println("natsbot finished")
    114 }
    115 
    116 func processEvt(L *lua.LState, evt *internalEvent) {
    117 	tbl, idx := stateConnTable(L)
    118 	connLua := L.RawGetInt(tbl, idx[evt.nc])
    119 	fn := L.GetField(L.GetField(L.GetMetatable(connLua), "__index").(*lua.LTable), evt.name)
    120 	if lua.LVIsFalse(fn) {
    121 		return
    122 	}
    123 
    124 	var subLua lua.LValue
    125 	if evt.subs == nil {
    126 		subLua = lua.LNil
    127 	} else {
    128 		tbl, idx := stateSubsTable(L)
    129 		subLua = L.RawGetInt(tbl, idx[evt.subs])
    130 	}
    131 
    132 	var errLua lua.LValue
    133 	if evt.err == nil {
    134 		errLua = lua.LNil
    135 	} else {
    136 		errLua = lua.LString(evt.err.Error())
    137 	}
    138 
    139 	err := L.CallByParam(lua.P{Fn: fn, NRet: 0, Protect: true},
    140 		connLua,
    141 		subLua,
    142 		errLua)
    143 	if err != nil {
    144 		panic(err)
    145 	}
    146 }
    147 
    148 func processMsg(L *lua.LState, msg *nats.Msg) {
    149 	tbl, idx := stateSubsTable(L)
    150 	subs := L.RawGetInt(tbl, idx[msg.Sub])
    151 	fn := L.GetField(L.GetMetatable(subs), "__call")
    152 	err := L.CallByParam(lua.P{Fn: fn, NRet: 0, Protect: true},
    153 		subs,
    154 		lua.LString(msg.Subject),
    155 		lua.LString(string(msg.Data)),
    156 		luaHeaders(L, msg.Reply, msg.Header))
    157 	if err != nil {
    158 		panic(err)
    159 	}
    160 }
    161 
    162 /********** State Object in the Lua Interpreter **********/
    163 
    164 const luaStateName = "_natsbot"
    165 const (
    166 	_ = iota
    167 	keyEvtChan
    168 	keyMsgChan
    169 	keyCfgMap
    170 	keyConnTable
    171 	keySubsTable
    172 	keyTimerTable
    173 )
    174 
    175 type subsMap map[*nats.Subscription]int
    176 
    177 func registerState(L *lua.LState, evtChan chan *internalEvent, msgChan chan *nats.Msg) {
    178 	conns := L.NewTable()
    179 	L.RawSetInt(conns, 1, newUserData(L, make(connMap)))
    180 
    181 	subs := L.NewTable()
    182 	L.RawSetInt(subs, 1, newUserData(L, make(subsMap)))
    183 
    184 	st := L.NewTable()
    185 	L.RawSetInt(st, keyEvtChan, newUserData(L, evtChan))
    186 	L.RawSetInt(st, keyMsgChan, newUserData(L, msgChan))
    187 	L.RawSetInt(st, keyCfgMap, newUserData(L, make(natsConfigMap)))
    188 	L.RawSetInt(st, keyConnTable, conns)
    189 	L.RawSetInt(st, keySubsTable, subs)
    190 	L.RawSetInt(st, keyTimerTable, L.NewTable())
    191 	stateSet(L, st)
    192 }
    193 
    194 func stateUncheckedGet(L *lua.LState) *lua.LTable {
    195 	v := L.GetField(L.Get(lua.RegistryIndex), luaStateName)
    196 	if result, ok := v.(*lua.LTable); ok {
    197 		return result
    198 	} else {
    199 		return nil
    200 	}
    201 }
    202 
    203 func stateGet(L *lua.LState) *lua.LTable {
    204 	result := stateUncheckedGet(L)
    205 	if result == nil {
    206 		panic("Missing internal state object")
    207 	}
    208 	return result
    209 }
    210 
    211 func stateSet(L *lua.LState, newState *lua.LTable) {
    212 	if stateUncheckedGet(L) != nil {
    213 		panic("Overwriting internal state object")
    214 	}
    215 	L.SetField(L.Get(lua.RegistryIndex), luaStateName, newState)
    216 }
    217 
    218 func stateValue(L *lua.LState, key int) lua.LValue {
    219 	return L.RawGetInt(stateGet(L), key)
    220 }
    221 
    222 func stateEvtChan(L *lua.LState) chan *internalEvent {
    223 	ud := stateValue(L, keyEvtChan)
    224 	return ud.(*lua.LUserData).Value.(chan *internalEvent)
    225 }
    226 
    227 func stateMsgChan(L *lua.LState) chan *nats.Msg {
    228 	ud := stateValue(L, keyMsgChan)
    229 	return ud.(*lua.LUserData).Value.(chan *nats.Msg)
    230 }
    231 
    232 func stateCfgMap(L *lua.LState) natsConfigMap {
    233 	return stateValue(L, keyCfgMap).(*lua.LUserData).Value.(natsConfigMap)
    234 }
    235 
    236 func stateConnTable(L *lua.LState) (*lua.LTable, connMap) {
    237 	tbl := stateValue(L, keyConnTable).(*lua.LTable)
    238 	idx := L.RawGetInt(tbl, keyIndex).(*lua.LUserData).Value.(connMap)
    239 	return tbl, idx
    240 }
    241 
    242 func stateSubsTable(L *lua.LState) (*lua.LTable, subsMap) {
    243 	tbl := stateValue(L, keySubsTable).(*lua.LTable)
    244 	idx := L.RawGetInt(tbl, keyIndex).(*lua.LUserData).Value.(subsMap)
    245 	return tbl, idx
    246 }
    247 
    248 func stateTimerTable(L *lua.LState) *lua.LTable {
    249 	return stateValue(L, keyTimerTable).(*lua.LTable)
    250 }
    251 
    252 /********** NATS Connection Configuration **********/
    253 
    254 type natsConfig struct {
    255 	url      string
    256 	name     string
    257 	nkey     string
    258 	user     string
    259 	password string
    260 	token    string
    261 }
    262 
    263 type natsConfigMap map[natsConfig]*nats.Conn
    264 
    265 type natsCbMap map[string]*lua.LFunction
    266 
    267 func connConfig(L *lua.LState) (*natsConfig, natsCbMap, error) {
    268 	arg := L.Get(1)
    269 	if url, ok := arg.(lua.LString); ok {
    270 		if cfg, cbmap, err := toConfig(L, L.Get(2)); err != nil {
    271 			return nil, nil, err
    272 		} else if cfg.url == "" {
    273 			cfg.url = string(url)
    274 			return cfg, cbmap, nil
    275 		} else if cfg.url != string(url) {
    276 			return nil, nil, fmt.Errorf("incompatible URLs %q and %q", cfg.url, string(url))
    277 		} else {
    278 			return cfg, cbmap, nil
    279 		}
    280 	} else {
    281 		return toConfig(L, arg)
    282 	}
    283 }
    284 
    285 func toConfig(L *lua.LState, lv lua.LValue) (*natsConfig, natsCbMap, error) {
    286 	tbl, ok := lv.(*lua.LTable)
    287 	if !ok {
    288 		return nil, nil, errors.New("configuration is not a table")
    289 	}
    290 
    291 	var result natsConfig
    292 	cbmap := make(natsCbMap)
    293 	var errStr []string
    294 
    295 	L.ForEach(tbl, func(key, value lua.LValue) {
    296 		skey, ok := key.(lua.LString)
    297 		if !ok {
    298 			errStr = append(errStr, fmt.Sprintf("bad key: %q", lua.LVAsString(key)))
    299 			return
    300 		}
    301 
    302 		switch skey {
    303 		case "closed":
    304 			fallthrough
    305 		case "disconnected":
    306 			fallthrough
    307 		case "error":
    308 			fallthrough
    309 		case "reconnect_error":
    310 			fallthrough
    311 		case "reconnected":
    312 			if s, ok := value.(*lua.LFunction); ok {
    313 				cbmap[string(skey)] = s
    314 			} else {
    315 				errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value)))
    316 			}
    317 
    318 		case "url":
    319 			if s, ok := value.(lua.LString); ok {
    320 				result.url = string(s)
    321 			} else {
    322 				errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value)))
    323 			}
    324 
    325 		case "name":
    326 			if s, ok := value.(lua.LString); ok {
    327 				result.name = string(s)
    328 			} else {
    329 				errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value)))
    330 			}
    331 
    332 		case "nkey":
    333 			if s, ok := value.(lua.LString); ok {
    334 				result.nkey = string(s)
    335 			} else {
    336 				errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value)))
    337 			}
    338 
    339 		case "user":
    340 			if s, ok := value.(lua.LString); ok {
    341 				result.user = string(s)
    342 			} else {
    343 				errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value)))
    344 			}
    345 
    346 		case "password":
    347 			if s, ok := value.(lua.LString); ok {
    348 				result.password = string(s)
    349 			} else {
    350 				errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value)))
    351 			}
    352 
    353 		case "token":
    354 			if s, ok := value.(lua.LString); ok {
    355 				result.token = string(s)
    356 			} else {
    357 				errStr = append(errStr, fmt.Sprintf("bad value for key %q: %q", skey, lua.LVAsString(value)))
    358 			}
    359 
    360 		default:
    361 			errStr = append(errStr, fmt.Sprintf("Unknown key %q", skey))
    362 		}
    363 	})
    364 
    365 	if len(errStr) > 0 {
    366 		return nil, nil, errors.New(strings.Join(errStr, ", "))
    367 	} else {
    368 		return &result, cbmap, nil
    369 	}
    370 }
    371 
    372 func newConn(evtChan chan *internalEvent, cfg *natsConfig) (*nats.Conn, error) {
    373 	opt := []nats.Option{
    374 		nats.DisconnectErrHandler(newConnErrHandler(evtChan, "disconnected")),
    375 		nats.ReconnectHandler(newConnHandler(evtChan, "reconnected")),
    376 		nats.ReconnectErrHandler(newConnErrHandler(evtChan, "reconnect_error")),
    377 		nats.ClosedHandler(newConnHandler(evtChan, "closed")),
    378 		nats.ErrorHandler(newErrHandler(evtChan, "error")),
    379 	}
    380 
    381 	if cfg.name != "" {
    382 		opt = append(opt, nats.Name(cfg.name))
    383 	}
    384 	if cfg.nkey != "" {
    385 		if o, err := nats.NkeyOptionFromSeed(cfg.nkey); err != nil {
    386 			return nil, err
    387 		} else {
    388 			opt = append(opt, o)
    389 		}
    390 	}
    391 	if cfg.user != "" || cfg.password != "" {
    392 		opt = append(opt, nats.UserInfo(cfg.user, cfg.password))
    393 	}
    394 	if cfg.token != "" {
    395 		opt = append(opt, nats.Token(cfg.token))
    396 	}
    397 
    398 	return nats.Connect(cfg.url, opt...)
    399 }
    400 
    401 /********** Lua Object for NATS connection **********/
    402 
    403 const luaNatsConnTypeName = "natsconn"
    404 const keyIndex = 1
    405 
    406 type connMap map[*nats.Conn]int
    407 
    408 func registerConnType(L *lua.LState) {
    409 	L.SetGlobal(luaNatsConnTypeName, L.NewFunction(natsConnect))
    410 }
    411 
    412 func natsConnect(L *lua.LState) int {
    413 	pcfg, cbmap, err := connConfig(L)
    414 	if err != nil {
    415 		log.Println(err)
    416 		L.Push(lua.LNil)
    417 		L.Push(lua.LString(err.Error()))
    418 		return 2
    419 	}
    420 	cfg := *pcfg
    421 
    422 	cfgMap := stateCfgMap(L)
    423 	if nc, found := cfgMap[cfg]; found {
    424 		tbl, idx := stateConnTable(L)
    425 		if id, ok := idx[nc]; ok {
    426 			res := L.RawGetInt(tbl, id)
    427 			if lua.LVIsFalse(res) {
    428 				panic("Inconsistent connection table")
    429 			}
    430 			L.Push(res)
    431 			return 1
    432 		} else {
    433 			panic("Inconsistent connection table")
    434 		}
    435 	}
    436 
    437 	nc, err := newConn(stateEvtChan(L), pcfg)
    438 	if err != nil {
    439 		log.Println("newConn", err)
    440 		L.Push(lua.LNil)
    441 		L.Push(lua.LString(err.Error()))
    442 		return 2
    443 	}
    444 
    445 	cfgMap[cfg] = nc
    446 	L.Push(wrapConn(L, nc, cbmap))
    447 	return 1
    448 }
    449 
    450 func wrapConn(L *lua.LState, nc *nats.Conn, cbmap natsCbMap) lua.LValue {
    451 	luaConn := newUserData(L, nc)
    452 
    453 	index := L.NewTable()
    454 	L.SetField(index, "publish", L.NewFunction(natsPublish))
    455 	L.SetField(index, "subscribe", L.NewFunction(natsSubscribe))
    456 
    457 	for key, fn := range cbmap {
    458 		L.SetField(index, key, fn)
    459 	}
    460 
    461 	mt := L.NewTable()
    462 	L.SetField(mt, "__index", index)
    463 	L.SetField(mt, "__newindex", L.NewFunction(connNewIndex))
    464 	L.SetMetatable(luaConn, mt)
    465 
    466 	tbl, idx := stateConnTable(L)
    467 	id := tbl.Len() + 1
    468 	L.RawSetInt(tbl, id, luaConn)
    469 
    470 	if _, found := idx[nc]; found {
    471 		panic("id already in connection table")
    472 	}
    473 	idx[nc] = id
    474 
    475 	return luaConn
    476 }
    477 
    478 func connNewIndex(L *lua.LState) int {
    479 	ud := L.CheckUserData(1)
    480 
    481 	if _, ok := ud.Value.(*nats.Conn); !ok {
    482 		L.ArgError(1, "connection expected")
    483 		return 0
    484 	}
    485 
    486 	event := L.CheckString(2)
    487 	if event != "disconnected" &&
    488 		event != "reconnected" &&
    489 		event != "reconnect_error" &&
    490 		event != "closed" &&
    491 		event != "error" {
    492 		L.ArgError(2, "unsupported callback name")
    493 	}
    494 
    495 	fn := L.Get(3)
    496 	if _, ok := fn.(*lua.LFunction); ok && fn != lua.LNil {
    497 		L.ArgError(3, "function expected")
    498 	}
    499 
    500 	mt := L.GetMetatable(ud)
    501 	idx := L.GetField(mt, "__index").(*lua.LTable)
    502 	L.SetField(idx, event, fn)
    503 	return 0
    504 }
    505 
    506 func newConnHandler(evtChan chan *internalEvent, name string) nats.ConnHandler {
    507 	return func(nc *nats.Conn) {
    508 		evtChan <- &internalEvent{name: name, nc: nc}
    509 	}
    510 }
    511 
    512 func newConnErrHandler(evtChan chan *internalEvent, name string) nats.ConnErrHandler {
    513 	return func(nc *nats.Conn, err error) {
    514 		evtChan <- &internalEvent{name: name, nc: nc, err: err}
    515 	}
    516 }
    517 
    518 func newErrHandler(evtChan chan *internalEvent, name string) nats.ErrHandler {
    519 	return func(nc *nats.Conn, s *nats.Subscription, err error) {
    520 		evtChan <- &internalEvent{name: name, nc: nc, subs: s, err: err}
    521 	}
    522 }
    523 
    524 func checkConn(L *lua.LState, index int) *nats.Conn {
    525 	ud := L.CheckUserData(index)
    526 
    527 	if v, ok := ud.Value.(*natsSubs); ok {
    528 		return v.nc
    529 	}
    530 
    531 	if v, ok := ud.Value.(*nats.Conn); ok {
    532 		return v
    533 	}
    534 
    535 	L.ArgError(index, "connection expected")
    536 	return nil
    537 }
    538 
    539 func natsPublish(L *lua.LState) int {
    540 	nc := checkConn(L, 1)
    541 	subject := L.CheckString(2)
    542 	data := L.OptString(3, "")
    543 
    544 	if err := nc.Publish(subject, []byte(data)); err != nil {
    545 		log.Println("Publish:", err)
    546 		L.Push(lua.LNil)
    547 		L.Push(lua.LString(err.Error()))
    548 		return 2
    549 	} else {
    550 		L.Push(lua.LTrue)
    551 		return 1
    552 	}
    553 }
    554 
    555 func natsSubscribe(L *lua.LState) int {
    556 	nc := checkConn(L, 1)
    557 	subject := L.CheckString(2)
    558 	fn := L.CheckFunction(3)
    559 
    560 	if s, err := nc.ChanSubscribe(subject, stateMsgChan(L)); err != nil {
    561 		log.Println("Subscribe:", err)
    562 		L.Push(lua.LNil)
    563 		L.Push(lua.LString(err.Error()))
    564 		return 2
    565 	} else {
    566 		L.Push(wrapSubs(L, fn, s, nc))
    567 		return 1
    568 	}
    569 }
    570 
    571 /********** Lua Object for NATS subscription **********/
    572 
    573 type natsSubs struct {
    574 	id   int
    575 	nc   *nats.Conn
    576 	subs *nats.Subscription
    577 }
    578 
    579 func wrapSubs(L *lua.LState, fn lua.LValue, ns *nats.Subscription, nc *nats.Conn) lua.LValue {
    580 	tbl, subsIdx := stateSubsTable(L)
    581 	id := tbl.Len() + 1
    582 	luaSub := newUserData(L, &natsSubs{id: id, nc: nc, subs: ns})
    583 
    584 	subsIdx[ns] = id
    585 	L.RawSetInt(tbl, id, luaSub)
    586 
    587 	index := L.NewTable()
    588 	L.SetField(index, "callback", fn)
    589 	L.SetField(index, "id", lua.LNumber(id))
    590 	L.SetField(index, "subject", lua.LString(string(ns.Subject)))
    591 
    592 	L.SetField(index, "publish", L.NewFunction(natsPublish))
    593 	L.SetField(index, "subscribe", L.NewFunction(natsSubscribe))
    594 	L.SetField(index, "unsubscribe", L.NewFunction(natsUnsubscribe))
    595 
    596 	mt := L.NewTable()
    597 	L.SetField(mt, "__call", fn)
    598 	L.SetField(mt, "__index", index)
    599 	L.SetField(mt, "__newindex", L.NewFunction(subsNewIndex))
    600 
    601 	L.SetMetatable(luaSub, mt)
    602 	return luaSub
    603 }
    604 
    605 func subsNewIndex(L *lua.LState) int {
    606 	_ = checkSubs(L, 1)
    607 	mt := L.GetMetatable(L.Get(1))
    608 	key := L.Get(2)
    609 
    610 	if s, ok := key.(lua.LString); !ok || string(s) != "callback" {
    611 		L.RaiseError("attempt to change bad subscription field")
    612 		return 0
    613 	}
    614 
    615 	fn := L.CheckFunction(3)
    616 	L.SetField(mt, "__call", fn)
    617 	index := L.GetField(mt, "__index").(*lua.LTable)
    618 	L.SetField(index, "callback", fn)
    619 	return 0
    620 }
    621 
    622 func checkSubs(L *lua.LState, index int) *natsSubs {
    623 	ud := L.CheckUserData(index)
    624 
    625 	if v, ok := ud.Value.(*natsSubs); ok {
    626 		return v
    627 	}
    628 
    629 	L.ArgError(index, "subscription expected")
    630 	return nil
    631 }
    632 
    633 func natsUnsubscribe(L *lua.LState) int {
    634 	s := checkSubs(L, 1)
    635 	count := L.OptInt(2, 0)
    636 
    637 	if err := s.subs.AutoUnsubscribe(count); err != nil {
    638 		log.Println("Unsubscribe:", err)
    639 		L.Push(lua.LNil)
    640 		L.Push(lua.LString(err.Error()))
    641 		return 2
    642 	} else {
    643 		L.Push(lua.LTrue)
    644 		return 1
    645 	}
    646 }
    647 
    648 /********** Lua Object for timers **********/
    649 
    650 const luaTimerTypeName = "timer"
    651 
    652 func registerTimerType(L *lua.LState) {
    653 	mt := L.NewTypeMetatable(luaTimerTypeName)
    654 	L.SetGlobal(luaTimerTypeName, mt)
    655 	L.SetField(mt, "new", L.NewFunction(newTimer))
    656 	L.SetField(mt, "schedule", L.NewFunction(timerSchedule))
    657 	L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), timerMethods))
    658 }
    659 
    660 func newTimer(L *lua.LState) int {
    661 	atTime := L.Get(1)
    662 	cb := L.CheckFunction(2)
    663 	L.Pop(2)
    664 	L.SetMetatable(cb, L.GetTypeMetatable(luaTimerTypeName))
    665 	L.Push(cb)
    666 	L.Push(atTime)
    667 	return timerSchedule(L)
    668 }
    669 
    670 var timerMethods = map[string]lua.LGFunction{
    671 	"cancel":   timerCancel,
    672 	"schedule": timerSchedule,
    673 }
    674 
    675 func timerCancel(L *lua.LState) int {
    676 	timer := L.CheckFunction(1)
    677 	L.RawSet(stateTimerTable(L), timer, lua.LNil)
    678 	return 0
    679 }
    680 
    681 func timerSchedule(L *lua.LState) int {
    682 	timer := L.CheckFunction(1)
    683 	atTime := lua.LNil
    684 	if L.Get(2) != lua.LNil {
    685 		atTime = L.CheckNumber(2)
    686 	}
    687 
    688 	L.RawSet(stateTimerTable(L), timer, atTime)
    689 	return 0
    690 }
    691 
    692 func toTime(lsec lua.LNumber) time.Time {
    693 	fsec := float64(lsec)
    694 	sec := int64(fsec)
    695 	nsec := int64((fsec - float64(sec)) * 1.0e9)
    696 
    697 	return time.Unix(sec, nsec)
    698 }
    699 
    700 func runTimers(L *lua.LState, parentTimer *time.Timer) {
    701 	hasNext := false
    702 	var nextTime time.Time
    703 
    704 	now := time.Now()
    705 	timers := stateTimerTable(L)
    706 
    707 	timer, luaT := timers.Next(lua.LNil)
    708 	for timer != lua.LNil {
    709 		t := toTime(luaT.(lua.LNumber))
    710 		if t.Compare(now) <= 0 {
    711 			L.RawSet(timers, timer, lua.LNil)
    712 			err := L.CallByParam(lua.P{Fn: timer, NRet: 0, Protect: true}, timer, luaT)
    713 			if err != nil {
    714 				panic(err)
    715 			}
    716 			timer = lua.LNil
    717 			hasNext = false
    718 		} else if !hasNext || t.Compare(nextTime) < 0 {
    719 			hasNext = true
    720 			nextTime = t
    721 		}
    722 
    723 		timer, luaT = timers.Next(timer)
    724 	}
    725 
    726 	if hasNext {
    727 		parentTimer.Reset(time.Until(nextTime))
    728 	} else {
    729 		parentTimer.Stop()
    730 	}
    731 }
    732 
    733 /********** Tools **********/
    734 
    735 func luaHeader(L *lua.LState, header []string) lua.LValue {
    736 	switch len(header) {
    737 	case 0:
    738 		return lua.LNil
    739 	case 1:
    740 		return lua.LString(header[0])
    741 	default:
    742 		result := L.CreateTable(len(header), 0)
    743 		for i, v := range header {
    744 			L.RawSetInt(result, i+1, lua.LString(v))
    745 		}
    746 		return result
    747 	}
    748 }
    749 
    750 func luaHeaders(L *lua.LState, reply string, headers map[string][]string) lua.LValue {
    751 	result := L.NewTable()
    752 
    753 	if reply != "" {
    754 		L.RawSetInt(result, 1, lua.LString(reply))
    755 	}
    756 
    757 	for key, values := range headers {
    758 		L.SetField(result, key, luaHeader(L, values))
    759 	}
    760 
    761 	return result
    762 }
    763 
    764 func newUserData(L *lua.LState, v interface{}) *lua.LUserData {
    765 	res := L.NewUserData()
    766 	res.Value = v
    767 	return res
    768 }
    769 
    770 func tableIsEmpty(t *lua.LTable) bool {
    771 	key, _ := t.Next(lua.LNil)
    772 	return key == lua.LNil
    773 }
    774 
    775 func tableWithIndexIsEmpty(t *lua.LTable, idx interface{}) bool {
    776 	key, _ := t.Next(lua.LNil)
    777 
    778 	if n, ok := key.(lua.LNumber); ok && n == lua.LNumber(keyIndex) {
    779 		key, _ = t.Next(key)
    780 	}
    781 
    782 	return key == lua.LNil
    783 }