Language: yield/signal/coroutine become keywords, start is contextual. Coroutine methods (func Name() coroutine) with start and yield / wait / frames / fixed / await; signals with Emit/Connect and Connection handles; ScriptRef<T> and node.GetScript<T>() for fields, plain methods and signals of other scripts. Diagnostics AES2050-AES2059. Bytecode v3: ScriptRefs, XFields, XMethods and Connects tables resolved by name in vm.Link, per-script signals, coroutine flag on functions; new opcodes START/YIELD/EMIT/CONNECT/DISCONNECT/CONNECTED/COSTOP/CORUN/ SELFREF/GETSCRIPT/GETXF/SETXF/CALLR, checked by verify. Runtime: script/coroutine scheduler (frames/fixed counters, time heap, await lists; only due coroutines are visited) and script/signal registry (snapshot per Emit, deferred removal, no allocations in steady state). Hot reload cancels coroutines of changed scripts, rebinds connections by name and signature, then runs Reloaded. Tooling: script/build removed, aego check/test/dump run on the workspace (test builds include _test.aes); langsvc completion, hover, signatures and semantic tokens for the new syntax; editor highlighting, snippets with placeholders, coroutine/signal profiler graphs and stats. Docs (script.md sections 16-19, editor.md, ARCHITECTURE), test fixtures and golden files, platformer enemies wired to the player, scriptreload demo, and run.Config.FrameTime for deterministic headless fixed steps. Modules (6.5): engine/module registry and ComponentBuilder are the one registration path for engine subsystems and native code; render2d, render3d and script/engine are modules over scene/frame, the script component moved to scene/scriptcomp, and scene/components and scene/systems are gone. Editor inspector, templates, picking shapes and extension hooks are driven by descriptors and editor/builtins; sdk gains Component/EditorHook; depcheck checks the module graph and the aego_runtime graph. Also: stop tracking built tool binaries; gl33 activates the scratch unit before binding for texture updates. Verification: typed component method adapters (Method0..2, Proc0..2) with direct field access and no allocations; app.Build as the single construction path for run and the editor; modules attach per scene document; depcheck aego_editor pass; diagnostic codes in one place and every code covered by a test; indexed coroutine heaps with eager cancel; dead component references and nil intrinsics fault with AES6004, host faults keep their code; GC roots of struct fields checked in verify and vm.Link; 255-field limit; AES4010 for script packages shadowing host modules; architecture registry D111-D147. Golden: the harness renders on the thread that owns the GL context, ReadPixels returns rows in memory order (render targets are top-down per D17), and the first rendering references are committed.
223 lines
4.8 KiB
Go
223 lines
4.8 KiB
Go
package reload
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"sort"
|
|
|
|
"aego/script/abi"
|
|
"aego/script/bytecode"
|
|
"aego/script/diag"
|
|
"aego/script/source"
|
|
)
|
|
|
|
type Conversion uint8
|
|
|
|
const (
|
|
Same Conversion = iota
|
|
Default
|
|
EnumByName
|
|
Struct
|
|
Slice
|
|
Reresolve
|
|
)
|
|
|
|
type FieldMap struct {
|
|
Name string
|
|
OldSlot int
|
|
NewSlot int
|
|
OldSlots int
|
|
NewSlots int
|
|
Old abi.TypeRef
|
|
New abi.TypeRef
|
|
Conv Conversion
|
|
}
|
|
|
|
type TypeChange struct {
|
|
Name string
|
|
Old *bytecode.ScriptDef
|
|
New *bytecode.ScriptDef
|
|
Fields []FieldMap
|
|
Reset []string
|
|
Removed []string
|
|
Changed bool
|
|
}
|
|
|
|
type Plan struct {
|
|
From Program
|
|
To Program
|
|
Types []TypeChange
|
|
Diags []diag.Diagnostic
|
|
from *layout
|
|
to *layout
|
|
byName map[string]*TypeChange
|
|
}
|
|
|
|
func (p *Plan) warn(format string, args ...any) {
|
|
p.Diags = append(p.Diags, diag.Warnf("", source.Span{}, format, args...))
|
|
}
|
|
|
|
func (p *Plan) changed(name string) bool {
|
|
tc := p.byName[name]
|
|
return tc == nil || tc.Changed
|
|
}
|
|
|
|
func sameLayout(a, b abi.TypeRef, from, to *layout) bool {
|
|
if !a.Equal(b) {
|
|
return false
|
|
}
|
|
switch a.Kind {
|
|
case abi.Struct:
|
|
os, ns := from.structs[a.Name], to.structs[b.Name]
|
|
if os == nil || ns == nil {
|
|
return os == nil && ns == nil
|
|
}
|
|
if os.Slots != ns.Slots || len(os.Fields) != len(ns.Fields) {
|
|
return false
|
|
}
|
|
for i := range os.Fields {
|
|
of, nf := os.Fields[i], ns.Fields[i]
|
|
if of.Name != nf.Name || of.Slot != nf.Slot || of.Slots != nf.Slots {
|
|
return false
|
|
}
|
|
ot, ok1 := abi.Parse(of.Type)
|
|
nt, ok2 := abi.Parse(nf.Type)
|
|
if !ok1 || !ok2 || !sameLayout(ot, nt, from, to) {
|
|
return false
|
|
}
|
|
}
|
|
case abi.Slice:
|
|
return a.Elem != nil && b.Elem != nil && sameLayout(*a.Elem, *b.Elem, from, to)
|
|
case abi.Enum:
|
|
return slices.Equal(from.enums[a.Name], to.enums[b.Name])
|
|
}
|
|
return true
|
|
}
|
|
|
|
func classify(a, b abi.TypeRef, from, to *layout) Conversion {
|
|
switch {
|
|
case sameLayout(a, b, from, to):
|
|
if a.Kind == abi.ComponentRef {
|
|
return Reresolve
|
|
}
|
|
return Same
|
|
case a.Kind != b.Kind || a.Name != b.Name:
|
|
return Default
|
|
}
|
|
switch a.Kind {
|
|
case abi.Struct:
|
|
if from.structs[a.Name] != nil && to.structs[b.Name] != nil {
|
|
return Struct
|
|
}
|
|
case abi.Enum:
|
|
if from.enums[a.Name] != nil && to.enums[b.Name] != nil {
|
|
return EnumByName
|
|
}
|
|
case abi.Slice:
|
|
if a.Elem != nil && b.Elem != nil && classify(*a.Elem, *b.Elem, from, to) != Default {
|
|
return Slice
|
|
}
|
|
}
|
|
return Default
|
|
}
|
|
|
|
func findField(od *bytecode.ScriptDef, name string, former []string) *bytecode.FieldDef {
|
|
for _, n := range append([]string{name}, former...) {
|
|
for i := range od.Fields {
|
|
if od.Fields[i].Name == n {
|
|
return &od.Fields[i]
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Build(from, to Program) (*Plan, error) {
|
|
p := &Plan{From: from, To: to, from: newLayout(from), to: newLayout(to), byName: map[string]*TypeChange{}}
|
|
olds := map[string]*bytecode.ScriptDef{}
|
|
oldPkg := map[string]string{}
|
|
for pkg, m := range from.Modules {
|
|
for i := range m.Scripts {
|
|
d := &m.Scripts[i]
|
|
olds[d.FullName] = d
|
|
oldPkg[d.FullName] = pkg
|
|
}
|
|
}
|
|
matched := map[string]bool{}
|
|
for _, nm := range to.Ordered() {
|
|
pkg := nm.Package
|
|
for i := range nm.Scripts {
|
|
nd := &nm.Scripts[i]
|
|
od := olds[nd.FullName]
|
|
if od == nil {
|
|
for _, f := range nd.Former {
|
|
if od = olds[f]; od != nil {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
tc := TypeChange{Name: nd.FullName, New: nd, Old: od, Changed: true}
|
|
if od != nil {
|
|
matched[od.FullName] = true
|
|
tc.Changed = od.FullName != nd.FullName || from.Hashes[oldPkg[od.FullName]] != to.Hashes[pkg]
|
|
if err := p.fields(&tc); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
p.Types = append(p.Types, tc)
|
|
}
|
|
}
|
|
for i := range p.Types {
|
|
p.byName[p.Types[i].Name] = &p.Types[i]
|
|
}
|
|
names := make([]string, 0, len(olds))
|
|
for n := range olds {
|
|
if !matched[n] {
|
|
names = append(names, n)
|
|
}
|
|
}
|
|
sort.Strings(names)
|
|
for _, n := range names {
|
|
p.warn("script %s was removed, its instances stop", n)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (p *Plan) fields(tc *TypeChange) error {
|
|
od, nd := tc.Old, tc.New
|
|
used := map[string]bool{}
|
|
for _, nf := range nd.Fields {
|
|
of := findField(od, nf.Name, nf.Former)
|
|
if of == nil {
|
|
continue
|
|
}
|
|
used[of.Name] = true
|
|
ot, ok1 := abi.Parse(of.Type)
|
|
nt, ok2 := abi.Parse(nf.Type)
|
|
if !ok1 || !ok2 {
|
|
return fmt.Errorf("script %s field %s has a malformed type", nd.FullName, nf.Name)
|
|
}
|
|
conv := classify(ot, nt, p.from, p.to)
|
|
if conv == Default {
|
|
tc.Reset = append(tc.Reset, nf.Name)
|
|
p.warn("field %s.%s changed from %s to %s, reset to its default", nd.FullName, nf.Name, of.Type, nf.Type)
|
|
}
|
|
tc.Fields = append(tc.Fields, FieldMap{
|
|
Name: nf.Name,
|
|
OldSlot: int(of.Slot),
|
|
NewSlot: int(nf.Slot),
|
|
OldSlots: int(of.Slots),
|
|
NewSlots: int(nf.Slots),
|
|
Old: ot,
|
|
New: nt,
|
|
Conv: conv,
|
|
})
|
|
}
|
|
for _, of := range od.Fields {
|
|
if !used[of.Name] {
|
|
tc.Removed = append(tc.Removed, of.Name)
|
|
}
|
|
}
|
|
return nil
|
|
}
|