13 Commits
Author SHA1 Message Date
sanspie cffa66514b Stage 9.6c2: prefab apply and data editing without the editor (D298)
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / checks (push) Successful in 21m2s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 22m8s
2026-10-20 13:05:10 +03:00
sanspie 2744cff481 Stage 9.6c1: project refs and refactorings without the editor (D298)
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 23m31s
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci / checks (push) Successful in 21m57s
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
2026-10-20 12:12:18 +03:00
sanspie 91052a54db make lint: staticcheck the editor build
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / checks (push) Successful in 16m26s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 17m48s
2026-10-20 12:06:56 +03:00
sanspie 7f4d87ef2c Stage 9: gameplay toolkit, export and web, agent tooling 2026-10-20 12:06:56 +03:00
sanspie 6cc0673082 CI on master, docs auto-deploy and repo link, macOS build fixes
ci / checks (push) Successful in 13m36s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 14m40s
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
2026-10-16 12:10:43 +03:00
sanspie ab9b003457 Stage 8: physics and module boundaries 2026-10-15 19:16:09 +03:00
sanspie 41d949717d deploy: push-to-deploy docs site and playground
make site-headless builds the CLI without cgo (aego_nonative) and the
static site with the wasm playground. deploy/ holds the image, the
container nginx config (gzip_static with Vary), compose and deploy.sh
run by the post-receive hook on mainserv.

.gitignore ignores only the root site/ output; tools/gendocs/site was
never committed because of it.

Docs site: index and section summaries render code, bold and italic
instead of raw markdown; search.json gets the plain text. Page summaries
drop a leading list or quote marker. The nav toggle is three CSS bars
centred in a 32px button.

Playground: a loading overlay in the editor pane shows a spinner, a
progress bar and the downloaded size while aego.wasm streams in, then
'Starting engine' until the runtime is ready; a failed start keeps the
error on the overlay. docstool writes the wasm size and a version hash
into index.html; play.js, play.css, wasm_exec.js and aego.wasm are
referenced with ?v=<hash> so a deploy never mixes cached old files with
new ones.

Instruction budget: default 1M per callback (was 100k), playground 2M;
the donut needs ~120k per frame. Playground: software 3D donut example,
multi-line log entries render as monospace blocks, a fault is reported
once instead of also as a "script stopped" log line.

The compose project is named aego explicitly; the default name came from
the deploy/ directory and collided with other stacks on the host.
2026-10-14 22:40:20 +03:00
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
sanspie cf68c71f05 Stage 6: coroutines, signals, ScriptRef, workspace CLI, editor tooling
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.
2026-10-12 14:22:08 +03:00
sanspie 08f0b15e1e docs: update architecture spec and script reference 2026-10-08 13:31:21 +03:00
sanspie 17cb5e4291 Stage 2-5: assets, scene/ECS, editor, 3D, and Aego Script
Stage 2: render pipeline and UI toolkit over gpu/platform.
Stage 3: schema registry, asset pipeline + runtime loader, scene graph and ECS.
Stage 4: editor — docking, inspector, scene/game views, play mode, asset browser, console, profiler; 4.5 adds the 3D foundation (mixed 2D/3D spaces, meshes, depth).
Stage 5: Aego Script — compiler (lexer/parser/types/resolver/checker/asm/compiler), bytecode with verifier and register VM, instance runtime, stdlib, native bindings, and editor/CLI integration (aego run/edit/check/test/dump).
2026-10-08 13:17:28 +03:00
sanspie eb9ac1e589 Initial commit 2026-09-26 16:32:40 +03:00
sanspie dae4bc19e1 Stage 1: engine core, platform, gpu, vfs
Foundation for the aego engine.

core/    errs, log, handle arena, mathx, clock, stats
platform SDL3 backend plus a headless one, input state, events
gpu      device abstraction with gl33 and null backends
vfs      scheme-based virtual filesystem with overlay mounts
engine   builder, module registry, phase-ordered system loop, jobs
demos    basic, resize, input, headless; sandbox driver

OpenGL bindings are generated into gpu/gl33/gl by tools/gen-gl.sh,
so the module has no external dependencies.
2026-09-18 20:51:54 +03:00
247 changed files with 595 additions and 17871 deletions
-1
View File
@@ -5,4 +5,3 @@ site
exports exports
**/.aego **/.aego
*.test *.test
.claude
+1 -37
View File
@@ -3,7 +3,6 @@ name: ci
on: on:
push: push:
branches: [master] branches: [master]
tags: ["v*"]
pull_request: pull_request:
permissions: permissions:
@@ -112,39 +111,4 @@ jobs:
- name: example project - name: example project
run: | run: |
/tmp/aego check examples/native-rotator /tmp/aego check examples/native-rotator
/tmp/aego run examples/native-rotator --headless --frames 60 /tmp/aego run examples/native-rotator --headless --frames 60
release:
needs: [checks, build]
if: github.event_name == 'push'
runs-on: ubuntu-24.04
permissions:
contents: write
env:
CGO_ENABLED: "1"
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITEA_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: linux packages
run: sudo apt-get update && sudo apt-get install -y $LINUX_PKGS
- uses: actions/cache@v4
continue-on-error: true
with:
path: .cache/native
key: native-linux_amd64-${{ hashFiles('third_party/sdl/version.txt', 'tools/bootstrap-native.sh') }}
- run: bash tools/bootstrap-native.sh
- name: windows tools
run: sudo apt-get install -y mingw-w64 nsis zip
- name: publish
env:
REQUIRE_WINDOWS: "1"
GITEA_RESOLVE: git.akarpov.ru:443:79.142.92.250
run: |
case "$GITHUB_REF" in
refs/tags/v*) sh tools/release/release.sh "$GITHUB_REF_NAME" ;;
refs/heads/master) sh tools/release/release.sh nightly ;;
esac
+3 -99
View File
File diff suppressed because one or more lines are too long
+3 -25
View File
@@ -38,7 +38,7 @@ BENCH_PHYSICS_PKGS :=
.DEFAULT_GOAL := bootstrap .DEFAULT_GOAL := bootstrap
-include modules/*/module.mk -include modules/*/module.mk
.PHONY: bootstrap build test test-nonative test-editor test-runtime test-gl vet lint depcheck smoke run genfont genicons icons icons-doc res genpreviews golden golden-update site-headless bench bench-baseline bench-scripts bench-build bench-compare bench-profile bench-scripts-run bench-allocs playground playground-size site site-check cli example example-platformer check-all ci clean fuzz runtime export-smoke editor-app shaders test-wasm web-smoke web-smoke-update try-size shots shots-check .PHONY: bootstrap build test test-nonative test-editor test-runtime test-gl vet lint depcheck smoke run genfont genicons icons icons-doc res genpreviews golden golden-update site-headless bench bench-baseline bench-scripts bench-build bench-compare bench-profile bench-scripts-run bench-allocs playground playground-size site site-check cli example example-platformer check-all ci clean fuzz runtime export-smoke editor-app shaders test-wasm web-smoke web-smoke-update try-size
XVFB := $(shell command -v xvfb-run 2>/dev/null) XVFB := $(shell command -v xvfb-run 2>/dev/null)
GL_OVERRIDE = MESA_GL_VERSION_OVERRIDE=3.3 MESA_GLSL_VERSION_OVERRIDE=330 GL_OVERRIDE = MESA_GL_VERSION_OVERRIDE=3.3 MESA_GLSL_VERSION_OVERRIDE=330
@@ -212,7 +212,7 @@ site: cli
@$(TRY_SIZE_CHECK) @$(TRY_SIZE_CHECK)
playground: playground:
@if GOOS=js GOARCH=wasm go list -deps ./cmd/aego-playground | grep -E '^aego/(platform/sdl|gpu/gl33|asset/pipeline|asset/importers)'; then echo "playground graph pulls a native or pipeline package"; exit 1; fi @if GOOS=js GOARCH=wasm go list -deps ./cmd/aego-playground | grep -E '^aego/(platform/sdl|gpu/gl33|asset/pipeline|asset/importers|modules/render3d)'; then echo "playground graph pulls a native or pipeline package"; exit 1; fi
$(MAKE) site $(MAKE) site
site-headless: site-headless:
@@ -282,29 +282,7 @@ fuzz:
go test -run '^$$' -fuzz '^FuzzDecodeVerify$$' -fuzztime $(FUZZTIME) ./script/verify go test -run '^$$' -fuzz '^FuzzDecodeVerify$$' -fuzztime $(FUZZTIME) ./script/verify
go test -run '^$$' -fuzz '^FuzzDecodeVerifyV3$$' -fuzztime $(FUZZTIME) ./script/verify go test -run '^$$' -fuzz '^FuzzDecodeVerifyV3$$' -fuzztime $(FUZZTIME) ./script/verify
ci: build vet lint depcheck test test-nonative test-editor test-runtime test-wasm runtime smoke fuzz golden shots-check test-gl cli playground web-smoke site-check example example-platformer check-all export-smoke $(MODULE_CI) bench-allocs ci: build vet lint depcheck test test-nonative test-editor test-runtime test-wasm runtime smoke fuzz golden test-gl cli playground web-smoke site-check example example-platformer check-all export-smoke $(MODULE_CI) bench-allocs
# Documentation screenshots of the real editor on the platformer example, under a clean user profile.
SHOTS_TMP = $(CURDIR)/.cache/shots
ifneq ($(XVFB),)
SHOTS_ENV = $(XVFB) -a -s "-screen 0 1600x900x24" env SDL_VIDEODRIVER=x11 LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe $(GL_OVERRIDE)
else
SHOTS_ENV = $(GL_ENV)
endif
SHOTS_RUN = rm -rf $(SHOTS_TMP)/project $(SHOTS_TMP)/home && mkdir -p $(SHOTS_TMP)/home && cp -R examples/platformer $(SHOTS_TMP)/project && \
rm -rf $(SHOTS_TMP)/project/.aego $(SHOTS_TMP)/project/exports && \
sed -i.bak 's|=> ../..|=> $(CURDIR)|' $(SHOTS_TMP)/project/go.mod && \
XDG_CONFIG_HOME=$(SHOTS_TMP)/home XDG_DATA_HOME=$(SHOTS_TMP)/home $(SHOTS_ENV) env AEGO_SHOTS=$(1) .cache/bin/aego edit $(SHOTS_TMP)/project --hidden
shots: cli
rm -rf docs/shots
$(call SHOTS_RUN,$(CURDIR)/docs/shots)
shots-check: cli
rm -rf $(SHOTS_TMP)/out
$(call SHOTS_RUN,$(SHOTS_TMP)/out)
go run ./tools/shotdiff -want docs/shots -got $(SHOTS_TMP)/out
runtime: bootstrap runtime: bootstrap
go build -tags aego_runtime -o .cache/bin/aego-runtime ./cmd/aego-runtime go build -tags aego_runtime -o .cache/bin/aego-runtime ./cmd/aego-runtime
-146
View File
File diff suppressed because it is too large Load Diff
-85
View File
@@ -1,85 +0,0 @@
package agent
import (
"bytes"
"encoding/json"
"testing"
)
func mustFind(t *testing.T, name string) *Command {
t.Helper()
c, ok := Find(name)
if !ok {
t.Fatalf("no command %s", name)
}
return c
}
func TestParseArgs(t *testing.T) {
set := mustFind(t, "scene.set")
a, f, err := parseArgs(set, []string{"s.aescene", "--json", "Player", "--dry-run", "Body2D.mass", "-3"})
if err != nil || !f.json || a.Str("value") != "-3" || a.Str("node") != "Player" || !a.Bool("dry-run") {
t.Fatalf("flags after positionals: %v %+v %v", a, f, err)
}
add := mustFind(t, "scene.add-node")
for _, toks := range [][]string{{"s", "N", "--index=1"}, {"s", "N", "--index", "1"}} {
a, _, err := parseArgs(add, toks)
if n, _ := a.Int("index", -1); err != nil || n != 1 {
t.Fatalf("%v: %v %v", toks, a, err)
}
}
a, _, err = parseArgs(set, []string{"s", "--", "Player", "Body2D.mass", "--odd"})
if err != nil || a.Str("value") != "--odd" {
t.Fatalf("after --: %v %v", a, err)
}
if _, _, err := parseArgs(set, []string{"s", "--nope"}); err == nil {
t.Fatal("an unknown flag was accepted")
}
if _, _, err := parseArgs(set, []string{"s", "a", "b", "c", "d"}); err == nil {
t.Fatal("an extra positional was accepted")
}
var out, errw bytes.Buffer
if code := Main(nil, []string{"scene", "set", "s", "--nope"}, &out, &errw, Options{}); code != 2 {
t.Fatalf("unknown flag exit %d", code)
}
if code := Main(nil, []string{"scene", "set", "a", "b", "c", "d", "e"}, &out, &errw, Options{}); code != 2 {
t.Fatalf("extra positional exit %d", code)
}
if code := Main(nil, []string{"scene", "help"}, &out, &errw, Options{}); code != 0 {
t.Fatalf("help exit %d", code)
}
}
func TestSchemasAndToolNames(t *testing.T) {
seen := map[string]bool{}
for _, c := range Commands() {
if seen[c.Tool()] {
t.Fatalf("tool name %s is used twice", c.Tool())
}
seen[c.Tool()] = true
b, err := json.Marshal(c.Schema())
if err != nil {
t.Fatal(err)
}
var sch struct {
Type string `json:"type"`
Properties map[string]map[string]any `json:"properties"`
Required []string `json:"required"`
}
if err := json.Unmarshal(b, &sch); err != nil || sch.Type != "object" {
t.Fatalf("%s: %s", c.Name, b)
}
var want []string
for _, x := range c.AllArgs() {
if x.Required {
want = append(want, x.Name)
}
if _, ok := sch.Properties[x.Name]; !ok {
t.Fatalf("%s: no property %s", c.Name, x.Name)
}
}
if len(want) != len(sch.Required) {
t.Fatalf("%s: required %v, want %v", c.Name, sch.Required, want)
}
}
}
File diff suppressed because it is too large Load Diff
-194
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-180
View File
File diff suppressed because it is too large Load Diff
-66
View File
@@ -1,66 +0,0 @@
package agent
import (
"strconv"
"aego/asset"
"aego/project/refs"
"aego/scene/mutate"
)
func projectCommands() []*Command {
return []*Command{
{Name: "project.validate", Doc: "missing assets and nodes, import errors, prefabs and scenes that do not load", Run: (*Session).projectValidate},
}
}
func (s *Session) projectValidate(Args) (*Output, error) {
out := &Output{}
add := func(err bool, node, msg string) {
out.Problems = append(out.Problems, mutate.Problem{Error: err, Node: node, Message: msg})
}
for _, m := range refs.MissingAssets(s.Project, s.Pipeline) {
add(true, "", "missing asset "+m.Label())
}
for _, m := range refs.MissingNodes(s.Pipeline) {
add(true, m.Scene+" "+m.Path, m.Owner+"."+m.Field+" references a node that is not in the scene")
}
if s.scriptErr != nil {
add(true, "", "script types: "+s.scriptErr.Error())
}
for _, e := range s.libErrs {
add(true, "", e.Error())
}
for _, x := range s.Pipeline.Assets() {
if x.Error != "" {
add(true, x.Path, x.Error)
continue
}
if x.Kind != asset.ArtScene && x.Kind != asset.ArtPrefab {
continue
}
src, err := s.read(x.Path)
if err != nil {
add(true, x.Path, err.Error())
continue
}
doc, err := mutate.Load(src, x.Path, s.env())
if err != nil {
add(true, x.Path, err.Error())
continue
}
for _, p := range doc.Problems {
add(p.Error, x.Path+" "+p.Node, p.Message)
}
}
errs, warns := 0, 0
for _, p := range out.Problems {
if p.Error {
errs++
} else {
warns++
}
}
out.Notes = []string{"project: " + strconv.Itoa(errs) + " error(s), " + strconv.Itoa(warns) + " warning(s)"}
return out, nil
}
-237
View File
File diff suppressed because it is too large Load Diff
-393
View File
File diff suppressed because it is too large Load Diff
-166
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -6
View File
@@ -6,7 +6,6 @@ import (
"aego/engine/module" "aego/engine/module"
"aego/modules/accessibility" "aego/modules/accessibility"
"aego/modules/audio" "aego/modules/audio"
"aego/modules/camera"
"aego/modules/dataasset" "aego/modules/dataasset"
"aego/modules/debug" "aego/modules/debug"
"aego/modules/display" "aego/modules/display"
@@ -72,10 +71,6 @@ func tweenEntry() assembly.Entry {
return assembly.Entry{ID: tween.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return tween.New() }} return assembly.Entry{ID: tween.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return tween.New() }}
} }
func cameraEntry() assembly.Entry {
return assembly.Entry{ID: camera.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return camera.New() }}
}
func dataEntry() assembly.Entry { func dataEntry() assembly.Entry {
return assembly.Entry{ID: dataasset.ID, New: func(assembly.Options) module.Module { return dataasset.New() }} return assembly.Entry{ID: dataasset.ID, New: func(assembly.Options) module.Module { return dataasset.New() }}
} }
@@ -90,5 +85,5 @@ func savegameEntry() assembly.Entry {
func Default() assembly.Catalog { func Default() assembly.Catalog {
return assembly.Catalog{core(), dataEntry(), inputEntry(), render2dEntry(), render3dEntry(), scriptEntry(), physicsEntry(), debugEntry(), return assembly.Catalog{core(), dataEntry(), inputEntry(), render2dEntry(), render3dEntry(), scriptEntry(), physicsEntry(), debugEntry(),
displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), cameraEntry(), spawnEntry(), savegameEntry()} displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), spawnEntry(), savegameEntry()}
} }
+1 -1
View File
@@ -3,5 +3,5 @@ package builtin
import "aego/app/assembly" import "aego/app/assembly"
func Headless() assembly.Catalog { func Headless() assembly.Catalog {
return assembly.Catalog{core(), dataEntry(), inputEntry(), scriptEntry(), physicsEntry(), debugEntry(), displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), cameraEntry(), spawnEntry(), savegameEntry()} return assembly.Catalog{core(), dataEntry(), inputEntry(), scriptEntry(), physicsEntry(), debugEntry(), displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), spawnEntry(), savegameEntry()}
} }
-3
View File
@@ -2,8 +2,6 @@ package editors
import ( import (
"aego/app/assembly" "aego/app/assembly"
"aego/modules/camera"
cameraeditor "aego/modules/camera/editor"
"aego/modules/debug" "aego/modules/debug"
debugeditor "aego/modules/debug/editor" debugeditor "aego/modules/debug/editor"
inputmod "aego/modules/input" inputmod "aego/modules/input"
@@ -22,6 +20,5 @@ func With(c assembly.Catalog) assembly.Catalog {
With(render2d.ID, render2deditor.Hook). With(render2d.ID, render2deditor.Hook).
With(render3d.ID, render3deditor.Hook). With(render3d.ID, render3deditor.Hook).
With(physics.ID, physicseditor.Hook). With(physics.ID, physicseditor.Hook).
With(camera.ID, cameraeditor.Hook).
With(debug.ID, debugeditor.Hook) With(debug.ID, debugeditor.Hook)
} }
-6
View File
@@ -4,14 +4,11 @@ import (
"aego/app/assembly" "aego/app/assembly"
"aego/engine/base" "aego/engine/base"
"aego/engine/module" "aego/engine/module"
"aego/modules/camera"
"aego/modules/debug" "aego/modules/debug"
inputmod "aego/modules/input" inputmod "aego/modules/input"
"aego/modules/physics" "aego/modules/physics"
"aego/modules/render2d" "aego/modules/render2d"
"aego/modules/render3d"
"aego/modules/scripting" "aego/modules/scripting"
"aego/modules/tween"
"aego/script/stdlib" "aego/script/stdlib"
"aego/script/vm" "aego/script/vm"
) )
@@ -26,9 +23,6 @@ func Catalog() assembly.Catalog {
}}, }},
{ID: physics.ID, New: func(assembly.Options) module.Module { return physics.New() }}, {ID: physics.ID, New: func(assembly.Options) module.Module { return physics.New() }},
{ID: debug.ID, New: func(assembly.Options) module.Module { return debug.New() }}, {ID: debug.ID, New: func(assembly.Options) module.Module { return debug.New() }},
{ID: tween.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return tween.New() }},
{ID: camera.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return camera.New() }},
{ID: render3d.ID, New: func(assembly.Options) module.Module { return render3d.New() }},
} }
} }
-33
View File
@@ -1,33 +0,0 @@
package docstool
import (
"aego/app/playground"
"aego/engine/docs"
"aego/script/diag"
)
func diagnoser(pg *playground.Playground) func(string, *docs.DiagInfo) {
return func(code string, d *docs.DiagInfo) {
got, err := pg.Reproduce(code)
if err != nil || got.Code == "" {
return
}
d.Severity, d.At, d.Message = got.Severity, got.File, got.Message
d.Line, d.Col, d.EndLine, d.EndCol = got.Line, got.Col, got.EndLine, got.EndCol
}
}
// DiagnosticProblems lists codes whose example is missing or no longer produces the code.
func DiagnosticProblems() []docs.Problem {
pg, err := playground.New()
if err != nil {
return []docs.Problem{{ID: docs.DiagnosticsID, Error: true, Message: "cannot start the docs runtime: " + err.Error()}}
}
var out []docs.Problem
for _, c := range diag.Codes() {
if _, err := pg.Reproduce(c); err != nil {
out = append(out, docs.Problem{ID: c, Error: true, Message: err.Error()})
}
}
return out
}
+1 -15
View File
@@ -138,7 +138,7 @@ func TestF1Refs(t *testing.T) {
if err := os.WriteFile(path, []byte(probe), 0o644); err != nil { if err := os.WriteFile(path, []byte(probe), 0o644); err != nil {
t.Fatal(err) t.Fatal(err)
} }
reg, ws, err := build(p, nil, nil) reg, ws, err := build(p, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -169,17 +169,3 @@ func TestF1Refs(t *testing.T) {
} }
} }
} }
func TestDiagnosticExamplesExported(t *testing.T) {
if ps := DiagnosticProblems(); len(ps) > 0 {
t.Fatalf("diagnostics examples: %v", ps)
}
reg, err := ExportRegistry(nil, nil)
if err != nil {
t.Fatal(err)
}
e, ok := reg.Get("AES2001")
if !ok || e.Diag == nil || e.Diag.Line == 0 || e.Diag.Severity != "error" || !strings.Contains(e.Diag.Message, "heigth") {
t.Fatalf("AES2001 example was not diagnosed: %+v", e.Diag)
}
}
+4 -16
View File
@@ -9,7 +9,6 @@ import (
"aego/app/assembly" "aego/app/assembly"
"aego/app/assembly/builtin" "aego/app/assembly/builtin"
"aego/app/playground"
"aego/engine/docs" "aego/engine/docs"
"aego/engine/module" "aego/engine/module"
scriptsystems "aego/modules/scripting/systems" scriptsystems "aego/modules/scripting/systems"
@@ -21,7 +20,7 @@ import (
"aego/script/workspace" "aego/script/workspace"
) )
func build(p *project.Project, mods []module.Module, diagnose func(string, *docs.DiagInfo)) (*docs.Registry, *workspace.Workspace, error) { func build(p *project.Project, mods []module.Module) (*docs.Registry, *workspace.Workspace, error) {
types, err := assembly.NewTypes() types, err := assembly.NewTypes()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@@ -42,7 +41,6 @@ func build(p *project.Project, mods []module.Module, diagnose func(string, *docs
return nil, nil, err return nil, nil, err
} }
src := docs.SourcesOf(bm, types.Schema(), man) src := docs.SourcesOf(bm, types.Schema(), man)
src.Diagnose = diagnose
var ws *workspace.Workspace var ws *workspace.Workspace
if p != nil { if p != nil {
ws, err = workspace.Open(workspace.Config{Root: p.Root, Module: p.Module, Scripts: p.ScriptsDir(), Bindings: reg.Table(), Docs: bm}) ws, err = workspace.Open(workspace.Config{Root: p.Root, Module: p.Module, Scripts: p.ScriptsDir(), Bindings: reg.Table(), Docs: bm})
@@ -63,17 +61,7 @@ func build(p *project.Project, mods []module.Module, diagnose func(string, *docs
} }
func Registry(p *project.Project, mods []module.Module) (*docs.Registry, error) { func Registry(p *project.Project, mods []module.Module) (*docs.Registry, error) {
r, _, err := build(p, mods, nil) r, _, err := build(p, mods)
return r, err
}
// ExportRegistry is Registry with the diagnostics examples compiled for their messages and positions.
func ExportRegistry(p *project.Project, mods []module.Module) (*docs.Registry, error) {
pg, err := playground.New()
if err != nil {
return nil, err
}
r, _, err := build(p, mods, diagnoser(pg))
return r, err return r, err
} }
@@ -96,9 +84,9 @@ func WriteExport(reg *docs.Registry, title, path string, w io.Writer) error {
return os.WriteFile(path, data, 0o644) return os.WriteFile(path, data, 0o644)
} }
func Check(reg *docs.Registry, strict bool, w io.Writer, extra ...docs.Problem) int { func Check(reg *docs.Registry, strict bool, w io.Writer) int {
errs, warns := 0, 0 errs, warns := 0, 0
for _, p := range append(reg.Check(strict), extra...) { for _, p := range reg.Check(strict) {
level := "warn " level := "warn "
if p.Error { if p.Error {
level = "error" level = "error"
+3 -15
View File
@@ -16,7 +16,6 @@ import (
"strings" "strings"
"time" "time"
"aego/app/assembly/builtin"
"aego/engine" "aego/engine"
"aego/engine/module" "aego/engine/module"
"aego/project" "aego/project"
@@ -41,7 +40,7 @@ func (e *Env) export(path string) error {
} }
return e.Native(args...) return e.Native(args...)
} }
reg, err := ExportRegistry(e.Project, e.Modules) reg, err := Registry(e.Project, e.Modules)
if err != nil { if err != nil {
return err return err
} }
@@ -72,7 +71,7 @@ func (e *Env) Check(strict bool) int {
fmt.Fprintln(e.Stderr, "aego:", err) fmt.Fprintln(e.Stderr, "aego:", err)
return 1 return 1
} }
code := Check(reg, strict, e.Stdout, DiagnosticProblems()...) code := Check(reg, strict, e.Stdout)
if CheckTry(reg, e.Stdout) > 0 && code == 0 { if CheckTry(reg, e.Stdout) > 0 && code == 0 {
code = 1 code = 1
} }
@@ -99,10 +98,7 @@ func (e *Env) git(args ...string) string {
} }
func (e *Env) commit() string { func (e *Env) commit() string {
if c := e.git("rev-parse", "--short", "HEAD"); c != "" { return e.git("rev-parse", "--short", "HEAD")
return c
}
return os.Getenv("AEGO_COMMIT")
} }
func (e *Env) branch() string { func (e *Env) branch() string {
@@ -154,14 +150,6 @@ func (e *Env) Build(out string, play bool) error {
if c := e.commit(); c != "" { if c := e.commit(); c != "" {
args = append(args, "-commit", c) args = append(args, "-commit", c)
} }
var icons []string
for _, d := range builtin.IconSources() {
icons = append(icons, filepath.Join(e.Engine, filepath.FromSlash(d)))
}
args = append(args, "-icons", strings.Join(icons, ","))
if shots := filepath.Join(e.Engine, "docs", "shots"); fileExists(filepath.Join(shots, "demo.png")) {
args = append(args, "-poster", filepath.Join(shots, "demo.png"), "-shots", shots)
}
if hero := filepath.Join(e.Engine, "docs", "hero.png"); fileExists(hero) { if hero := filepath.Join(e.Engine, "docs", "hero.png"); fileExists(hero) {
args = append(args, "-hero", hero) args = append(args, "-hero", hero)
} }
+2 -6
View File
@@ -21,11 +21,7 @@ func Tool(p *project.Project, args []string, stdout, stderr io.Writer, mods ...m
if err := fs.Parse(args[1:]); err != nil { if err := fs.Parse(args[1:]); err != nil {
return 2 return 2
} }
build := Registry reg, err := Registry(p, mods)
if args[0] == "export" {
build = ExportRegistry
}
reg, err := build(p, mods)
if err != nil { if err != nil {
fmt.Fprintln(stderr, "aego:", err) fmt.Fprintln(stderr, "aego:", err)
return 1 return 1
@@ -38,7 +34,7 @@ func Tool(p *project.Project, args []string, stdout, stderr io.Writer, mods ...m
} }
return 0 return 0
case "check": case "check":
return Check(reg, *strict, stdout, DiagnosticProblems()...) return Check(reg, *strict, stdout)
} }
fmt.Fprintf(stderr, "unknown docs command %q\n", args[0]) fmt.Fprintf(stderr, "unknown docs command %q\n", args[0])
return 2 return 2
+4 -23
View File
@@ -7,7 +7,6 @@ import (
playmods "aego/app/assembly/playground" playmods "aego/app/assembly/playground"
"aego/asset" "aego/asset"
"aego/asset/loader" "aego/asset/loader"
"aego/contracts/debugdraw"
"aego/core/log" "aego/core/log"
"aego/core/mathx" "aego/core/mathx"
edclip "aego/editor/core/clipboard" edclip "aego/editor/core/clipboard"
@@ -21,7 +20,6 @@ import (
"aego/engine/base" "aego/engine/base"
"aego/engine/module" "aego/engine/module"
"aego/format/aetext" "aego/format/aetext"
"aego/modules/physics"
scriptsystems "aego/modules/scripting/systems" scriptsystems "aego/modules/scripting/systems"
"aego/platform" "aego/platform"
"aego/render" "aego/render"
@@ -43,11 +41,9 @@ const (
maxSprites = 16384 maxSprites = 16384
gameShare = 0.6 gameShare = 0.6
stackBelow = 640 stackBelow = 640
// inspectorMin keeps field names and values readable in narrow embeds. maxLines = 200
inspectorMin = 400 defaultW = 960
maxLines = 200 defaultH = 440
defaultW = 960
defaultH = 440
) )
var coreBlocks = []string{"window", "defaults"} var coreBlocks = []string{"window", "defaults"}
@@ -81,7 +77,6 @@ type App struct {
log *log.Logger log *log.Logger
lines []Line lines []Line
textOn bool textOn bool
shown *engine.World
closed bool closed bool
} }
@@ -275,7 +270,7 @@ func layout(body mathx.Rect, gap float32) (game, insp mathx.Rect) {
return mathx.Rect{X: body.X, Y: body.Y, W: body.W, H: h}, return mathx.Rect{X: body.X, Y: body.Y, W: body.W, H: h},
mathx.Rect{X: body.X, Y: body.Y + h + gap, W: body.W, H: max(0, body.H-h-gap)} mathx.Rect{X: body.X, Y: body.Y + h + gap, W: body.W, H: max(0, body.H-h-gap)}
} }
w := min(body.W*gameShare, body.W-inspectorMin) w := body.W * gameShare
return mathx.Rect{X: body.X, Y: body.Y, W: w, H: body.H}, return mathx.Rect{X: body.X, Y: body.Y, W: w, H: body.H},
mathx.Rect{X: body.X + w + gap, Y: body.Y, W: max(0, body.W-w-gap), H: body.H} mathx.Rect{X: body.X + w + gap, Y: body.Y, W: max(0, body.W-w-gap), H: body.H}
} }
@@ -313,24 +308,10 @@ func (a *App) toolbar(r mathx.Rect) {
} }
} }
// showPhysics draws collider and joint outlines in the game view, since docs scenes have no art for them.
func (a *App) showPhysics() {
w := a.session.Controller().World()
if w == nil || w == a.shown {
return
}
if d, ok := engine.ServiceOf[debugdraw.Draw](w, debugdraw.ServiceID); ok {
d.Hide(physics.ChannelShapes, false)
d.Hide(physics.ChannelJoints, false)
a.shown = w
}
}
func (a *App) Update(dt float64) { func (a *App) Update(dt float64) {
e := a.e e := a.e
now := e.Platform().Now() now := e.Platform().Now()
a.session.Tick(dt) a.session.Tick(dt)
a.showPhysics()
ui := a.ui ui := a.ui
ui.Time = now ui.Time = now
m := e.Metrics() m := e.Metrics()
+1 -1
View File
@@ -27,7 +27,7 @@ func tries(t *testing.T) []docs.Entry {
t.Fatal(err) t.Fatal(err)
} }
out := p.TryScenes() out := p.TryScenes()
if len(out) < 10 { if len(out) < 4 {
t.Fatalf("only %d try scenes; does the module embed res/docs/try?", len(out)) t.Fatalf("only %d try scenes; does the module embed res/docs/try?", len(out))
} }
return out return out
+5 -16
View File
@@ -7,7 +7,6 @@ import (
"strconv" "strconv"
"aego/editor/core/docsession" "aego/editor/core/docsession"
"aego/scene/sceneio"
) )
type request struct { type request struct {
@@ -19,11 +18,10 @@ type request struct {
} }
type Reply struct { type Reply struct {
Text string `json:"text,omitempty"` Text string `json:"text,omitempty"`
Values map[string]string `json:"values,omitempty"` Error string `json:"error,omitempty"`
Error string `json:"error,omitempty"` State *docsession.State `json:"state,omitempty"`
State *docsession.State `json:"state,omitempty"` Log []Line `json:"log,omitempty"`
Log []Line `json:"log,omitempty"`
} }
func (a *App) Call(method string, payload []byte) Reply { func (a *App) Call(method string, payload []byte) Reply {
@@ -63,16 +61,7 @@ func (a *App) Call(method string, payload []byte) Reply {
return Reply{Error: err.Error()} return Reply{Error: err.Error()}
} }
st := s.State() st := s.State()
return Reply{Text: string(s.Text()), Values: Values(s.Text()), State: &st} return Reply{Text: string(s.Text()), State: &st}
}
// Values flattens a scene file into the field keys the docs scene tree uses.
func Values(text []byte) map[string]string {
nodes, err := sceneio.Outline(text)
if err != nil {
return nil
}
return sceneio.OutlineValues(nodes)
} }
func (a *App) Reply(method, payload string) string { func (a *App) Reply(method, payload string) string {
-3
View File
@@ -154,9 +154,6 @@ func Run(p *project.Project, cfg Config, mods ...sdk.Module) error {
panic(rec) panic(rec)
} }
}() }()
if dir := os.Getenv(EnvShots); dir != "" {
return s.shots(dir)
}
if err := s.Run(); err != nil { if err := s.Run(); err != nil {
return err return err
} }
-155
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -21,7 +21,6 @@ canvas { display: block; width: 100%; height: 100%; outline: none; touch-action:
#fullscreen { position: fixed; top: 10px; right: 10px; width: 36px; height: 36px; border: 0; border-radius: 6px; background: rgba(0, 0, 0, 0.45); color: #fff; font-size: 18px; cursor: pointer; opacity: 0.5; } #fullscreen { position: fixed; top: 10px; right: 10px; width: 36px; height: 36px; border: 0; border-radius: 6px; background: rgba(0, 0, 0, 0.45); color: #fff; font-size: 18px; cursor: pointer; opacity: 0.5; }
#fullscreen:hover { opacity: 1; } #fullscreen:hover { opacity: 1; }
</style> </style>
<link rel="preload" href="game.wasm?v={{.Build}}" as="fetch" type="application/wasm" crossorigin>
</head> </head>
<body> <body>
<canvas id="game" data-aego tabindex="0" aria-label="{{.Title}}"></canvas> <canvas id="game" data-aego tabindex="0" aria-label="{{.Title}}"></canvas>
-31
View File
@@ -1,31 +0,0 @@
package playground
import (
"testing"
"aego/app/assembly/builtin"
)
func TestCheckSnippetsCompile(t *testing.T) {
p, err := NewWith(builtin.Default())
if err != nil {
t.Fatal(err)
}
n := 0
for _, e := range p.docs.All() {
for _, s := range e.Snippets {
if !s.Check || s.Lang != "aes" {
continue
}
n++
for _, d := range p.Compile(map[string]string{"main.aes": s.Code}) {
if d.Severity == "error" {
t.Errorf("%s (line %d of its page): %d:%d %s %s", e.ID, s.Line, d.Line, d.Col, d.Code, d.Message)
}
}
}
}
if n == 0 {
t.Fatal("no aes check blocks in the documentation")
}
}
-65
View File
@@ -1,65 +0,0 @@
package playground
import (
"errors"
"fmt"
"strings"
"aego/script/diag"
)
const exampleFrames = 3
// Reproduce runs the diagnostics example of code and returns what its broken version reports.
func (p *Playground) Reproduce(code string) (Diagnostic, error) {
ex, ok := diag.Example(code)
if !ok {
return Diagnostic{}, errors.New("no example in script/diag/examples.go")
}
if ex.Why != "" {
return Diagnostic{}, nil
}
if ex.Bad == "" || ex.Good == "" {
return Diagnostic{}, errors.New("the example needs both a broken and a fixed version")
}
bad := reports(p.Run(ex.Sources(false), exampleFrames))
var found *Diagnostic
for i := range bad {
if bad[i].Code == code {
found = &bad[i]
break
}
}
if found == nil {
return Diagnostic{}, fmt.Errorf("the broken example does not report %s, it reports %s", code, list(bad))
}
var wrong []Diagnostic
for _, d := range reports(p.Run(ex.Sources(true), exampleFrames)) {
if d.Code == code || d.Severity == "error" {
wrong = append(wrong, d)
}
}
if len(wrong) > 0 {
return *found, fmt.Errorf("the fixed example still reports %s", list(wrong))
}
return *found, nil
}
func reports(res Result) []Diagnostic {
out := append([]Diagnostic(nil), res.Diagnostics...)
for _, f := range res.Faults {
out = append(out, Diagnostic{File: f.File, Line: f.Line, Code: f.Code, Severity: "error", Message: f.Message})
}
return out
}
func list(ds []Diagnostic) string {
if len(ds) == 0 {
return "nothing"
}
parts := make([]string, len(ds))
for i, d := range ds {
parts[i] = strings.TrimSpace(d.Code + " " + d.Message)
}
return strings.Join(parts, "; ")
}
-27
View File
@@ -1,27 +0,0 @@
package playground
import (
"testing"
"aego/script/diag"
)
func TestDiagnosticExamples(t *testing.T) {
p := open(t)
var skipped []string
for _, code := range diag.Codes() {
d, err := p.Reproduce(code)
if err != nil {
t.Errorf("%s: %v", code, err)
continue
}
if d.Code == "" {
skipped = append(skipped, code)
continue
}
t.Logf("%s %s:%d:%d-%d:%d %s %s", code, d.File, d.Line, d.Col, d.EndLine, d.EndCol, d.Severity, d.Message)
}
if len(skipped) > 10 {
t.Errorf("%d codes are not reproducible: %v", len(skipped), skipped)
}
}
+10 -36
View File
@@ -101,16 +101,9 @@ type Playground struct {
proj *exampleProject proj *exampleProject
gameCfg GameConfig gameCfg GameConfig
game *Game game *Game
simEngine *engine.Engine
} }
func New() (*Playground, error) { func New() (*Playground, error) {
return NewWith(playmods.Catalog())
}
// NewWith builds a playground over another module catalog, for checks that need every module's types.
func NewWith(cat assembly.Catalog) (*Playground, error) {
proj, err := loadProject() proj, err := loadProject()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -120,7 +113,7 @@ func NewWith(cat assembly.Catalog) (*Playground, error) {
return nil, err return nil, err
} }
reg := bind.New() reg := bind.New()
man, err := cat.Load(types, reg, assembly.Options{Seed: 1, Testing: true}) man, err := playmods.Catalog().Load(types, reg, assembly.Options{Seed: 1, Testing: true})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -212,23 +205,6 @@ func diagnostics(sm *source.Manager, ds []diag.Diagnostic) []Diagnostic {
return out return out
} }
func blocked(failed map[string][]diag.Diagnostic) []Diagnostic {
pkgs := make([]string, 0, len(failed))
for pkg := range failed {
pkgs = append(pkgs, pkg)
}
sort.Strings(pkgs)
var out []Diagnostic
for _, pkg := range pkgs {
for _, d := range failed[pkg] {
if d.Code == diag.CompileBlocked {
out = append(out, Diagnostic{Code: d.Code, Message: d.Message, Severity: severity(d)})
}
}
}
return out
}
func failed(err error) []Diagnostic { func failed(err error) []Diagnostic {
return []Diagnostic{{Severity: "error", Message: err.Error()}} return []Diagnostic{{Severity: "error", Message: err.Error()}}
} }
@@ -287,7 +263,6 @@ func (p *Playground) Run(files map[string]string, frames int) Result {
gen, ds := p.ws.Build() gen, ds := p.ws.Build()
res := Result{Diagnostics: diagnostics(gen.Sources, ds)} res := Result{Diagnostics: diagnostics(gen.Sources, ds)}
if len(gen.Failed) > 0 || diag.HasErrors(ds) { if len(gen.Failed) > 0 || diag.HasErrors(ds) {
res.Diagnostics = append(res.Diagnostics, blocked(gen.Failed)...)
return res return res
} }
pkgs := make([]string, 0, len(gen.Modules)) pkgs := make([]string, 0, len(gen.Modules))
@@ -320,17 +295,16 @@ func (p *Playground) Run(files map[string]string, frames int) Result {
} }
w := engine.NewWorld("playground") w := engine.NewWorld("playground")
w.Input = &input.State{} w.Input = &input.State{}
w.SetService(scene.ServiceID, s) ctx := engine.Context{World: w, Dt: 1.0 / 60}
w.SetService(runtime.ServiceID, rt)
e, detach, err := p.attach(w, s, lg)
if err != nil {
res.Diagnostics = append(res.Diagnostics, failed(err)...)
return res
}
defer detach()
for i := 0; i < frames; i++ { for i := 0; i < frames; i++ {
frame = uint64(i + 1) ctx.Frame++
e.TickWorld(w, engine.ScopePlay, 1.0/60) frame = ctx.Frame
rt.RunDeferred()
rt.FixedUpdate(&ctx)
rt.Update(&ctx)
rt.Coroutines(&ctx)
s.Flush()
s.UpdateTransforms()
} }
res.Frames = frames res.Frames = frames
for _, f := range rt.Faults() { for _, f := range rt.Faults() {
-43
View File
@@ -1,43 +0,0 @@
package playground
import (
"aego/core/log"
"aego/engine"
"aego/engine/module"
"aego/scene"
"aego/scene/frame"
)
// sim is a windowless engine that steps the module systems of a console run, so tweens, timers and physics work there as in Play.
func (p *Playground) sim() (*engine.Engine, error) {
if p.simEngine != nil {
return p.simEngine, nil
}
b := engine.NewBuilder(engine.Config{Headless: true, HeadlessFrameTime: 1.0 / 60, Seed: 1, AppID: "aego-playground-console"}).WithLogger(log.Nop())
if err := frame.Register(b.Registry()); err != nil {
return nil, err
}
e, err := b.Use(p.man.Engine()).Build()
if err != nil {
return nil, err
}
if err := p.man.Init(&module.InitContext{Engine: e, Log: log.Nop(), Sections: p.sections}); err != nil {
e.Close()
return nil, err
}
p.simEngine = e
return e, nil
}
func (p *Playground) attach(w *engine.World, s *scene.Scene, lg *log.Logger) (*engine.Engine, func(), error) {
e, err := p.sim()
if err != nil {
return nil, nil, err
}
w.Time.Configure(e.TimeConfig())
detach, err := p.man.Attach(&module.AttachContext{World: w, Scene: s, Log: lg, Play: true})
if err != nil {
return nil, nil, err
}
return e, detach, nil
}
-65
View File
@@ -1,65 +0,0 @@
package run
import (
"bytes"
"encoding/hex"
"strconv"
"strings"
"aego/app/agent"
"aego/core/log"
"aego/core/rewind"
"aego/project"
"aego/sdk"
)
const maxRunLog = 200
func AgentRunner(mods ...sdk.Module) agent.Runner {
return func(p *project.Project, r agent.RunRequest) (agent.RunReport, error) {
var buf bytes.Buffer
lg := log.New(log.NewTextSink(&buf))
lg.SetLevel(log.Warn)
frames, seed := r.Frames, r.Seed
if frames <= 0 {
frames = 120
}
if seed == 0 {
seed = 1
}
a, err := Open(p, Config{Headless: true, FrameTime: defaultDetStep, Seed: seed, ScenePath: r.Scene, Logger: lg, NoProfile: true}, mods...)
if err != nil {
return agent.RunReport{Log: logLines(buf.String())}, err
}
rep := agent.RunReport{}
driver := a.Driver()
for rep.Frames < frames {
if !a.Engine.Step(driver) {
rep.Error = "the game stopped at frame " + strconv.Itoa(rep.Frames+1)
if err := a.Engine.Err(); err != nil {
rep.Error += ": " + err.Error()
}
break
}
rep.Frames++
}
sum, _, _ := WorldHash(a.Engine.MainWorld(), rewind.NewHasher())
rep.Hash = hex.EncodeToString(sum[:])
a.Close()
rep.Log = logLines(buf.String())
return rep, nil
}
}
func logLines(s string) []string {
var out []string
for _, l := range strings.Split(strings.TrimSpace(s), "\n") {
if l = strings.TrimSpace(l); l != "" {
out = append(out, l)
}
}
if len(out) > maxRunLog {
out = append(out[:maxRunLog], "…")
}
return out
}
+1 -1
View File
@@ -78,7 +78,7 @@ func WorldHash(w *engine.World, h *rewind.Hasher) (sum [32]byte, covered, skippe
} }
func determinismPass(p *project.Project, o DeterminismOptions, mods []sdk.Module) ([]checkpoint, []string, []string, error) { func determinismPass(p *project.Project, o DeterminismOptions, mods []sdk.Module) ([]checkpoint, []string, []string, error) {
a, err := Open(p, Config{Headless: true, FrameTime: o.FrameTime, Seed: o.Seed, ScenePath: o.ScenePath, Logger: log.Nop(), NoProfile: true}, mods...) a, err := Open(p, Config{Headless: true, FrameTime: o.FrameTime, Seed: o.Seed, ScenePath: o.ScenePath, Logger: log.Nop()}, mods...)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
-2
View File
@@ -43,7 +43,6 @@ type Config struct {
Profile string Profile string
Logger *log.Logger Logger *log.Logger
Seed uint64 Seed uint64
NoProfile bool
} }
type App struct { type App struct {
@@ -139,7 +138,6 @@ func Open(p *project.Project, cfg Config, mods ...sdk.Module) (*App, error) {
Debug: cfg.Debug, Debug: cfg.Debug,
FailOnScriptErrors: true, FailOnScriptErrors: true,
Profile: cfg.Profile, Profile: cfg.Profile,
NoProfile: cfg.NoProfile,
}, sdk.AdaptAll(mods)...) }, sdk.AdaptAll(mods)...)
if err != nil { if err != nil {
return nil, err return nil, err
-7
View File
@@ -1,13 +1,10 @@
package run package run
import ( import (
"aego/app/agent"
"aego/app/agent/mcp"
"bytes" "bytes"
"flag" "flag"
"fmt" "fmt"
"io" "io"
"os"
"sort" "sort"
"strings" "strings"
@@ -42,10 +39,6 @@ func Tool(p *project.Project, args []string, stdout, stderr io.Writer, mods ...s
return toolTest(p, args[1:], stdout, stderr, mods) return toolTest(p, args[1:], stdout, stderr, mods)
case "docs": case "docs":
return docstool.Tool(p, args[1:], stdout, stderr, sdk.AdaptAll(mods)...) return docstool.Tool(p, args[1:], stdout, stderr, sdk.AdaptAll(mods)...)
case "agent":
return agent.Main(p, args[1:], stdout, stderr, agent.Options{Mods: mods, Run: AgentRunner(mods...)})
case "mcp":
return mcp.Main(p, args[1:], os.Stdin, stdout, stderr, agent.Options{Mods: mods, Run: AgentRunner(mods...)})
} }
fmt.Fprintf(stderr, "unknown tool %q\n", args[0]) fmt.Fprintf(stderr, "unknown tool %q\n", args[0])
return 2 return 2
+1 -3
View File
@@ -3,7 +3,6 @@
package main package main
import ( import (
"encoding/json"
"syscall/js" "syscall/js"
"aego/app/docsview" "aego/app/docsview"
@@ -18,8 +17,7 @@ func main() {
} else { } else {
app.OnChange = func(text string) { app.OnChange = func(text string) {
if f := g.Get("aegoDocsChanged"); f.Type() == js.TypeFunction { if f := g.Get("aegoDocsChanged"); f.Type() == js.TypeFunction {
values, _ := json.Marshal(docsview.Values([]byte(text))) f.Invoke(text)
f.Invoke(text, string(values))
} }
} }
api.Set("call", js.FuncOf(func(_ js.Value, args []js.Value) any { api.Set("call", js.FuncOf(func(_ js.Value, args []js.Value) any {
File diff suppressed because it is too large Load Diff
-44
View File
@@ -1,14 +1,11 @@
package main package main
import ( import (
"aego/app/agent"
"aego/app/agent/mcp"
"flag" "flag"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"slices"
"strings" "strings"
"aego/app/bootstrap" "aego/app/bootstrap"
@@ -53,8 +50,6 @@ func dispatch(args []string) int {
return cmdCheck(args[1:]) return cmdCheck(args[1:])
case "icons": case "icons":
return cmdIcons(args[1:]) return cmdIcons(args[1:])
case "mcp":
return cmdMcp(args[1:])
case "docs": case "docs":
return cmdDocs(args[1:]) return cmdDocs(args[1:])
case "test": case "test":
@@ -65,9 +60,6 @@ func dispatch(args []string) int {
usage() usage()
return 0 return 0
} }
if slices.Contains(agent.Groups(), args[0]) {
return cmdAgent(args)
}
fmt.Fprintf(os.Stderr, "unknown command %q\n", args[0]) fmt.Fprintf(os.Stderr, "unknown command %q\n", args[0])
usage() usage()
return 2 return 2
@@ -91,8 +83,6 @@ func usage() {
run *_test.aes script tests headless, or run the game twice and compare state hashes run *_test.aes script tests headless, or run the game twice and compare state hashes
dump script <pkg|Type> [--func name] disassemble a script package with source lines dump script <pkg|Type> [--func name] disassemble a script package with source lines
docs export|check|build|serve documentation registry, site and playground docs export|check|build|serve documentation registry, site and playground
mcp [--project dir] [--watch] serve the project to an AI agent over MCP (stdio)
scene|prefab|data|refs|asset|schema|project <verb> ... edit project files without the editor, see aego scene help
`) `)
} }
@@ -107,40 +97,6 @@ func openProject(dir string) (*project.Project, error) {
return project.Load(root) return project.Load(root)
} }
func projectFlag(args []string) string {
for i, a := range args {
switch {
case a == "--project" && i+1 < len(args):
return args[i+1]
case strings.HasPrefix(a, "--project="):
return strings.TrimPrefix(a, "--project=")
}
}
return ""
}
func cmdAgent(args []string) int {
p, err := openProject(projectFlag(args))
if err != nil {
return fail(err)
}
if bootstrap.NeedsNative(p) {
return scriptTool(p, append([]string{"agent"}, args...)...)
}
return agent.Main(p, args, os.Stdout, os.Stderr, agent.Options{Run: run.AgentRunner()})
}
func cmdMcp(args []string) int {
p, err := openProject(projectFlag(args))
if err != nil {
return fail(err)
}
if bootstrap.NeedsNative(p) {
return scriptTool(p, append([]string{"mcp"}, args...)...)
}
return mcp.Main(p, args, os.Stdin, os.Stdout, os.Stderr, agent.Options{Run: run.AgentRunner()})
}
func firstArg(args []string) (string, []string) { func firstArg(args []string) (string, []string) {
if len(args) > 0 && len(args[0]) > 0 && args[0][0] != '-' { if len(args) > 0 && len(args[0]) > 0 && args[0][0] != '-' {
return args[0], args[1:] return args[0], args[1:]
-1
View File
@@ -23,7 +23,6 @@ func scriptTool(p *project.Project, args ...string) int {
} }
cmd := exec.Command(binary, append([]string{"--tool"}, args...)...) cmd := exec.Command(binary, append([]string{"--tool"}, args...)...)
cmd.Dir = p.Root cmd.Dir = p.Root
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
return exitCode(cmd.Run()) return exitCode(cmd.Run())
-20
View File
@@ -1,20 +0,0 @@
package camera2d
import (
"aego/render/view"
"aego/scene"
)
const (
ServiceID = "camera2d.adjust"
AccessibilityKind = "camera2d.accessibility"
)
type Adjuster interface {
Adjust(node scene.NodeHandle, v view.View) view.View
}
type Accessibility struct {
ScreenShake string
ReducedMotion string
}
+1 -8
View File
@@ -1,19 +1,12 @@
FROM golang:1.27-alpine AS build FROM golang:1.27-alpine AS build
RUN apk add --no-cache make gzip brotli RUN apk add --no-cache make gzip
WORKDIR /src WORKDIR /src
COPY go.mod go.sum ./ COPY go.mod go.sum ./
COPY tools/gendocs/go.mod tools/gendocs/go.sum tools/gendocs/ COPY tools/gendocs/go.mod tools/gendocs/go.sum tools/gendocs/
RUN go mod download && cd tools/gendocs && go mod download RUN go mod download && cd tools/gendocs && go mod download
COPY . . COPY . .
ARG AEGO_COMMIT=""
ENV AEGO_COMMIT=$AEGO_COMMIT
RUN make site-headless RUN make site-headless
RUN find site -type f \( -name '*.js' -o -name '*.css' -o -name '*.html' -o -name '*.json' -o -name '*.svg' \) -exec gzip -9 -k -f {} \; RUN find site -type f \( -name '*.js' -o -name '*.css' -o -name '*.html' -o -name '*.json' -o -name '*.svg' \) -exec gzip -9 -k -f {} \;
RUN find site -type f -name '*.wasm' | while read -r f; do [ -f "$f.gz" ] || gzip -9 -k -f "$f"; [ -f "$f.br" ] || brotli -q 11 -k -f "$f"; done
COPY tools/release/install.sh tools/release/install.ps1 site/
# the edge proxy serves the site from its own copy: everything but the wasm binaries, which it fetches separately
RUN mkdir -p site/__edge && tar -czf site/__edge/site.tar.gz --exclude='./__edge' --exclude='*.wasm' --exclude='*.wasm.gz' --exclude='*.wasm.br' -C site . \
&& sha256sum site/__edge/site.tar.gz | cut -c1-16 > site/__edge/version
FROM nginx:1.27-alpine FROM nginx:1.27-alpine
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
-2
View File
@@ -5,8 +5,6 @@ services:
build: build:
context: .. context: ..
dockerfile: deploy/Dockerfile dockerfile: deploy/Dockerfile
args:
AEGO_COMMIT: ${AEGO_COMMIT:-}
image: aego-site:latest image: aego-site:latest
container_name: aego-site container_name: aego-site
restart: unless-stopped restart: unless-stopped
+1 -1
View File
@@ -15,7 +15,7 @@ else
git checkout -q -f --detach "origin/$ref" git checkout -q -f --detach "origin/$ref"
fi fi
sha=$(git rev-parse HEAD) sha=$(git rev-parse HEAD)
AEGO_COMMIT=$(git rev-parse --short HEAD) docker compose -f deploy/compose.yml build docker compose -f deploy/compose.yml build
docker compose -f deploy/compose.yml up -d --wait --remove-orphans docker compose -f deploy/compose.yml up -d --wait --remove-orphans
docker image prune -f >/dev/null docker image prune -f >/dev/null
echo "$sha" >"$state/deployed" echo "$sha" >"$state/deployed"
File diff suppressed because it is too large Load Diff
-87
View File
@@ -1,87 +0,0 @@
#!/bin/sh
# Copies the wasm binaries of the deployed aego site from mainserv to this edge, so the edge serves
# them itself (see aego-edge.conf). Runs from a timer; slow downloads happen here, not for visitors.
set -eu
host=aego.akarpov.ru
origin=79.142.92.250
dir=/var/www/aego-wasm
exec 9>/run/aego-wasm-sync.lock
flock -n 9 || exit 0
get() {
curl -fsS -m 900 --retry 20 --retry-delay 2 --retry-all-errors --resolve "$host:443:$origin" "$@"
}
# pull resumes a large download across the drops of the route to mainserv
pull() {
dst=$1
shift
n=0
until curl -fsS -m 900 -C - --resolve "$host:443:$origin" -o "$dst" "$@"; do
n=$((n + 1))
[ "$n" -lt 30 ] || return 1
sleep 2
done
}
meta() {
sed -n "s/.*name=\"$1\" content=\"\([0-9a-f]*\)\".*/\1/p" | head -n 1
}
site=/var/www/aego-site
ver=$(get "https://$host/__edge/version" | tr -cd '0-9a-f')
if [ -n "$ver" ] && [ ! -d "$site/$ver" ]; then
mkdir -p "$site"
rm -rf "$site/$ver.part"
mkdir "$site/$ver.part"
rm -f "$site/$ver.tar.gz"
if pull "$site/$ver.tar.gz" "https://$host/__edge/site.tar.gz" &&
tar -xzf "$site/$ver.tar.gz" -C "$site/$ver.part"; then
chmod -R a+rX "$site/$ver.part"
mv "$site/$ver.part" "$site/$ver"
ln -sfn "$ver" "$site/current.new"
mv -T "$site/current.new" "$site/current"
echo "aego-wasm-sync: site $ver"
fi
rm -rf "$site/$ver.part" "$site/$ver.tar.gz"
fi
cur=$(readlink "$site/current" 2>/dev/null || true)
find "$site" -mindepth 1 -maxdepth 1 -type d -mtime +2 | while read -r d; do
[ "$(basename "$d")" = "$cur" ] || rm -rf "$d"
done
page=$(get "https://$host/index.html")
try=$(printf '%s' "$page" | meta aego-try)
play=$(printf '%s' "$page" | meta aego-play)
demo=$(get "https://$host/demo/" | sed -n 's/.*data-build="\([0-9a-f]*\)".*/\1/p' | head -n 1)
fetch() {
v=$1 path=$2
[ -n "$v" ] || return 0
for enc in br gzip; do
ext=$enc
[ "$enc" = gzip ] && ext=gz
out="$dir/$v/$path.$ext"
[ -s "$out" ] && continue
mkdir -p "$(dirname "$out")"
tmp="$out.part"
hdr="$out.hdr"
rm -f "$tmp"
if pull "$tmp" -H "Accept-Encoding: $enc" -D "$hdr" "https://$host/$path?v=$v" &&
grep -qi "^content-encoding: $enc" "$hdr"; then
mv "$tmp" "$out"
chmod a+r "$out"
echo "aego-wasm-sync: $v/$path.$ext"
fi
rm -f "$tmp" "$hdr"
done
}
fetch "$try" try/aego-docs.wasm
fetch "$play" play/aego.wasm
fetch "$demo" demo/game.wasm
# keep the current versions and anything from the last two weeks
find "$dir" -mindepth 1 -maxdepth 1 -type d -mtime +14 | while read -r d; do
case "$(basename "$d")" in "$try" | "$play" | "$demo") ;; *) rm -rf "$d" ;; esac
done
-8
View File
@@ -1,8 +0,0 @@
[Unit]
Description=Copy aego wasm binaries from mainserv to the edge
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/aego-wasm-sync
Nice=10

Some files were not shown because too many files have changed in this diff Show More