ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
ci-platforms / darwin arm64 smoke (push) Skipped
ci / checks (push) Successful in 8m33s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 12m38s
ci / release (push) Successful in 9m25s
make web-bench measures the exported platformer in Chromium and Firefox in lockstep, with exact WebGL counters against a per-mode baseline, time compared with Mann-Whitney and a profile pass that gates allocations in the engine frame to zero, except listed first-decode and growth sites. On the web the frame loop fills the audio output, the stream feeder waits for ring space, per-event allocations are gone and Go allocations per frame drop from 7.2 to 3.8 in Chromium. The pixel ratio is capped at 1.5 by default (display.maxPixelRatio) and Frame Limit works on the web. The game loop stays on the main thread; a Worker is not built (D391).
214 lines
3.9 KiB
Go
214 lines
3.9 KiB
Go
package scene
|
|
|
|
import (
|
|
"aego/core/ids"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"aego/core/strs"
|
|
)
|
|
|
|
func (s *Scene) ID(h NodeHandle) ids.NodeID {
|
|
n, ok := s.nodes.Get(h)
|
|
if !ok {
|
|
return ids.NodeID{}
|
|
}
|
|
return n.id
|
|
}
|
|
|
|
func (s *Scene) Handle(id ids.NodeID) NodeHandle {
|
|
return s.byID[id]
|
|
}
|
|
|
|
func (s *Scene) Name(h NodeHandle) string {
|
|
n, ok := s.nodes.Get(h)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return s.interner.String(n.name)
|
|
}
|
|
|
|
func (s *Scene) NameID(h NodeHandle) strs.StringID {
|
|
n, ok := s.nodes.Get(h)
|
|
if !ok {
|
|
return strs.Empty
|
|
}
|
|
return n.name
|
|
}
|
|
|
|
func (s *Scene) SetName(h NodeHandle, name string) string {
|
|
n, ok := s.node(h)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
if name == "" {
|
|
name = "Node"
|
|
}
|
|
s.removeNameIndex(h, n)
|
|
unique := s.uniqueName(n.parent, name, h)
|
|
n.name = s.interner.Intern(unique)
|
|
s.insertNameIndex(h, n)
|
|
s.touch()
|
|
s.emit(Event{Kind: EvNodeRenamed, Node: h, ID: n.id})
|
|
return unique
|
|
}
|
|
|
|
func (s *Scene) uniqueName(parent NodeHandle, name string, self NodeHandle) string {
|
|
id, ok := s.interner.Lookup(name)
|
|
if ok {
|
|
if existing, taken := s.byName[nameKey{parent: parent, name: id}]; !taken || existing == self {
|
|
return name
|
|
}
|
|
} else {
|
|
return name
|
|
}
|
|
base := name
|
|
if i := strings.LastIndexByte(name, '_'); i > 0 {
|
|
if _, err := strconv.Atoi(name[i+1:]); err == nil {
|
|
base = name[:i]
|
|
}
|
|
}
|
|
for n := 2; n < 1<<20; n++ {
|
|
candidate := base + "_" + strconv.Itoa(n)
|
|
cid, ok := s.interner.Lookup(candidate)
|
|
if !ok {
|
|
return candidate
|
|
}
|
|
if existing, taken := s.byName[nameKey{parent: parent, name: cid}]; !taken || existing == self {
|
|
return candidate
|
|
}
|
|
}
|
|
return base + "_" + ids.NewNodeID().String()[:8]
|
|
}
|
|
|
|
func (s *Scene) insertNameIndex(h NodeHandle, n *nodeData) {
|
|
if n.name == strs.Empty {
|
|
return
|
|
}
|
|
s.byName[nameKey{parent: n.parent, name: n.name}] = h
|
|
}
|
|
|
|
func (s *Scene) removeNameIndex(h NodeHandle, n *nodeData) {
|
|
key := nameKey{parent: n.parent, name: n.name}
|
|
if existing, ok := s.byName[key]; ok && existing == h {
|
|
delete(s.byName, key)
|
|
}
|
|
}
|
|
|
|
func (s *Scene) Parent(h NodeHandle) NodeHandle {
|
|
n, ok := s.nodes.Get(h)
|
|
if !ok {
|
|
return NodeHandle{}
|
|
}
|
|
return n.parent
|
|
}
|
|
|
|
func (s *Scene) FirstChild(h NodeHandle) NodeHandle {
|
|
n, ok := s.nodes.Get(h)
|
|
if !ok {
|
|
return NodeHandle{}
|
|
}
|
|
return n.firstChild
|
|
}
|
|
|
|
func (s *Scene) NextSibling(h NodeHandle) NodeHandle {
|
|
n, ok := s.nodes.Get(h)
|
|
if !ok {
|
|
return NodeHandle{}
|
|
}
|
|
return n.nextSib
|
|
}
|
|
|
|
func (s *Scene) PrevSibling(h NodeHandle) NodeHandle {
|
|
n, ok := s.nodes.Get(h)
|
|
if !ok {
|
|
return NodeHandle{}
|
|
}
|
|
return n.prevSib
|
|
}
|
|
|
|
func (s *Scene) ChildCount(h NodeHandle) int {
|
|
n, ok := s.nodes.Get(h)
|
|
if !ok {
|
|
return 0
|
|
}
|
|
return int(n.childCount)
|
|
}
|
|
|
|
func (s *Scene) Children(h NodeHandle, fn func(NodeHandle) bool) {
|
|
n, ok := s.nodes.Get(h)
|
|
if !ok {
|
|
return
|
|
}
|
|
for c := n.firstChild; !c.IsZero(); {
|
|
child, ok := s.nodes.Get(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
next := child.nextSib
|
|
if !fn(c) {
|
|
return
|
|
}
|
|
c = next
|
|
}
|
|
}
|
|
|
|
func (s *Scene) Child(parent NodeHandle, name string) NodeHandle {
|
|
id, ok := s.interner.Lookup(name)
|
|
if !ok {
|
|
return NodeHandle{}
|
|
}
|
|
return s.byName[nameKey{parent: parent, name: id}]
|
|
}
|
|
|
|
func (s *Scene) Find(from NodeHandle, path string) NodeHandle {
|
|
if path == "" {
|
|
return from
|
|
}
|
|
cur := from
|
|
if path[0] == '/' {
|
|
cur = s.root
|
|
path = path[1:]
|
|
if path == "" {
|
|
return cur
|
|
}
|
|
}
|
|
for path != "" {
|
|
part := path
|
|
if i := strings.IndexByte(path, '/'); i >= 0 {
|
|
part, path = path[:i], path[i+1:]
|
|
} else {
|
|
path = ""
|
|
}
|
|
switch part {
|
|
case "", ".":
|
|
continue
|
|
case "..":
|
|
cur = s.Parent(cur)
|
|
default:
|
|
cur = s.Child(cur, part)
|
|
}
|
|
if cur.IsZero() {
|
|
return NodeHandle{}
|
|
}
|
|
}
|
|
return cur
|
|
}
|
|
|
|
func (s *Scene) Path(h NodeHandle) string {
|
|
if h == s.root {
|
|
return "/"
|
|
}
|
|
var parts []string
|
|
for cur := h; !cur.IsZero() && cur != s.root; {
|
|
parts = append(parts, s.Name(cur))
|
|
cur = s.Parent(cur)
|
|
}
|
|
var sb strings.Builder
|
|
for i := len(parts) - 1; i >= 0; i-- {
|
|
sb.WriteByte('/')
|
|
sb.WriteString(parts[i])
|
|
}
|
|
return sb.String()
|
|
}
|