Files
aego/script/langsvc/async.go
T
sanspie 69d19e501f Stage 7: documentation, help, playground; templates, aego new/bootstrap/icons, launcher
Documentation: engine/docs assembles one registry from the script
bindings, the schema, module docs and module-owned script paths; problems
are reported as warnings at build time. Module registries record doc
sources and script module owners; diag.Codes lists every diagnostic code.

Project bootstrap: project.defaultSpace ("2d"/"3d"), EnsureDirs creates
the default folders when the editor opens a project (reported in the
console), prefabs/ is a pipeline root. Native builds drop -trimpath so
binding locations resolve inside native/.

Editor files: create scene/prefab/script from menus, move/rename/duplicate
/delete with undo, script package retargeting on move, open scenes and
script panels follow renames, Save As / Rename Scene prompts, scene
picker, startup scene. Scoped shortcuts fall back to the global command
when disabled; Save As is Ctrl+Shift+S, Save All Ctrl+Alt+S.

Script editor: Go to Implementation (stub → native/ source), host
definitions always resolve to stubs, bracket match/mismatch states,
rainbow brackets (persisted), mono code font (ui-mono atlases, per-command
fonts in draw.List, PushFont/PopFont restored by Mark/Restore).

Tests: langsvc definition/implementation, bracket states, dispatch
fallback, script renames, font stack, editor file-op integration, golden
mono glyph and bracket references; depcheck rule for engine/docs.

Docs pipeline: engine, render2d and script/engine ship their own res/docs;
keyword hover text comes from the registry through manifest.Keywords;
project script symbols join the registry; aego docs export|check|build|
serve, --tool docs in native binaries, aego check --explain; app/docstool
and the tools/gendocs module (goldmark) build the static site.

Playground: app/playground + cmd/aego-playground (js/wasm, 3.3 MB gzip)
compile, highlight and run scripts in the browser; run blocks in the
language docs become examples. engine/builtin/headless is the module set
without render3d; workspace.Config.FS; StubFor/StubPaths; backends and the
script importer drop out of js builds.

VM: one frame pointer in the interpreter loop, direct host calls, panic
recovery once per run instead of per host call (Update2k -19%,
Schedule10k -24% against the previous head). bench-build/compare/profile
run pre-built, pinned, interleaved binaries.

Editor: structural bracket levels and current-block highlight, indent
guides, occurrence highlighting with write marks, multi-format clipboard
(copy/cut/paste assets, paste images as sprites or textures). gl33 probes
a renderable depth format and reports framebuffer failures by stage.

7 E:
- project templates empty/2d/2.5d/3d, app/projects as the single creation path (D148)
- launcher and recent projects (D149)
- orthographic Camera3D and Scene View (D150)
- reference states in the Inspector, ids.Short (D151)
- sceneio remaps node references when loading without KeepIDs (D152)
- exported ComponentRef<T> (D153)
- aego icons via module.Audit (D146)
2026-10-13 01:08:56 +03:00

229 lines
6.4 KiB
Go

package langsvc
import (
"strings"
"aego/script/abi"
"aego/script/ast"
"aego/script/checker"
"aego/script/resolver"
"aego/script/types"
"aego/script/workspace"
)
func paramsText(sg *checker.ScriptSignal) string {
parts := make([]string, len(sg.Params))
for i, p := range sg.Params {
parts[i] = sg.Names[i] + " " + typeString(p)
}
return "(" + strings.Join(parts, ", ") + ")"
}
func signalDecl(sg *checker.ScriptSignal) string {
return "signal " + sg.Name + paramsText(sg)
}
func lastElem(pkg string) string {
return pkg[strings.LastIndexByte(pkg, '/')+1:]
}
func shortScript(full string) string {
return full[strings.LastIndexByte(full, '.')+1:]
}
func signalOf(a *checker.Analysis, x ast.Expr) *checker.ScriptSignal {
switch e := x.(type) {
case *ast.Ident:
if o := object(a, e); o != nil && o.Kind == resolver.ObjSignal {
sg, _ := o.Data.(*checker.ScriptSignal)
return sg
}
case *ast.SelectorExpr:
if sel := a.Selections[e]; sel != nil && sel.Kind == checker.SelSignal {
return sel.Signal
}
}
return nil
}
func scriptAt(a *checker.Analysis, sd *ast.ScriptDecl) *checker.ScriptType {
if sd == nil {
return nil
}
for _, st := range a.Scripts {
if st.Decl == sd {
return st
}
}
return nil
}
func sameParams(f *checker.Func, sg *checker.ScriptSignal) bool {
if len(f.Params) != len(sg.Params) {
return false
}
for i, p := range f.Params {
if p.Type != sg.Params[i] {
return false
}
}
return true
}
func handlerCandidate(st *checker.ScriptType, f *checker.Func) bool {
return !f.Decl.Coroutine && !st.IsLifecycle(f) && f.Result == types.Void
}
func startItems(c *collector, snap *workspace.Snapshot, f *ast.File, off uint32) {
_, sd := enclosing(f, off)
st := scriptAt(snap.Analysis, sd)
if st == nil {
return
}
for _, fn := range st.Funcs {
if fn.Decl.Coroutine {
c.add(Item{Label: fn.Name, Detail: funcSig(fn), Doc: fn.Decl.Doc, Kind: ItemMethod, Insert: fn.Name + "("})
}
}
}
func yieldItems(c *collector, snap *workspace.Snapshot) {
for _, y := range []Item{
{Label: "wait", Detail: "yield wait(seconds float)", Insert: "wait(${1:seconds})"},
{Label: "frames", Detail: "yield frames(n int)", Insert: "frames(${1:n})"},
{Label: "fixed", Detail: "yield fixed(n int)", Insert: "fixed(${1:n})"},
} {
y.Kind = ItemSnippet
y.Doc = builtinText(snap, "yield "+y.Label)
c.add(y)
}
}
func signalMembers(c *collector, snap *workspace.Snapshot, sg *checker.ScriptSignal) {
c.add(Item{Label: "Emit", Detail: "Emit" + paramsText(sg), Doc: builtinText(snap, "Emit"), Kind: ItemMethod, Insert: "Emit("})
c.add(Item{Label: "Connect", Detail: "Connect(handler func" + paramsText(sg) + ") Connection", Doc: builtinText(snap, "Connect"), Kind: ItemMethod, Insert: "Connect("})
}
func scriptRefMembers(c *collector, snap *workspace.Snapshot, t *types.Type, pkg string) {
st := snap.Analysis.ScriptByName(t.Script)
if st == nil {
return
}
visible := func(name string) bool { return st.Pkg == pkg || abi.Exported(name) }
for _, f := range st.Fields {
if visible(f.Name) {
c.add(Item{Label: f.Name, Detail: scriptField(f), Doc: f.Decl.Doc, Kind: ItemField})
}
}
for _, sg := range st.Signals {
if visible(sg.Name) {
c.add(Item{Label: sg.Name, Detail: signalDecl(sg), Doc: sg.Decl.Doc, Kind: ItemSignal})
}
}
for _, fn := range st.Funcs {
if visible(fn.Name) && !st.IsLifecycle(fn) && !fn.Decl.Coroutine {
c.add(Item{Label: fn.Name, Detail: funcSig(fn), Doc: fn.Decl.Doc, Kind: ItemMethod, Insert: fn.Name + "("})
}
}
}
func handlerItems(c *collector, snap *workspace.Snapshot, f *ast.File, src []byte, dot, off uint32) {
end := dot
for end > 0 && (src[end-1] == ' ' || src[end-1] == '\t') {
end--
}
a := snap.Analysis
x := exprEndingAt(f, end)
if x == nil {
return
}
sg := signalOf(a, x)
_, sd := enclosing(f, off)
st := scriptAt(a, sd)
if sg == nil || st == nil {
return
}
for _, fn := range st.Funcs {
if !handlerCandidate(st, fn) {
continue
}
it := Item{Label: fn.Name, Detail: funcSig(fn), Doc: fn.Decl.Doc, Kind: ItemMethod}
c.rank = rankLocal
if !sameParams(fn, sg) {
c.rank = rankScript
it.Detail += " · does not match " + signalDecl(sg)
}
c.add(it)
}
}
func signalNamed(a *checker.Analysis, pkg, name string) *checker.ScriptSignal {
var found *checker.ScriptSignal
for _, st := range a.Scripts {
sg := st.Signal(name)
if sg == nil {
continue
}
if st.Pkg == pkg {
return sg
}
if found == nil {
found = sg
}
}
return found
}
func scriptMethodNamed(a *checker.Analysis, pkg, name string) *checker.Func {
var found *checker.Func
for _, st := range a.Scripts {
f := st.Methods[name]
if f == nil || st.IsLifecycle(f) || f.Decl.Coroutine {
continue
}
if st.Pkg == pkg {
return f
}
if found == nil {
found = f
}
}
return found
}
func signalSignature(snap *workspace.Snapshot, sg *checker.ScriptSignal, method string) Signature {
var sb sigBuilder
sb.b.WriteString("func (signal " + sg.Name + ") " + method + "(")
if method == "Emit" {
for i, p := range sg.Params {
sb.param(sg.Names[i] + " " + typeString(p))
}
sb.b.WriteByte(')')
} else {
sb.param("handler func" + paramsText(sg))
sb.b.WriteString(") Connection")
}
return Signature{Label: sb.b.String(), Params: sb.params, Doc: builtinText(snap, method)}
}
func asyncCallHover(snap *workspace.Snapshot, k *checker.Call) (Hover, bool) {
switch k.Kind {
case checker.CallEmit:
return Hover{Signature: "func (signal " + k.Signal.Name + ") Emit" + paramsText(k.Signal), Doc: builtinText(snap, "Emit")}, true
case checker.CallConnect:
return Hover{Signature: "func (signal " + k.Signal.Name + ") Connect(handler func" + paramsText(k.Signal) + ") Connection", Doc: builtinText(snap, "Connect")}, true
case checker.CallDisconnect:
return Hover{Signature: "func (Connection) Disconnect()", Doc: builtinText(snap, "Disconnect")}, true
case checker.CallConnected:
return Hover{Signature: "func (Connection) Connected() bool", Doc: builtinText(snap, "Connected")}, true
case checker.CallCoStop:
return Hover{Signature: "func (Coroutine) Stop()", Doc: builtinText(snap, "Stop")}, true
case checker.CallCoRunning:
return Hover{Signature: "func (Coroutine) Running() bool", Doc: builtinText(snap, "Running")}, true
case checker.CallGetScript:
s := shortScript(k.Target)
return Hover{Signature: "func (NodeRef) GetScript<" + s + ">() ScriptRef<" + s + ">", Doc: builtinText(snap, "GetScript")}, true
}
return Hover{}, false
}