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.
225 lines
5.1 KiB
Go
225 lines
5.1 KiB
Go
package script
|
|
|
|
import (
|
|
"path"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"aego/asset"
|
|
"aego/asset/pipeline"
|
|
"aego/core/ids"
|
|
"aego/core/log"
|
|
"aego/script/bytecode"
|
|
"aego/script/diag"
|
|
"aego/script/source"
|
|
"aego/script/workspace"
|
|
)
|
|
|
|
const (
|
|
ImporterID = "script"
|
|
Group = "scripts"
|
|
Root = "scripts"
|
|
)
|
|
|
|
type CompileError struct {
|
|
Package string
|
|
Summary string
|
|
Text string
|
|
Diagnostics []diag.Diagnostic
|
|
}
|
|
|
|
func (e *CompileError) Error() string {
|
|
return e.Summary
|
|
}
|
|
|
|
type Problem struct {
|
|
Package string
|
|
Path string
|
|
Line int
|
|
Col int
|
|
Code string
|
|
Error bool
|
|
Message string
|
|
Text string
|
|
}
|
|
|
|
type page struct {
|
|
hash [32]byte
|
|
data []byte
|
|
}
|
|
|
|
type Importer struct {
|
|
ws *workspace.Workspace
|
|
pages map[string]page
|
|
mu sync.Mutex
|
|
problems []Problem
|
|
rev uint64
|
|
}
|
|
|
|
func New(ws *workspace.Workspace) *Importer {
|
|
return &Importer{ws: ws, pages: map[string]page{}}
|
|
}
|
|
|
|
func (*Importer) ID() string { return ImporterID }
|
|
func (*Importer) Version() int { return 1 }
|
|
func (*Importer) GroupVersion() int { return 2 }
|
|
func (*Importer) Extensions() []string { return []string{".aes"} }
|
|
func (*Importer) Defaults() pipeline.Settings { return pipeline.Settings{} }
|
|
|
|
func (*Importer) Import(ctx *pipeline.ImportContext) (*pipeline.Result, error) {
|
|
return &pipeline.Result{Kind: asset.ArtScript, Group: Group, Source: ctx.Source}, nil
|
|
}
|
|
|
|
func (i *Importer) Workspace() *workspace.Workspace {
|
|
return i.ws
|
|
}
|
|
|
|
func (i *Importer) Module() string {
|
|
if i.ws == nil {
|
|
return "game"
|
|
}
|
|
return i.ws.Module()
|
|
}
|
|
|
|
func PackageOf(module, file string) string {
|
|
rel := strings.TrimPrefix(strings.TrimPrefix(path.Dir(file), Root), "/")
|
|
if rel == "" || rel == "." {
|
|
return module
|
|
}
|
|
return module + "/" + rel
|
|
}
|
|
|
|
func (i *Importer) Problems() ([]Problem, uint64) {
|
|
i.mu.Lock()
|
|
defer i.mu.Unlock()
|
|
return i.problems, i.rev
|
|
}
|
|
|
|
func (i *Importer) Report() string {
|
|
ps, _ := i.Problems()
|
|
var b strings.Builder
|
|
for _, p := range ps {
|
|
if p.Error {
|
|
b.WriteString(p.Text)
|
|
}
|
|
}
|
|
return strings.TrimRight(b.String(), "\n")
|
|
}
|
|
|
|
func (i *Importer) publish(ps []Problem) {
|
|
i.mu.Lock()
|
|
i.problems = ps
|
|
i.rev++
|
|
i.mu.Unlock()
|
|
}
|
|
|
|
func problemOf(sm *source.Manager, pkg string, d diag.Diagnostic) Problem {
|
|
line, col := sm.LineCol(d.Primary.File, d.Primary.Start)
|
|
return Problem{
|
|
Package: pkg,
|
|
Path: sm.Path(d.Primary.File),
|
|
Line: line,
|
|
Col: col,
|
|
Code: d.Code,
|
|
Error: d.Severity == diag.Error,
|
|
Message: d.Message,
|
|
Text: diag.Format(sm, d),
|
|
}
|
|
}
|
|
|
|
func (i *Importer) encode(pkg string, g *workspace.Generation) []byte {
|
|
h := g.Hashes[pkg]
|
|
if p, ok := i.pages[pkg]; ok && p.hash == h {
|
|
return p.data
|
|
}
|
|
data := bytecode.Encode(g.Modules[pkg])
|
|
i.pages[pkg] = page{hash: h, data: data}
|
|
return data
|
|
}
|
|
|
|
func (i *Importer) Finalize(ctx *pipeline.GroupContext, members []pipeline.GroupMember) (map[ids.AssetID]*pipeline.Result, []pipeline.Produced, error) {
|
|
results := make(map[ids.AssetID]*pipeline.Result, len(members))
|
|
files := make(map[string][]byte, len(members))
|
|
owners := map[string][]ids.AssetID{}
|
|
for _, m := range members {
|
|
results[m.ID] = &pipeline.Result{Kind: asset.ArtScript}
|
|
if m.Result == nil {
|
|
continue
|
|
}
|
|
files[m.Path] = m.Result.Source
|
|
if i.ws == nil || strings.HasSuffix(m.Path, "_test.aes") {
|
|
continue
|
|
}
|
|
if pkg, ok := i.ws.PackageOf(m.Path); ok {
|
|
owners[pkg] = append(owners[pkg], m.ID)
|
|
}
|
|
}
|
|
if i.ws == nil {
|
|
i.publish(nil)
|
|
return results, nil, nil
|
|
}
|
|
i.ws.Sync(files)
|
|
gen, ds := i.ws.Build()
|
|
sm := gen.Sources
|
|
var problems []Problem
|
|
for _, d := range ds {
|
|
if d.Severity == diag.Info {
|
|
continue
|
|
}
|
|
pkg, _ := i.ws.PackageOf(sm.Path(d.Primary.File))
|
|
problems = append(problems, problemOf(sm, pkg, d))
|
|
}
|
|
failed := make([]string, 0, len(gen.Failed))
|
|
for pkg := range gen.Failed {
|
|
failed = append(failed, pkg)
|
|
}
|
|
sort.Strings(failed)
|
|
for _, pkg := range failed {
|
|
fds := gen.Failed[pkg]
|
|
for _, d := range fds {
|
|
if !d.Primary.Valid() {
|
|
problems = append(problems, problemOf(sm, pkg, d))
|
|
}
|
|
}
|
|
err := failure(sm, pkg, fds)
|
|
for _, id := range owners[pkg] {
|
|
results[id].Error = err
|
|
}
|
|
}
|
|
var pages []pipeline.Produced
|
|
for pkg := range gen.Modules {
|
|
pages = append(pages, pipeline.Produced{Kind: asset.ArtScript, Name: pkg, Data: i.encode(pkg, gen)})
|
|
}
|
|
for _, pkg := range failed {
|
|
if _, ok := gen.Modules[pkg]; ok || ctx.Previous == nil {
|
|
continue
|
|
}
|
|
if data, ok := ctx.Previous(pkg); ok {
|
|
pages = append(pages, pipeline.Produced{Kind: asset.ArtScript, Name: pkg, Data: data})
|
|
ctx.Log.Warn("script package has errors, keeping the last good build", log.F("package", pkg))
|
|
}
|
|
}
|
|
sort.Slice(pages, func(a, b int) bool { return pages[a].Name < pages[b].Name })
|
|
i.publish(problems)
|
|
return results, pages, nil
|
|
}
|
|
|
|
func failure(sm *source.Manager, pkg string, ds []diag.Diagnostic) *CompileError {
|
|
var b strings.Builder
|
|
n := 0
|
|
for _, d := range ds {
|
|
if d.Severity == diag.Error {
|
|
b.WriteString(diag.Format(sm, d))
|
|
n++
|
|
}
|
|
}
|
|
return &CompileError{
|
|
Package: pkg,
|
|
Summary: "package " + pkg + " has " + strconv.Itoa(n) + " error(s)",
|
|
Text: b.String(),
|
|
Diagnostics: ds,
|
|
}
|
|
}
|