lua handlers working via goroutines

trunk
vilmibm 2022-12-28 00:19:42 -05:00
parent 5456518feb
commit ace32f9801
4 changed files with 151 additions and 41 deletions

View File

@ -104,14 +104,35 @@ func newServer() (*gameWorldServer, error) {
} }
func (s *gameWorldServer) verbHandler(verb, rest string, sender, target db.Object) error { func (s *gameWorldServer) verbHandler(verb, rest string, sender, target db.Object) error {
// I think i should rethink this. sc should maybe be permanent and then they re-create LStates
s.scriptsMutex.RLock() s.scriptsMutex.RLock()
sc, ok := s.scripts[target.ID] sc, ok := s.scripts[target.ID]
s.scriptsMutex.RUnlock() s.scriptsMutex.RUnlock()
var err error var err error
sid, _ := s.db.SessionIDForAvatar(target)
tell := func(_ int, _ string) {}
if sid != "" {
send := s.msgRouter[sid]
tell = func(senderID int, msg string) {
senderName := "a mysterious stranger"
sender, err := s.db.GetObjectByID(senderID)
if err == nil {
senderName = sender.Data["name"]
}
cm := proto.ClientMessage{
Type: proto.ClientMessage_OVERHEARD,
Text: msg,
Speaker: &senderName,
}
send(&cm)
}
}
if !ok { if !ok {
sc, err = witch.NewScriptContext() sc, err = witch.NewScriptContext(tell)
if err != nil { if err != nil {
return err return err
} }

View File

@ -29,11 +29,13 @@ type DB interface {
// General // General
GetObject(owner, name string) (*Object, error) GetObject(owner, name string) (*Object, error)
GetObjectByID(ID int) (*Object, error)
// Defaults // Defaults
Ensure() error Ensure() error
// Presence // Presence
SessionIDForAvatar(Object) (string, error)
AvatarBySessionID(string) (*Object, error) AvatarBySessionID(string) (*Object, error)
BedroomBySessionID(string) (*Object, error) BedroomBySessionID(string) (*Object, error)
MoveInto(toMove Object, container Object) error MoveInto(toMove Object, container Object) error
@ -281,6 +283,26 @@ func (db *pgDB) EndSession(sid string) (err error) {
return return
} }
func (db *pgDB) SessionIDForAvatar(obj Object) (string, error) {
if !obj.Avatar {
return "", nil
}
fmt.Printf("%#v", obj)
ctx := context.Background()
stmt := `SELECT id FROM sessions WHERE account = $1`
var sid *string
err := db.pool.QueryRow(ctx, stmt, obj.OwnerID).Scan(&sid)
if err != nil {
return "", err
}
if sid == nil {
return "", nil
}
return *sid, nil
}
func (db *pgDB) AvatarBySessionID(sid string) (avatar *Object, err error) { func (db *pgDB) AvatarBySessionID(sid string) (avatar *Object, err error) {
avatar = &Object{} avatar = &Object{}
@ -358,6 +380,18 @@ func (db *pgDB) Earshot(obj Object) ([]Object, error) {
return out, nil return out, nil
} }
func (db *pgDB) GetObjectByID(ID int) (*Object, error) {
ctx := context.Background()
obj := &Object{}
stmt := `
SELECT id, avatar, data, owner, script
FROM objects
WHERE id = $1`
err := db.pool.QueryRow(ctx, stmt, ID).Scan(
&obj.ID, &obj.Avatar, &obj.Data, &obj.OwnerID, &obj.Script)
return obj, err
}
func (db *pgDB) GetObject(owner, name string) (obj *Object, err error) { func (db *pgDB) GetObject(owner, name string) (obj *Object, err error) {
ctx := context.Background() ctx := context.Background()
obj = &Object{} obj = &Object{}

View File

@ -1,31 +1,51 @@
package witch package witch
import ( import (
"fmt"
"log" "log"
"github.com/vilmibm/hermeticum/server/db" "github.com/vilmibm/hermeticum/server/db"
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
) )
func hasWrapper(obj db.Object) func(*lua.LState) int { const DefaultAvatarScript = `
return func(ls *lua.LState) int { has({
lv := ls.ToTable(1) name =
log.Println(lv) })
return 0 `
func SetDefaultAvatarScript(obj *db.Object) {
hasInvocation := "has({\n"
for k, v := range obj.Data {
hasInvocation += fmt.Sprintf(`%s = "%s"\n`, k, v)
} }
hasInvocation += "})"
obj.Script = fmt.Sprintf(`%s
hears(".*", function()
tellMe(sender, msg)
end)
`, hasInvocation)
} }
func hearsWrapper(obj db.Object) func(*lua.LState) int { func witchHas(l *lua.LState) int {
return func(ls *lua.LState) int { lv := l.ToTable(1)
// TODO get handler from _handlers log.Println(lv)
// TODO call it return 0
// TODO how to get message in here?
return 0
}
} }
func does(ls *lua.LState) int { func witchHears(l *lua.LState) int {
// TODO register handler
handlers := l.GetGlobal("_handlers").(*lua.LTable)
log.Println(handlers)
pattern := l.ToString(1)
cb := l.ToFunction(2)
addHandler(l, "say", pattern, cb)
return 0
}
func witchDoes(ls *lua.LState) int {
// TODO how to feed events back into the server? // TODO how to feed events back into the server?
// it needs to behave like an event showing up in Commands stream // it needs to behave like an event showing up in Commands stream
// this handler needs a reference to the gateway which has a channel for sending events that the server will see? // this handler needs a reference to the gateway which has a channel for sending events that the server will see?
@ -52,15 +72,19 @@ func does(ls *lua.LState) int {
} }
*/ */
func addHandler(ls *lua.LState) int { func addHandler(l *lua.LState, verb, pattern string, cb *lua.LFunction) int {
verb := ls.ToString(1) handlers := l.GetGlobal("_handlers").(*lua.LTable)
pattern := ls.ToString(2)
cb := ls.ToFunction(3) verbHandlers, ok := handlers.RawGetString(verb).(*lua.LTable)
handlers := ls.GetGlobal("_handlers").(*lua.LTable) if !ok {
newHandler := ls.NewTable() verbHandlers = l.NewTable()
newHandler.RawSetString(pattern, cb) handlers.RawSetString(verb, verbHandlers)
handlerMap := handlers.RawGetString(verb).(*lua.LTable) }
handlerMap.RawSetString(verb, newHandler)
log.Println("addHandler")
log.Printf("%#v", cb)
verbHandlers.RawSetString(pattern, cb)
return 0 return 0
} }

View File

@ -1,7 +1,9 @@
package witch package witch
import ( import (
"fmt"
"log" "log"
"regexp"
"github.com/vilmibm/hermeticum/server/db" "github.com/vilmibm/hermeticum/server/db"
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
@ -23,12 +25,10 @@ has({
description = "a plate of pasta covered in pomodoro sauce" description = "a plate of pasta covered in pomodoro sauce"
}) })
hears(".*eat.*", function(msg) hears(".*", function()
does("quivers nervously") print(sender.name)
end) print(msg)
tellMe(msg)
hears(".*", function(msg)
tellMe(sender().name + " says " + msg)
end) end)
` `
@ -46,11 +46,6 @@ end)
` `
*/ */
// TODO figure out channel stuff
// TODO figure out how to inject WITCH header
// - do i inject from Go or prepend some Lua code?
// TODO figure out how the Lua code can affect Go and thus the database
type VerbContext struct { type VerbContext struct {
Verb string Verb string
Rest string Rest string
@ -61,10 +56,13 @@ type VerbContext struct {
type ScriptContext struct { type ScriptContext struct {
script string script string
incoming chan VerbContext incoming chan VerbContext
tell func(int, string)
} }
func NewScriptContext() (*ScriptContext, error) { func NewScriptContext(tell func(int, string)) (*ScriptContext, error) {
sc := &ScriptContext{} sc := &ScriptContext{
tell: tell,
}
sc.incoming = make(chan VerbContext) sc.incoming = make(chan VerbContext)
go func() { go func() {
@ -78,9 +76,17 @@ func NewScriptContext() (*ScriptContext, error) {
//sc.script = vc.Target.Script //sc.script = vc.Target.Script
sc.script = dummyScript sc.script = dummyScript
l = lua.NewState() l = lua.NewState()
l.SetGlobal("has", l.NewFunction(hasWrapper(vc.Target))) l.SetGlobal("has", l.NewFunction(witchHas))
l.SetGlobal("hears", l.NewFunction(hearsWrapper(vc.Target))) l.SetGlobal("hears", l.NewFunction(witchHears))
l.SetGlobal("_handlers", l.NewTable()) l.SetGlobal("_handlers", l.NewTable())
l.SetGlobal("tellMe", l.NewFunction(func(l *lua.LState) int {
sender := l.GetGlobal("sender").(*lua.LTable)
senderID := int(lua.LVAsNumber(sender.RawGetString("ID")))
msg := l.ToString(1)
log.Printf("%#v %s\n", sender, msg)
sc.tell(senderID, msg)
return 0
}))
// TODO other setup // TODO other setup
//if err := l.DoString(obj.Script); err != nil { //if err := l.DoString(obj.Script); err != nil {
if err = l.DoString(dummyScript); err != nil { if err = l.DoString(dummyScript); err != nil {
@ -88,7 +94,32 @@ func NewScriptContext() (*ScriptContext, error) {
} }
} }
// TODO actually trigger the Lua script // TODO check execute permission and bail out potentially
senderT := l.NewTable()
senderT.RawSetString("name", lua.LString(vc.Sender.Data["name"]))
senderT.RawSetString("ID", lua.LString(vc.Sender.Data["ID"]))
l.SetGlobal("sender", senderT)
l.SetGlobal("msg", lua.LString(vc.Rest))
handlers := l.GetGlobal("_handlers").(*lua.LTable)
handlers.ForEach(func(k, v lua.LValue) {
if k.String() != vc.Verb {
return
}
v.(*lua.LTable).ForEach(func(kk, vv lua.LValue) {
pattern := regexp.MustCompile(kk.String())
if pattern.MatchString(vc.Rest) {
// TODO TODO TODO TODO TODO
// this could be a remote code execution vuln; but by being here, I
// believe vc.Verb has been effectively validated as "not a pile of
// lua code" since it matched a handler.
if err = l.DoString(fmt.Sprintf(`_handlers.%s["%s"]()`, vc.Verb, pattern)); err != nil {
log.Println(err.Error())
}
}
})
})
} }
}() }()