Files
2026-09-26 16:32:40 +03:00

359 lines
9.4 KiB
Go

package schema
import (
"reflect"
"strings"
"unsafe"
"aego/core/errs"
"aego/core/log"
"aego/core/mathx"
)
type vec2 = mathx.Vec2
type color = mathx.Color
type rect = mathx.Rect
var (
vec2Type = reflect.TypeFor[mathx.Vec2]()
colorType = reflect.TypeFor[mathx.Color]()
rectType = reflect.TypeFor[mathx.Rect]()
assetRefType = reflect.TypeFor[AssetRef]()
nodeRefType = reflect.TypeFor[NodeRef]()
)
type enumInfo struct {
name string
values []EnumValue
}
type Registry struct {
frozen bool
byID map[TypeID]*TypeDescriptor
byName map[string]*TypeDescriptor
byType map[reflect.Type]*TypeDescriptor
enums map[reflect.Type]*enumInfo
order []*TypeDescriptor
}
func NewRegistry() *Registry {
return &Registry{
byID: map[TypeID]*TypeDescriptor{},
byName: map[string]*TypeDescriptor{},
byType: map[reflect.Type]*TypeDescriptor{},
enums: map[reflect.Type]*enumInfo{},
}
}
func (r *Registry) Frozen() bool {
return r.frozen
}
func (r *Registry) ByID(id TypeID) (*TypeDescriptor, bool) {
t, ok := r.byID[id]
return t, ok
}
func (r *Registry) ByName(name string) (*TypeDescriptor, bool) {
t, ok := r.byName[name]
return t, ok
}
func (r *Registry) ByGoType(t reflect.Type) (*TypeDescriptor, bool) {
d, ok := r.byType[t]
return d, ok
}
func (r *Registry) Field(t TypeID, name string) (*FieldDescriptor, bool) {
td, ok := r.byID[t]
if !ok {
return nil, false
}
return td.Field(name)
}
func (r *Registry) Each(fn func(*TypeDescriptor)) {
for _, t := range r.order {
fn(t)
}
}
func (r *Registry) Components() []*TypeDescriptor {
out := make([]*TypeDescriptor, 0, len(r.order))
for _, t := range r.order {
if t.Kind == TypeComponent {
out = append(out, t)
}
}
return out
}
func (r *Registry) Len() int {
return len(r.order)
}
func RegisterEnum[T ~int8 | ~int16 | ~int32 | ~int64 | ~int | ~uint8 | ~uint16 | ~uint32](r *Registry, name string, values ...EnumValue) error {
if r.frozen {
return errs.New(errs.SchemaFrozen, "registry is frozen", log.F("enum", name))
}
rt := reflect.TypeFor[T]()
if _, dup := r.enums[rt]; dup {
return errs.New(errs.SchemaDuplicate, "enum type is already registered", log.F("enum", name))
}
if len(values) == 0 {
return errs.New(errs.SchemaBadValue, "enum needs at least one value", log.F("enum", name))
}
r.enums[rt] = &enumInfo{name: name, values: values}
return nil
}
func Register[T any](r *Registry, opts ...Option) (*TypeDescriptor, error) {
return r.register(reflect.TypeFor[T](), TypeComponent, opts)
}
func RegisterStruct[T any](r *Registry, opts ...Option) (*TypeDescriptor, error) {
return r.register(reflect.TypeFor[T](), TypeStruct, opts)
}
func (r *Registry) register(rt reflect.Type, kind TypeKind, opts []Option) (*TypeDescriptor, error) {
if r.frozen {
return nil, errs.New(errs.SchemaFrozen, "registry is frozen", log.F("type", rt.String()))
}
if rt.Kind() != reflect.Struct {
return nil, errs.New(errs.SchemaNotStruct, "only structs can be registered",
log.F("type", rt.String()))
}
var o regOptions
for _, fn := range opts {
fn(&o)
}
full := o.name
if full == "" {
full = defaultName(rt)
}
if existing, dup := r.byName[full]; dup {
return nil, errs.New(errs.SchemaDuplicate, "type name is already registered",
log.F("name", full), log.F("existing", existing.FullName))
}
if _, dup := r.byType[rt]; dup {
return nil, errs.New(errs.SchemaDuplicate, "go type is already registered",
log.F("type", rt.String()))
}
id := HashName(full)
if existing, clash := r.byID[id]; clash {
return nil, errs.New(errs.SchemaCollision, "type name hash collides",
log.F("name", full), log.F("existing", existing.FullName))
}
td := &TypeDescriptor{
ID: id,
FullName: full,
Former: o.former,
Version: o.version,
Size: rt.Size(),
Kind: kind,
Doc: o.doc,
index: map[string]int{},
byID: map[FieldID]int{},
New: func() unsafe.Pointer {
return reflect.New(rt).UnsafePointer()
},
Copy: func(dst, src unsafe.Pointer) {
reflect.NewAt(rt, dst).Elem().Set(reflect.NewAt(rt, src).Elem())
},
}
if td.Version == 0 {
td.Version = 1
}
for i := 0; i < rt.NumField(); i++ {
sf := rt.Field(i)
if !sf.IsExported() {
continue
}
info, err := parseTag(full, sf.Name, sf.Tag.Get("aego"))
if err != nil {
return nil, err
}
if info.skip {
continue
}
fd, err := r.buildField(full, sf, info)
if err != nil {
return nil, err
}
if former, ok := o.formerFields[fd.Name]; ok {
fd.Former = append(fd.Former, former...)
}
fd.ID = HashField(id, fd.Name)
if prev, dup := td.index[fd.Name]; dup {
return nil, errs.New(errs.SchemaDuplicate, "duplicate field name",
log.F("type", full), log.F("field", fd.Name), log.F("previous", td.Fields[prev].Name))
}
if prev, clash := td.byID[fd.ID]; clash {
return nil, errs.New(errs.SchemaCollision, "field name hash collides",
log.F("type", full), log.F("field", fd.Name), log.F("existing", td.Fields[prev].Name))
}
td.index[fd.Name] = len(td.Fields)
td.byID[fd.ID] = len(td.Fields)
td.Fields = append(td.Fields, fd)
}
r.byID[id] = td
r.byName[full] = td
r.byType[rt] = td
r.order = append(r.order, td)
return td, nil
}
func (r *Registry) buildField(typeName string, sf reflect.StructField, info tagInfo) (FieldDescriptor, error) {
fd := FieldDescriptor{
Name: info.name,
Former: info.former,
Flags: info.flags,
AssetKind: info.assetKind,
Min: info.min,
Max: info.max,
HasMin: info.hasMin,
HasMax: info.hasMax,
Hints: info.hints,
Doc: info.doc,
Offset: sf.Offset,
Size: sf.Type.Size(),
}
ft := sf.Type
kind, elem, structID, err := r.kindOf(typeName, info.name, ft)
if err != nil {
return fd, err
}
fd.Kind = kind
fd.Elem = elem
fd.Struct = structID
if kind == KindEnum {
if ei, ok := r.enums[ft]; ok {
fd.Enum = ei.values
fd.EnumName = ei.name
}
}
if kind == KindAssetRef && fd.AssetKind == "" {
zero := reflect.New(ft).Interface().(AssetRef)
fd.AssetKind = zero.AssetKind()
}
fd.Fast = kind.IsPOD()
if err := r.makeAccessors(&fd, ft, sf.Offset); err != nil {
return fd, err
}
return fd, nil
}
func (r *Registry) kindOf(typeName, field string, ft reflect.Type) (Kind, Kind, TypeID, error) {
if ei, ok := r.enums[ft]; ok {
_ = ei
return KindEnum, KindInvalid, 0, nil
}
if reflect.PointerTo(ft).Implements(assetRefType) {
return KindAssetRef, KindInvalid, 0, nil
}
if reflect.PointerTo(ft).Implements(nodeRefType) {
return KindNodeRef, KindInvalid, 0, nil
}
switch ft {
case vec2Type:
return KindVec2, KindInvalid, 0, nil
case colorType:
return KindColor, KindInvalid, 0, nil
case rectType:
return KindRect, KindInvalid, 0, nil
}
switch ft.Kind() {
case reflect.Bool:
return KindBool, KindInvalid, 0, nil
case reflect.Int32:
return KindInt32, KindInvalid, 0, nil
case reflect.Int, reflect.Int64:
return KindInt64, KindInvalid, 0, nil
case reflect.Float32:
return KindFloat32, KindInvalid, 0, nil
case reflect.Float64:
return KindFloat64, KindInvalid, 0, nil
case reflect.String:
return KindString, KindInvalid, 0, nil
case reflect.Slice:
elemKind, _, structID, err := r.kindOf(typeName, field, ft.Elem())
if err != nil {
return KindInvalid, KindInvalid, 0, err
}
if elemKind == KindList {
return KindInvalid, KindInvalid, 0, tagError(typeName, field, "nested slices are not supported")
}
return KindList, elemKind, structID, nil
case reflect.Struct:
td, ok := r.byType[ft]
if !ok {
return KindInvalid, KindInvalid, 0, errs.New(errs.SchemaUnknownType,
"struct field type must be registered with RegisterStruct first",
log.F("type", typeName), log.F("field", field), log.F("fieldType", ft.String()))
}
return KindStruct, KindInvalid, td.ID, nil
}
return KindInvalid, KindInvalid, 0, tagError(typeName, field,
"unsupported field type "+ft.String()+"; tag it with aego:\"-\" to skip")
}
func (r *Registry) Freeze() error {
if r.frozen {
return nil
}
seen := map[string]*TypeDescriptor{}
for _, td := range r.order {
seen[td.FullName] = td
}
for _, td := range r.order {
for _, alias := range td.Former {
if other, dup := seen[alias]; dup {
return errs.New(errs.SchemaDuplicate, "former name collides with an existing type",
log.F("type", td.FullName), log.F("former", alias), log.F("other", other.FullName))
}
id := HashName(alias)
if other, clash := r.byID[id]; clash && other != td {
return errs.New(errs.SchemaCollision, "former name hash collides",
log.F("type", td.FullName), log.F("former", alias), log.F("other", other.FullName))
}
seen[alias] = td
r.byName[alias] = td
r.byID[id] = td
}
for i := range td.Fields {
fd := &td.Fields[i]
if fd.Kind == KindStruct || fd.Elem == KindStruct {
if _, ok := r.byID[fd.Struct]; !ok {
return errs.New(errs.SchemaUnknownType, "field references an unregistered struct",
log.F("type", td.FullName), log.F("field", fd.Name))
}
}
for _, alias := range fd.Former {
if _, dup := td.index[alias]; dup {
return errs.New(errs.SchemaDuplicate, "former field name collides with an existing field",
log.F("type", td.FullName), log.F("field", fd.Name), log.F("former", alias))
}
td.index[alias] = td.index[fd.Name]
}
}
}
r.frozen = true
return nil
}
func defaultName(rt reflect.Type) string {
pkg := rt.PkgPath()
if i := strings.LastIndexByte(pkg, '/'); i >= 0 {
pkg = pkg[i+1:]
}
if pkg == "" {
return rt.Name()
}
return pkg + "." + rt.Name()
}