187 lines
4.8 KiB
Go
187 lines
4.8 KiB
Go
package bind
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"unsafe"
|
|
|
|
"aego/core/ids"
|
|
"aego/script/abi"
|
|
"aego/script/vm"
|
|
)
|
|
|
|
const (
|
|
DataModule = "aego/data"
|
|
DataPrefix = "data:"
|
|
TablePrefix = "table:"
|
|
refMethods = "#AssetRef"
|
|
)
|
|
|
|
var (
|
|
idType = reflect.TypeFor[ids.AssetID]()
|
|
errNilData = &vm.Fault{Code: vm.CodeNil, Message: "data reference is nil"}
|
|
)
|
|
|
|
type DataLoaders[T any] struct {
|
|
Record func(c *Ctx, id ids.AssetID) (*T, error)
|
|
Row func(c *Ctx, table ids.AssetID, row string) (*T, bool, error)
|
|
Rows func(c *Ctx, table ids.AssetID) (int, error)
|
|
}
|
|
|
|
func refLayout(rt reflect.Type) bool {
|
|
return rt.Kind() == reflect.Struct && rt.NumField() == 1 && rt.Field(0).Type == idType && rt.Size() == idType.Size()
|
|
}
|
|
|
|
func AssetRefType[T any](r *Registry, kind string) error {
|
|
rt := reflect.TypeFor[T]()
|
|
if !refLayout(rt) {
|
|
return fmt.Errorf("%s must be a struct with a single ids.AssetID field", rt)
|
|
}
|
|
if prev, dup := r.refs[rt]; dup {
|
|
if prev == kind {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("%s is already an asset reference of kind %s", rt, prev)
|
|
}
|
|
r.refs[rt] = kind
|
|
r.handles[rt] = codec[T]{
|
|
ref: abi.AssetOf(kind),
|
|
slots: 1,
|
|
get: func(_ *vm.Machine, w []vm.Value) T { return cast[T](AssetOf(w[0])) },
|
|
put: func(_ *vm.Machine, w []vm.Value, v T) { w[0] = AssetValue(cast[ids.AssetID](v)) },
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Registry) dataStruct(m *Module, name string, rt reflect.Type, opts []Option) (*StructDesc, []string, error) {
|
|
if rt.Kind() != reflect.Struct {
|
|
return nil, nil, fmt.Errorf("%s is not a struct", rt)
|
|
}
|
|
if _, dup := r.structs[rt]; dup {
|
|
//lint:ignore ST1005 "Go" is a proper noun
|
|
return nil, nil, fmt.Errorf("Go type %s is already a script struct", rt)
|
|
}
|
|
d := &StructDesc{Module: m.path, Name: name}
|
|
def := &abi.StructDef{Module: m.path, Name: name}
|
|
var skipped []string
|
|
for i := 0; i < rt.NumField(); i++ {
|
|
sf := rt.Field(i)
|
|
if !sf.IsExported() || sf.Anonymous || sf.Tag.Get("aego") == "-" {
|
|
continue
|
|
}
|
|
ref, ls, n, err := r.leafFor(sf.Type)
|
|
if err != nil {
|
|
skipped = append(skipped, sf.Name)
|
|
continue
|
|
}
|
|
for _, l := range ls {
|
|
l.off += sf.Offset
|
|
l.off2 += sf.Offset
|
|
l.slot += d.Slots
|
|
d.leaves = append(d.leaves, l)
|
|
}
|
|
d.Fields = append(d.Fields, StructField{Name: sf.Name, Type: ref, Slot: d.Slots, Slots: n})
|
|
def.Fields = append(def.Fields, abi.StructField{Name: sf.Name, Type: ref})
|
|
d.Slots += n
|
|
}
|
|
def.Slots = d.Slots
|
|
if err := r.table.AddStruct(def); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
r.structs[rt] = d
|
|
if err := r.describe(m.path, name, opts, -1); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return d, skipped, nil
|
|
}
|
|
|
|
func hostFault(err error) error {
|
|
if f, ok := err.(*vm.Fault); ok {
|
|
return f
|
|
}
|
|
return &vm.Fault{Code: vm.CodeHost, Message: err.Error()}
|
|
}
|
|
|
|
func DataAccess[T any](r *Registry, name string, ld DataLoaders[T], opts ...Option) ([]string, error) {
|
|
d, skipped, err := r.dataStruct(r.Module(DataModule), name, reflect.TypeFor[T](), opts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("data type %s: %w", name, err)
|
|
}
|
|
q := d.Qualified()
|
|
res := abi.StructOf(q)
|
|
rec, tab := abi.AssetOf(DataPrefix+name), abi.AssetOf(TablePrefix+name)
|
|
str := abi.T(abi.String)
|
|
ls := d.leaves
|
|
r.Intrinsic(refMethods, "Copy<"+q+">", abi.Sig(res, rec), func(mc *vm.Machine, w []vm.Value) error {
|
|
x, err := ctxOf(mc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id := AssetOf(w[0])
|
|
if id.IsZero() {
|
|
return errNilData
|
|
}
|
|
p, err := ld.Record(x, id)
|
|
if err != nil {
|
|
return hostFault(err)
|
|
}
|
|
encodeLeaves(unsafe.Pointer(p), ls, w)
|
|
return x.finish()
|
|
})
|
|
r.Intrinsic(refMethods, "Row<"+q+">", abi.Sig(res, tab, str), func(mc *vm.Machine, w []vm.Value) error {
|
|
x, err := ctxOf(mc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id := AssetOf(w[0])
|
|
key, _ := mc.Str(w[1])
|
|
if id.IsZero() {
|
|
return errNilData
|
|
}
|
|
p, ok, err := ld.Row(x, id, key)
|
|
switch {
|
|
case err != nil:
|
|
return hostFault(err)
|
|
case !ok:
|
|
return &vm.Fault{Code: vm.CodeNil, Message: "table has no row " + strconv.Quote(key)}
|
|
}
|
|
encodeLeaves(unsafe.Pointer(p), ls, w)
|
|
return x.finish()
|
|
})
|
|
r.Intrinsic(refMethods, "Has<"+q+">", abi.Sig(abi.T(abi.Bool), tab, str), func(mc *vm.Machine, w []vm.Value) error {
|
|
x, err := ctxOf(mc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id := AssetOf(w[0])
|
|
key, _ := mc.Str(w[1])
|
|
if id.IsZero() {
|
|
return errNilData
|
|
}
|
|
_, ok, err := ld.Row(x, id, key)
|
|
if err != nil {
|
|
return hostFault(err)
|
|
}
|
|
w[0] = vm.Bool(ok)
|
|
return x.finish()
|
|
})
|
|
r.Intrinsic(refMethods, "Len<"+q+">", abi.Sig(abi.T(abi.Int), tab), func(mc *vm.Machine, w []vm.Value) error {
|
|
x, err := ctxOf(mc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id := AssetOf(w[0])
|
|
if id.IsZero() {
|
|
return errNilData
|
|
}
|
|
n, err := ld.Rows(x, id)
|
|
if err != nil {
|
|
return hostFault(err)
|
|
}
|
|
w[0] = vm.Int(int64(n))
|
|
return x.finish()
|
|
})
|
|
return skipped, nil
|
|
}
|