1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-08-25 09:36:24 -07:00

Add a simple event system and the ability for lua plugins to register and trigger events

This commit is contained in:
Eggbertx 2023-01-05 21:31:28 -08:00
parent 6af9f7155f
commit 05e01c6366
4 changed files with 180 additions and 3 deletions

51
pkg/events/events.go Normal file
View file

@ -0,0 +1,51 @@
package events
import (
"fmt"
"os"
"strings"
"github.com/gochan-org/gochan/pkg/gcutil"
)
var (
registeredEvents map[string][]EventHandler
testingMode bool
)
type EventHandler func(string, ...interface{})
// RegisterEvent registers a new event handler to be called when any of the elements of triggers are passed
// to TriggerEvent
func RegisterEvent(triggers []string, handler func(trigger string, i ...interface{})) {
for _, t := range triggers {
registeredEvents[t] = append(registeredEvents[t], handler)
}
}
// TriggerEvent triggers the event handler registered to trigger
func TriggerEvent(trigger string, data ...interface{}) (handled bool, recovered bool) {
errEv := gcutil.LogError(nil).Caller(1)
defer func() {
if a := recover(); a != nil {
if !testingMode {
errEv.Err(fmt.Errorf("%s", a)).
Str("event", trigger).
Msg("Recovered from panic while handling event")
}
handled = true
recovered = true
}
}()
for _, handler := range registeredEvents[trigger] {
handler(trigger, data...)
handled = true
}
errEv.Discard()
return
}
func init() {
registeredEvents = map[string][]EventHandler{}
testingMode = strings.HasSuffix(os.Args[0], ".test")
}

49
pkg/events/events_test.go Normal file
View file

@ -0,0 +1,49 @@
package events
import "testing"
func TestPanicRecover(t *testing.T) {
RegisterEvent([]string{"TestPanicRecoverEvt"}, func(tr string, i ...interface{}) {
t.Log("Testing panic recover")
t.Log(i[0])
})
handled, recovered := TriggerEvent("TestPanicRecoverEvt") // should panic
if !handled {
t.Fatal("TriggerEvent for TestPanicRecoverEvt wasn't handled")
}
t.Log("TestPanicRecoverEvt recovered: ", recovered)
if !recovered {
t.Fatal("TestPanicRecoverEvt should have caused a panic and recovered from it")
}
}
func TestEventEditValue(t *testing.T) {
RegisterEvent([]string{"TestEventEditValue"}, func(tr string, i ...interface{}) {
p := i[0].(*int)
*p += 1
})
var a int
t.Logf("a before TestEventEditValue triggered: %d", a)
TriggerEvent("TestEventEditValue", &a)
if a == 0 {
t.Fatal("TestEventEditValue event didn't properly increment the pointer to a passed to it when triggered")
}
t.Logf("a after TestEventEditValue triggered: %d", a)
}
func TestMultipleEventTriggers(t *testing.T) {
triggered := map[string]bool{}
RegisterEvent([]string{"a", "b"}, func(tr string, i ...interface{}) {
triggered[tr] = true
})
TriggerEvent("a")
TriggerEvent("b")
aTriggered := triggered["a"]
bTriggered := triggered["b"]
if !aTriggered {
t.Fatal("a event not triggered")
}
if !bTriggered {
t.Fatal("b event not triggered")
}
}

View file

@ -2,8 +2,10 @@ package gcplugin
import (
"errors"
"fmt"
"github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/events"
"github.com/gochan-org/gochan/pkg/gcutil"
lua "github.com/yuin/gopher-lua"
@ -28,6 +30,26 @@ func ClosePlugins() {
}
}
func lvalueToInterface(v lua.LValue) interface{} {
lt := v.Type()
switch lt {
case lua.LTNil:
return nil
case lua.LTBool:
return lua.LVAsBool(v)
case lua.LTNumber:
return lua.LVAsNumber(v)
case lua.LTString:
return v.String()
default:
gcutil.LogError(nil).Caller(1).
Interface("lvalue", v).
Str("type", lt.String()).
Msg("Unrecognized or unsupported Lua type")
}
return nil
}
func createLuaLogFunc(which string) lua.LGFunction {
return func(l *lua.LState) int {
switch which {
@ -47,10 +69,48 @@ func createLuaLogFunc(which string) lua.LGFunction {
}
}
func luaEventRegisterHandlerAdapter(l *lua.LState, fn *lua.LFunction) events.EventHandler {
return func(trigger string, data ...interface{}) {
args := []lua.LValue{
luar.New(l, trigger),
}
for _, i := range data {
args = append(args, luar.New(l, i))
}
l.CallByParam(lua.P{
Fn: fn,
NRet: 0,
Protect: true,
}, args...)
}
}
func registerLuaFunctions() {
lState.Register("info_log", createLuaLogFunc("info"))
lState.Register("warn_log", createLuaLogFunc("warn"))
lState.Register("error_log", createLuaLogFunc("error"))
lState.Register("event_register", func(l *lua.LState) int {
table := l.CheckTable(-2)
var triggers []string
table.ForEach(func(i, val lua.LValue) {
triggers = append(triggers, val.String())
})
fn := l.CheckFunction(-1)
events.RegisterEvent(triggers, luaEventRegisterHandlerAdapter(l, fn))
return 0
})
lState.Register("event_trigger", func(l *lua.LState) int {
trigger := l.CheckString(1)
numArgs := l.GetTop()
var data []interface{}
for i := 2; i <= numArgs; i++ {
v := l.CheckAny(i)
data = append(data, lvalueToInterface(v))
}
fmt.Println("triggering", trigger)
events.TriggerEvent(trigger, data...)
return 0
})
lState.SetGlobal("_GOCHAN_VERSION", lua.LString(config.GetVersion().String()))
}

View file

@ -17,10 +17,19 @@ print(string.format("Message before changing: %q", post.MessageRaw))
post.MessageRaw = "Message modified by a plugin\n"
post.Message = "Message modified by a plugin<br />"
print(string.format("Modified message text: %q", post.MessageText))`
eventsTestingStr = `event_register({"newPost"}, function(tr, ...)
print("newPost triggered :D")
for i, v in ipairs(arg) do
print(i .. ": " .. tostring(v))
end
end)
event_trigger("newPost", "blah", 16, 3.14, true, nil)`
)
func initPluginTests() {
config.SetVersion("3.1")
config.SetVersion("3.4.1")
initLua()
}
@ -31,8 +40,8 @@ func TestVersionFunction(t *testing.T) {
t.Fatal(err.Error())
}
testingVersionStr := lState.Get(-1).(lua.LString)
if testingVersionStr != "3.1" {
t.Fatalf("%q != \"3.1\"", testingVersionStr)
if testingVersionStr != "3.4.1" {
t.Fatalf(`%q != "3.4.1"`, testingVersionStr)
}
}
@ -54,3 +63,11 @@ func TestStructPassing(t *testing.T) {
t.Fatal("message was not properly modified by plugin")
}
}
func TestEventPlugins(t *testing.T) {
initPluginTests()
err := lState.DoString(eventsTestingStr)
if err != nil {
t.Fatal(err.Error())
}
}