123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- package luaexecutor
- import (
- "fmt"
- "github.com/Shopify/go-lua"
- "github.com/kpmy/xippo/c2s/stream"
- "github.com/kpmy/xippo/entity"
- "path/filepath"
- "sync"
- "time"
- )
- const sleepDuration time.Duration = 1 * time.Second
- const callbacksLocation string = "clbks"
- // An utility struct for incoming events.
- type IncomingEvent struct {
- Type string
- Data map[string]string
- }
- // Executor executes Lua scripts in a shared Lua VM.
- type Executor struct {
- incomingScripts chan string
- outgoingMsgs chan string
- incomingEvents chan IncomingEvent
- stateMutex sync.Mutex
- state *lua.State
- xmppStream stream.Stream
- }
- func NewExecutor(s stream.Stream) *Executor {
- e := &Executor{
- incomingScripts: make(chan string),
- outgoingMsgs: make(chan string),
- incomingEvents: make(chan IncomingEvent),
- }
- e.xmppStream = s
- e.state = lua.NewState()
- lua.OpenLibraries(e.state)
- send := func(l *lua.State) int {
- str, _ := l.ToString(1)
- e.outgoingMsgs <- str
- return 0
- }
- registerClbk := func(l *lua.State) int {
- // get events table
- l.PushString(callbacksLocation)
- l.Table(lua.RegistryIndex)
- // get callbacks table
- evtName := lua.CheckString(l, 1)
- l.PushString(evtName)
- l.Table(-2)
- // create new table if one doesn't exist
- if l.IsNil(-1) {
- l.Pop(1)
- // create and add table to the events table
- l.PushString(evtName)
- l.NewTable()
- l.SetTable(-3)
- // and get it back
- l.PushString(evtName)
- l.Table(-2)
- }
- // set callback
- l.PushValue(2)
- l.PushValue(3)
- l.SetTable(-3)
- l.SetTop(0)
- return 0
- }
- listClbks := func(l *lua.State) int {
- clbkNames := []string{}
- // get events table
- l.PushString(callbacksLocation)
- l.Table(lua.RegistryIndex)
- // get callbacks for the event
- evtName := lua.CheckString(l, 1)
- l.PushString(evtName)
- l.Table(-2)
- if !l.IsNil(-1) {
- // loop
- l.PushNil()
- for l.Next(-2) {
- key, ok := l.ToString(-2)
- // ignore non-string shit
- if ok {
- clbkNames = append(clbkNames, key)
- }
- l.Pop(1)
- }
- l.SetTop(0)
- l.NewTable()
- // build a list from callback names
- for i, key := range clbkNames {
- l.PushInteger(i + 1)
- l.PushString(key)
- l.SetTable(-3)
- }
- } else {
- l.SetTop(0)
- l.PushNil()
- }
- return 1
- }
- var chatLibrary = []lua.RegistryFunction{
- lua.RegistryFunction{"send", send},
- lua.RegistryFunction{"addEventHandler", registerClbk},
- lua.RegistryFunction{"listEventHandlers", listClbks},
- }
- lua.NewLibrary(e.state, chatLibrary)
- e.state.SetGlobal("chat")
- // set up callbacks table
- e.state.PushString(callbacksLocation)
- e.state.NewTable()
- e.state.SetTable(lua.RegistryIndex)
- lua.DoFile(e.state, filepath.Join("static", "bootstrap.lua"))
- return e
- }
- func (e *Executor) execute() {
- for script := range e.incomingScripts {
- e.stateMutex.Lock()
- err := lua.DoString(e.state, script)
- if err != nil {
- fmt.Printf("lua fucking shit error: %s\n", err)
- m := entity.MSG(entity.GROUPCHAT)
- m.To = "golang@conference.jabber.ru"
- m.Body = err.Error()
- e.xmppStream.Write(entity.ProduceStatic(m))
- }
- e.stateMutex.Unlock()
- }
- }
- func (e *Executor) sendingRoutine() {
- for msg := range e.outgoingMsgs {
- m := entity.MSG(entity.GROUPCHAT)
- m.To = "golang@conference.jabber.ru"
- m.Body = msg
- err := e.xmppStream.Write(entity.ProduceStatic(m))
- if err != nil {
- fmt.Printf("send error: %s", err)
- }
- time.Sleep(sleepDuration)
- }
- }
- func (e *Executor) processIncomingEvents() {
- for evt := range e.incomingEvents {
- e.stateMutex.Lock()
- // get events table
- e.state.PushString(callbacksLocation)
- e.state.Table(lua.RegistryIndex)
- // get callbacks table for the specific event
- e.state.PushString(evt.Type)
- e.state.Table(-2)
- // loop over callbacks
- if !e.state.IsNil(-1) {
- e.state.PushNil()
- for e.state.Next(-2) {
- if e.state.IsFunction(-1) {
- // create the table which will be passed to the handler
- e.state.NewTable()
- // loop over the event data, populating the table
- for k, v := range evt.Data {
- e.state.PushString(k)
- e.state.PushString(v)
- e.state.SetTable(-3)
- }
- err := e.state.ProtectedCall(1, 0, 0)
- if err != nil {
- m := entity.MSG(entity.GROUPCHAT)
- m.To = "golang@conference.jabber.ru"
- m.Body, _ = e.state.ToString(-1)
- e.xmppStream.Write(entity.ProduceStatic(m))
- e.state.Pop(1)
- }
- } else {
- e.state.Pop(1)
- }
- }
- }
- // pop callbacks table or nil value
- e.state.Pop(1)
- e.stateMutex.Unlock()
- }
- }
- func (e *Executor) Start() {
- go e.sendingRoutine()
- go e.execute()
- go e.processIncomingEvents()
- }
- func (e *Executor) Stop() {
- close(e.incomingScripts)
- close(e.incomingEvents)
- close(e.outgoingMsgs)
- }
- func (e *Executor) Run(script string) {
- e.incomingScripts <- script
- }
- // Call this on every event - it's required for event handlers to work
- func (e *Executor) NewEvent(evt IncomingEvent) {
- e.incomingEvents <- evt
- }
|