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

263 lines
6.0 KiB
Go

package props
import (
"math"
"aego/core/errs"
"aego/core/ids"
"aego/core/log"
"aego/editor/history"
"aego/editor/history/commands"
"aego/scene"
"aego/schema"
)
type Access struct {
s *scene.Scene
h *history.History
}
func New(s *scene.Scene, h *history.History) *Access {
return &Access{s: s, h: h}
}
func (a *Access) Scene() *scene.Scene {
return a.s
}
func (a *Access) History() *history.History {
return a.h
}
func (a *Access) Descriptor(t schema.TypeID, f schema.FieldID) (*schema.TypeDescriptor, *schema.FieldDescriptor, error) {
td, ok := a.s.Registry().ByID(t)
if !ok {
return nil, nil, errs.New(errs.SchemaUnknownType, "component type is not registered")
}
fd, ok := td.FieldByID(f)
if !ok {
return td, nil, errs.New(errs.SchemaBadField, "unknown field", log.F("type", td.FullName))
}
return td, fd, nil
}
func (a *Access) handle(node ids.NodeID) (scene.NodeHandle, error) {
h := a.s.Handle(node)
if h.IsZero() || !a.s.IsAlive(h) {
return h, errs.New(errs.SceneDeadNode, "node is not in the scene", log.F("node", node.String()))
}
return h, nil
}
func (a *Access) Get(node ids.NodeID, t schema.TypeID, f schema.FieldID) (schema.Value, error) {
h, err := a.handle(node)
if err != nil {
return schema.Value{}, err
}
return a.s.GetProperty(h, t, f)
}
func (a *Access) Common(nodes []ids.NodeID, t schema.TypeID, f schema.FieldID) (schema.Value, bool, error) {
var first schema.Value
for i, n := range nodes {
v, err := a.Get(n, t, f)
if err != nil {
return schema.Value{}, false, err
}
if i == 0 {
first = v
continue
}
if !Equal(first, v) {
return first, true, nil
}
}
return first, false, nil
}
func (a *Access) Validate(t schema.TypeID, f schema.FieldID, v schema.Value) error {
_, err := a.Normalize(t, f, v)
return err
}
func (a *Access) Normalize(t schema.TypeID, f schema.FieldID, v schema.Value) (schema.Value, error) {
_, fd, err := a.Descriptor(t, f)
if err != nil {
return v, err
}
return Normalize(fd, v)
}
func Label(fd *schema.FieldDescriptor) string {
if fd.Hints.DisplayName != "" {
return fd.Hints.DisplayName
}
return fd.Name
}
func (a *Access) within(tx *history.Transaction, label string, fn func(*history.Transaction) error) error {
own := tx == nil
if own {
tx = a.h.Begin(label)
}
if err := fn(tx); err != nil {
if own {
tx.Abort()
}
return err
}
if own {
tx.Commit()
}
return nil
}
func (a *Access) Set(tx *history.Transaction, nodes []ids.NodeID, t schema.TypeID, f schema.FieldID, v schema.Value) error {
_, fd, err := a.Descriptor(t, f)
if err != nil {
return err
}
nv, err := Normalize(fd, v)
if err != nil {
return err
}
name := Label(fd)
return a.within(tx, "Set "+name, func(tx *history.Transaction) error {
for _, n := range nodes {
before, err := a.Get(n, t, f)
if err != nil {
return err
}
if Equal(before, nv) {
continue
}
if err := tx.Exec(&commands.SetProperty{Node: n, Type: t, Field: f, Before: before, After: nv, Name: name}); err != nil {
return err
}
}
return nil
})
}
func numeric(k schema.Kind) bool {
switch k {
case schema.KindInt32, schema.KindInt64, schema.KindFloat32, schema.KindFloat64, schema.KindEnum:
return true
}
return false
}
func badKind(fd *schema.FieldDescriptor, v schema.Value) error {
return errs.New(errs.SchemaBadValue, "value kind does not match the field",
log.F("field", fd.Name), log.F("got", v.Kind.String()), log.F("want", fd.Kind.String()))
}
func clampFloat(fd *schema.FieldDescriptor, f float64) float64 {
if fd.HasMin && f < fd.Min {
f = fd.Min
}
if fd.HasMax && f > fd.Max {
f = fd.Max
}
return f
}
func Normalize(fd *schema.FieldDescriptor, v schema.Value) (schema.Value, error) {
if fd.Flags.Has(schema.FlagReadOnly) {
return v, errs.New(errs.SceneReadOnly, "field is read-only", log.F("field", fd.Name))
}
switch fd.Kind {
case schema.KindBool:
if v.Kind != schema.KindBool {
return v, badKind(fd, v)
}
return v, nil
case schema.KindInt32, schema.KindInt64:
if !numeric(v.Kind) {
return v, badKind(fd, v)
}
n := int64(math.Round(clampFloat(fd, v.AsFloat())))
if fd.Kind == schema.KindInt32 {
n = max(min(n, math.MaxInt32), math.MinInt32)
return schema.Int32(int32(n)), nil
}
return schema.Int64(n), nil
case schema.KindFloat32, schema.KindFloat64:
if !numeric(v.Kind) {
return v, badKind(fd, v)
}
f := v.AsFloat()
if math.IsNaN(f) || math.IsInf(f, 0) {
return v, errs.New(errs.SchemaBadValue, "value is not a finite number", log.F("field", fd.Name))
}
f = clampFloat(fd, f)
if fd.Kind == schema.KindFloat32 {
return schema.Float32(float32(f)), nil
}
return schema.Float64(f), nil
case schema.KindEnum:
if !numeric(v.Kind) {
return v, badKind(fd, v)
}
n := v.AsInt()
if len(fd.Enum) > 0 {
if _, ok := fd.EnumNameOf(n); !ok {
return v, errs.New(errs.SchemaBadValue, "value is not a member of the enum", log.F("field", fd.Name))
}
}
return schema.Enum(n), nil
}
if v.Kind != fd.Kind {
return v, badKind(fd, v)
}
return v, nil
}
func Equal(a, b schema.Value) bool {
if a.Kind != b.Kind {
return false
}
switch a.Kind {
case schema.KindBool, schema.KindInt32, schema.KindInt64, schema.KindEnum:
return a.I == b.I
case schema.KindFloat32, schema.KindFloat64:
return a.F == b.F
case schema.KindVec2:
return a.V == b.V
case schema.KindColor:
return a.C == b.C
case schema.KindRect:
return a.R == b.R
case schema.KindString:
return a.S == b.S
case schema.KindAssetRef:
return a.Asset == b.Asset
case schema.KindNodeRef:
return a.Node == b.Node
case schema.KindList:
if len(a.List) != len(b.List) {
return false
}
for i := range a.List {
if !Equal(a.List[i], b.List[i]) {
return false
}
}
return true
case schema.KindStruct:
if a.Obj == nil || b.Obj == nil {
return a.Obj == b.Obj
}
if a.Obj.Type != b.Obj.Type || len(a.Obj.Fields) != len(b.Obj.Fields) {
return false
}
for i := range a.Obj.Fields {
if !Equal(a.Obj.Fields[i], b.Obj.Fields[i]) {
return false
}
}
return true
}
return true
}