Files
aego/modules/savegame/capture.go
T
2026-10-19 00:37:50 +03:00

204 lines
4.8 KiB
Go

package savegame
import (
"errors"
"slices"
"strings"
"time"
"aego/contracts/scriptevents"
"aego/core/ids"
"aego/engine"
"aego/format/aetext"
"aego/save"
"aego/scene"
"aego/schema"
)
func hasContent(n *aetext.Node) bool {
return len(n.Attrs) > 0 || len(n.Fields) > 0 || len(n.Blocks) > 0
}
func (s *Service) collectSaves() {
clear(s.writers)
sink := s.sink()
if sink == nil || s.m.ev.Save == scriptevents.NoEvent {
return
}
l, ok := sink.(scriptevents.Listener)
if !ok {
return
}
sc := s.scene
root := sc.Root()
sent := false
for _, h := range sc.PreOrder() {
if h != root && sc.IsAlive(h) && l.ListensOn(h, s.m.ev.Save) {
sink.Emit(h, s.m.ev.Save, nil)
sent = true
}
}
if sent {
s.capturing = true
sink.Flush()
s.capturing = false
}
}
func (s *Service) content(h scene.NodeHandle, n *aetext.Node, spawned bool) {
sc := s.scene
mark, marked := s.marked(h)
if spawned || (marked && !mark.NoEnabled) {
n.Fields = append(n.Fields, aetext.Field{Name: "enabled", Value: aetext.Bool(sc.Enabled(h))})
}
if spawned || (marked && !mark.NoTransform) {
n.Blocks = append(n.Blocks, transformNode(sc, h))
}
s.components(h, n)
s.scriptFields(h, n)
if w := s.writers[h]; w != nil && !w.Empty() {
n.Blocks = append(n.Blocks, w.Node())
}
}
func (s *Service) components(h scene.NodeHandle, n *aetext.Node) {
sc := s.scene
reg := sc.Registry()
sc.ComponentsOf(h, func(t schema.TypeID) {
td, ok := reg.ByID(t)
if !ok {
return
}
p, ok := sc.GetByType(h, t)
if !ok {
return
}
var c *aetext.Node
for i := range td.Fields {
fd := &td.Fields[i]
if !fd.Flags.Has(schema.FlagPersist) {
continue
}
var v schema.Value
fd.Get(p, &v)
av, ok := s.fieldValue(reg, fd, v)
if !ok {
continue
}
if c == nil {
c = &aetext.Node{Head: "component", Label: td.FullName}
n.Blocks = append(n.Blocks, c)
}
c.Fields = append(c.Fields, aetext.Field{Name: fd.Name, Value: av})
}
})
}
func (s *Service) scriptFields(h scene.NodeHandle, n *aetext.Node) {
ps := s.scripts()
if ps == nil {
return
}
s.vals = ps.Persisted(h, s.vals[:0])
var cur *aetext.Node
for _, sv := range s.vals {
av, ok := s.scriptValue(sv)
if !ok {
continue
}
if cur == nil || cur.Label != sv.Script {
cur = &aetext.Node{Head: "script", Label: sv.Script}
n.Blocks = append(n.Blocks, cur)
}
cur.Fields = append(cur.Fields, aetext.Field{Name: sv.Field, Value: av})
}
}
func (s *Service) children(root, h scene.NodeHandle, n *aetext.Node) {
sc := s.scene
for c := sc.FirstChild(h); !c.IsZero(); c = sc.NextSibling(c) {
if !sc.IsAlive(c) {
continue
}
child := &aetext.Node{Head: "node", Label: s.relPath(root, c)}
s.content(c, child, false)
if hasContent(child) {
n.Blocks = append(n.Blocks, child)
}
s.children(root, c, n)
}
}
func (s *Service) Capture(slot string) (*save.Doc, error) {
sc := s.scene
s.collectSaves()
defer clear(s.writers)
doc := &save.Doc{}
root := sc.Root()
for _, h := range sc.PreOrder() {
if h == root || !sc.IsAlive(h) {
continue
}
id := sc.ID(h)
if !s.base[id] {
continue
}
if _, _, under := s.spawnRoot(h); under {
continue
}
n := &aetext.Node{Head: "node", Label: id.String()}
s.content(h, n, false)
if hasContent(n) {
doc.Nodes = append(doc.Nodes, n)
}
}
for id := range s.base {
if h := sc.Handle(id); h.IsZero() || !sc.IsAlive(h) {
doc.Destroyed = append(doc.Destroyed, id)
}
}
slices.SortFunc(doc.Destroyed, func(a, b ids.NodeID) int { return strings.Compare(a.String(), b.String()) })
if sp := s.spawner(); sp != nil {
sp.EachSpawned(func(h scene.NodeHandle, prefab ids.AssetID, pid ids.NodeID) {
n := &aetext.Node{Head: "spawned", Label: pid.String()}
n.Fields = append(n.Fields, aetext.Field{Name: "prefab", Value: aetext.AssetRef(prefab.String())})
if p := sc.Parent(h); p != root {
if v, ok := s.RefValue(p); ok {
n.Fields = append(n.Fields, aetext.Field{Name: "parent", Value: v})
}
}
s.content(h, n, true)
s.children(h, h, n)
doc.Spawned = append(doc.Spawned, n)
})
doc.Meta.Counter = sp.Counter()
}
doc.Global = cloneFields(s.m.global.Fields)
var errsList []error
known := map[string]bool{}
for _, o := range s.m.sections {
known[o.ID] = true
w := save.NewWriter(&aetext.Node{}, s)
if err := o.Section.Save(s.world, w); err != nil {
errsList = append(errsList, err)
continue
}
doc.Sections = append(doc.Sections, save.Section{ID: o.ID, Version: o.Section.Version(), Node: w.Node()})
}
for _, u := range s.m.unknown {
if !known[u.ID] {
doc.Sections = append(doc.Sections, u)
}
}
doc.Meta.Name = slot
doc.Meta.Game = s.m.game
doc.Meta.Engine = engine.Version
if h := s.host(); h != nil {
doc.Meta.Scene = h.SceneID()
}
doc.Meta.Date = time.Now()
doc.Meta.Playtime = s.m.playtime
doc.Meta.Objects = len(doc.Nodes) + len(doc.Spawned)
return doc, errors.Join(errsList...)
}