9 Commits
Author SHA1 Message Date
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
1568 changed files with 4668 additions and 127669 deletions
-1
View File
@@ -5,4 +5,3 @@ site
exports exports
**/.aego **/.aego
*.test *.test
.claude
+1 -11
View File
@@ -94,14 +94,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
- name: export packages
run: go test -count=1 ./runtime/... ./vfs/exportfs ./app/export/...
- name: export and run headless
run: go test -count=1 -run 'TestExport' -v ./tests/integration
- name: editor app bundle
if: runner.os == 'macOS'
run: |
make editor-app
plutil -lint ".cache/Aego Editor.app/Contents/Info.plist"
codesign --verify --deep ".cache/Aego Editor.app"
+1 -41
View File
@@ -3,7 +3,6 @@ name: ci
on: on:
push: push:
branches: [master] branches: [master]
tags: ["v*"]
pull_request: pull_request:
permissions: permissions:
@@ -37,10 +36,6 @@ jobs:
- run: go vet ./platform/sdl - run: go vet ./platform/sdl
- run: make lint - run: make lint
- run: make depcheck - run: make depcheck
- name: packaging tools
run: sudo apt-get install -y nsis squashfs-tools
- name: installer dry run
run: make nsis-check
build: build:
strategy: strategy:
@@ -116,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
-6
View File
@@ -11,10 +11,4 @@ tools/genicons/genicons
/aego /aego
/aego-editor /aego-editor
/aego-playground /aego-playground
/aego-runtime
*.syso
*.test *.test
/tests/golden/testdata/actual-*
tools/websmoke/node_modules/
tools/websmoke/golden/actual-*.png
+50 -411
View File
File diff suppressed because one or more lines are too long
+11 -166
View File
File diff suppressed because it is too large Load Diff
-8
View File
@@ -1,8 +0,0 @@
# Aego
A 2D and 3D game engine with an editor, a scripting language and a web export, written in Go.
- Documentation and the playground: <https://aego.akarpov.ru>
- Installing and building from source: [engine/res/docs/install.md](engine/res/docs/install.md)
- Contributing, check levels and rules for engine code: [docs/contributing.md](docs/contributing.md)
- Architecture and decisions: [ARCHITECTURE.md](ARCHITECTURE.md)
-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
-439
View File
File diff suppressed because it is too large Load Diff
-222
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2
View File
@@ -8,6 +8,7 @@ import (
"aego/core/log" "aego/core/log"
"aego/engine/base" "aego/engine/base"
"aego/engine/module" "aego/engine/module"
"aego/platform/input"
"aego/scene" "aego/scene"
"aego/script/bind" "aego/script/bind"
) )
@@ -15,6 +16,7 @@ import (
type Options struct { type Options struct {
Seed uint64 Seed uint64
Testing bool Testing bool
Input *input.AxisConfig
} }
type Entry struct { type Entry struct {
+2 -63
View File
@@ -4,22 +4,10 @@ import (
"aego/app/assembly" "aego/app/assembly"
"aego/engine/base" "aego/engine/base"
"aego/engine/module" "aego/engine/module"
"aego/modules/accessibility"
"aego/modules/audio"
"aego/modules/camera"
"aego/modules/dataasset"
"aego/modules/debug"
"aego/modules/display"
"aego/modules/gameplay"
inputmod "aego/modules/input"
"aego/modules/physics" "aego/modules/physics"
"aego/modules/render2d" "aego/modules/render2d"
"aego/modules/render3d" "aego/modules/render3d"
"aego/modules/savegame"
"aego/modules/scripting" "aego/modules/scripting"
settingsmod "aego/modules/settings"
"aego/modules/spawn"
"aego/modules/tween"
"aego/script/stdlib" "aego/script/stdlib"
) )
@@ -27,10 +15,6 @@ func core() assembly.Entry {
return assembly.Entry{ID: base.CoreID, New: func(assembly.Options) module.Module { return base.Core{} }} return assembly.Entry{ID: base.CoreID, New: func(assembly.Options) module.Module { return base.Core{} }}
} }
func inputEntry() assembly.Entry {
return assembly.Entry{ID: inputmod.ID, New: func(assembly.Options) module.Module { return inputmod.New() }}
}
func render2dEntry() assembly.Entry { func render2dEntry() assembly.Entry {
return assembly.Entry{ID: render2d.ID, New: func(assembly.Options) module.Module { return render2d.New() }} return assembly.Entry{ID: render2d.ID, New: func(assembly.Options) module.Module { return render2d.New() }}
} }
@@ -41,7 +25,7 @@ func render3dEntry() assembly.Entry {
func scriptEntry() assembly.Entry { func scriptEntry() assembly.Entry {
return assembly.Entry{ID: scripting.ID, New: func(o assembly.Options) module.Module { return assembly.Entry{ID: scripting.ID, New: func(o assembly.Options) module.Module {
return scripting.New(stdlib.Options{Seed: o.Seed, Testing: o.Testing}) return scripting.New(stdlib.Options{Seed: o.Seed, Testing: o.Testing, Input: o.Input})
}} }}
} }
@@ -49,51 +33,6 @@ func physicsEntry() assembly.Entry {
return assembly.Entry{ID: physics.ID, New: func(assembly.Options) module.Module { return physics.New() }} return assembly.Entry{ID: physics.ID, New: func(assembly.Options) module.Module { return physics.New() }}
} }
func debugEntry() assembly.Entry {
return assembly.Entry{ID: debug.ID, New: func(assembly.Options) module.Module { return debug.New() }}
}
func displayEntry() assembly.Entry {
return assembly.Entry{ID: display.ID, New: func(assembly.Options) module.Module { return display.New() }}
}
func audioEntry() assembly.Entry {
return assembly.Entry{ID: audio.ID, New: func(assembly.Options) module.Module { return audio.New() }}
}
func accessibilityEntry() assembly.Entry {
return assembly.Entry{ID: accessibility.ID, New: func(assembly.Options) module.Module { return accessibility.New() }}
}
func settingsEntry() assembly.Entry {
return assembly.Entry{ID: settingsmod.ID, Requires: []string{inputmod.ID}, New: func(assembly.Options) module.Module { return settingsmod.New() }}
}
func tweenEntry() assembly.Entry {
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 gameplayEntry() assembly.Entry {
return assembly.Entry{ID: gameplay.ID, Requires: []string{physics.ID, scripting.ID}, New: func(assembly.Options) module.Module { return gameplay.New() }}
}
func dataEntry() assembly.Entry {
return assembly.Entry{ID: dataasset.ID, New: func(assembly.Options) module.Module { return dataasset.New() }}
}
func spawnEntry() assembly.Entry {
return assembly.Entry{ID: spawn.ID, New: func(assembly.Options) module.Module { return spawn.New() }}
}
func savegameEntry() assembly.Entry {
return assembly.Entry{ID: savegame.ID, Requires: []string{scripting.ID, spawn.ID}, New: func(assembly.Options) module.Module { return savegame.New() }}
}
func Default() assembly.Catalog { func Default() assembly.Catalog {
return assembly.Catalog{core(), dataEntry(), inputEntry(), render2dEntry(), render3dEntry(), scriptEntry(), physicsEntry(), debugEntry(), return assembly.Catalog{core(), render2dEntry(), render3dEntry(), scriptEntry(), physicsEntry()}
displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), cameraEntry(), gameplayEntry(), 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(), scriptEntry(), physicsEntry()}
} }
-7
View File
@@ -1,7 +0,0 @@
package builtin
import "aego/app/assembly"
func Runtime() assembly.Catalog {
return Default()
}
+1 -5
View File
@@ -24,10 +24,6 @@ func Manifest(user ...module.Module) (*manifest.Manifest, error) {
} }
func AuditIcons(user ...module.Module) (*module.IconReport, error) { func AuditIcons(user ...module.Module) (*module.IconReport, error) {
return AuditIconsWith(nil, user...)
}
func AuditIconsWith(extra []module.IconUse, user ...module.Module) (*module.IconReport, error) {
types, err := assembly.NewTypes() types, err := assembly.NewTypes()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -36,7 +32,7 @@ func AuditIconsWith(extra []module.IconUse, user ...module.Module) (*module.Icon
if err != nil { if err != nil {
return nil, err return nil, err
} }
return module.AuditUses(extra, types, bind.NewManifest(), append(mods, user...)...) return module.Audit(types, bind.NewManifest(), append(mods, user...)...)
} }
func IconSources() []string { func IconSources() []string {
+1 -16
View File
@@ -2,16 +2,6 @@ package editors
import ( import (
"aego/app/assembly" "aego/app/assembly"
"aego/modules/audio"
audioeditor "aego/modules/audio/editor"
"aego/modules/camera"
cameraeditor "aego/modules/camera/editor"
"aego/modules/debug"
debugeditor "aego/modules/debug/editor"
"aego/modules/gameplay"
gameplayeditor "aego/modules/gameplay/editor"
inputmod "aego/modules/input"
inputeditor "aego/modules/input/editor"
"aego/modules/physics" "aego/modules/physics"
physicseditor "aego/modules/physics/editor" physicseditor "aego/modules/physics/editor"
"aego/modules/render2d" "aego/modules/render2d"
@@ -22,12 +12,7 @@ import (
func With(c assembly.Catalog) assembly.Catalog { func With(c assembly.Catalog) assembly.Catalog {
return c. return c.
With(inputmod.ID, inputeditor.Hook).
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(gameplay.ID, gameplayeditor.Hook).
With(audio.ID, audioeditor.Hook).
With(debug.ID, debugeditor.Hook)
} }
-11
View File
@@ -44,14 +44,3 @@ func (h hooked) Shutdown() {
sd.Shutdown() sd.Shutdown()
} }
} }
func (h hooked) Reconfigure(ctx *module.AttachContext) error {
if rc, ok := h.inner.(module.Reconfigurer); ok {
return rc.Reconfigure(ctx)
}
return nil
}
func (h hooked) Unwrap() module.Module {
return h.inner
}
+1 -32
View File
@@ -4,48 +4,17 @@ import (
"aego/app/assembly" "aego/app/assembly"
"aego/engine/base" "aego/engine/base"
"aego/engine/module" "aego/engine/module"
"aego/modules/audio"
"aego/modules/camera"
"aego/modules/debug"
"aego/modules/gameplay"
inputmod "aego/modules/input"
"aego/modules/physics"
"aego/modules/render2d" "aego/modules/render2d"
"aego/modules/render3d"
"aego/modules/savegame"
"aego/modules/scripting" "aego/modules/scripting"
"aego/modules/spawn"
"aego/modules/tween"
"aego/script/stdlib" "aego/script/stdlib"
"aego/script/vm"
) )
func Catalog() assembly.Catalog { func Catalog() assembly.Catalog {
return assembly.Catalog{ return assembly.Catalog{
{ID: base.CoreID, New: func(assembly.Options) module.Module { return base.Core{} }}, {ID: base.CoreID, New: func(assembly.Options) module.Module { return base.Core{} }},
{ID: inputmod.ID, New: func(assembly.Options) module.Module { return inputmod.New() }},
{ID: render2d.ID, New: func(assembly.Options) module.Module { return render2d.New() }}, {ID: render2d.ID, New: func(assembly.Options) module.Module { return render2d.New() }},
{ID: scripting.ID, New: func(o assembly.Options) module.Module { {ID: scripting.ID, New: func(o assembly.Options) module.Module {
return scripting.New(stdlib.Options{Seed: o.Seed, Testing: o.Testing}) return scripting.New(stdlib.Options{Seed: o.Seed, Testing: o.Testing, Input: o.Input})
}}, }},
{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: 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: gameplay.ID, Requires: []string{physics.ID, scripting.ID}, New: func(assembly.Options) module.Module { return gameplay.New() }},
{ID: render3d.ID, New: func(assembly.Options) module.Module { return render3d.New() }},
{ID: spawn.ID, New: func(assembly.Options) module.Module { return spawn.New() }},
{ID: savegame.ID, Requires: []string{scripting.ID, spawn.ID}, New: func(assembly.Options) module.Module { return savegame.New() }},
{ID: audio.ID, New: func(assembly.Options) module.Module { return audio.New() }},
} }
} }
const (
SandboxBudget = 2_000_000
SandboxHeap = 64 << 20
SandboxNodes = 10_000
)
func SandboxVM() vm.Options {
return vm.Options{Budget: SandboxBudget, MaxHeap: SandboxHeap}
}
-61
View File
@@ -1,61 +0,0 @@
package playground
import (
"io/fs"
"path"
"sort"
"strings"
"time"
"aego/contracts/persistence"
"aego/engine"
"aego/platform"
"aego/save"
)
// UseMemorySaves gives e a save store kept in memory, for runs in a browser page that must not touch the visitor's storage.
func UseMemorySaves(e *engine.Engine) {
e.SetService(persistence.StoreID, save.NewStoreFS("saves", &memUser{files: map[string][]byte{}}))
}
type memUser struct {
files map[string][]byte
}
func (m *memUser) ReadFile(p string) ([]byte, error) {
data, ok := m.files[path.Clean(p)]
if !ok {
return nil, fs.ErrNotExist
}
return append([]byte(nil), data...), nil
}
func (m *memUser) WriteFile(p string, data []byte) error {
m.files[path.Clean(p)] = append([]byte(nil), data...)
return nil
}
func (m *memUser) Remove(p string) error {
delete(m.files, path.Clean(p))
return nil
}
func (m *memUser) Stat(p string) (platform.UserEntry, error) {
data, ok := m.files[path.Clean(p)]
if !ok {
return platform.UserEntry{}, fs.ErrNotExist
}
return platform.UserEntry{Name: path.Base(p), Size: int64(len(data)), Mod: time.Time{}}, nil
}
func (m *memUser) List(dir string) ([]platform.UserEntry, error) {
prefix := path.Clean(dir) + "/"
var out []platform.UserEntry
for p, data := range m.files {
if rest, ok := strings.CutPrefix(p, prefix); ok && !strings.Contains(rest, "/") {
out = append(out, platform.UserEntry{Name: rest, Size: int64(len(data))})
}
}
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
return out, nil
}
-62
View File
@@ -1,62 +0,0 @@
package playground
import (
"encoding/base64"
"strings"
"aego/asset"
"aego/asset/loader"
"aego/core/ids"
"aego/script/bytecode"
"aego/vfs"
"aego/vfs/memfs"
)
// EncodeTry packs the compiled script of a try scene for a docs page: base64 modules joined by commas.
func EncodeTry(mods []*bytecode.Module) string {
parts := make([]string, len(mods))
for i, m := range mods {
parts[i] = base64.StdEncoding.EncodeToString(bytecode.Encode(m))
}
return strings.Join(parts, ",")
}
func DecodeTry(code string) ([]*bytecode.Module, error) {
if code == "" {
return nil, nil
}
var mods []*bytecode.Module
for _, part := range strings.Split(code, ",") {
b, err := base64.StdEncoding.DecodeString(part)
if err != nil {
return nil, err
}
m, err := bytecode.Decode(b)
if err != nil {
return nil, err
}
mods = append(mods, m)
}
return mods, nil
}
// MountTry gives the loader the prefabs of a try scene, keyed by asset id; none unmounts them.
func MountTry(l *loader.Loader, prefabs map[string]string) error {
idx := asset.NewIndex()
files := map[string][]byte{}
for key, text := range prefabs {
id, err := ids.ParseAssetID(key)
if err != nil {
return err
}
p := id.String() + ".aeprefab"
files[p] = []byte(text)
idx.Put(asset.Entry{ID: id, Kind: asset.ArtPrefab, Name: p, Source: p,
Artifacts: []asset.Artifact{{Kind: asset.ArtPrefab, Path: "try://" + p}}})
}
fs, err := vfs.New(vfs.Mount{Scheme: "try", ReadOnly: true, Source: memfs.New(files)})
if err != nil {
return err
}
return l.Mount(idx, fs)
}
-2
View File
@@ -9,7 +9,6 @@ import (
"sort" "sort"
"strings" "strings"
"aego/app/proc"
"aego/core/errs" "aego/core/errs"
"aego/core/log" "aego/core/log"
"aego/project" "aego/project"
@@ -170,7 +169,6 @@ func Build(p *project.Project, t Target, mode Mode, engineVersion string, lg *lo
cmd := exec.Command("go", args...) cmd := exec.Command("go", args...)
cmd.Dir = p.Root cmd.Dir = p.Root
cmd.Env = append(os.Environ(), "GOOS="+t.OS, "GOARCH="+t.Arch) cmd.Env = append(os.Environ(), "GOOS="+t.OS, "GOARCH="+t.Arch)
proc.Hide(cmd)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
os.RemoveAll(outDir) os.RemoveAll(outDir)
-3
View File
@@ -8,7 +8,6 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"aego/app/proc"
"aego/core/errs" "aego/core/errs"
"aego/core/log" "aego/core/log"
"aego/project" "aego/project"
@@ -33,14 +32,12 @@ func RunIcons(p *project.Project, engineVersion string, out io.Writer) (int, err
defer os.Remove(bin) defer os.Remove(bin)
build := exec.Command("go", "build", "-tags", strings.Join(t.Tags, ","), "-o", bin, "./.aego/generated/icons") build := exec.Command("go", "build", "-tags", strings.Join(t.Tags, ","), "-o", bin, "./.aego/generated/icons")
build.Dir = p.Root build.Dir = p.Root
proc.Hide(build)
if output, err := build.CombinedOutput(); err != nil { if output, err := build.CombinedOutput(); err != nil {
return 2, errs.New(errs.BuildFailed, "native icons helper does not compile", return 2, errs.New(errs.BuildFailed, "native icons helper does not compile",
log.F("output", strings.TrimSpace(string(output)))) log.F("output", strings.TrimSpace(string(output))))
} }
run := exec.Command(bin) run := exec.Command(bin)
run.Dir, run.Stdout, run.Stderr = p.Root, out, out run.Dir, run.Stdout, run.Stderr = p.Root, out, out
proc.Hide(run)
err = run.Run() err = run.Run()
var ee *exec.ExitError var ee *exec.ExitError
if errors.As(err, &ee) { if errors.As(err, &ee) {
-3
View File
@@ -6,7 +6,6 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"aego/app/proc"
"aego/core/errs" "aego/core/errs"
"aego/core/log" "aego/core/log"
"aego/project" "aego/project"
@@ -53,7 +52,6 @@ func BuildManifest(p *project.Project, engineVersion string, lg *log.Logger) (st
defer os.Remove(bin) defer os.Remove(bin)
build := exec.Command("go", "build", "-tags", strings.Join(t.Tags, ","), "-o", bin, "./.aego/generated/manifest") build := exec.Command("go", "build", "-tags", strings.Join(t.Tags, ","), "-o", bin, "./.aego/generated/manifest")
build.Dir = p.Root build.Dir = p.Root
proc.Hide(build)
if output, err := build.CombinedOutput(); err != nil { if output, err := build.CombinedOutput(); err != nil {
return "", errs.New(errs.BuildFailed, "native bindings helper does not compile", return "", errs.New(errs.BuildFailed, "native bindings helper does not compile",
log.F("output", strings.TrimSpace(string(output)))) log.F("output", strings.TrimSpace(string(output))))
@@ -61,7 +59,6 @@ func BuildManifest(p *project.Project, engineVersion string, lg *log.Logger) (st
tmp := out + ".tmp" tmp := out + ".tmp"
run := exec.Command(bin, tmp) run := exec.Command(bin, tmp)
run.Dir = p.Root run.Dir = p.Root
proc.Hide(run)
if output, err := run.CombinedOutput(); err != nil { if output, err := run.CombinedOutput(); err != nil {
os.Remove(tmp) os.Remove(tmp)
return "", errs.New(errs.BuildFailed, "native module Register failed", return "", errs.New(errs.BuildFailed, "native module Register failed",
-33
View File
@@ -1,33 +0,0 @@
package brand
import (
"bytes"
"embed"
"image"
"image/png"
"strconv"
)
//go:embed png/aego-*.png
var files embed.FS
var windowSizes = []int{16, 24, 32, 48, 64, 128, 256}
func PNG(n int) ([]byte, bool) {
data, err := files.ReadFile("png/aego-" + strconv.Itoa(n) + ".png")
return data, err == nil
}
func Window() []image.Image {
out := make([]image.Image, 0, len(windowSizes))
for _, n := range windowSizes {
data, ok := PNG(n)
if !ok {
continue
}
if img, err := png.Decode(bytes.NewReader(data)); err == nil {
out = append(out, img)
}
}
return out
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

+5 -53
View File
File diff suppressed because it is too large Load Diff
-140
View File
File diff suppressed because it is too large Load Diff
-27
View File
@@ -1,27 +0,0 @@
package crash
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestBundleHidesHome(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
root := filepath.Join(home, "game")
dir, err := Write(Bundle{Root: root, Kind: "panic", Where: "panel inspector", Panic: "boom in " + root, Stack: []byte(root + "/x.go:1")})
if err != nil {
t.Fatal(err)
}
for _, f := range []string{"info.aetext", "go.stack"} {
data, err := os.ReadFile(filepath.Join(dir, f))
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(data), home) || !strings.Contains(string(data), "~/game") {
t.Fatalf("%s is not sanitized: %s", f, data)
}
}
}
-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)
}
}
-70
View File
@@ -1,70 +0,0 @@
package docstool
import (
"fmt"
"io"
"strings"
"aego/app/playground"
"aego/engine/docs"
"aego/engine/module"
)
// CheckExamples loads the example scene of every module code, validates it and runs it headless; the code must be among the problems it reports (D146).
func CheckExamples(reg *docs.Registry, w io.Writer) int {
pg, err := playground.New()
if err != nil {
fmt.Fprintln(w, "examples: cannot start the docs runtime:", err)
return 1
}
pg.SetGame(playground.GameConfig{Headless: true, FrameTime: 1.0 / 60})
defer func() {
if g := pg.Game(); g != nil {
g.Close()
}
}()
bad := 0
for _, e := range reg.All() {
if e.Kind != docs.Code || e.Diag == nil || e.Diag.Example == "" {
continue
}
found, why := false, ""
code := ""
if e.Diag.ExampleScript != "" {
c, err := pg.TryCode(e.Diag.ExampleScript)
if err != nil {
fmt.Fprintf(w, "%s: error: example script does not compile: %v\n", e.ID, err)
bad++
continue
}
code = c
}
if res := pg.PlayScene("example-"+e.ID, []byte(e.Diag.Example), code, nil); !res.Started {
why = "does not load: " + res.Error
} else {
for _, d := range module.ValidateScene(pg.Game().Scene(), nil) {
found = found || d.Code == e.ID
}
if err := pg.Game().Step(tryFrames); err != nil {
why = "stops the engine: " + err.Error()
}
}
poll := pg.Poll()
for _, f := range poll.Faults {
found = found || f.Code == e.ID
}
for _, l := range poll.Log {
found = found || strings.Contains(l.Text, e.ID)
}
switch {
case why != "":
fmt.Fprintf(w, "%s: error: example %s %s\n", e.ID, e.Diag.ExampleFile, why)
bad++
case !found:
fmt.Fprintf(w, "%s: error: example of %s does not produce %s\n", e.ID, e.ID, e.ID)
bad++
}
}
pg.Stop()
return bad
}
+4 -20
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,8 +20,7 @@ import (
"aego/script/workspace" "aego/script/workspace"
) )
// build assembles the registry; with pg it also compiles the diagnostics examples and the try scripts. func build(p *project.Project, mods []module.Module) (*docs.Registry, *workspace.Workspace, error) {
func build(p *project.Project, mods []module.Module, pg *playground.Playground) (*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
@@ -43,10 +41,6 @@ func build(p *project.Project, mods []module.Module, pg *playground.Playground)
return nil, nil, err return nil, nil, err
} }
src := docs.SourcesOf(bm, types.Schema(), man) src := docs.SourcesOf(bm, types.Schema(), man)
if pg != nil {
src.Diagnose = diagnoser(pg)
src.CompileTry = pg.TryCode
}
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})
@@ -67,17 +61,7 @@ func build(p *project.Project, mods []module.Module, pg *playground.Playground)
} }
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, and the try scripts compiled.
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, pg)
return r, err return r, err
} }
@@ -100,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"
+27 -130
View File
File diff suppressed because it is too large Load Diff

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