Compare commits
21
Commits
master
...
stage-9.5c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
208875264f | ||
|
|
8c21400bb1 | ||
|
|
e83521a34a | ||
|
|
3f5584d062 | ||
|
|
6ce72aa1ec | ||
|
|
9ee2b0b05c | ||
|
|
99dacd7a75 | ||
|
|
5447383f2b | ||
|
|
6745d027fb | ||
|
|
e7dd01371e | ||
|
|
68802195b6 | ||
|
|
7cba364aed | ||
|
|
35e9849cae | ||
|
|
f21d455d68 | ||
|
|
d1a16d8208 | ||
|
|
2a1826f876 | ||
|
|
3af2d5d977 | ||
|
|
b61a1656ad | ||
|
|
0fbf22a090 | ||
|
|
5e763aa73c | ||
|
|
6093ca9d8b |
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
+181
-39
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,17 +4,34 @@ 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"
|
||||
"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() }},
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
SandboxBudget = 2_000_000
|
||||
SandboxHeap = 64 << 20
|
||||
SandboxNodes = 10_000
|
||||
)
|
||||
|
||||
func SandboxVM() vm.Options {
|
||||
return vm.Options{Budget: SandboxBudget, MaxHeap: SandboxHeap}
|
||||
}
|
||||
|
||||
+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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
-26
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
||||
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)
|
||||
}
|
||||
switch res := pg.PlayScene(e.ID, []byte(e.Try)); {
|
||||
case !sandbox[e.ID]:
|
||||
add("belongs to a module the docs runtime does not include")
|
||||
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,73 @@
|
||||
//go:build aego_editor
|
||||
|
||||
package docsview
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"aego/editor/core/docsession"
|
||||
)
|
||||
|
||||
type request struct {
|
||||
Scene string `json:"scene"`
|
||||
Select string `json:"select"`
|
||||
Component string `json:"component"`
|
||||
Field string `json:"field"`
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
type Reply struct {
|
||||
Text string `json:"text,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":
|
||||
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()), State: &st}
|
||||
}
|
||||
|
||||
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,70 @@
|
||||
//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
|
||||
}
|
||||
|
||||
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
@@ -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,67 @@
|
||||
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()
|
||||
case "js":
|
||||
return j.packWeb()
|
||||
}
|
||||
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,37 @@
|
||||
<!doctype html>
|
||||
<html lang="en" data-build="{{.Build}}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{.Title}}</title>
|
||||
{{if .Icon}}<link rel="icon" href="favicon.png">{{else}}<link rel="icon" href="data:,">{{end}}
|
||||
<style>
|
||||
html, body { margin: 0; height: 100%; background: #0d0e10; color: #e4e3df; overflow: hidden; font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
|
||||
canvas { display: block; width: 100%; height: 100%; outline: none; touch-action: none; }
|
||||
.loader { position: fixed; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background: #0d0e10; transition: opacity 0.25s; }
|
||||
.loader.done { opacity: 0; pointer-events: none; }
|
||||
.loader .title { font-size: 20px; font-weight: 600; }
|
||||
.progress { width: min(360px, 80vw); height: 6px; border-radius: 3px; background: #2d3036; overflow: hidden; }
|
||||
#bar { width: 0; height: 100%; background: #6e9bf5; transition: width 0.1s; }
|
||||
#bar.indeterminate { width: 30%; animation: slide 1s ease-in-out infinite alternate; }
|
||||
@keyframes slide { from { margin-left: 0; } to { margin-left: 70%; } }
|
||||
#status { color: #9a9890; font-size: 13px; }
|
||||
.loader.failed #bar { background: #f0776c; }
|
||||
#error { max-width: min(640px, 90vw); max-height: 40vh; overflow: auto; margin: 0; padding: 10px 12px; white-space: pre-wrap; color: #f0776c; background: #1d1f23; border-radius: 6px; font: 12px/1.5 ui-monospace, Menlo, Consolas, monospace; }
|
||||
#fullscreen { position: fixed; top: 10px; right: 10px; width: 36px; height: 36px; border: 0; border-radius: 6px; background: rgba(0, 0, 0, 0.45); color: #fff; font-size: 18px; cursor: pointer; opacity: 0.5; }
|
||||
#fullscreen:hover { opacity: 1; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="game" data-aego tabindex="0" aria-label="{{.Title}}"></canvas>
|
||||
<div id="loader" class="loader" role="status" aria-live="polite">
|
||||
<div class="title">{{.Title}}</div>
|
||||
<div class="progress"><div id="bar"></div></div>
|
||||
<div id="status">Loading…</div>
|
||||
<pre id="error" hidden></pre>
|
||||
</div>
|
||||
<button id="fullscreen" type="button" title="Fullscreen" aria-label="Fullscreen" hidden>⛶</button>
|
||||
<script src="wasm_exec.js?v={{.Build}}"></script>
|
||||
<script src="loader.js?v={{.Build}}"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
||||
package export
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"aego/project"
|
||||
"aego/runtime/bundle"
|
||||
)
|
||||
|
||||
var webTarget = Target{OS: "js", Arch: "wasm"}
|
||||
|
||||
func TestParseWeb(t *testing.T) {
|
||||
for _, s := range []string{"web", "WEB", "js/wasm", "wasm"} {
|
||||
tg, err := ParseTarget(s)
|
||||
if err != nil || !tg.Web() || tg.String() != "web" {
|
||||
t.Fatalf("%s: %+v %v", s, tg, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebLayout(t *testing.T) {
|
||||
p := &project.Project{Name: "My Game", Root: t.TempDir()}
|
||||
l := layoutFor(webTarget, "/out", p)
|
||||
if l.binary() != filepath.Join("/out", WebWasm) || l.data != filepath.Join("/out", bundle.DataDir) {
|
||||
t.Fatalf("layout %+v", l)
|
||||
}
|
||||
if !strings.HasSuffix(DefaultOut(p, webTarget, bundle.Release), "web-"+bundle.Release) {
|
||||
t.Fatal(DefaultOut(p, webTarget, bundle.Release))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackWeb(t *testing.T) {
|
||||
root, err := goRoot()
|
||||
if err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
out := t.TempDir()
|
||||
wasm := bytes.Repeat([]byte("wasm"), 4096)
|
||||
j := &job{
|
||||
p: &project.Project{Name: `<Game & "Co">`, Module: "game"}, t: webTarget, out: out, goroot: root,
|
||||
rep: &Report{Hash: strings.Repeat("ab", 32), BinarySize: int64(len(wasm))},
|
||||
files: []webFile{{Path: "artifacts/a.tex", Size: 3}}, manifestSize: 8,
|
||||
}
|
||||
j.l = layoutFor(webTarget, out, j.p)
|
||||
if err := os.MkdirAll(j.l.data, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(j.l.binary(), wasm, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := j.packWeb(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
page, _ := os.ReadFile(filepath.Join(out, webIndex))
|
||||
if !strings.Contains(string(page), "<Game &") || strings.Contains(string(page), "<Game") {
|
||||
t.Fatal("the title is not escaped")
|
||||
}
|
||||
if !strings.Contains(string(page), `data-build="abababababab"`) {
|
||||
t.Fatal("the build key is missing")
|
||||
}
|
||||
raw, err := os.ReadFile(filepath.Join(j.l.data, WebFiles))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var list webList
|
||||
if err := json.Unmarshal(raw, &list); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if list.Wasm != int64(len(wasm)) || list.Manifest.Size != 8 || len(list.Files) != 1 || list.Files[0].Path != "artifacts/a.tex" {
|
||||
t.Fatalf("files.json %+v", list)
|
||||
}
|
||||
for _, f := range []string{webExec, webLoader, WebWasm + ".gz"} {
|
||||
if _, err := os.Stat(filepath.Join(out, f)); err != nil {
|
||||
t.Errorf("%s is missing", f)
|
||||
}
|
||||
}
|
||||
if j.rep.BinaryGzip <= 0 || j.rep.BinaryGzip >= j.rep.BinarySize {
|
||||
t.Fatalf("wasm gzip size %d of %d", j.rep.BinaryGzip, j.rep.BinarySize)
|
||||
}
|
||||
}
|
||||
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/.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user