Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f5584d062 | ||
|
|
6ce72aa1ec | ||
|
|
9ee2b0b05c | ||
|
|
99dacd7a75 | ||
|
|
5447383f2b | ||
|
|
6745d027fb | ||
|
|
e7dd01371e | ||
|
|
68802195b6 | ||
|
|
7cba364aed | ||
|
|
35e9849cae | ||
|
|
f21d455d68 | ||
|
|
d1a16d8208 | ||
|
|
2a1826f876 | ||
|
|
3af2d5d977 | ||
|
|
b61a1656ad | ||
|
|
0fbf22a090 | ||
|
|
5e763aa73c | ||
|
|
6093ca9d8b | ||
|
|
6cc0673082 |
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [master]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
@@ -27,6 +27,7 @@ jobs:
|
||||
- name: system 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') }}
|
||||
@@ -42,9 +43,6 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- { os: ubuntu-24.04, goos: linux, goarch: amd64 }
|
||||
- { os: windows-2022, goos: windows, goarch: amd64 }
|
||||
- { os: macos-15-intel, goos: darwin, goarch: amd64 }
|
||||
- { os: macos-15, goos: darwin, goarch: arm64 }
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
@@ -74,6 +72,7 @@ jobs:
|
||||
if: runner.os == 'macOS'
|
||||
run: brew list ninja >/dev/null 2>&1 || brew install ninja
|
||||
- uses: actions/cache@v4
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: .cache/native
|
||||
key: native-${{ matrix.goos }}_${{ matrix.goarch }}-${{ hashFiles('third_party/sdl/version.txt', 'tools/bootstrap-native.sh') }}
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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 {
|
||||
|
||||
@@ -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()}
|
||||
}
|
||||
|
||||
@@ -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()}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package builtin
|
||||
|
||||
import "aego/app/assembly"
|
||||
|
||||
func Runtime() assembly.Catalog {
|
||||
return Default()
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
-1
@@ -16,6 +16,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"aego/engine"
|
||||
"aego/engine/module"
|
||||
"aego/project"
|
||||
)
|
||||
@@ -27,6 +28,7 @@ type Env struct {
|
||||
Native func(args ...string) error
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Repo string
|
||||
}
|
||||
|
||||
func (e *Env) export(path string) error {
|
||||
@@ -80,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")
|
||||
@@ -100,10 +123,19 @@ 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, "-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)
|
||||
}
|
||||
@@ -113,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 {
|
||||
|
||||
@@ -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, "")
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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 & B") || !strings.Contains(p, "dev.aego.my.game.x") {
|
||||
t.Fatal(p)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -0,0 +1,2 @@
|
||||
.aego/
|
||||
exports/
|
||||
@@ -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/.
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user