github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/lib/runtimelib/ctx.go (about)

     1  package runtimelib
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	rt "github.com/arnodel/golua/runtime"
     8  )
     9  
    10  var contextRegistryKey = rt.AsValue(contextRegistry{})
    11  
    12  func createContextMetatable(r *rt.Runtime) {
    13  	contextMeta := rt.NewTable()
    14  
    15  	rt.SolemnlyDeclareCompliance(
    16  		rt.ComplyCpuSafe|rt.ComplyMemSafe|rt.ComplyTimeSafe|rt.ComplyIoSafe,
    17  
    18  		r.SetEnvGoFunc(contextMeta, "__index", context__index, 2, false),
    19  		r.SetEnvGoFunc(contextMeta, "__tostring", context__tostring, 1, false),
    20  	)
    21  
    22  	resourcesMeta := rt.NewTable()
    23  	rt.SolemnlyDeclareCompliance(
    24  		rt.ComplyCpuSafe|rt.ComplyMemSafe|rt.ComplyTimeSafe|rt.ComplyIoSafe,
    25  
    26  		r.SetEnvGoFunc(resourcesMeta, "__index", resources__index, 2, false),
    27  		r.SetEnvGoFunc(resourcesMeta, "__tostring", resources__tostring, 1, false),
    28  	)
    29  	r.SetRegistry(contextRegistryKey, rt.AsValue(&contextRegistry{
    30  		contextMeta:   contextMeta,
    31  		resourcesMeta: resourcesMeta,
    32  	}))
    33  }
    34  
    35  type contextRegistry struct {
    36  	contextMeta   *rt.Table
    37  	resourcesMeta *rt.Table
    38  }
    39  
    40  func getRegistry(r *rt.Runtime) *contextRegistry {
    41  	return r.Registry(contextRegistryKey).Interface().(*contextRegistry)
    42  }
    43  
    44  func newContextValue(r *rt.Runtime, ctx rt.RuntimeContext) rt.Value {
    45  	return r.NewUserDataValue(ctx, getRegistry(r).contextMeta)
    46  }
    47  
    48  func valueToContext(v rt.Value) (rt.RuntimeContext, bool) {
    49  	u, ok := v.TryUserData()
    50  	if !ok {
    51  		return nil, false
    52  	}
    53  	ctx, ok := u.Value().(rt.RuntimeContext)
    54  	if !ok {
    55  		return nil, false
    56  	}
    57  	return ctx, true
    58  }
    59  
    60  func newResourcesValue(r *rt.Runtime, res rt.RuntimeResources) rt.Value {
    61  	return r.NewUserDataValue(res, getRegistry(r).resourcesMeta)
    62  }
    63  
    64  func valueToResources(v rt.Value) (res rt.RuntimeResources, ok bool) {
    65  	var u *rt.UserData
    66  	u, ok = v.TryUserData()
    67  	if !ok {
    68  		return
    69  	}
    70  	res, ok = u.Value().(rt.RuntimeResources)
    71  	return
    72  }
    73  
    74  func contextArg(c *rt.GoCont, n int) (rt.RuntimeContext, error) {
    75  	ctx, ok := valueToContext(c.Arg(n))
    76  	if ok {
    77  		return ctx, nil
    78  	}
    79  	return nil, fmt.Errorf("#%d must be a runtime context", n+1)
    80  }
    81  
    82  func optContextArg(t *rt.Thread, c *rt.GoCont, n int) (rt.RuntimeContext, error) {
    83  	if n >= c.NArgs() {
    84  		return t.RuntimeContext(), nil
    85  	}
    86  	return contextArg(c, n)
    87  }
    88  
    89  func resourcesArg(c *rt.GoCont, n int) (rt.RuntimeResources, error) {
    90  	res, ok := valueToResources(c.Arg(n))
    91  	if ok {
    92  		return res, nil
    93  	}
    94  	return res, fmt.Errorf("#%d must be a runtime resources", n+1)
    95  }
    96  
    97  func context__index(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
    98  	ctx, err := contextArg(c, 0)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	key, err := c.StringArg(1)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	val := rt.NilValue
   107  	switch key {
   108  	case "kill":
   109  		val = newResourcesValue(t.Runtime, ctx.HardLimits())
   110  	case "stop":
   111  		val = newResourcesValue(t.Runtime, ctx.SoftLimits())
   112  	case "used":
   113  		val = newResourcesValue(t.Runtime, ctx.UsedResources())
   114  	case "status":
   115  		val = statusValue(ctx.Status())
   116  	case "parent":
   117  		val = rt.NilValue
   118  	case "flags":
   119  		val = rt.StringValue(strings.Join(ctx.RequiredFlags().Names(), " "))
   120  	case "due":
   121  		val = rt.BoolValue(ctx.Due())
   122  	case "killnow":
   123  		val = rt.FunctionValue(killnowGoF)
   124  	case "stopnow":
   125  		val = rt.FunctionValue(stopnowGoF)
   126  	}
   127  	return c.PushingNext1(t.Runtime, val), nil
   128  }
   129  
   130  func context__tostring(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
   131  	ctx, err := contextArg(c, 0)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	return c.PushingNext1(t.Runtime, statusValue(ctx.Status())), nil
   136  }
   137  
   138  func resources__index(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
   139  	res, err := resourcesArg(c, 0)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	key, err := c.StringArg(1)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	val := rt.NilValue
   148  	switch key {
   149  	case cpuName:
   150  		n := res.Cpu
   151  		if n > 0 {
   152  			val = resToVal(n)
   153  		}
   154  	case memoryName:
   155  		n := res.Memory
   156  		if n > 0 {
   157  			val = resToVal(n)
   158  		}
   159  	case secondsName:
   160  		n := res.Millis
   161  		if n > 0 {
   162  			val = rt.FloatValue(float64(n) / 1000)
   163  		}
   164  	case millisName:
   165  		n := res.Millis
   166  		if n > 0 {
   167  			val = rt.FloatValue(float64(n))
   168  		}
   169  	default:
   170  		// We'll return nil
   171  	}
   172  	return c.PushingNext1(t.Runtime, val), nil
   173  }
   174  
   175  func resources__tostring(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
   176  	res, err := resourcesArg(c, 0)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	vals := make([]string, 0, 3)
   181  	if res.Cpu > 0 {
   182  		vals = append(vals, fmt.Sprintf("%s=%d", cpuName, res.Cpu))
   183  	}
   184  	if res.Memory > 0 {
   185  		vals = append(vals, fmt.Sprintf("%s=%d", memoryName, res.Memory))
   186  	}
   187  	if res.Millis > 0 {
   188  		vals = append(vals, fmt.Sprintf("%s=%g", secondsName, float64(res.Millis)/1000))
   189  	}
   190  	s := "[" + strings.Join(vals, ",") + "]"
   191  	t.RequireBytes(len(s))
   192  	return c.PushingNext1(t.Runtime, rt.StringValue(s)), nil
   193  }
   194  
   195  func resToVal(v uint64) rt.Value {
   196  	return rt.IntValue(int64(v))
   197  }
   198  
   199  func statusValue(st rt.RuntimeContextStatus) rt.Value {
   200  	s := st.String()
   201  	if s == "" {
   202  		return rt.NilValue
   203  	}
   204  	return rt.StringValue(s)
   205  }
   206  
   207  func killnow(t *rt.Thread, c *rt.GoCont) (next rt.Cont, err error) {
   208  	ctx, err := optContextArg(t, c, 0)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  	ctx.SetStopLevel(rt.HardStop)
   213  	return nil, nil
   214  }
   215  
   216  func stopnow(t *rt.Thread, c *rt.GoCont) (next rt.Cont, err error) {
   217  	ctx, err := optContextArg(t, c, 0)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	ctx.SetStopLevel(rt.SoftStop)
   222  	return c.Next(), nil
   223  }
   224  
   225  func due(t *rt.Thread, c *rt.GoCont) (next rt.Cont, retErr error) {
   226  	ctx, err := optContextArg(t, c, 0)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  	return c.PushingNext1(t.Runtime, rt.BoolValue(ctx.Due())), nil
   231  }
   232  
   233  var (
   234  	killnowGoF = rt.NewGoFunction(killnow, "killnow", 1, false)
   235  	stopnowGoF = rt.NewGoFunction(stopnow, "stopnow", 1, false)
   236  	dueGoF     = rt.NewGoFunction(due, "due", 1, false)
   237  )
   238  
   239  func init() {
   240  	rt.SolemnlyDeclareCompliance(
   241  		rt.ComplyCpuSafe|rt.ComplyMemSafe|rt.ComplyTimeSafe|rt.ComplyIoSafe,
   242  		killnowGoF,
   243  		stopnowGoF,
   244  		dueGoF,
   245  	)
   246  }