Compare commits
58
Commits
stage-9.5b
...
9da9851538
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9da9851538 | ||
|
|
4fd1e11741 | ||
|
|
8f9a0299dc | ||
|
|
ca3eedf9b0 | ||
|
|
4f0662eb3b | ||
|
|
9abf5c75ab | ||
|
|
96fef1f6a8 | ||
|
|
6230d30620 | ||
|
|
91ea202e74 | ||
|
|
6128c86685 | ||
|
|
2be6ce1d64 | ||
|
|
2e664ae508 | ||
|
|
aab6e0b0c9 | ||
|
|
9ba3e1631f | ||
|
|
4068e00375 | ||
|
|
914cf39150 | ||
|
|
dede4d8af3 | ||
|
|
b6dd46f523 | ||
|
|
9d32052624 | ||
|
|
e636801578 | ||
|
|
01fdeef7a8 | ||
|
|
71354f6a60 | ||
|
|
8ae6832503 | ||
|
|
183d75ed50 | ||
|
|
19edcbf9b2 | ||
|
|
8ed77a7638 | ||
|
|
ca3c9bf2e7 | ||
|
|
db59f117a4 | ||
|
|
0d2c20da26 | ||
|
|
dd41ff15a5 | ||
|
|
599e09a35d | ||
|
|
60614938be | ||
|
|
a60ca47d31 | ||
|
|
00ae2418a0 | ||
|
|
8fc9b4c287 | ||
|
|
b29934b51d | ||
|
|
c475b42e5d | ||
|
|
b103816400 | ||
|
|
487170dd9c | ||
|
|
57579356d6 | ||
|
|
d5377410e4 | ||
|
|
f6c83a6e0c | ||
|
|
8d7712d671 | ||
|
|
63980faccb | ||
|
|
0041e7e639 | ||
|
|
e334be569f | ||
|
|
b488958444 | ||
|
|
5875a65193 | ||
|
|
6ab66cb8ae | ||
|
|
f8fe54e9c5 | ||
|
|
e4326e7e7c | ||
|
|
6a22396467 | ||
|
|
21e03128a8 | ||
|
|
e5b1964f3e | ||
|
|
cffa66514b | ||
|
|
2744cff481 | ||
|
|
91052a54db | ||
|
|
7f4d87ef2c |
@@ -5,3 +5,4 @@ site
|
||||
exports
|
||||
**/.aego
|
||||
*.test
|
||||
.claude
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -3,6 +3,7 @@ name: ci
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
tags: ["v*"]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
@@ -111,4 +112,39 @@ 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
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
+285
-40
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
||||
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
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,66 @@
|
||||
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
|
||||
}
|
||||
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
@@ -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,21 @@ import (
|
||||
"aego/app/assembly"
|
||||
"aego/engine/base"
|
||||
"aego/engine/module"
|
||||
"aego/modules/accessibility"
|
||||
"aego/modules/audio"
|
||||
"aego/modules/camera"
|
||||
"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 +26,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 +40,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 +48,47 @@ 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 cameraEntry() assembly.Entry {
|
||||
return assembly.Entry{ID: camera.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return camera.New() }}
|
||||
}
|
||||
|
||||
func dataEntry() assembly.Entry {
|
||||
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(), cameraEntry(), 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(), cameraEntry(), 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,12 @@ package editors
|
||||
|
||||
import (
|
||||
"aego/app/assembly"
|
||||
"aego/modules/camera"
|
||||
cameraeditor "aego/modules/camera/editor"
|
||||
"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 +18,10 @@ 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(camera.ID, cameraeditor.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,17 +4,44 @@ import (
|
||||
"aego/app/assembly"
|
||||
"aego/engine/base"
|
||||
"aego/engine/module"
|
||||
"aego/modules/camera"
|
||||
"aego/modules/debug"
|
||||
inputmod "aego/modules/input"
|
||||
"aego/modules/physics"
|
||||
"aego/modules/render2d"
|
||||
"aego/modules/render3d"
|
||||
"aego/modules/savegame"
|
||||
"aego/modules/scripting"
|
||||
"aego/modules/spawn"
|
||||
"aego/modules/tween"
|
||||
"aego/script/stdlib"
|
||||
"aego/script/vm"
|
||||
)
|
||||
|
||||
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() }},
|
||||
{ID: tween.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return tween.New() }},
|
||||
{ID: camera.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return camera.New() }},
|
||||
{ID: render3d.ID, New: func(assembly.Options) module.Module { return render3d.New() }},
|
||||
{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() }},
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
SandboxBudget = 2_000_000
|
||||
SandboxHeap = 64 << 20
|
||||
SandboxNodes = 10_000
|
||||
)
|
||||
|
||||
func SandboxVM() vm.Options {
|
||||
return vm.Options{Budget: SandboxBudget, MaxHeap: SandboxHeap}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
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)
|
||||
}
|
||||
+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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
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
|
||||
}
|
||||
@@ -138,7 +138,7 @@ func TestF1Refs(t *testing.T) {
|
||||
if err := os.WriteFile(path, []byte(probe), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reg, ws, err := build(p, nil)
|
||||
reg, ws, err := build(p, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -169,3 +169,17 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"aego/app/assembly"
|
||||
"aego/app/assembly/builtin"
|
||||
"aego/app/playground"
|
||||
"aego/engine/docs"
|
||||
"aego/engine/module"
|
||||
scriptsystems "aego/modules/scripting/systems"
|
||||
@@ -20,7 +21,8 @@ import (
|
||||
"aego/script/workspace"
|
||||
)
|
||||
|
||||
func build(p *project.Project, mods []module.Module) (*docs.Registry, *workspace.Workspace, error) {
|
||||
// build assembles the registry; with pg it also compiles the diagnostics examples and the try scripts.
|
||||
func build(p *project.Project, mods []module.Module, pg *playground.Playground) (*docs.Registry, *workspace.Workspace, error) {
|
||||
types, err := assembly.NewTypes()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -41,6 +43,10 @@ func build(p *project.Project, mods []module.Module) (*docs.Registry, *workspace
|
||||
return nil, nil, err
|
||||
}
|
||||
src := docs.SourcesOf(bm, types.Schema(), man)
|
||||
if pg != nil {
|
||||
src.Diagnose = diagnoser(pg)
|
||||
src.CompileTry = pg.TryCode
|
||||
}
|
||||
var ws *workspace.Workspace
|
||||
if p != nil {
|
||||
ws, err = workspace.Open(workspace.Config{Root: p.Root, Module: p.Module, Scripts: p.ScriptsDir(), Bindings: reg.Table(), Docs: bm})
|
||||
@@ -61,7 +67,17 @@ func build(p *project.Project, mods []module.Module) (*docs.Registry, *workspace
|
||||
}
|
||||
|
||||
func Registry(p *project.Project, mods []module.Module) (*docs.Registry, error) {
|
||||
r, _, err := build(p, mods)
|
||||
r, _, err := build(p, mods, nil)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -84,9 +100,9 @@ func WriteExport(reg *docs.Registry, title, path string, w io.Writer) error {
|
||||
return os.WriteFile(path, data, 0o644)
|
||||
}
|
||||
|
||||
func Check(reg *docs.Registry, strict bool, w io.Writer) int {
|
||||
func Check(reg *docs.Registry, strict bool, w io.Writer, extra ...docs.Problem) int {
|
||||
errs, warns := 0, 0
|
||||
for _, p := range reg.Check(strict) {
|
||||
for _, p := range append(reg.Check(strict), extra...) {
|
||||
level := "warn "
|
||||
if p.Error {
|
||||
level = "error"
|
||||
|
||||
+127
-27
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,11 @@ func Tool(p *project.Project, args []string, stdout, stderr io.Writer, mods ...m
|
||||
if err := fs.Parse(args[1:]); err != nil {
|
||||
return 2
|
||||
}
|
||||
reg, err := Registry(p, mods)
|
||||
build := Registry
|
||||
if args[0] == "export" {
|
||||
build = ExportRegistry
|
||||
}
|
||||
reg, err := build(p, mods)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, "aego:", err)
|
||||
return 1
|
||||
@@ -34,7 +38,7 @@ func Tool(p *project.Project, args []string, stdout, stderr io.Writer, mods ...m
|
||||
}
|
||||
return 0
|
||||
case "check":
|
||||
return Check(reg, *strict, stdout)
|
||||
return Check(reg, *strict, stdout, DiagnosticProblems()...)
|
||||
}
|
||||
fmt.Fprintf(stderr, "unknown docs command %q\n", args[0])
|
||||
return 2
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package docstool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"aego/app/playground"
|
||||
"aego/engine/docs"
|
||||
)
|
||||
|
||||
const tryFrames = 60
|
||||
|
||||
func CheckTry(reg *docs.Registry, w io.Writer) int {
|
||||
pg, err := playground.New()
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, "try: 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()
|
||||
}
|
||||
}()
|
||||
sandbox := map[string]bool{}
|
||||
for _, e := range pg.TryScenes() {
|
||||
sandbox[e.ID] = true
|
||||
}
|
||||
bad := 0
|
||||
for _, e := range reg.All() {
|
||||
if e.Try == "" {
|
||||
continue
|
||||
}
|
||||
var msgs []string
|
||||
add := func(msg string) {
|
||||
for _, m := range msgs {
|
||||
if m == msg || strings.Contains(m, msg) || strings.Contains(msg, m) {
|
||||
return
|
||||
}
|
||||
}
|
||||
msgs = append(msgs, msg)
|
||||
}
|
||||
var code string
|
||||
var err error
|
||||
if e.TryScript != "" {
|
||||
code, err = pg.TryCode(e.TryScript)
|
||||
}
|
||||
switch res := pg.PlayScene(e.ID, []byte(e.Try), code, e.TryPrefabs); {
|
||||
case !sandbox[e.ID]:
|
||||
add("belongs to a module the docs runtime does not include")
|
||||
case err != nil:
|
||||
add("script does not compile: " + err.Error())
|
||||
case !res.Started:
|
||||
add("does not load: " + res.Error)
|
||||
default:
|
||||
if err := pg.Game().Step(tryFrames); err != nil {
|
||||
add("stops the engine: " + err.Error())
|
||||
}
|
||||
}
|
||||
poll := pg.Poll()
|
||||
for _, l := range poll.Log {
|
||||
if l.Level != "INFO" && l.Level != "DEBUG" {
|
||||
add(l.Text)
|
||||
}
|
||||
}
|
||||
for _, f := range poll.Faults {
|
||||
add(f.Code + " " + f.Message)
|
||||
}
|
||||
for _, m := range msgs {
|
||||
fmt.Fprintf(w, "%s: error: try scene %s\n", e.ID, m)
|
||||
}
|
||||
bad += len(msgs)
|
||||
}
|
||||
pg.Stop()
|
||||
return bad
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,96 @@
|
||||
//go:build aego_editor
|
||||
|
||||
package docsview
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
playmods "aego/app/assembly/playground"
|
||||
"aego/editor/core/docsession"
|
||||
"aego/scene/sceneio"
|
||||
)
|
||||
|
||||
type request struct {
|
||||
Scene string `json:"scene"`
|
||||
Select string `json:"select"`
|
||||
Code string `json:"code"`
|
||||
Prefabs map[string]string `json:"prefabs"`
|
||||
Component string `json:"component"`
|
||||
Field string `json:"field"`
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
type Reply struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
Values map[string]string `json:"values,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
State *docsession.State `json:"state,omitempty"`
|
||||
Log []Line `json:"log,omitempty"`
|
||||
}
|
||||
|
||||
func (a *App) Call(method string, payload []byte) Reply {
|
||||
var req request
|
||||
if len(payload) > 0 {
|
||||
if err := json.Unmarshal(payload, &req); err != nil {
|
||||
return Reply{Error: err.Error()}
|
||||
}
|
||||
}
|
||||
s := a.session
|
||||
var err error
|
||||
switch method {
|
||||
case "load":
|
||||
mods, derr := playmods.DecodeTry(req.Code)
|
||||
if err = derr; err == nil {
|
||||
err = a.scripts.use(mods)
|
||||
}
|
||||
if err == nil {
|
||||
err = playmods.MountTry(a.loader, req.Prefabs)
|
||||
}
|
||||
if err == nil {
|
||||
err = s.Load([]byte(req.Scene), req.Select)
|
||||
}
|
||||
case "reset":
|
||||
err = s.Reset()
|
||||
case "text":
|
||||
case "play":
|
||||
s.TogglePause()
|
||||
case "undo":
|
||||
err = s.Undo()
|
||||
case "redo":
|
||||
err = s.Redo()
|
||||
case "set":
|
||||
err = s.Set(req.Component, req.Field, req.Value)
|
||||
case "state":
|
||||
st := s.State()
|
||||
return Reply{State: &st}
|
||||
case "log":
|
||||
out := Reply{Log: a.lines}
|
||||
a.lines = nil
|
||||
return out
|
||||
default:
|
||||
return Reply{Error: "unknown method " + strconv.Quote(method)}
|
||||
}
|
||||
if err != nil {
|
||||
return Reply{Error: err.Error()}
|
||||
}
|
||||
st := s.State()
|
||||
return Reply{Text: string(s.Text()), Values: Values(s.Text()), State: &st}
|
||||
}
|
||||
|
||||
// Values flattens a scene file into the field keys the docs scene tree uses.
|
||||
func Values(text []byte) map[string]string {
|
||||
nodes, err := sceneio.Outline(text)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return sceneio.OutlineValues(nodes)
|
||||
}
|
||||
|
||||
func (a *App) Reply(method, payload string) string {
|
||||
out, err := json.Marshal(a.Call(method, []byte(payload)))
|
||||
if err != nil {
|
||||
return `{"error":"cannot encode the reply"}`
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//go:build aego_editor
|
||||
|
||||
package docsview
|
||||
|
||||
import (
|
||||
playmods "aego/app/assembly/playground"
|
||||
"aego/core/log"
|
||||
"aego/editor/core/context"
|
||||
"aego/engine"
|
||||
"aego/engine/module"
|
||||
"aego/scene"
|
||||
"aego/script/bind"
|
||||
"aego/script/bytecode"
|
||||
scriptruntime "aego/script/runtime"
|
||||
)
|
||||
|
||||
type scriptHost struct {
|
||||
reg *bind.Registry
|
||||
types *scene.Types
|
||||
man *module.Manifest
|
||||
log *log.Logger
|
||||
mods []*bytecode.Module
|
||||
}
|
||||
|
||||
// use makes mods the scripts of the next scene and publishes their types, so the scene keeps their fields.
|
||||
func (h *scriptHost) use(mods []*bytecode.Module) error {
|
||||
if err := scriptruntime.Publish(h.types.Schema(), mods, h.reg.Surface()); err != nil {
|
||||
return err
|
||||
}
|
||||
h.mods = mods
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *scriptHost) Errors() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (h *scriptHost) Attach(w *engine.World, s *scene.Scene) (func(), error) {
|
||||
rt := scriptruntime.New(scriptruntime.Options{Bindings: h.reg, Schema: h.types.Schema(), Log: h.log, VM: playmods.SandboxVM()})
|
||||
if err := rt.Link(h.mods); err != nil {
|
||||
rt.Close()
|
||||
return nil, err
|
||||
}
|
||||
w.SetService(scriptruntime.ServiceID, rt)
|
||||
if err := rt.Instantiate(s); err != nil {
|
||||
w.RemoveService(scriptruntime.ServiceID)
|
||||
rt.Close()
|
||||
return nil, err
|
||||
}
|
||||
detach, err := h.man.Attach(&module.AttachContext{World: w, Scene: s, Log: h.log, Editor: true, Play: true})
|
||||
if err != nil {
|
||||
w.RemoveService(scriptruntime.ServiceID)
|
||||
rt.Close()
|
||||
return nil, err
|
||||
}
|
||||
return func() {
|
||||
detach()
|
||||
w.RemoveService(scriptruntime.ServiceID)
|
||||
rt.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *scriptHost) Problems() ([]context.ScriptProblem, uint64) {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
func (h *scriptHost) Stats() (context.ScriptStats, bool) {
|
||||
return context.ScriptStats{}, false
|
||||
}
|
||||
|
||||
func (h *scriptHost) Status() (uint64, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (h *scriptHost) Watch(*engine.World, scene.NodeHandle, string, string, int) (string, float64, bool, bool) {
|
||||
return "", 0, false, false
|
||||
}
|
||||
|
||||
var _ context.ScriptHost = (*scriptHost)(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
@@ -116,12 +116,8 @@ func TestPlayLeavesEditSceneByteIdentical(t *testing.T) {
|
||||
t.Fatal("Spin is not registered")
|
||||
}
|
||||
speed, _ := td.Field("speed")
|
||||
add := &commands.AddComponent{
|
||||
Node: spinner,
|
||||
Type: td.ID,
|
||||
Name: td.FullName,
|
||||
Values: []commands.FieldValue{{Field: speed.ID, Value: schema.Float32(3)}},
|
||||
}
|
||||
add := commands.NewMutation("Add "+td.FullName, commands.EnvFor(es),
|
||||
commands.AddComp(es, spinner, td, []commands.FieldValue{{Field: speed.ID, Value: schema.Float32(3)}}))
|
||||
if err := d.History.Exec(add); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func TestColliderFitsSprite(t *testing.T) {
|
||||
}
|
||||
for _, name := range []string{"Crate", "Bare"} {
|
||||
id := es.ID(es.Find(es.Root(), name))
|
||||
if err := d.History.Exec(&commands.AddComponent{Node: id, Type: td.ID, Name: td.FullName, OnAdd: ctx.OnAdd(td)}); err != nil {
|
||||
if err := d.History.Exec(commands.NewMutation("Add "+td.FullName, commands.WithOnAdd(commands.EnvFor(es), ctx.OnAdd), commands.AddComp(es, id, td, nil))); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -51,12 +51,8 @@ func openSpinner(t *testing.T) *spinFixture {
|
||||
}
|
||||
fd, _ := td.Field("speed")
|
||||
f.typ, f.speed = td.ID, fd.ID
|
||||
add := &commands.AddComponent{
|
||||
Node: f.spinner,
|
||||
Type: td.ID,
|
||||
Name: td.FullName,
|
||||
Values: []commands.FieldValue{{Field: fd.ID, Value: schema.Float32(3)}},
|
||||
}
|
||||
add := commands.NewMutation("Add "+td.FullName, commands.EnvFor(es),
|
||||
commands.AddComp(es, f.spinner, td, []commands.FieldValue{{Field: fd.ID, Value: schema.Float32(3)}}))
|
||||
if err := d.History.Exec(add); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
+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)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user