Author SHA1 Message Date
sanspie 3f5584d062 Stage 9.5b3b: component, settings and CLI reference (D292) 2026-10-20 00:11:43 +03:00
sanspie 6ce72aa1ec Stage 9.5b3a: docs IA and site shell (D292) 2026-10-19 23:27:11 +03:00
sanspie 9ee2b0b05c Stage 9.5b2: playground with Game View (D291) 2026-10-19 23:10:55 +03:00
sanspie 99dacd7a75 Stage 9.5b1: web platform and WebGL2 backend (D289–D290) 2026-10-19 22:47:08 +03:00
sanspie 5447383f2b gitignore: aego-runtime binary and .syso resources 2026-10-19 20:39:03 +03:00
sanspie 6745d027fb Stage 9.5a: export v1 (D286–D288) 2026-10-19 20:27:58 +03:00
sanspie e7dd01371e script/abi: Parse accepts the untyped Handle<> the encoder writes 2026-10-19 20:27:40 +03:00
sanspie 68802195b6 Stage 9.0: editor workflow 2026-10-19 20:27:40 +03:00
sanspie 7cba364aed Stage 9.4d4: aego test --determinism 2026-10-19 02:18:58 +03:00
sanspie 35e9849cae Stage 9.4d3: keyframes 2026-10-19 02:15:10 +03:00
sanspie f21d455d68 Stage 9.4d3a: CloneInto, spawn pool 2026-10-19 01:51:39 +03:00
sanspie d1a16d8208 Stage 9.4d2: rewind 2026-10-19 01:47:39 +03:00
sanspie 2a1826f876 Stage 9.4d1: save games 2026-10-19 00:37:50 +03:00
sanspie 3af2d5d977 Stage 9.4c: data assets, validators 2026-10-18 22:58:02 +03:00
sanspie b61a1656ad Stage 9.4b: signals, tags, spawn 2026-10-18 22:58:02 +03:00
sanspie 0fbf22a090 Stage 9.4a: curves, tween, timers
core/curve with presets, keys, wraps; KindCurve in schema, scenes, prefabs and the inspector curve editor.
modules/tween: tweens and timers in per-world pools, game and unscaled domains, script field targets via contracts/scriptfields.
Host handle types (abi.Handle, bind.Handle, manifest v5), yield on awaitable handles via contracts/scriptwait.
Script modules aego/curve, aego/tween, aego/timer; platformer enemy uses a cooldown and a fade-out tween.
Docs: tween.md, curves.md, language reference; golden for the curve editor.
2026-10-18 21:32:39 +03:00
sanspie 5e763aa73c Stage 9.2–9.3: input and settings 2026-10-18 21:32:39 +03:00
sanspie 6093ca9d8b Stage 9.1: time and debug 2026-10-17 23:14:21 +03:00
969 changed files with 66394 additions and 2823 deletions
+11 -1
View File
@@ -94,4 +94,14 @@ jobs:
- name: example project
run: |
/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"
+6
View File
@@ -11,4 +11,10 @@ tools/genicons/genicons
/aego
/aego-editor
/aego-playground
/aego-runtime
*.syso
*.test
/tests/golden/testdata/actual-*
tools/websmoke/node_modules/
tools/websmoke/golden/actual-*.png
+176 -38
View File
File diff suppressed because it is too large Load Diff
+61 -5
View File
File diff suppressed because it is too large Load Diff
-2
View File
@@ -8,7 +8,6 @@ import (
"aego/core/log"
"aego/engine/base"
"aego/engine/module"
"aego/platform/input"
"aego/scene"
"aego/script/bind"
)
@@ -16,7 +15,6 @@ import (
type Options struct {
Seed uint64
Testing bool
Input *input.AxisConfig
}
type Entry struct {
+54 -3
View File
@@ -4,10 +4,20 @@ import (
"aego/app/assembly"
"aego/engine/base"
"aego/engine/module"
"aego/modules/accessibility"
"aego/modules/audio"
"aego/modules/dataasset"
"aego/modules/debug"
"aego/modules/display"
inputmod "aego/modules/input"
"aego/modules/physics"
"aego/modules/render2d"
"aego/modules/render3d"
"aego/modules/savegame"
"aego/modules/scripting"
settingsmod "aego/modules/settings"
"aego/modules/spawn"
"aego/modules/tween"
"aego/script/stdlib"
)
@@ -15,6 +25,10 @@ func core() assembly.Entry {
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 {
return assembly.Entry{ID: render2d.ID, New: func(assembly.Options) module.Module { return render2d.New() }}
}
@@ -25,7 +39,7 @@ func render3dEntry() assembly.Entry {
func scriptEntry() assembly.Entry {
return assembly.Entry{ID: scripting.ID, New: func(o assembly.Options) module.Module {
return scripting.New(stdlib.Options{Seed: o.Seed, Testing: o.Testing, Input: o.Input})
return scripting.New(stdlib.Options{Seed: o.Seed, Testing: o.Testing})
}}
}
@@ -33,6 +47,43 @@ func physicsEntry() assembly.Entry {
return assembly.Entry{ID: physics.ID, New: func(assembly.Options) module.Module { return physics.New() }}
}
func Default() assembly.Catalog {
return assembly.Catalog{core(), render2dEntry(), render3dEntry(), scriptEntry(), physicsEntry()}
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 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 {
return assembly.Catalog{core(), dataEntry(), inputEntry(), render2dEntry(), render3dEntry(), scriptEntry(), physicsEntry(), debugEntry(),
displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), spawnEntry(), savegameEntry()}
}
+1 -1
View File
@@ -3,5 +3,5 @@ package builtin
import "aego/app/assembly"
func Headless() assembly.Catalog {
return assembly.Catalog{core(), scriptEntry(), physicsEntry()}
return assembly.Catalog{core(), dataEntry(), inputEntry(), scriptEntry(), physicsEntry(), debugEntry(), displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), spawnEntry(), savegameEntry()}
}
+7
View File
@@ -0,0 +1,7 @@
package builtin
import "aego/app/assembly"
func Runtime() assembly.Catalog {
return Default()
}
+5 -1
View File
@@ -24,6 +24,10 @@ func Manifest(user ...module.Module) (*manifest.Manifest, 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()
if err != nil {
return nil, err
@@ -32,7 +36,7 @@ func AuditIcons(user ...module.Module) (*module.IconReport, error) {
if err != nil {
return nil, err
}
return module.Audit(types, bind.NewManifest(), append(mods, user...)...)
return module.AuditUses(extra, types, bind.NewManifest(), append(mods, user...)...)
}
func IconSources() []string {
+7 -1
View File
@@ -2,6 +2,10 @@ package editors
import (
"aego/app/assembly"
"aego/modules/debug"
debugeditor "aego/modules/debug/editor"
inputmod "aego/modules/input"
inputeditor "aego/modules/input/editor"
"aego/modules/physics"
physicseditor "aego/modules/physics/editor"
"aego/modules/render2d"
@@ -12,7 +16,9 @@ import (
func With(c assembly.Catalog) assembly.Catalog {
return c.
With(inputmod.ID, inputeditor.Hook).
With(render2d.ID, render2deditor.Hook).
With(render3d.ID, render3deditor.Hook).
With(physics.ID, physicseditor.Hook)
With(physics.ID, physicseditor.Hook).
With(debug.ID, debugeditor.Hook)
}
+11
View File
@@ -44,3 +44,14 @@ func (h hooked) 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
}
+7 -1
View File
@@ -4,6 +4,9 @@ import (
"aego/app/assembly"
"aego/engine/base"
"aego/engine/module"
"aego/modules/debug"
inputmod "aego/modules/input"
"aego/modules/physics"
"aego/modules/render2d"
"aego/modules/scripting"
"aego/script/stdlib"
@@ -12,9 +15,12 @@ import (
func Catalog() assembly.Catalog {
return assembly.Catalog{
{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: scripting.ID, New: func(o assembly.Options) module.Module {
return scripting.New(stdlib.Options{Seed: o.Seed, Testing: o.Testing, Input: o.Input})
return scripting.New(stdlib.Options{Seed: o.Seed, Testing: o.Testing})
}},
{ID: physics.ID, New: func(assembly.Options) module.Module { return physics.New() }},
{ID: debug.ID, New: func(assembly.Options) module.Module { return debug.New() }},
}
}
+48 -2
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
@@ -0,0 +1,27 @@
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)
}
}
}
+35 -2
View File
@@ -16,6 +16,7 @@ import (
"strings"
"time"
"aego/engine"
"aego/engine/module"
"aego/project"
)
@@ -81,6 +82,27 @@ func (e *Env) goCmd(dir string, env []string, args ...string) error {
return cmd.Run()
}
const defaultBranch = "master"
func (e *Env) git(args ...string) string {
out, err := exec.Command("git", append([]string{"-C", e.Engine}, args...)...).Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
func (e *Env) commit() string {
return e.git("rev-parse", "--short", "HEAD")
}
func (e *Env) branch() string {
if b := e.git("rev-parse", "--abbrev-ref", "HEAD"); b != "" && b != "HEAD" {
return b
}
return defaultBranch
}
func (e *Env) Build(out string, play bool) error {
if e.Engine == "" {
return errors.New("cannot locate the aego source tree; pass --engine <dir> or set AEGO_ROOT")
@@ -101,12 +123,18 @@ func (e *Env) Build(out string, play bool) error {
if err := e.export(tmp.Name()); err != nil {
return err
}
args := []string{"run", ".", "-in", tmp.Name(), "-out", abs}
args := []string{"run", ".", "-in", tmp.Name(), "-out", abs, "-version", engine.Version}
if play {
args = append(args, "-playground")
}
if e.Repo != "" {
args = append(args, "-repo", e.Repo)
args = append(args, "-repo", e.Repo, "-edit", strings.TrimRight(e.Repo, "/")+"/src/branch/"+e.branch())
}
if c := e.commit(); c != "" {
args = append(args, "-commit", c)
}
if hero := filepath.Join(e.Engine, "docs", "hero.png"); fileExists(hero) {
args = append(args, "-hero", hero)
}
if err := e.goCmd(filepath.Join(e.Engine, "tools", "gendocs"), nil, args...); err != nil {
return fmt.Errorf("gendocs failed: %w", err)
@@ -117,6 +145,11 @@ func (e *Env) Build(out string, play bool) error {
return nil
}
func fileExists(p string) bool {
st, err := os.Stat(p)
return err == nil && !st.IsDir()
}
func copyFile(src, dst string) error {
data, err := os.ReadFile(src)
if err != nil {
+97
View File
@@ -0,0 +1,97 @@
package edit
import (
"strconv"
"strings"
"aego/app/crash"
"aego/core/ids"
"aego/core/log"
"aego/editor/core/context"
"aego/scene"
"aego/scene/sceneio"
"aego/script/runtime"
"aego/script/vm"
)
func scriptStack(f *vm.Fault) string {
var b strings.Builder
for _, fr := range f.Stack {
b.WriteString(fr.File + ":" + strconv.Itoa(fr.Line) + " " + fr.Function + "\n")
}
return b.String()
}
func names(s *scene.Scene) func(ids.NodeID) string {
return func(id ids.NodeID) string {
if h := s.Handle(id); !h.IsZero() && s.IsAlive(h) {
return s.Path(h)
}
return id.String()[:8]
}
}
func (s *Session) moduleIDs() string {
var out []string
for _, m := range s.Modules.Modules {
out = append(out, m.ID)
}
return strings.Join(out, ",")
}
func (s *Session) crash(kind, where string, rec any, stack []byte, script string) {
defer func() { recover() }()
b := crash.Bundle{
Root: s.Project.Root, Kind: kind, Where: where, Panic: rec, Stack: stack, Script: script,
Trace: s.Engine.Trace(), Log: s.ring,
Info: [][2]string{{"project", s.Project.Name}, {"gpu", s.Engine.Caps().Renderer}, {"modules", s.moduleIDs()}},
}
var ctx *context.Editor
if s.App != nil {
ctx = s.App.Context()
}
if ctx != nil {
ctx.Docs.WriteRecovery()
if pc := s.App.Play(); pc != nil && pc.World() != nil {
b.Events = pc.Recorder()
if sc := pc.Scene(); sc != nil {
b.Names = names(sc)
if s.crashScene {
b.Scene = sceneio.Save(sc)
}
}
}
}
dir, err := crash.Write(b)
if err != nil {
s.log.Error("cannot write the crash bundle", log.F("err", err.Error()))
return
}
s.log.Error("crash bundle written", log.F("dir", dir), log.F("where", where))
if ctx != nil {
ctx.Notify(context.LevelError, "Crash report saved to "+dir)
}
}
func (s *Session) scriptPanic(boundary string, v any, stack []byte) {
s.crash("panic", boundary, v, stack, "")
}
func (s *Session) fault(rep runtime.FaultReport) {
if rep.Fault != nil && rep.Fault.Code == vm.CodeNativePanic {
s.crash("fault", rep.Type, rep.Fault.Message, []byte(rep.Fault.Detail), scriptStack(rep.Fault))
}
}
func (s *Session) spike() {
if s.spikeMs <= 0 {
return
}
ms := s.Engine.Stats().FrameTime * 1000
now := s.Engine.Platform().Now()
if ms < s.spikeMs || now-s.lastSpike < 10 {
return
}
s.lastSpike = now
s.crash("spike", strconv.FormatFloat(ms, 'f', 1, 64)+" ms frame", nil, nil, "")
}
+32
View File
File diff suppressed because it is too large Load Diff
+89
View File
@@ -0,0 +1,89 @@
package edit
import (
"os/exec"
"runtime"
"aego/app/bootstrap"
"aego/app/export"
"aego/app/projects"
"aego/asset/pipeline"
"aego/editor/core/services"
"aego/engine/problems"
"aego/project"
)
type exporter struct {
p *project.Project
pl *pipeline.Pipeline
engine string
}
func newExporter(p *project.Project, pl *pipeline.Pipeline) services.Exporter {
if pl == nil {
return nil
}
root, err := projects.EngineRoot("")
if err != nil {
return nil
}
return &exporter{p: p, pl: pl, engine: root}
}
func (x *exporter) Targets() []string {
return export.Targets()
}
func (x *exporter) Out(target, profile string) string {
t, err := export.ParseTarget(target)
if err != nil {
return ""
}
prof, err := export.ParseProfile(profile)
if err != nil {
return ""
}
return export.DefaultOut(x.p, t, prof)
}
func (x *exporter) Prepare() any {
return export.Take(x.pl)
}
func (x *exporter) Check(prep any, profile string) []problems.Diagnostic {
snap, ok := prep.(*export.Snapshot)
if !ok {
return nil
}
prof, err := export.ParseProfile(profile)
if err != nil {
return nil
}
return export.Check(x.p, snap, prof, bootstrap.NeedsNative(x.p))
}
func (x *exporter) Export(prep any, req services.ExportRequest, progress func(float32, string)) (services.ExportReport, error) {
snap, _ := prep.(*export.Snapshot)
rep, err := export.Export(export.Options{
Project: x.p, Engine: x.engine, Target: req.Target, Profile: req.Profile, Out: req.Out,
Snapshot: snap, Progress: progress,
})
var out services.ExportReport
if rep != nil {
out = services.ExportReport{Out: rep.Out, Lines: rep.Lines(), Issues: rep.Diagnostics, Blocked: rep.Blocked}
}
return out, err
}
func (x *exporter) Reveal(path string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmd = exec.Command("open", path)
case "windows":
cmd = exec.Command("explorer", path)
default:
cmd = exec.Command("xdg-open", path)
}
return cmd.Start()
}
+59 -4
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
package edit
import (
"os"
"runtime"
"testing"
)
func TestMain(m *testing.M) {
dir, err := os.MkdirTemp("", "aego-config-")
if err != nil {
panic(err)
}
switch runtime.GOOS {
case "darwin":
os.Setenv("HOME", dir)
case "windows":
os.Setenv("AppData", dir)
default:
os.Setenv("XDG_CONFIG_HOME", dir)
}
code := m.Run()
os.RemoveAll(dir)
os.Exit(code)
}
+3 -3
View File
@@ -170,7 +170,7 @@ func TestProfilerSectionsFollowPlay(t *testing.T) {
}
prof := s.App.Profiler()
drawPanel(s, prof)
expectSections(t, prof.Sections(), "Frame")
expectSections(t, prof.Sections(), "Frame", "Memory")
pc := s.App.Play()
if err := pc.Start(d); err != nil {
t.Fatal(err)
@@ -179,7 +179,7 @@ func TestProfilerSectionsFollowPlay(t *testing.T) {
s.Step()
}
drawPanel(s, prof)
expectSections(t, prof.Sections(), "Frame", "Game", "Scripts", "Physics")
expectSections(t, prof.Sections(), "Frame", "Memory", "Game", "Scripts", "Physics")
if _, err := ctx.Docs.Open("scenes/other.aescene"); err != nil {
t.Fatal(err)
}
@@ -189,5 +189,5 @@ func TestProfilerSectionsFollowPlay(t *testing.T) {
pc.Stop()
s.Step()
drawPanel(s, prof)
expectSections(t, prof.Sections(), "Frame")
expectSections(t, prof.Sections(), "Frame", "Memory")
}
+11 -1
View File
@@ -36,6 +36,8 @@ type scriptHost struct {
probRev uint64
probs []context.ScriptProblem
stats context.ScriptStats
fault func(runtime.FaultReport)
panicked func(boundary string, v any, stack []byte)
}
func (h *scriptHost) channel() *log.ChannelLogger {
@@ -158,7 +160,7 @@ func (h *scriptHost) Attach(w *engine.World, s *scene.Scene) (func(), error) {
if err := h.sync(); err != nil {
return nil, err
}
rt := runtime.New(runtime.Options{Bindings: h.bindings, Schema: h.schema, Log: h.log})
rt := runtime.New(runtime.Options{Bindings: h.bindings, Schema: h.schema, Log: h.log, OnFault: h.fault, OnPanic: h.panicked})
if err := rt.Link(h.prog.Ordered()); err != nil {
return nil, err
}
@@ -193,3 +195,11 @@ func (h *scriptHost) Attach(w *engine.World, s *scene.Scene) (func(), error) {
}
}, nil
}
func (h *scriptHost) Watch(w *engine.World, node scene.NodeHandle, script, field string, lane int) (string, float64, bool, bool) {
rt, ok := engine.ServiceOf[*runtime.Runtime](w, runtime.ServiceID)
if !ok || rt == nil {
return "", 0, false, false
}
return rt.WatchField(node, script, field, lane)
}
+193
View File
File diff suppressed because it is too large Load Diff
+198
View File
File diff suppressed because it is too large Load Diff
+187
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
package macos
import (
"encoding/binary"
"image"
"strings"
"testing"
)
func TestICNSHeader(t *testing.T) {
data, err := ICNS(image.NewNRGBA(image.Rect(0, 0, 64, 64)))
if err != nil {
t.Fatal(err)
}
if string(data[:4]) != "icns" || int(binary.BigEndian.Uint32(data[4:])) != len(data) {
t.Fatal("bad icns header")
}
}
func TestPlistEscapesAndBundleID(t *testing.T) {
p := string(Plist(Info{Name: "A & B", Executable: "ab", ID: BundleID("My_Game/x"), Version: "1.0"}, "icon"))
if !strings.Contains(p, "A &amp; B") || !strings.Contains(p, "dev.aego.my.game.x") {
t.Fatal(p)
}
}
+65
View File
@@ -0,0 +1,65 @@
package export
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"aego/app/export/icon"
"aego/app/export/macos"
"aego/core/errs"
)
const desktopEntry = "[Desktop Entry]\nType=Application\nName=%s\nExec=sh -c 'cd \"$(dirname \"%%k\")\" && exec ./%s'\nTerminal=false\nCategories=Game;\n"
func (j *job) pack() error {
switch j.t.OS {
case "linux":
return j.packLinux()
case "darwin":
return j.packDarwin()
}
return nil
}
func (j *job) packLinux() error {
if j.icon != nil {
data, err := icon.PNG(icon.Resize(j.icon, 256, 256))
if err != nil {
return err
}
if err := os.WriteFile(filepath.Join(j.out, "icon.png"), data, 0o644); err != nil {
return errs.Wrap(err, errs.VFSIO, "cannot write the icon")
}
}
entry := fmt.Sprintf(desktopEntry, j.p.Name, j.l.exe)
if err := os.WriteFile(filepath.Join(j.out, j.l.exe+".desktop"), []byte(entry), 0o755); err != nil {
return errs.Wrap(err, errs.VFSIO, "cannot write the .desktop file")
}
return nil
}
func (j *job) packDarwin() error {
info := macos.Info{
Name: j.p.Name, Executable: j.l.exe, ID: macos.BundleID(j.p.Module),
Version: GameVersion(j.p), MinOS: "11.0", Category: "public.app-category.games",
}
if err := macos.Fill(j.l.app, info, j.icon); err != nil {
return err
}
if runtime.GOOS != "darwin" {
return nil
}
path, err := exec.LookPath("codesign")
if err != nil {
return nil
}
out, err := exec.Command(path, "--force", "--deep", "--sign", "-", j.l.app).CombinedOutput()
if err != nil {
return errs.New(errs.BuildFailed, "ad-hoc signing failed: "+strings.TrimSpace(string(out)))
}
return nil
}
File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
package export
import (
"slices"
"aego/app/projects"
"aego/app/run"
"aego/asset"
"aego/asset/pipeline"
"aego/core/log"
"aego/project"
"aego/vfs"
)
type Snapshot struct {
idx *asset.Index
files vfs.FS
assets []pipeline.AssetInfo
issues []pipeline.MetaIssue
}
func Take(pl *pipeline.Pipeline) *Snapshot {
issues, _ := pl.MetaIssues()
return &Snapshot{idx: pl.Index().Clone(), files: pl.Artifacts(), assets: pl.Assets(), issues: slices.Clone(issues)}
}
func Fresh(p *project.Project) (*Snapshot, func(), error) {
ws, err := projects.OpenWorkspace(p, "", false)
if err != nil {
return nil, nil, err
}
imps, err := run.Importers(ws)
if err != nil {
return nil, nil, err
}
pl, err := pipeline.Open(p, imps, log.Nop())
if err != nil {
return nil, nil, err
}
if _, _, err := pl.Scan(); err != nil {
pl.Close()
return nil, nil, err
}
if _, err := pl.ImportAll(); err != nil {
pl.Close()
return nil, nil, err
}
return Take(pl), pl.Close, nil
}
func (s *Snapshot) Revision() uint64 {
return 1
}
func (s *Snapshot) Index() *asset.Index {
return s.idx
}
func (s *Snapshot) Artifacts() vfs.FS {
return s.files
}
File diff suppressed because it is too large Load Diff
+34
View File
@@ -0,0 +1,34 @@
package winres
import (
"slices"
"testing"
)
func TestSysoHasResourceTypes(t *testing.T) {
data, err := Syso("amd64", []Icon{{Size: 16, PNG: []byte{1, 2, 3}}, {Size: 256, PNG: []byte{4, 5}}},
Info{Product: "Game", Original: "game.exe", Version: ParseVersion("1.2.3-beta")}, true)
if err != nil {
t.Fatal(err)
}
types, err := Types(data)
if err != nil {
t.Fatal(err)
}
if !slices.Equal(types, []uint16{rtIcon, rtGroupIcon, rtVersion, rtManifest}) {
t.Fatalf("resource types %v", types)
}
}
func TestParseVersion(t *testing.T) {
if v := ParseVersion("v2.10.3-rc1"); v != (Version{2, 10, 3, 0}) {
t.Fatalf("got %v", v)
}
}
func TestVersionInfoIsAligned(t *testing.T) {
b := versionInfo(Info{Product: "G", Version: Version{1, 0, 0, 0}})
if int(b[0])|int(b[1])<<8 != len(b) {
t.Fatalf("wLength %d, size %d", int(b[0])|int(b[1])<<8, len(b))
}
}
+2
View File
@@ -1,6 +1,7 @@
package app
import (
"aego/asset/importers/preset"
"sort"
"aego/asset/importers/atlas"
@@ -20,6 +21,7 @@ func CoreImporters() (*pipeline.ImporterRegistry, error) {
atlas.Wrap(texImporter.New()),
scenefile.NewScene(),
scenefile.NewPrefab(),
preset.New(),
} {
if err := reg.Register(imp); err != nil {
return nil, err
+41
View File
@@ -0,0 +1,41 @@
package app
import (
"aego/asset/pipeline"
"aego/engine/problems"
)
const OwnerAssets = "assets"
func metaSeverity(k pipeline.MetaIssueKind) problems.Severity {
switch k {
case pipeline.MetaDuplicate:
return problems.Error
case pipeline.MetaStale:
return problems.Info
}
return problems.Warning
}
func MetaProblems(pl *pipeline.Pipeline) problems.Source {
return problems.Source{
Revision: func() uint64 {
_, rev := pl.MetaIssues()
return rev
},
Append: func(dst []problems.Diagnostic) []problems.Diagnostic {
list, _ := pl.MetaIssues()
for _, is := range list {
dst = append(dst, problems.Diagnostic{
Owner: OwnerAssets,
Code: is.Code(),
Severity: metaSeverity(is.Kind),
Message: is.Message(),
Help: "Assets → Repair Meta Files… will " + is.Action(),
Subject: problems.Subject{Kind: problems.SubjectFile, File: is.Path},
})
}
return dst
},
}
}
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
.aego/
exports/
+8
View File
@@ -0,0 +1,8 @@
# game
Created from the Aego 2d template.
aego edit .
aego run .
Scenes live in scenes/, scripts in scripts/<package>/, textures and other assets in assets/.
+32
View File
@@ -0,0 +1,32 @@
project version=1 {
name="playground-examples"
module="game"
mainScene=asset("06e1b9da8af4e739c5cbc7693f032366")
native=false
window {
width=1280
height=720
title="Aego Playground"
}
input version=2 {
stack=[Gameplay]
action {
name=Move
type=axis1
bind=["Key.A/Key.D", "Key.Left/Key.Right", "Pad.LeftX"]
}
action {
name=Jump
type=button
bind=["Key.Space", "Key.W", "Key.Up", "Pad.A"]
buffer=0.1
}
context {
name=Gameplay
actions=[Move, Jump]
}
}
physics {
gravity=[0, 9.81]
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,6 @@
meta version=1 {
id="76725b4589e01ff6a3b47de8da279435"
importer="scene"
importerVersion=1
settings {}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,6 @@
meta version=1 {
id="06e1b9da8af4e739c5cbc7693f032366"
importer="scene"
importerVersion=1
settings {}
}
@@ -0,0 +1,18 @@
package boxes
import "aego/input"
script Kick {
@export
var Impulse Vec2 = Vec2{0, -4}
func Update(dt float) {
if !input.Pressed("Jump") {
return
}
body := node.Get<Body2D>()
if body != nil {
body.ApplyImpulse(Impulse)
}
}
}
@@ -0,0 +1,6 @@
meta version=1 {
id="7380e5e549612d38c8073d6d8a6d545d"
importer="script"
importerVersion=1
settings {}
}
@@ -0,0 +1,48 @@
package platformer
import (
"aego/input"
"aego/log"
"aego/physics"
)
script Player {
@export(min=0, max=20, unit="u/s")
var Speed float = 6
@export(gizmo="vector")
var JumpImpulse Vec2 = Vec2{0, -6}
var ground int
func Ready() {
log.Info("A/D or arrows to run, Space to jump")
}
func OnCollisionEnter(other NodeRef, c physics.Contact) {
if c.Normal.Y < -0.5 {
ground++
}
}
func OnCollisionExit(other NodeRef, c physics.Contact) {
if c.Normal.Y < -0.5 && ground > 0 {
ground--
}
}
func FixedUpdate(dt float) {
body := node.Get<Body2D>()
if body == nil {
return
}
v := body.Velocity
v.X = input.Axis("Move") * Speed
body.SetVelocity(v)
if ground > 0 && input.Buffered("Jump", 0) {
input.Consume("Jump")
body.SetVelocity(Vec2{v.X, 0})
body.ApplyImpulse(JumpImpulse)
}
}
}
@@ -0,0 +1,6 @@
meta version=1 {
id="4e19fda2519b87c1f742a049a6887c27"
importer="script"
importerVersion=1
settings {}
}

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