206 lines
5.5 KiB
Go
206 lines
5.5 KiB
Go
package bind
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"aego/asset"
|
|
"aego/core/mathx"
|
|
"aego/scene"
|
|
"aego/schema"
|
|
"aego/script/abi"
|
|
"aego/script/vm"
|
|
)
|
|
|
|
var (
|
|
tCtx = reflect.TypeFor[*Ctx]()
|
|
errNoTarget = errors.New("component method called without a scene")
|
|
)
|
|
|
|
type dyn struct {
|
|
ref abi.TypeRef
|
|
slots int
|
|
get func(m *vm.Machine, w []vm.Value) reflect.Value
|
|
put func(m *vm.Machine, w []vm.Value, v reflect.Value)
|
|
}
|
|
|
|
func dynOf[T any](r *Registry) (dyn, error) {
|
|
c, err := codecOf[T](r)
|
|
if err != nil {
|
|
return dyn{}, err
|
|
}
|
|
return dyn{
|
|
ref: c.ref,
|
|
slots: c.slots,
|
|
get: func(m *vm.Machine, w []vm.Value) reflect.Value { return reflect.ValueOf(c.get(m, w)) },
|
|
put: func(m *vm.Machine, w []vm.Value, v reflect.Value) { c.put(m, w, v.Interface().(T)) },
|
|
}, nil
|
|
}
|
|
|
|
func dynFor(r *Registry, rt reflect.Type) (dyn, error) {
|
|
switch rt {
|
|
case tVec2:
|
|
return dynOf[mathx.Vec2](r)
|
|
case tVec3:
|
|
return dynOf[mathx.Vec3](r)
|
|
case tColor:
|
|
return dynOf[mathx.Color](r)
|
|
case tQuat:
|
|
return dynOf[mathx.Quat](r)
|
|
case tRect:
|
|
return dynOf[mathx.Rect](r)
|
|
case tNode:
|
|
return dynOf[scene.NodeHandle](r)
|
|
case tT2:
|
|
return dynOf[scene.Transform2D](r)
|
|
case tT3:
|
|
return dynOf[scene.Transform3D](r)
|
|
case tTex:
|
|
return dynOf[asset.Ref[asset.Texture]](r)
|
|
case tScene:
|
|
return dynOf[asset.Ref[asset.Scene]](r)
|
|
case tPrefab:
|
|
return dynOf[asset.Ref[asset.Prefab]](r)
|
|
case tMesh:
|
|
return dynOf[asset.Ref[asset.Mesh]](r)
|
|
}
|
|
return scalarDyn(r, rt)
|
|
}
|
|
|
|
func scalarDyn(r *Registry, rt reflect.Type) (dyn, error) {
|
|
d := dyn{slots: 1}
|
|
switch rt.Kind() {
|
|
case reflect.Bool:
|
|
d.ref = abi.T(abi.Bool)
|
|
d.get = func(_ *vm.Machine, w []vm.Value) reflect.Value { return reflect.ValueOf(w[0].Bool()).Convert(rt) }
|
|
d.put = func(_ *vm.Machine, w []vm.Value, v reflect.Value) { w[0] = vm.Bool(v.Bool()) }
|
|
case reflect.Float32, reflect.Float64:
|
|
d.ref = abi.T(abi.Float)
|
|
d.get = func(_ *vm.Machine, w []vm.Value) reflect.Value {
|
|
return reflect.ValueOf(float64(w[0].Float())).Convert(rt)
|
|
}
|
|
d.put = func(_ *vm.Machine, w []vm.Value, v reflect.Value) { w[0] = vm.Float(float32(v.Float())) }
|
|
case reflect.String:
|
|
d.ref = abi.T(abi.String)
|
|
d.get = func(m *vm.Machine, w []vm.Value) reflect.Value {
|
|
s, _ := m.Str(w[0])
|
|
return reflect.ValueOf(s).Convert(rt)
|
|
}
|
|
d.put = func(m *vm.Machine, w []vm.Value, v reflect.Value) { w[0] = m.NewString(v.String()) }
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
ref, err := intRef(r, rt)
|
|
if err != nil {
|
|
return d, err
|
|
}
|
|
d.ref = ref
|
|
d.get = func(_ *vm.Machine, w []vm.Value) reflect.Value {
|
|
out := reflect.New(rt).Elem()
|
|
out.SetInt(w[0].Int())
|
|
return out
|
|
}
|
|
d.put = func(_ *vm.Machine, w []vm.Value, v reflect.Value) { w[0] = vm.Int(v.Int()) }
|
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
|
ref, err := intRef(r, rt)
|
|
if err != nil {
|
|
return d, err
|
|
}
|
|
d.ref = ref
|
|
d.get = func(_ *vm.Machine, w []vm.Value) reflect.Value {
|
|
out := reflect.New(rt).Elem()
|
|
out.SetUint(uint64(w[0].Int()))
|
|
return out
|
|
}
|
|
d.put = func(_ *vm.Machine, w []vm.Value, v reflect.Value) { w[0] = vm.Int(int64(v.Uint())) }
|
|
default:
|
|
//lint:ignore ST1005 "Go" is a proper noun
|
|
return d, fmt.Errorf("Go type %s cannot cross the script boundary", rt)
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func intRef(r *Registry, rt reflect.Type) (abi.TypeRef, error) {
|
|
if rt.PkgPath() == "" {
|
|
return abi.T(abi.Int), nil
|
|
}
|
|
if name, ok := r.enums[rt]; ok {
|
|
return abi.EnumOf(name), nil
|
|
}
|
|
return abi.TypeRef{}, fmt.Errorf("integer type %s is neither a builtin int nor a registered enum", rt)
|
|
}
|
|
|
|
func Method(m *Module, name string, typ schema.TypeID, fn any, opts ...Option) error {
|
|
fv := reflect.ValueOf(fn)
|
|
if !fv.IsValid() || fv.Kind() != reflect.Func {
|
|
return fmt.Errorf("%s is not a function", name)
|
|
}
|
|
ft := fv.Type()
|
|
if ft.IsVariadic() || ft.NumIn() < 2 || ft.In(0) != tCtx || ft.In(1).Kind() != reflect.Pointer || ft.NumOut() > 1 {
|
|
return fmt.Errorf("%s must be func(*bind.Ctx, *Component, args...) with at most one result", name)
|
|
}
|
|
if m.method {
|
|
return fmt.Errorf("%s: component methods cannot live in a method module", name)
|
|
}
|
|
self := ft.In(1).Elem()
|
|
args := make([]dyn, ft.NumIn()-2)
|
|
offs := make([]int, len(args))
|
|
params := []abi.TypeRef{abi.T(abi.NodeRef)}
|
|
off := 1
|
|
for i := range args {
|
|
d, err := dynFor(m.reg, ft.In(i+2))
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", name, err)
|
|
}
|
|
args[i], offs[i] = d, off
|
|
off += d.slots
|
|
params = append(params, d.ref)
|
|
}
|
|
result := abi.T(abi.Void)
|
|
var out dyn
|
|
hasOut := ft.NumOut() == 1
|
|
if hasOut {
|
|
d, err := dynFor(m.reg, ft.Out(0))
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", name, err)
|
|
}
|
|
out, result = d, d.ref
|
|
}
|
|
miss := name + ": the " + self.Name() + " reference is nil, its node is destroyed or the component was removed"
|
|
before := len(m.reg.errs)
|
|
m.addRecv(name, params, result, func(mc *vm.Machine, w []vm.Value) error {
|
|
x, err := ctxOf(mc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if x.Scene == nil {
|
|
return errNoTarget
|
|
}
|
|
h := NodeOf(w[0])
|
|
if !x.Scene.IsAlive(h) {
|
|
return &vm.Fault{Code: vm.CodeNil, Message: miss}
|
|
}
|
|
p, ok := x.Scene.GetByType(h, typ)
|
|
if !ok {
|
|
return &vm.Fault{Code: vm.CodeNil, Message: miss}
|
|
}
|
|
x.Recv = h
|
|
in := make([]reflect.Value, len(args)+2)
|
|
in[0] = reflect.ValueOf(x)
|
|
in[1] = reflect.NewAt(self, p)
|
|
for i := range args {
|
|
in[i+2] = args[i].get(mc, w[offs[i]:])
|
|
}
|
|
res := fv.Call(in)
|
|
if hasOut {
|
|
out.put(mc, w, res[0])
|
|
}
|
|
return x.finish()
|
|
}, opts)
|
|
if len(m.reg.errs) > before {
|
|
err := errors.Join(m.reg.errs[before:]...)
|
|
m.reg.errs = m.reg.errs[:before]
|
|
return err
|
|
}
|
|
return nil
|
|
}
|