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 {
// I think i should rethink this. sc should maybe be permanent and then they re-create LStates
s.scriptsMutex.RLock()
sc, ok := s.scripts[target.ID]
s.scriptsMutex.RUnlock()
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 {
sc, err = witch.NewScriptContext()
sc, err = witch.NewScriptContext(tell)
if err != nil {
return err
}

View File

@ -29,11 +29,13 @@ type DB interface {
// General
GetObject(owner, name string) (*Object, error)
GetObjectByID(ID int) (*Object, error)
// Defaults
Ensure() error
// Presence
SessionIDForAvatar(Object) (string, error)
AvatarBySessionID(string) (*Object, error)
BedroomBySessionID(string) (*Object, error)
MoveInto(toMove Object, container Object) error
@ -281,6 +283,26 @@ func (db *pgDB) EndSession(sid string) (err error) {
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) {
avatar = &Object{}
@ -358,6 +380,18 @@ func (db *pgDB) Earshot(obj Object) ([]Object, error) {
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) {
ctx := context.Background()
obj = &Object{}

View File

@ -1,31 +1,51 @@
package witch
import (
"fmt"
"log"
"github.com/vilmibm/hermeticum/server/db"
lua "github.com/yuin/gopher-lua"
)
func hasWrapper(obj db.Object) func(*lua.LState) int {
return func(ls *lua.LState) int {
lv := ls.ToTable(1)
const DefaultAvatarScript = `
has({
name =
})
`
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 witchHas(l *lua.LState) int {
lv := l.ToTable(1)
log.Println(lv)
return 0
}
}
func hearsWrapper(obj db.Object) func(*lua.LState) int {
return func(ls *lua.LState) int {
// TODO get handler from _handlers
// TODO call it
// TODO how to get message in here?
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 does(ls *lua.LState) int {
func witchDoes(ls *lua.LState) int {
// TODO how to feed events back into the server?
// 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?
@ -52,15 +72,19 @@ func does(ls *lua.LState) int {
}
*/
func addHandler(ls *lua.LState) int {
verb := ls.ToString(1)
pattern := ls.ToString(2)
cb := ls.ToFunction(3)
handlers := ls.GetGlobal("_handlers").(*lua.LTable)
newHandler := ls.NewTable()
newHandler.RawSetString(pattern, cb)
handlerMap := handlers.RawGetString(verb).(*lua.LTable)
handlerMap.RawSetString(verb, newHandler)
func addHandler(l *lua.LState, verb, pattern string, cb *lua.LFunction) int {
handlers := l.GetGlobal("_handlers").(*lua.LTable)
verbHandlers, ok := handlers.RawGetString(verb).(*lua.LTable)
if !ok {
verbHandlers = l.NewTable()
handlers.RawSetString(verb, verbHandlers)
}
log.Println("addHandler")
log.Printf("%#v", cb)
verbHandlers.RawSetString(pattern, cb)
return 0
}

View File

@ -1,7 +1,9 @@
package witch
import (
"fmt"
"log"
"regexp"
"github.com/vilmibm/hermeticum/server/db"
lua "github.com/yuin/gopher-lua"
@ -23,12 +25,10 @@ has({
description = "a plate of pasta covered in pomodoro sauce"
})
hears(".*eat.*", function(msg)
does("quivers nervously")
end)
hears(".*", function(msg)
tellMe(sender().name + " says " + msg)
hears(".*", function()
print(sender.name)
print(msg)
tellMe(msg)
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 {
Verb string
Rest string
@ -61,10 +56,13 @@ type VerbContext struct {
type ScriptContext struct {
script string
incoming chan VerbContext
tell func(int, string)
}
func NewScriptContext() (*ScriptContext, error) {
sc := &ScriptContext{}
func NewScriptContext(tell func(int, string)) (*ScriptContext, error) {
sc := &ScriptContext{
tell: tell,
}
sc.incoming = make(chan VerbContext)
go func() {
@ -78,9 +76,17 @@ func NewScriptContext() (*ScriptContext, error) {
//sc.script = vc.Target.Script
sc.script = dummyScript
l = lua.NewState()
l.SetGlobal("has", l.NewFunction(hasWrapper(vc.Target)))
l.SetGlobal("hears", l.NewFunction(hearsWrapper(vc.Target)))
l.SetGlobal("has", l.NewFunction(witchHas))
l.SetGlobal("hears", l.NewFunction(witchHears))
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
//if err := l.DoString(obj.Script); 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())
}
}
})
})
}
}()