298 lines
7.8 KiB
Go
298 lines
7.8 KiB
Go
package module
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
"unsafe"
|
|
|
|
"aego/core/errs"
|
|
"aego/core/log"
|
|
"aego/scene"
|
|
"aego/schema"
|
|
"aego/script/bind"
|
|
)
|
|
|
|
type Integer interface {
|
|
~int8 | ~int16 | ~int32 | ~int64 | ~int | ~uint8 | ~uint16 | ~uint32
|
|
}
|
|
|
|
type ComponentBuilder[T any] struct {
|
|
r *Registry
|
|
desc *schema.TypeDescriptor
|
|
err error
|
|
}
|
|
|
|
func Component[T any](r *Registry, opts ...schema.Option) *ComponentBuilder[T] {
|
|
b := &ComponentBuilder[T]{r: r}
|
|
rt := reflect.TypeFor[T]()
|
|
if err := scene.RegisterComponent[T](r.types, opts...); err != nil {
|
|
b.err = r.wrap(err, "component "+rt.String()+" registration failed")
|
|
return b
|
|
}
|
|
td, ok := r.Schema().ByGoType(rt)
|
|
if !ok {
|
|
b.err = r.fail("component " + rt.String() + " has no descriptor after registration")
|
|
return b
|
|
}
|
|
td.Owner = r.Owner()
|
|
td.Builtin = r.builtin()
|
|
b.desc = td
|
|
r.comps = append(r.comps, td)
|
|
return b
|
|
}
|
|
|
|
func Struct[T any](r *Registry, opts ...schema.Option) error {
|
|
if _, err := schema.RegisterStruct[T](r.Schema(), opts...); err != nil {
|
|
return r.wrap(err, "struct "+reflect.TypeFor[T]().String()+" registration failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Enum[T Integer](r *Registry, name string, values ...schema.EnumValue) error {
|
|
if err := schema.RegisterEnum[T](r.Schema(), name, values...); err != nil {
|
|
return r.wrap(err, "enum "+name+" registration failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Err() error {
|
|
return b.err
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Descriptor() *schema.TypeDescriptor {
|
|
return b.desc
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) fail(msg string) {
|
|
if err := b.r.fail(msg); b.err == nil {
|
|
b.err = err
|
|
}
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) edit() *schema.TypeDescriptor {
|
|
if b.desc == nil {
|
|
return nil
|
|
}
|
|
if b.r.Schema().Frozen() {
|
|
if b.err == nil {
|
|
b.err = errs.New(errs.EngineFrozen, "component "+b.desc.FullName+" is changed after the schema was frozen", log.F("module", b.r.Owner()))
|
|
}
|
|
return nil
|
|
}
|
|
return b.desc
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Category(name string) *ComponentBuilder[T] {
|
|
if d := b.edit(); d != nil {
|
|
d.Category = name
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Display(name string) *ComponentBuilder[T] {
|
|
if d := b.edit(); d != nil {
|
|
d.DisplayName = name
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Icon(name string) *ComponentBuilder[T] {
|
|
if d := b.edit(); d != nil {
|
|
d.Icon = name
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Default(fn func() T) *ComponentBuilder[T] {
|
|
d := b.edit()
|
|
if d == nil {
|
|
return b
|
|
}
|
|
if fn == nil {
|
|
b.fail("component " + d.FullName + " has a nil default")
|
|
return b
|
|
}
|
|
d.Default = func() unsafe.Pointer {
|
|
v := fn()
|
|
return unsafe.Pointer(&v)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func scriptField(d *schema.TypeDescriptor, name string) (*schema.FieldDescriptor, bool) {
|
|
if fd, ok := d.Field(name); ok {
|
|
return fd, true
|
|
}
|
|
r, n := utf8.DecodeRuneInString(name)
|
|
return d.Field(string(unicode.ToLower(r)) + name[n:])
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Script(fields ...string) *ComponentBuilder[T] {
|
|
d := b.edit()
|
|
if d == nil {
|
|
return b
|
|
}
|
|
ids := make([]schema.FieldID, 0, len(fields))
|
|
for _, name := range fields {
|
|
fd, ok := scriptField(d, name)
|
|
switch {
|
|
case !ok:
|
|
b.fail("component " + d.FullName + " has no field " + name)
|
|
case !fd.Flags.Has(schema.FlagScript):
|
|
b.fail("field " + d.FullName + "." + fd.Name + " is tagged noscript")
|
|
default:
|
|
ids = append(ids, fd.ID)
|
|
}
|
|
}
|
|
d.ScriptFields = ids
|
|
return b
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) ScriptNone() *ComponentBuilder[T] {
|
|
if d := b.edit(); d != nil {
|
|
d.ScriptFields = []schema.FieldID{}
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) declared(d *schema.TypeDescriptor, name string) bool {
|
|
for _, m := range d.ScriptMethods {
|
|
if m.Name == name {
|
|
b.fail("script method " + name + " of " + d.FullName + " is declared twice")
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) ScriptMethod(name string, fn any, opts ...bind.Option) *ComponentBuilder[T] {
|
|
d := b.edit()
|
|
if d == nil {
|
|
return b
|
|
}
|
|
if name == "" || fn == nil || reflect.TypeOf(fn).Kind() != reflect.Func {
|
|
b.fail("script method " + name + " of " + d.FullName + " needs a name and a function")
|
|
return b
|
|
}
|
|
if b.declared(d, name) {
|
|
return b
|
|
}
|
|
conv := make([]any, len(opts))
|
|
for i, o := range opts {
|
|
conv[i] = o
|
|
}
|
|
d.ScriptMethods = append(d.ScriptMethods, schema.ScriptMethod{Name: name, Fn: fn, Opts: conv})
|
|
return b
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) typed(name string, missing bool, adapt func(*bind.Module, schema.TypeID)) *ComponentBuilder[T] {
|
|
d := b.edit()
|
|
if d == nil {
|
|
return b
|
|
}
|
|
if name == "" || missing {
|
|
b.fail("script method " + name + " of " + d.FullName + " needs a name and a function")
|
|
return b
|
|
}
|
|
if b.declared(d, name) {
|
|
return b
|
|
}
|
|
d.ScriptMethods = append(d.ScriptMethods, schema.ScriptMethod{Name: name, Bind: adapt})
|
|
return b
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Method0[R any](name string, fn func(*bind.Ctx, *T) R, opts ...bind.Option) *ComponentBuilder[T] {
|
|
return b.typed(name, fn == nil, func(m *bind.Module, t schema.TypeID) { bind.Method0(m, name, t, fn, opts...) })
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Method1[A, R any](name string, fn func(*bind.Ctx, *T, A) R, opts ...bind.Option) *ComponentBuilder[T] {
|
|
return b.typed(name, fn == nil, func(m *bind.Module, t schema.TypeID) { bind.Method1(m, name, t, fn, opts...) })
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Method2[A, B, R any](name string, fn func(*bind.Ctx, *T, A, B) R, opts ...bind.Option) *ComponentBuilder[T] {
|
|
return b.typed(name, fn == nil, func(m *bind.Module, t schema.TypeID) { bind.Method2(m, name, t, fn, opts...) })
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Proc0(name string, fn func(*bind.Ctx, *T), opts ...bind.Option) *ComponentBuilder[T] {
|
|
return b.typed(name, fn == nil, func(m *bind.Module, t schema.TypeID) { bind.MethodProc0(m, name, t, fn, opts...) })
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Proc1[A any](name string, fn func(*bind.Ctx, *T, A), opts ...bind.Option) *ComponentBuilder[T] {
|
|
return b.typed(name, fn == nil, func(m *bind.Module, t schema.TypeID) { bind.MethodProc1(m, name, t, fn, opts...) })
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Proc2[A, B any](name string, fn func(*bind.Ctx, *T, A, B), opts ...bind.Option) *ComponentBuilder[T] {
|
|
return b.typed(name, fn == nil, func(m *bind.Module, t schema.TypeID) { bind.MethodProc2(m, name, t, fn, opts...) })
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Template(name string, setup func(*scene.Scene, scene.NodeHandle)) *ComponentBuilder[T] {
|
|
d := b.edit()
|
|
if d == nil {
|
|
return b
|
|
}
|
|
if d.Template != nil {
|
|
b.fail("component " + d.FullName + " declares its template twice")
|
|
return b
|
|
}
|
|
if name == "" {
|
|
name = d.FullName[strings.LastIndexByte(d.FullName, '.')+1:]
|
|
}
|
|
d.Template = &schema.TemplateDesc{Name: name, Space: d.Space, Setup: componentSetup(d, setup)}
|
|
b.r.addTemplate(d.Template)
|
|
return b
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Requires(fullNames ...string) *ComponentBuilder[T] {
|
|
if d := b.edit(); d != nil {
|
|
d.Requires = append(d.Requires, fullNames...)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) SceneUnique() *ComponentBuilder[T] {
|
|
if d := b.edit(); d != nil {
|
|
d.SceneUnique = true
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (b *ComponentBuilder[T]) Inspector(fn any) *ComponentBuilder[T] {
|
|
d := b.edit()
|
|
if d == nil {
|
|
return b
|
|
}
|
|
if fn == nil {
|
|
b.fail("component " + d.FullName + " has a nil inspector")
|
|
return b
|
|
}
|
|
d.Inspector = fn
|
|
return b
|
|
}
|
|
|
|
func componentSetup(d *schema.TypeDescriptor, user func(*scene.Scene, scene.NodeHandle)) func(*scene.Scene, scene.NodeHandle) {
|
|
return func(s *scene.Scene, h scene.NodeHandle) {
|
|
for _, name := range d.Requires {
|
|
if td, ok := s.Registry().ByName(name); ok {
|
|
Ensure(s, h, td)
|
|
}
|
|
}
|
|
Ensure(s, h, d)
|
|
if user != nil {
|
|
user(s, h)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Ensure(s *scene.Scene, h scene.NodeHandle, td *schema.TypeDescriptor) {
|
|
if _, ok := s.GetByType(h, td.ID); ok {
|
|
return
|
|
}
|
|
p, err := s.AddByType(h, td.ID)
|
|
if err != nil || td.Default == nil || td.Copy == nil {
|
|
return
|
|
}
|
|
td.Copy(p, td.Default())
|
|
}
|