Files
aego/scene/properties.go
T
sanspie a6822156cd
ci-platforms / windows amd64 smoke (push) Skipped
ci / release (push) Successful in 8m58s
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / darwin arm64 smoke (push) Skipped
ci / checks (push) Successful in 4m46s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 13m28s
Finish Stage 12 editor fixes and refreshed platformer
2026-11-05 17:36:42 +03:00

111 lines
3.9 KiB
Go

package scene
import (
"aego/schema"
"fmt"
)
// PropertyBinding is an asset-driven component property resolved outside the animation hot path.
// Get/Set must reject obsolete resource generations and reacquire components via node handles, not retain pointers.
type PropertyBinding struct {
Key string
Kind schema.Kind
Valid func(*Scene, NodeHandle) bool
Get func(*Scene, NodeHandle, *schema.Value) bool
Set func(*Scene, NodeHandle, schema.Value) bool
}
type PropertyResolver func(*Scene, NodeHandle, string) (*PropertyBinding, error)
// BoundProperty pairs resource identity with shared access methods so playback can first resolve a dynamic property without per-operation closure allocation.
type BoundProperty struct {
Key string
Kind schema.Kind
Resource any
Generation uint64
Access *PropertyAccess
}
type PropertyAccess struct {
Valid func(BoundProperty, *Scene, NodeHandle) bool
Get func(BoundProperty, *Scene, NodeHandle, *schema.Value) bool
Set func(BoundProperty, *Scene, NodeHandle, schema.Value) bool
}
func (p BoundProperty) Valid(s *Scene, h NodeHandle) bool {
return p.Access != nil && (p.Access.Valid == nil || p.Access.Valid(p, s, h))
}
func (p BoundProperty) Get(s *Scene, h NodeHandle, v *schema.Value) bool {
return p.Access != nil && p.Access.Get != nil && p.Access.Get(p, s, h, v)
}
func (p BoundProperty) Set(s *Scene, h NodeHandle, v schema.Value) bool {
return p.Access != nil && p.Access.Set != nil && p.Access.Set(p, s, h, v)
}
type BoundPropertyResolver func(*Scene, NodeHandle, string) (BoundProperty, error)
var callbackAccess = PropertyAccess{
Valid: func(p BoundProperty, s *Scene, h NodeHandle) bool {
b := p.Resource.(*PropertyBinding)
return b.Valid == nil || b.Valid(s, h)
},
Get: func(p BoundProperty, s *Scene, h NodeHandle, v *schema.Value) bool {
return p.Resource.(*PropertyBinding).Get(s, h, v)
},
Set: func(p BoundProperty, s *Scene, h NodeHandle, v schema.Value) bool {
return p.Resource.(*PropertyBinding).Set(s, h, v)
},
}
// RegisterProperties preserves the callback provider API for modules.
func (t *Types) RegisterProperties(component string, resolve PropertyResolver) error {
if resolve == nil {
return fmt.Errorf("invalid property provider for %s", component)
}
return t.RegisterBoundProperties(component, func(s *Scene, h NodeHandle, key string) (BoundProperty, error) {
b, err := resolve(s, h, key)
if b == nil || err != nil {
return BoundProperty{}, err
}
return BoundProperty{Key: b.Key, Kind: b.Kind, Resource: b, Access: &callbackAccess}, nil
})
}
// RegisterBoundProperties extends a registered component through the existing type
// registry. Runtime property consumers do not know the defining module.
func (t *Types) RegisterBoundProperties(component string, resolve BoundPropertyResolver) error {
if t.schema.Frozen() {
return fmt.Errorf("property registration after schema freeze")
}
td, ok := t.schema.ByName(component)
if !ok || td.Kind != schema.TypeComponent || resolve == nil {
return fmt.Errorf("invalid property provider for %s", component)
}
if t.properties == nil {
t.properties = make(map[schema.TypeID]BoundPropertyResolver)
}
if t.properties[td.ID] != nil {
return fmt.Errorf("duplicate property provider for %s", component)
}
t.properties[td.ID] = resolve
return nil
}
func (t *Types) BindProperty(s *Scene, h NodeHandle, typ schema.TypeID, name string) (BoundProperty, error) {
if resolve := t.properties[typ]; resolve != nil {
return resolve(s, h, name)
}
return BoundProperty{}, nil
}
func (t *Types) ResolveProperty(s *Scene, h NodeHandle, typ schema.TypeID, name string) (*PropertyBinding, error) {
b, err := t.BindProperty(s, h, typ, name)
if err != nil || b.Access == nil {
return nil, err
}
if b.Access == &callbackAccess {
return b.Resource.(*PropertyBinding), nil
}
return &PropertyBinding{Key: b.Key, Kind: b.Kind, Valid: b.Valid, Get: b.Get, Set: b.Set}, nil
}