refactor!: use userdata where appropriate (#157)

any interface which has lua side objects that are from go side for its api (namely jobs) they will use userdata instead of always creating a table to represent the object. this might or might not bring lower ram usage since there is now only 1 single reference to an object instead of always creating a table to expose on lua

breaking change since methods need to be called with a colon instead of dot
runner-prompt
sammyette 2022-05-28 19:03:44 -04:00 committed by GitHub
parent e3c25586e4
commit b28a2c24c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 221 additions and 62 deletions

View File

@ -56,6 +56,9 @@ This is probably one of (if not the) biggest things in this release.
user input, exit code, and error. User input has been added to the return to
account for runners wanting to prompt for continued input, and to add it
properly to history.
- **Breaking Change:** Job objects and timers are now Lua userdata instead
of a table, so their functions require you to call them with a colon instead
of a dot. (ie. `job.stop()` -> `job:stop()`)
- All `fs` module functions which take paths now implicitly expand ~ to home.
### Fixed

4
api.go
View File

@ -476,7 +476,7 @@ func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
timer := timers.create(timerTimeout, interval, cb)
timer.start()
return c.PushingNext1(t.Runtime, timer.lua()), nil
return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil
}
// interval(cb, time)
@ -502,7 +502,7 @@ func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
timer := timers.create(timerInterval, interval, cb)
timer.start()
return c.PushingNext1(t.Runtime, timer.lua()), nil
return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil
}
// complete(scope, cb)

View File

@ -20,8 +20,11 @@ and `execPath` is an absolute path for the command executable.
- `disown(id)`: Removes a job by ID from the job table.
# Job Object
A job object on the Lua side is a table with some functions.
On the under side it represents a job in the job table.
A job object is a piece of `userdata`. All the functions of a job require
you to call them with a colon, since they are *methods* for the job object.
Example: hilbish.jobs.last():foreground()
Which will foreground the last job.
You can still have a job object for a disowned job,
it just won't be *working* anywhere. :^)

View File

@ -16,9 +16,17 @@ a timer via ID.
when the timer is triggered.
# Timer Object
Those previously mentioned functions return a `timer` object, to which you can
stop and start a timer again. The functions of the timers interface also
return a timer object.
All those previously mentioned functions return a `timer` object, to which
you can stop and start a timer again.
An example of usage:
local t = hilbish.timers.create(1, 5000, function()
print 'hello!'
end)
t:stop()
print(t.running, t.duration, t.type)
t:start()
## Properties
- `duration`: amount of time the timer runs for in milliseconds

144
job.go
View File

@ -3,6 +3,7 @@ package main
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
@ -15,6 +16,7 @@ import (
)
var jobs *jobHandler
var jobMetaKey = rt.StringValue("hshjob")
type job struct {
cmd string
@ -32,6 +34,7 @@ type job struct {
cmderr io.Writer
stdout *bytes.Buffer
stderr *bytes.Buffer
ud *rt.UserData
}
func (j *job) start() error {
@ -64,7 +67,7 @@ func (j *job) start() error {
j.pid = proc.Pid
j.running = true
hooks.Em.Emit("job.start", j.lua())
hooks.Em.Emit("job.start", rt.UserDataValue(j.ud))
return err
}
@ -79,7 +82,7 @@ func (j *job) stop() {
func (j *job) finish() {
j.running = false
hooks.Em.Emit("job.done", j.lua())
hooks.Em.Emit("job.done", rt.UserDataValue(j.ud))
}
func (j *job) wait() {
@ -107,28 +110,16 @@ func (j *job) getProc() *os.Process {
return nil
}
func (j *job) lua() rt.Value {
jobFuncs := map[string]util.LuaExport{
"stop": {j.luaStop, 0, false},
"start": {j.luaStart, 0, false},
"foreground": {j.luaForeground, 0, false},
"background": {j.luaBackground, 0, false},
func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
luaJob := rt.NewTable()
util.SetExports(l, luaJob, jobFuncs)
luaJob.Set(rt.StringValue("cmd"), rt.StringValue(j.cmd))
luaJob.Set(rt.StringValue("running"), rt.BoolValue(j.running))
luaJob.Set(rt.StringValue("id"), rt.IntValue(int64(j.id)))
luaJob.Set(rt.StringValue("pid"), rt.IntValue(int64(j.pid)))
luaJob.Set(rt.StringValue("exitCode"), rt.IntValue(int64(j.exitCode)))
luaJob.Set(rt.StringValue("stdout"), rt.StringValue(string(j.stdout.Bytes())))
luaJob.Set(rt.StringValue("stderr"), rt.StringValue(string(j.stderr.Bytes())))
j, err := jobArg(c, 0)
if err != nil {
return nil, err
}
return rt.TableValue(luaJob)
}
func (j *job) luaStart(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if !j.running {
err := j.start()
exit := handleExecErr(err)
@ -139,7 +130,16 @@ func (j *job) luaStart(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
func (j *job) luaStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
func luaStopJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
j, err := jobArg(c, 0)
if err != nil {
return nil, err
}
if j.running {
j.stop()
j.finish()
@ -148,7 +148,16 @@ func (j *job) luaStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
func (j *job) luaForeground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
func luaForegroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
j, err := jobArg(c, 0)
if err != nil {
return nil, err
}
if !j.running {
return nil, errors.New("job not running")
}
@ -157,7 +166,7 @@ func (j *job) luaForeground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
jobs.foreground = true
// this is kinda funny
// background continues the process incase it got suspended
err := j.background()
err = j.background()
if err != nil {
return nil, err
}
@ -171,12 +180,21 @@ func (j *job) luaForeground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
func (j *job) luaBackground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
func luaBackgroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
j, err := jobArg(c, 0)
if err != nil {
return nil, err
}
if !j.running {
return nil, errors.New("job not running")
}
err := j.background()
err = j.background()
if err != nil {
return nil, err
}
@ -215,8 +233,10 @@ func (j *jobHandler) add(cmd string, args []string, path string) *job {
stdout: &bytes.Buffer{},
stderr: &bytes.Buffer{},
}
jb.ud = jobUserData(jb)
j.jobs[j.latestID] = jb
hooks.Em.Emit("job.add", jb.lua())
hooks.Em.Emit("job.add", rt.UserDataValue(jb.ud))
return jb
}
@ -257,6 +277,44 @@ func (j *jobHandler) stopAll() {
}
func (j *jobHandler) loader(rtm *rt.Runtime) *rt.Table {
jobMethods := rt.NewTable()
jFuncs := map[string]util.LuaExport{
"stop": {luaStopJob, 1, false},
"start": {luaStartJob, 1, false},
"foreground": {luaForegroundJob, 1, false},
"background": {luaBackgroundJob, 1, false},
}
util.SetExports(l, jobMethods, jFuncs)
jobMeta := rt.NewTable()
jobIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
j, _ := jobArg(c, 0)
arg := c.Arg(1)
val := jobMethods.Get(arg)
if val != rt.NilValue {
return c.PushingNext1(t.Runtime, val), nil
}
keyStr, _ := arg.TryString()
switch keyStr {
case "cmd": val = rt.StringValue(j.cmd)
case "running": val = rt.BoolValue(j.running)
case "id": val = rt.IntValue(int64(j.id))
case "pid": val = rt.IntValue(int64(j.pid))
case "exitCode": val = rt.IntValue(int64(j.exitCode))
case "stdout": val = rt.StringValue(string(j.stdout.Bytes()))
case "stderr": val = rt.StringValue(string(j.stderr.Bytes()))
}
return c.PushingNext1(t.Runtime, val), nil
}
jobMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(jobIndex, "__index", 2, false)))
l.SetRegistry(jobMetaKey, rt.TableValue(jobMeta))
jobFuncs := map[string]util.LuaExport{
"all": {j.luaAllJobs, 0, false},
"last": {j.luaLastJob, 0, false},
@ -271,6 +329,30 @@ func (j *jobHandler) loader(rtm *rt.Runtime) *rt.Table {
return luaJob
}
func jobArg(c *rt.GoCont, arg int) (*job, error) {
j, ok := valueToJob(c.Arg(arg))
if !ok {
return nil, fmt.Errorf("#%d must be a job", arg + 1)
}
return j, nil
}
func valueToJob(val rt.Value) (*job, bool) {
u, ok := val.TryUserData()
if !ok {
return nil, false
}
j, ok := u.Value().(*job)
return j, ok
}
func jobUserData(j *job) *rt.UserData {
jobMeta := l.Registry(jobMetaKey)
return rt.NewUserData(j, jobMeta.AsTable())
}
func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
j.mu.RLock()
defer j.mu.RUnlock()
@ -288,7 +370,7 @@ func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
return c.PushingNext1(t.Runtime, job.lua()), nil
return c.PushingNext(t.Runtime, rt.UserDataValue(job.ud)), nil
}
func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -317,7 +399,7 @@ func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
jb := j.add(cmd, args, execPath)
return c.PushingNext1(t.Runtime, jb.lua()), nil
return c.PushingNext1(t.Runtime, rt.UserDataValue(jb.ud)), nil
}
func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -326,7 +408,7 @@ func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
jobTbl := rt.NewTable()
for id, job := range j.jobs {
jobTbl.Set(rt.IntValue(int64(id)), job.lua())
jobTbl.Set(rt.IntValue(int64(id)), rt.UserDataValue(job.ud))
}
return c.PushingNext1(t.Runtime, rt.TableValue(jobTbl)), nil
@ -358,5 +440,5 @@ func (j *jobHandler) luaLastJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
return c.PushingNext1(t.Runtime, job.lua()), nil
return c.PushingNext1(t.Runtime, rt.UserDataValue(job.ud)), nil
}

View File

@ -6,8 +6,6 @@ import (
"os"
"time"
"hilbish/util"
rt "github.com/arnodel/golua/runtime"
)
@ -25,6 +23,7 @@ type timer struct{
fun *rt.Closure
th *timerHandler
ticker *time.Ticker
ud *rt.UserData
channel chan struct{}
}
@ -74,8 +73,17 @@ func (t *timer) stop() error {
return nil
}
func (t *timer) luaStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
err := t.start()
func timerStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
t, err := timerArg(c, 0)
if err != nil {
return nil, err
}
err = t.start()
if err != nil {
return nil, err
}
@ -83,26 +91,20 @@ func (t *timer) luaStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
func (t *timer) luaStop(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
err := t.stop()
func timerStop(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
t, err := timerArg(c, 0)
if err != nil {
return nil, err
}
err = t.stop()
if err != nil {
return nil, err
}
return c.Next(), nil
}
func (t *timer) lua() rt.Value {
tExports := map[string]util.LuaExport{
"start": {t.luaStart, 0, false},
"stop": {t.luaStop, 0, false},
}
luaTimer := rt.NewTable()
util.SetExports(l, luaTimer, tExports)
luaTimer.Set(rt.StringValue("type"), rt.IntValue(int64(t.typ)))
luaTimer.Set(rt.StringValue("running"), rt.BoolValue(t.running))
luaTimer.Set(rt.StringValue("duration"), rt.IntValue(int64(t.dur / time.Millisecond)))
return rt.TableValue(luaTimer)
}

View File

@ -1,6 +1,7 @@
package main
import (
"fmt"
"sync"
"time"
@ -10,6 +11,8 @@ import (
)
var timers *timerHandler
var timerMetaKey = rt.StringValue("hshtimer")
type timerHandler struct {
mu *sync.RWMutex
wg *sync.WaitGroup
@ -44,6 +47,8 @@ func (th *timerHandler) create(typ timerType, dur time.Duration, fun *rt.Closure
th: th,
id: th.latestID,
}
t.ud = timerUserData(t)
th.timers[th.latestID] = t
return t
@ -75,7 +80,7 @@ func (th *timerHandler) luaCreate(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
timerTyp := timerType(timerTypInt)
tmr := th.create(timerTyp, time.Duration(ms) * time.Millisecond, cb)
return c.PushingNext1(t.Runtime, tmr.lua()), nil
return c.PushingNext1(t.Runtime, rt.UserDataValue(tmr.ud)), nil
}
func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -89,13 +94,45 @@ func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
t := th.get(int(id))
if t != nil {
return c.PushingNext1(thr.Runtime, t.lua()), nil
return c.PushingNext1(thr.Runtime, rt.UserDataValue(t.ud)), nil
}
return c.Next(), nil
}
func (th *timerHandler) loader(rtm *rt.Runtime) *rt.Table {
timerMethods := rt.NewTable()
timerFuncs := map[string]util.LuaExport{
"start": {timerStart, 1, false},
"stop": {timerStop, 1, false},
}
util.SetExports(rtm, timerMethods, timerFuncs)
timerMeta := rt.NewTable()
timerIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
ti, _ := timerArg(c, 0)
arg := c.Arg(1)
val := timerMethods.Get(arg)
if val != rt.NilValue {
return c.PushingNext1(t.Runtime, val), nil
}
keyStr, _ := arg.TryString()
switch keyStr {
case "type": val = rt.IntValue(int64(ti.typ))
case "running": val = rt.BoolValue(ti.running)
case "duration": val = rt.IntValue(int64(ti.dur / time.Millisecond))
}
return c.PushingNext1(t.Runtime, val), nil
}
timerMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(timerIndex, "__index", 2, false)))
l.SetRegistry(timerMetaKey, rt.TableValue(timerMeta))
thExports := map[string]util.LuaExport{
"create": {th.luaCreate, 3, false},
"get": {th.luaGet, 1, false},
@ -106,3 +143,27 @@ func (th *timerHandler) loader(rtm *rt.Runtime) *rt.Table {
return luaTh
}
func timerArg(c *rt.GoCont, arg int) (*timer, error) {
j, ok := valueToTimer(c.Arg(arg))
if !ok {
return nil, fmt.Errorf("#%d must be a timer", arg + 1)
}
return j, nil
}
func valueToTimer(val rt.Value) (*timer, bool) {
u, ok := val.TryUserData()
if !ok {
return nil, false
}
j, ok := u.Value().(*timer)
return j, ok
}
func timerUserData(j *timer) *rt.UserData {
timerMeta := l.Registry(timerMetaKey)
return rt.NewUserData(j, timerMeta.AsTable())
}