Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8391ebcd98 | ||
|
|
3ab43e84f1 | ||
|
|
62b5f640fc | ||
|
|
814cd2bf07 | ||
|
|
8e717a4fd1 | ||
|
|
a2c3a24381 | ||
|
|
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 | ||
|
|
6cc0673082 | ||
|
|
ab9b003457 | ||
|
|
41d949717d | ||
|
|
69d19e501f | ||
|
|
cf68c71f05 | ||
|
|
08f0b15e1e | ||
|
|
17cb5e4291 | ||
|
|
eb9ac1e589 | ||
|
|
dae4bc19e1 |
@@ -37,6 +37,10 @@ jobs:
|
|||||||
- run: go vet ./platform/sdl
|
- run: go vet ./platform/sdl
|
||||||
- run: make lint
|
- run: make lint
|
||||||
- run: make depcheck
|
- run: make depcheck
|
||||||
|
- name: packaging tools
|
||||||
|
run: sudo apt-get install -y nsis squashfs-tools
|
||||||
|
- name: installer dry run
|
||||||
|
run: make nsis-check
|
||||||
|
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ BENCH_COUNT ?= 10
|
|||||||
BENCH_CPU ?= 2
|
BENCH_CPU ?= 2
|
||||||
BENCH_PIN = $(shell command -v taskset >/dev/null && echo taskset -c $(BENCH_CPU))
|
BENCH_PIN = $(shell command -v taskset >/dev/null && echo taskset -c $(BENCH_CPU))
|
||||||
EDITOR_VERSION ?= 0.1.0
|
EDITOR_VERSION ?= 0.1.0
|
||||||
EDITOR_ICON ?= third_party/brand/aego-1024.png
|
EDITOR_ICON ?= app/brand/png
|
||||||
EXPORT_SMOKE = $(CURDIR)/.cache/export-smoke
|
EXPORT_SMOKE = $(CURDIR)/.cache/export-smoke
|
||||||
EXPORT_HOME = $(CURDIR)/.cache/export-home
|
EXPORT_HOME = $(CURDIR)/.cache/export-home
|
||||||
CHROMIUM ?= $(shell command -v chromium || command -v chromium-browser || command -v google-chrome)
|
CHROMIUM ?= $(shell command -v chromium || command -v chromium-browser || command -v google-chrome)
|
||||||
@@ -26,6 +26,7 @@ WEB_DEMO_BUDGET = @size=$$(stat -c %s site/demo/game.wasm.gz); echo "demo game.w
|
|||||||
if [ $$size -gt $(WASM_BUDGET) ]; then echo "the web demo is over the 8 MB gzip budget"; exit 1; fi
|
if [ $$size -gt $(WASM_BUDGET) ]; then echo "the web demo is over the 8 MB gzip budget"; exit 1; fi
|
||||||
TRY_BUDGET = 10485760
|
TRY_BUDGET = 10485760
|
||||||
WEB_TRY = cd tools/websmoke && CHROMIUM=$(CHROMIUM) node try.mjs --site=$(CURDIR)/site
|
WEB_TRY = cd tools/websmoke && CHROMIUM=$(CHROMIUM) node try.mjs --site=$(CURDIR)/site
|
||||||
|
WEB_AUDIO = cd tools/websmoke && CHROMIUM=$(CHROMIUM) node audio.mjs --site=$(CURDIR)/site
|
||||||
TRY_SIZE_CHECK = gzip -9 -k -f site/try/aego-docs.wasm; size=$$(stat -c %s site/try/aego-docs.wasm.gz); echo "aego-docs.wasm.gz: $$size bytes"; \
|
TRY_SIZE_CHECK = gzip -9 -k -f site/try/aego-docs.wasm; size=$$(stat -c %s site/try/aego-docs.wasm.gz); echo "aego-docs.wasm.gz: $$size bytes"; \
|
||||||
if [ $$size -gt $(TRY_BUDGET) ]; then echo "the docs runtime is over the 10 MB gzip budget, see make try-size"; exit 1; fi
|
if [ $$size -gt $(TRY_BUDGET) ]; then echo "the docs runtime is over the 10 MB gzip budget, see make try-size"; exit 1; fi
|
||||||
# Module targets, appended by modules/*/module.mk.
|
# Module targets, appended by modules/*/module.mk.
|
||||||
@@ -38,7 +39,7 @@ BENCH_PHYSICS_PKGS :=
|
|||||||
.DEFAULT_GOAL := bootstrap
|
.DEFAULT_GOAL := bootstrap
|
||||||
-include modules/*/module.mk
|
-include modules/*/module.mk
|
||||||
|
|
||||||
.PHONY: bootstrap build test test-nonative test-editor test-runtime test-gl vet lint depcheck smoke run genfont genicons icons icons-doc res genpreviews golden golden-update site-headless bench bench-baseline bench-scripts bench-build bench-compare bench-profile bench-scripts-run bench-allocs playground playground-size site site-check cli example example-platformer check-all ci clean fuzz runtime export-smoke editor-app shaders test-wasm web-smoke web-smoke-update try-size shots shots-check
|
.PHONY: dmg nsis-check appimage bootstrap build test test-nonative test-editor test-runtime test-gl vet lint depcheck smoke run genfont genicons icons icons-doc res genpreviews golden golden-update site-headless bench bench-baseline bench-scripts bench-build bench-compare bench-profile bench-scripts-run bench-allocs playground playground-size site site-check cli example example-platformer check-all ci clean fuzz runtime export-smoke editor-app shaders test-wasm web-smoke web-smoke-update try-size shots shots-check
|
||||||
|
|
||||||
XVFB := $(shell command -v xvfb-run 2>/dev/null)
|
XVFB := $(shell command -v xvfb-run 2>/dev/null)
|
||||||
GL_OVERRIDE = MESA_GL_VERSION_OVERRIDE=3.3 MESA_GLSL_VERSION_OVERRIDE=330
|
GL_OVERRIDE = MESA_GL_VERSION_OVERRIDE=3.3 MESA_GLSL_VERSION_OVERRIDE=330
|
||||||
@@ -49,7 +50,7 @@ GL_ENV = env SDL_VIDEODRIVER=offscreen LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=ll
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
NONATIVE_PKGS = $$(go list ./... | grep -v -e '/platform/sdl$$' -e '/gpu/gl33$$' -e '/gpu/gl33/gl$$')
|
NONATIVE_PKGS = $$(go list ./... | grep -v -e '/platform/sdl$$' -e '/gpu/gl33$$' -e '/gpu/gl33/gl$$')
|
||||||
LINT_PKGS = $$(go list ./... | grep -v '/gpu/gl33/gl$$')
|
LINT_PKGS = $$(go list ./... | grep -v -e '/gpu/gl33/gl$$' -e '/audio/codec/vorbis/internal/')
|
||||||
|
|
||||||
bootstrap: $(STAMP)
|
bootstrap: $(STAMP)
|
||||||
|
|
||||||
@@ -246,6 +247,7 @@ web-smoke: site
|
|||||||
$(WEB_SMOKE) --report=$(CURDIR)/.cache/web-bench.json
|
$(WEB_SMOKE) --report=$(CURDIR)/.cache/web-bench.json
|
||||||
$(WEB_DEMO)
|
$(WEB_DEMO)
|
||||||
$(WEB_TRY)
|
$(WEB_TRY)
|
||||||
|
$(WEB_AUDIO)
|
||||||
|
|
||||||
web-smoke-update: site
|
web-smoke-update: site
|
||||||
$(WEB_SMOKE) --update
|
$(WEB_SMOKE) --update
|
||||||
@@ -281,6 +283,8 @@ fuzz:
|
|||||||
go test -run '^$$' -fuzz '^FuzzParse$$' -fuzztime $(FUZZTIME) ./script/parser
|
go test -run '^$$' -fuzz '^FuzzParse$$' -fuzztime $(FUZZTIME) ./script/parser
|
||||||
go test -run '^$$' -fuzz '^FuzzDecodeVerify$$' -fuzztime $(FUZZTIME) ./script/verify
|
go test -run '^$$' -fuzz '^FuzzDecodeVerify$$' -fuzztime $(FUZZTIME) ./script/verify
|
||||||
go test -run '^$$' -fuzz '^FuzzDecodeVerifyV3$$' -fuzztime $(FUZZTIME) ./script/verify
|
go test -run '^$$' -fuzz '^FuzzDecodeVerifyV3$$' -fuzztime $(FUZZTIME) ./script/verify
|
||||||
|
go test -run '^$$' -fuzz '^FuzzWAV$$' -fuzztime $(FUZZTIME) ./audio/codec
|
||||||
|
go test -run '^$$' -fuzz '^FuzzVorbis$$' -fuzztime $(FUZZTIME) ./audio/codec
|
||||||
|
|
||||||
ci: build vet lint depcheck test test-nonative test-editor test-runtime test-wasm runtime smoke fuzz golden shots-check test-gl cli playground web-smoke site-check example example-platformer check-all export-smoke $(MODULE_CI) bench-allocs
|
ci: build vet lint depcheck test test-nonative test-editor test-runtime test-wasm runtime smoke fuzz golden shots-check test-gl cli playground web-smoke site-check example example-platformer check-all export-smoke $(MODULE_CI) bench-allocs
|
||||||
|
|
||||||
@@ -321,5 +325,15 @@ editor-app: bootstrap
|
|||||||
go build -o .cache/bin/aego-editor ./cmd/aego-editor
|
go build -o .cache/bin/aego-editor ./cmd/aego-editor
|
||||||
go run ./tools/appbundle -bin .cache/bin/aego-editor -out .cache -version $(EDITOR_VERSION) $(if $(wildcard $(EDITOR_ICON)),-icon $(EDITOR_ICON))
|
go run ./tools/appbundle -bin .cache/bin/aego-editor -out .cache -version $(EDITOR_VERSION) $(if $(wildcard $(EDITOR_ICON)),-icon $(EDITOR_ICON))
|
||||||
|
|
||||||
|
dmg: editor-app
|
||||||
|
tools/release/dmg.sh ".cache/Aego Editor.app" .cache/dist/Aego-$$(sed -n 's/^const Version = "\(.*\)"$$/\1/p' engine/config.go).dmg
|
||||||
|
|
||||||
|
nsis-check:
|
||||||
|
sh tools/release/nsis-check.sh
|
||||||
|
|
||||||
|
appimage: cli
|
||||||
|
mkdir -p .cache/stage/bin .cache/dist && cp .cache/bin/aego .cache/stage/bin/aego && printf 'dev\n' > .cache/stage/VERSION && cp tools/release/README.txt .cache/stage/
|
||||||
|
sh tools/release/appimage.sh .cache/stage .cache/dist/Aego-dev-x86_64.AppImage
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf .cache/build
|
rm -rf .cache/build
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package editors
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"aego/app/assembly"
|
"aego/app/assembly"
|
||||||
|
"aego/modules/audio"
|
||||||
|
audioeditor "aego/modules/audio/editor"
|
||||||
"aego/modules/camera"
|
"aego/modules/camera"
|
||||||
cameraeditor "aego/modules/camera/editor"
|
cameraeditor "aego/modules/camera/editor"
|
||||||
"aego/modules/debug"
|
"aego/modules/debug"
|
||||||
@@ -23,5 +25,6 @@ func With(c assembly.Catalog) assembly.Catalog {
|
|||||||
With(render3d.ID, render3deditor.Hook).
|
With(render3d.ID, render3deditor.Hook).
|
||||||
With(physics.ID, physicseditor.Hook).
|
With(physics.ID, physicseditor.Hook).
|
||||||
With(camera.ID, cameraeditor.Hook).
|
With(camera.ID, cameraeditor.Hook).
|
||||||
|
With(audio.ID, audioeditor.Hook).
|
||||||
With(debug.ID, debugeditor.Hook)
|
With(debug.ID, debugeditor.Hook)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,16 @@ import (
|
|||||||
"aego/app/assembly"
|
"aego/app/assembly"
|
||||||
"aego/engine/base"
|
"aego/engine/base"
|
||||||
"aego/engine/module"
|
"aego/engine/module"
|
||||||
|
"aego/modules/audio"
|
||||||
"aego/modules/camera"
|
"aego/modules/camera"
|
||||||
"aego/modules/debug"
|
"aego/modules/debug"
|
||||||
inputmod "aego/modules/input"
|
inputmod "aego/modules/input"
|
||||||
"aego/modules/physics"
|
"aego/modules/physics"
|
||||||
"aego/modules/render2d"
|
"aego/modules/render2d"
|
||||||
"aego/modules/render3d"
|
"aego/modules/render3d"
|
||||||
|
"aego/modules/savegame"
|
||||||
"aego/modules/scripting"
|
"aego/modules/scripting"
|
||||||
|
"aego/modules/spawn"
|
||||||
"aego/modules/tween"
|
"aego/modules/tween"
|
||||||
"aego/script/stdlib"
|
"aego/script/stdlib"
|
||||||
"aego/script/vm"
|
"aego/script/vm"
|
||||||
@@ -29,6 +32,9 @@ func Catalog() assembly.Catalog {
|
|||||||
{ID: tween.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return tween.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: 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: render3d.ID, New: func(assembly.Options) module.Module { return render3d.New() }},
|
||||||
|
{ID: spawn.ID, New: func(assembly.Options) module.Module { return spawn.New() }},
|
||||||
|
{ID: savegame.ID, Requires: []string{scripting.ID, spawn.ID}, New: func(assembly.Options) module.Module { return savegame.New() }},
|
||||||
|
{ID: audio.ID, New: func(assembly.Options) module.Module { return audio.New() }},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"aego/app/proc"
|
||||||
"aego/core/errs"
|
"aego/core/errs"
|
||||||
"aego/core/log"
|
"aego/core/log"
|
||||||
"aego/project"
|
"aego/project"
|
||||||
@@ -169,6 +170,7 @@ func Build(p *project.Project, t Target, mode Mode, engineVersion string, lg *lo
|
|||||||
cmd := exec.Command("go", args...)
|
cmd := exec.Command("go", args...)
|
||||||
cmd.Dir = p.Root
|
cmd.Dir = p.Root
|
||||||
cmd.Env = append(os.Environ(), "GOOS="+t.OS, "GOARCH="+t.Arch)
|
cmd.Env = append(os.Environ(), "GOOS="+t.OS, "GOARCH="+t.Arch)
|
||||||
|
proc.Hide(cmd)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.RemoveAll(outDir)
|
os.RemoveAll(outDir)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"aego/app/proc"
|
||||||
"aego/core/errs"
|
"aego/core/errs"
|
||||||
"aego/core/log"
|
"aego/core/log"
|
||||||
"aego/project"
|
"aego/project"
|
||||||
@@ -32,12 +33,14 @@ func RunIcons(p *project.Project, engineVersion string, out io.Writer) (int, err
|
|||||||
defer os.Remove(bin)
|
defer os.Remove(bin)
|
||||||
build := exec.Command("go", "build", "-tags", strings.Join(t.Tags, ","), "-o", bin, "./.aego/generated/icons")
|
build := exec.Command("go", "build", "-tags", strings.Join(t.Tags, ","), "-o", bin, "./.aego/generated/icons")
|
||||||
build.Dir = p.Root
|
build.Dir = p.Root
|
||||||
|
proc.Hide(build)
|
||||||
if output, err := build.CombinedOutput(); err != nil {
|
if output, err := build.CombinedOutput(); err != nil {
|
||||||
return 2, errs.New(errs.BuildFailed, "native icons helper does not compile",
|
return 2, errs.New(errs.BuildFailed, "native icons helper does not compile",
|
||||||
log.F("output", strings.TrimSpace(string(output))))
|
log.F("output", strings.TrimSpace(string(output))))
|
||||||
}
|
}
|
||||||
run := exec.Command(bin)
|
run := exec.Command(bin)
|
||||||
run.Dir, run.Stdout, run.Stderr = p.Root, out, out
|
run.Dir, run.Stdout, run.Stderr = p.Root, out, out
|
||||||
|
proc.Hide(run)
|
||||||
err = run.Run()
|
err = run.Run()
|
||||||
var ee *exec.ExitError
|
var ee *exec.ExitError
|
||||||
if errors.As(err, &ee) {
|
if errors.As(err, &ee) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"aego/app/proc"
|
||||||
"aego/core/errs"
|
"aego/core/errs"
|
||||||
"aego/core/log"
|
"aego/core/log"
|
||||||
"aego/project"
|
"aego/project"
|
||||||
@@ -52,6 +53,7 @@ func BuildManifest(p *project.Project, engineVersion string, lg *log.Logger) (st
|
|||||||
defer os.Remove(bin)
|
defer os.Remove(bin)
|
||||||
build := exec.Command("go", "build", "-tags", strings.Join(t.Tags, ","), "-o", bin, "./.aego/generated/manifest")
|
build := exec.Command("go", "build", "-tags", strings.Join(t.Tags, ","), "-o", bin, "./.aego/generated/manifest")
|
||||||
build.Dir = p.Root
|
build.Dir = p.Root
|
||||||
|
proc.Hide(build)
|
||||||
if output, err := build.CombinedOutput(); err != nil {
|
if output, err := build.CombinedOutput(); err != nil {
|
||||||
return "", errs.New(errs.BuildFailed, "native bindings helper does not compile",
|
return "", errs.New(errs.BuildFailed, "native bindings helper does not compile",
|
||||||
log.F("output", strings.TrimSpace(string(output))))
|
log.F("output", strings.TrimSpace(string(output))))
|
||||||
@@ -59,6 +61,7 @@ func BuildManifest(p *project.Project, engineVersion string, lg *log.Logger) (st
|
|||||||
tmp := out + ".tmp"
|
tmp := out + ".tmp"
|
||||||
run := exec.Command(bin, tmp)
|
run := exec.Command(bin, tmp)
|
||||||
run.Dir = p.Root
|
run.Dir = p.Root
|
||||||
|
proc.Hide(run)
|
||||||
if output, err := run.CombinedOutput(); err != nil {
|
if output, err := run.CombinedOutput(); err != nil {
|
||||||
os.Remove(tmp)
|
os.Remove(tmp)
|
||||||
return "", errs.New(errs.BuildFailed, "native module Register failed",
|
return "", errs.New(errs.BuildFailed, "native module Register failed",
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package brand
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"embed"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed png/aego-*.png
|
||||||
|
var files embed.FS
|
||||||
|
|
||||||
|
var windowSizes = []int{16, 24, 32, 48, 64, 128, 256}
|
||||||
|
|
||||||
|
func PNG(n int) ([]byte, bool) {
|
||||||
|
data, err := files.ReadFile("png/aego-" + strconv.Itoa(n) + ".png")
|
||||||
|
return data, err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Window() []image.Image {
|
||||||
|
out := make([]image.Image, 0, len(windowSizes))
|
||||||
|
for _, n := range windowSizes {
|
||||||
|
data, ok := PNG(n)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if img, err := png.Decode(bytes.NewReader(data)); err == nil {
|
||||||
|
out = append(out, img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 563 B |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 878 B |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -21,7 +21,8 @@ import (
|
|||||||
"aego/script/workspace"
|
"aego/script/workspace"
|
||||||
)
|
)
|
||||||
|
|
||||||
func build(p *project.Project, mods []module.Module, diagnose func(string, *docs.DiagInfo)) (*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()
|
types, err := assembly.NewTypes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -42,7 +43,10 @@ func build(p *project.Project, mods []module.Module, diagnose func(string, *docs
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
src := docs.SourcesOf(bm, types.Schema(), man)
|
src := docs.SourcesOf(bm, types.Schema(), man)
|
||||||
src.Diagnose = diagnose
|
if pg != nil {
|
||||||
|
src.Diagnose = diagnoser(pg)
|
||||||
|
src.CompileTry = pg.TryCode
|
||||||
|
}
|
||||||
var ws *workspace.Workspace
|
var ws *workspace.Workspace
|
||||||
if p != nil {
|
if p != nil {
|
||||||
ws, err = workspace.Open(workspace.Config{Root: p.Root, Module: p.Module, Scripts: p.ScriptsDir(), Bindings: reg.Table(), Docs: bm})
|
ws, err = workspace.Open(workspace.Config{Root: p.Root, Module: p.Module, Scripts: p.ScriptsDir(), Bindings: reg.Table(), Docs: bm})
|
||||||
@@ -67,13 +71,13 @@ func Registry(p *project.Project, mods []module.Module) (*docs.Registry, error)
|
|||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportRegistry is Registry with the diagnostics examples compiled for their messages and positions.
|
// 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) {
|
func ExportRegistry(p *project.Project, mods []module.Module) (*docs.Registry, error) {
|
||||||
pg, err := playground.New()
|
pg, err := playground.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
r, _, err := build(p, mods, diagnoser(pg))
|
r, _, err := build(p, mods, pg)
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,9 +41,16 @@ func CheckTry(reg *docs.Registry, w io.Writer) int {
|
|||||||
}
|
}
|
||||||
msgs = append(msgs, msg)
|
msgs = append(msgs, msg)
|
||||||
}
|
}
|
||||||
switch res := pg.PlayScene(e.ID, []byte(e.Try)); {
|
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]:
|
case !sandbox[e.ID]:
|
||||||
add("belongs to a module the docs runtime does not include")
|
add("belongs to a module the docs runtime does not include")
|
||||||
|
case err != nil:
|
||||||
|
add("script does not compile: " + err.Error())
|
||||||
case !res.Started:
|
case !res.Started:
|
||||||
add("does not load: " + res.Error)
|
add("does not load: " + res.Error)
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ type App struct {
|
|||||||
ui *core.Context
|
ui *core.Context
|
||||||
clip *docsession.Clipboard
|
clip *docsession.Clipboard
|
||||||
session *docsession.Session
|
session *docsession.Session
|
||||||
|
scripts *scriptHost
|
||||||
insp *inspector.Panel
|
insp *inspector.Panel
|
||||||
game *gameview.Panel
|
game *gameview.Panel
|
||||||
list *draw.List
|
list *draw.List
|
||||||
@@ -143,15 +144,17 @@ func New(opt Options) (*App, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
a.e.SetService(loader.ServiceID, a.loader)
|
a.e.SetService(loader.ServiceID, a.loader)
|
||||||
|
playmods.UseMemorySaves(a.e)
|
||||||
a.e.SetService(renderhost.ServiceID, a.renderer)
|
a.e.SetService(renderhost.ServiceID, a.renderer)
|
||||||
if err := a.man.Init(&module.InitContext{Engine: a.e, Assets: a.loader, Log: a.log, Editor: true, Sections: sections}); err != nil {
|
if err := a.man.Init(&module.InitContext{Engine: a.e, Assets: a.loader, Log: a.log, Editor: true, Sections: sections}); err != nil {
|
||||||
a.Close()
|
a.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
a.initUI()
|
a.initUI()
|
||||||
|
a.scripts = &scriptHost{reg: reg, types: a.types, man: a.man, log: a.log}
|
||||||
a.session = docsession.New(docsession.Options{
|
a.session = docsession.New(docsession.Options{
|
||||||
Engine: a.e, Renderer: a.renderer, Loader: a.loader, Types: a.types, Sections: sections,
|
Engine: a.e, Renderer: a.renderer, Loader: a.loader, Types: a.types, Sections: sections,
|
||||||
Scripts: &scriptHost{reg: reg, types: a.types, man: a.man, log: a.log},
|
Scripts: a.scripts,
|
||||||
UI: a.ui,
|
UI: a.ui,
|
||||||
Clipboard: a.clip,
|
Clipboard: a.clip,
|
||||||
Log: a.log,
|
Log: a.log,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package docsview
|
package docsview
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -46,11 +47,22 @@ func errorsOf(a *App) []string {
|
|||||||
|
|
||||||
func TestTryScenesRun(t *testing.T) {
|
func TestTryScenesRun(t *testing.T) {
|
||||||
a := newHeadless(t)
|
a := newHeadless(t)
|
||||||
|
p, err := playground.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
for _, e := range tries(t) {
|
for _, e := range tries(t) {
|
||||||
if err := a.session.Load([]byte(e.Try), e.TrySelect); err != nil {
|
var code string
|
||||||
t.Fatalf("%s: %v", e.ID, err)
|
if e.TryScript != "" {
|
||||||
|
if code, err = p.TryCode(e.TryScript); err != nil {
|
||||||
|
t.Fatalf("%s: %v", e.ID, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := a.Step(60); err != nil {
|
req, _ := json.Marshal(map[string]any{"scene": e.Try, "select": e.TrySelect, "code": code, "prefabs": e.TryPrefabs})
|
||||||
|
if r := a.Call("load", req); r.Error != "" {
|
||||||
|
t.Fatalf("%s: %s", e.ID, r.Error)
|
||||||
|
}
|
||||||
|
if err := a.Step(400); err != nil {
|
||||||
t.Fatalf("%s: %v", e.ID, err)
|
t.Fatalf("%s: %v", e.ID, err)
|
||||||
}
|
}
|
||||||
st := a.session.State()
|
st := a.session.State()
|
||||||
|
|||||||
@@ -6,16 +6,19 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
playmods "aego/app/assembly/playground"
|
||||||
"aego/editor/core/docsession"
|
"aego/editor/core/docsession"
|
||||||
"aego/scene/sceneio"
|
"aego/scene/sceneio"
|
||||||
)
|
)
|
||||||
|
|
||||||
type request struct {
|
type request struct {
|
||||||
Scene string `json:"scene"`
|
Scene string `json:"scene"`
|
||||||
Select string `json:"select"`
|
Select string `json:"select"`
|
||||||
Component string `json:"component"`
|
Code string `json:"code"`
|
||||||
Field string `json:"field"`
|
Prefabs map[string]string `json:"prefabs"`
|
||||||
Value float64 `json:"value"`
|
Component string `json:"component"`
|
||||||
|
Field string `json:"field"`
|
||||||
|
Value float64 `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Reply struct {
|
type Reply struct {
|
||||||
@@ -37,7 +40,16 @@ func (a *App) Call(method string, payload []byte) Reply {
|
|||||||
var err error
|
var err error
|
||||||
switch method {
|
switch method {
|
||||||
case "load":
|
case "load":
|
||||||
err = s.Load([]byte(req.Scene), req.Select)
|
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":
|
case "reset":
|
||||||
err = s.Reset()
|
err = s.Reset()
|
||||||
case "text":
|
case "text":
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ type scriptHost struct {
|
|||||||
mods []*bytecode.Module
|
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 {
|
func (h *scriptHost) Errors() int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package edit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"aego/engine"
|
||||||
|
"aego/platform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func startBoot(verb string) {
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
exe, _ := os.Executable()
|
||||||
|
gobin, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
gobin = "not on PATH"
|
||||||
|
}
|
||||||
|
_ = platform.StartBoot(platform.DefaultBootPath(),
|
||||||
|
"aego "+engine.Version+" "+runtime.GOOS+"/"+runtime.GOARCH+" "+verb,
|
||||||
|
"exe "+exe,
|
||||||
|
"args "+strings.Join(os.Args[1:], " "),
|
||||||
|
"cwd "+wd,
|
||||||
|
"go "+gobin,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
package edit
|
package edit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"aego/app/bootstrap"
|
"aego/app/bootstrap"
|
||||||
"aego/app/export"
|
"aego/app/export"
|
||||||
"aego/app/projects"
|
"aego/app/projects"
|
||||||
"aego/asset/pipeline"
|
"aego/asset/pipeline"
|
||||||
"aego/editor/core/services"
|
"aego/editor/core/services"
|
||||||
|
"aego/editor/core/sysopen"
|
||||||
"aego/engine/problems"
|
"aego/engine/problems"
|
||||||
"aego/project"
|
"aego/project"
|
||||||
)
|
)
|
||||||
@@ -87,3 +92,47 @@ func (x *exporter) Reveal(path string) error {
|
|||||||
}
|
}
|
||||||
return cmd.Start()
|
return cmd.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var webServers sync.Map
|
||||||
|
|
||||||
|
func serveWeb(dir string) (string, error) {
|
||||||
|
if u, ok := webServers.Load(dir); ok {
|
||||||
|
return u.(string), nil
|
||||||
|
}
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
files := http.FileServer(http.Dir(dir))
|
||||||
|
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
|
||||||
|
w.Header().Set("Cross-Origin-Embedder-Policy", "require-corp")
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
files.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
go http.Serve(ln, h)
|
||||||
|
u := "http://" + ln.Addr().String() + "/"
|
||||||
|
webServers.Store(dir, u)
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *exporter) Run(out, target string) error {
|
||||||
|
path, err := export.Launchable(x.p, target, out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t, _ := export.ParseTarget(target)
|
||||||
|
switch {
|
||||||
|
case t.Web():
|
||||||
|
u, err := serveWeb(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return sysopen.Open(u)
|
||||||
|
case t.OS == "darwin":
|
||||||
|
return exec.Command("open", path).Start()
|
||||||
|
}
|
||||||
|
cmd := exec.Command(path)
|
||||||
|
cmd.Dir = filepath.Dir(path)
|
||||||
|
return cmd.Start()
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package edit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"aego/app/projects"
|
||||||
|
"aego/editor/shell/launcher"
|
||||||
|
"aego/project"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLauncherCreatesAndOpens(t *testing.T) {
|
||||||
|
recent := filepath.Join(t.TempDir(), "recent")
|
||||||
|
ls, err := OpenLauncher(Config{Headless: true, Recent: recent})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ls.UI.SetTab(launcher.TabNew)
|
||||||
|
dir := filepath.Join(t.TempDir(), "Aego Projects", "game")
|
||||||
|
if _, err := ls.UI.Create(dir, projects.Template2D, "", ""); err != nil {
|
||||||
|
ls.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for i := 0; i < 3 && ls.Step(); i++ {
|
||||||
|
}
|
||||||
|
ls.Close()
|
||||||
|
root, ok := ls.UI.Result()
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("the launcher has no result")
|
||||||
|
}
|
||||||
|
p, err := project.Load(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s, err := Open(p, Config{Headless: true, NoWatch: true, Recent: recent})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
s.Step()
|
||||||
|
}
|
||||||
|
if s.App.Context().Docs.Active() == nil {
|
||||||
|
t.Fatal("no scene is open after creating the project")
|
||||||
|
}
|
||||||
|
list, _ := launcher.LoadRecent(recent)
|
||||||
|
if len(list) == 0 || list[0].Path != root {
|
||||||
|
t.Fatalf("recent list %+v", list)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package edit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"aego/core/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const keepLogs = 10
|
||||||
|
|
||||||
|
type logFile struct {
|
||||||
|
f *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogsDir() string {
|
||||||
|
dir, err := os.UserCacheDir()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(dir, "aego", "logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func attachLogs(cfg *Config) *logFile {
|
||||||
|
if cfg.Logger == nil {
|
||||||
|
cfg.Logger = log.New(log.NewTextSink(os.Stderr))
|
||||||
|
if cfg.Debug {
|
||||||
|
cfg.Logger.SetLevel(log.Debug)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dir := LogsDir()
|
||||||
|
if dir == "" || os.MkdirAll(dir, 0o755) != nil {
|
||||||
|
return &logFile{}
|
||||||
|
}
|
||||||
|
name := "editor-" + time.Now().Format("20060102-150405") + "-" + strconv.Itoa(os.Getpid()) + ".log"
|
||||||
|
f, err := os.Create(filepath.Join(dir, name))
|
||||||
|
if err != nil {
|
||||||
|
return &logFile{}
|
||||||
|
}
|
||||||
|
cfg.Logger.AddSink(log.NewTextSink(f))
|
||||||
|
pruneLogs(dir)
|
||||||
|
return &logFile{f: f}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logFile) writer(w io.Writer) io.Writer {
|
||||||
|
if l.f == nil {
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
return io.MultiWriter(w, l.f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logFile) close() {
|
||||||
|
if l.f != nil {
|
||||||
|
l.f.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pruneLogs(dir string) {
|
||||||
|
entries, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var names []string
|
||||||
|
for _, e := range entries {
|
||||||
|
if !e.IsDir() && strings.HasSuffix(e.Name(), ".log") {
|
||||||
|
names = append(names, e.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for i := 0; i+keepLogs < len(names); i++ {
|
||||||
|
os.Remove(filepath.Join(dir, names[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -179,7 +179,7 @@ func TestProfilerSectionsFollowPlay(t *testing.T) {
|
|||||||
s.Step()
|
s.Step()
|
||||||
}
|
}
|
||||||
drawPanel(s, prof)
|
drawPanel(s, prof)
|
||||||
expectSections(t, prof.Sections(), "Frame", "Memory", "Game", "Scripts", "Physics")
|
expectSections(t, prof.Sections(), "Frame", "Memory", "Game", "Scripts", "Physics", "Audio")
|
||||||
if _, err := ctx.Docs.Open("scenes/other.aescene"); err != nil {
|
if _, err := ctx.Docs.Open("scenes/other.aescene"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package edit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"aego/app/proc"
|
||||||
|
"aego/editor/core/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EnvEditorExe = "AEGO_EDITOR_EXE"
|
||||||
|
EnvEditorVerb = "AEGO_EDITOR_VERB"
|
||||||
|
)
|
||||||
|
|
||||||
|
func markEntry(verb string) {
|
||||||
|
if os.Getenv(EnvEditorExe) != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if img := os.Getenv("APPIMAGE"); img != "" {
|
||||||
|
os.Setenv(EnvEditorExe, img)
|
||||||
|
os.Setenv(EnvEditorVerb, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Setenv(EnvEditorExe, exe)
|
||||||
|
os.Setenv(EnvEditorVerb, verb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func relaunchArgs(verb, target, tab string) []string {
|
||||||
|
if verb != "" {
|
||||||
|
args := []string{verb}
|
||||||
|
if target != "" {
|
||||||
|
args = append(args, target)
|
||||||
|
}
|
||||||
|
if tab != "" {
|
||||||
|
args = append(args, "--launcher", tab)
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
var args []string
|
||||||
|
if tab != "" {
|
||||||
|
args = append(args, "-launcher", tab)
|
||||||
|
}
|
||||||
|
if target != "" {
|
||||||
|
args = append(args, target)
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func withoutEnv(env []string, key string) []string {
|
||||||
|
out := env[:0:0]
|
||||||
|
for _, kv := range env {
|
||||||
|
if !strings.HasPrefix(kv, key+"=") {
|
||||||
|
out = append(out, kv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func Relaunch(target, tab string) error {
|
||||||
|
exe := os.Getenv(EnvEditorExe)
|
||||||
|
if exe == "" {
|
||||||
|
var err error
|
||||||
|
if exe, err = os.Executable(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd := exec.Command(exe, relaunchArgs(os.Getenv(EnvEditorVerb), target, tab)...)
|
||||||
|
cmd.Env = withoutEnv(os.Environ(), session.EnvSupervised)
|
||||||
|
proc.Hide(cmd)
|
||||||
|
return cmd.Start()
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package edit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"aego/project"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRelaunchArgs(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
verb, target, tab string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{"", "/p", "", []string{"/p"}},
|
||||||
|
{"", "", "recent", []string{"-launcher", "recent"}},
|
||||||
|
{"edit", "/p", "", []string{"edit", "/p"}},
|
||||||
|
{"edit", "", "new", []string{"edit", "--launcher", "new"}},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if got := relaunchArgs(c.verb, c.target, c.tab); !slices.Equal(got, c.want) {
|
||||||
|
t.Errorf("%q %q %q: %v, want %v", c.verb, c.target, c.tab, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env := withoutEnv([]string{"A=1", "AEGO_SUPERVISED=1", "B=2"}, "AEGO_SUPERVISED")
|
||||||
|
if !slices.Equal(env, []string{"A=1", "B=2"}) {
|
||||||
|
t.Fatal(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveArg(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
os.WriteFile(filepath.Join(root, project.FileName), []byte("project version=1 {\n}\n"), 0o644)
|
||||||
|
os.MkdirAll(filepath.Join(root, "scenes"), 0o755)
|
||||||
|
scene := filepath.Join(root, "scenes", "main.aescene")
|
||||||
|
os.WriteFile(scene, []byte("scene version=1 {\n}\n"), 0o644)
|
||||||
|
if dir, file := resolveArg(filepath.Join(root, project.FileName)); dir != root || file != "" {
|
||||||
|
t.Fatalf("project.aego: %q %q", dir, file)
|
||||||
|
}
|
||||||
|
dir, file := resolveArg(scene)
|
||||||
|
if dir != filepath.Join(root, "scenes") || file != scene {
|
||||||
|
t.Fatalf("scene: %q %q", dir, file)
|
||||||
|
}
|
||||||
|
if rel, ok := relInside(root, file); !ok || rel != "scenes/main.aescene" {
|
||||||
|
t.Fatalf("rel %q %v", rel, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewer(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
latest, current string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"v0.9.0", "0.9.0-beta.1", true},
|
||||||
|
{"v0.9.0-beta.1", "0.9.0-beta.1", false},
|
||||||
|
{"v0.9.0-beta.2", "0.9.0-beta.1", true},
|
||||||
|
{"v0.8.5", "0.9.0-beta.1", false},
|
||||||
|
{"v1.0.0", "0.9.0", true},
|
||||||
|
{"nightly", "0.9.0", false},
|
||||||
|
{"", "0.9.0", false},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if got := newer(c.latest, c.current); got != c.want {
|
||||||
|
t.Errorf("newer(%q, %q) = %v", c.latest, c.current, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file := filepath.Join(t.TempDir(), "update")
|
||||||
|
st := updateState{Enabled: false, Latest: "v1.2.3"}
|
||||||
|
if err := saveUpdate(file, st); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if back := loadUpdate(file); back.Enabled || back.Latest != "v1.2.3" {
|
||||||
|
t.Fatalf("round trip %+v", back)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package edit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"aego/app/run"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Session) runTests(done func(out string, failed bool)) error {
|
||||||
|
p, mods := s.Project, s.mods
|
||||||
|
go func() {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
code := run.Tool(p, []string{"test", "-v"}, &buf, &buf, mods...)
|
||||||
|
s.Engine.Jobs().OnMain(func() { done(buf.String(), code != 0) })
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"aego/app/export/icon"
|
"aego/app/export/icon"
|
||||||
"aego/app/export/winres"
|
"aego/app/export/winres"
|
||||||
|
"aego/app/proc"
|
||||||
"aego/core/errs"
|
"aego/core/errs"
|
||||||
"aego/core/log"
|
"aego/core/log"
|
||||||
"aego/runtime/bundle"
|
"aego/runtime/bundle"
|
||||||
@@ -121,7 +122,7 @@ func (j *job) resources(dir string) error {
|
|||||||
var icons []winres.Icon
|
var icons []winres.Icon
|
||||||
if j.icon != nil {
|
if j.icon != nil {
|
||||||
for _, n := range winres.IconSizes {
|
for _, n := range winres.IconSizes {
|
||||||
data, err := icon.PNG(icon.Resize(j.icon, n, n))
|
data, err := icon.PNG(icon.Sized(j.icon, n))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -175,6 +176,7 @@ func (j *job) build() error {
|
|||||||
args = append(args, pkg)
|
args = append(args, pkg)
|
||||||
cmd := exec.Command("go", args...)
|
cmd := exec.Command("go", args...)
|
||||||
cmd.Dir = root
|
cmd.Dir = root
|
||||||
|
proc.Hide(cmd)
|
||||||
cgo := "CGO_ENABLED=1"
|
cgo := "CGO_ENABLED=1"
|
||||||
if j.t.Web() {
|
if j.t.Web() {
|
||||||
cgo = "CGO_ENABLED=0"
|
cgo = "CGO_ENABLED=0"
|
||||||
|
|||||||
@@ -189,8 +189,9 @@ func (j *job) license(lib bundle.Lib, src, name string) bundle.Lib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (j *job) libs() []bundle.Lib {
|
func (j *job) libs() []bundle.Lib {
|
||||||
|
vorbis := j.license(bundle.Lib{Name: "oggvorbis", Link: "static"}, filepath.Join(j.opt.Engine, "audio", "codec", "vorbis", "internal", "oggvorbis", "LICENSE"), "oggvorbis.txt")
|
||||||
if j.t.Web() {
|
if j.t.Web() {
|
||||||
return []bundle.Lib{j.license(bundle.Lib{Name: "Go wasm_exec.js", Link: "script", File: webExec}, filepath.Join(j.goroot, "LICENSE"), "Go.txt")}
|
return []bundle.Lib{j.license(bundle.Lib{Name: "Go wasm_exec.js", Link: "script", File: webExec}, filepath.Join(j.goroot, "LICENSE"), "Go.txt"), vorbis}
|
||||||
}
|
}
|
||||||
return []bundle.Lib{j.license(bundle.Lib{Name: "SDL3", Link: "static"}, j.sdlLicense(), "SDL3.txt")}
|
return []bundle.Lib{j.license(bundle.Lib{Name: "SDL3", Link: "static"}, j.sdlLicense(), "SDL3.txt"), vorbis}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,3 +307,21 @@ func (j *job) prepareOut() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Launchable(p *project.Project, target, out string) (string, error) {
|
||||||
|
t, err := ParseTarget(target)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !t.Web() && !t.Host() {
|
||||||
|
return "", errs.New(errs.EngineConfig, "the build is for "+t.String()+"; copy it to that system to run it")
|
||||||
|
}
|
||||||
|
l := layoutFor(t, out, p)
|
||||||
|
switch {
|
||||||
|
case t.Web():
|
||||||
|
return l.root, nil
|
||||||
|
case t.OS == "darwin":
|
||||||
|
return l.app, nil
|
||||||
|
}
|
||||||
|
return l.binary(), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ import (
|
|||||||
"aego/core/errs"
|
"aego/core/errs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DocType struct {
|
||||||
|
Name string
|
||||||
|
Extensions []string
|
||||||
|
Role string
|
||||||
|
}
|
||||||
|
|
||||||
type Info struct {
|
type Info struct {
|
||||||
Name string
|
Name string
|
||||||
Executable string
|
Executable string
|
||||||
@@ -22,6 +28,7 @@ type Info struct {
|
|||||||
MinOS string
|
MinOS string
|
||||||
Category string
|
Category string
|
||||||
Copyright string
|
Copyright string
|
||||||
|
Documents []DocType
|
||||||
}
|
}
|
||||||
|
|
||||||
var icnsTypes = []struct {
|
var icnsTypes = []struct {
|
||||||
@@ -80,6 +87,23 @@ func Plist(i Info, iconFile string) []byte {
|
|||||||
str("LSMinimumSystemVersion", i.MinOS)
|
str("LSMinimumSystemVersion", i.MinOS)
|
||||||
str("LSApplicationCategoryType", i.Category)
|
str("LSApplicationCategoryType", i.Category)
|
||||||
str("NSHumanReadableCopyright", i.Copyright)
|
str("NSHumanReadableCopyright", i.Copyright)
|
||||||
|
if len(i.Documents) > 0 {
|
||||||
|
b.WriteString("\t<key>CFBundleDocumentTypes</key>\n\t<array>\n")
|
||||||
|
for _, d := range i.Documents {
|
||||||
|
role := d.Role
|
||||||
|
if role == "" {
|
||||||
|
role = "Editor"
|
||||||
|
}
|
||||||
|
b.WriteString("\t\t<dict>\n\t\t\t<key>CFBundleTypeName</key>\n\t\t\t<string>" + esc(d.Name) + "</string>\n")
|
||||||
|
b.WriteString("\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>" + esc(role) + "</string>\n")
|
||||||
|
b.WriteString("\t\t\t<key>CFBundleTypeExtensions</key>\n\t\t\t<array>\n")
|
||||||
|
for _, e := range d.Extensions {
|
||||||
|
b.WriteString("\t\t\t\t<string>" + esc(e) + "</string>\n")
|
||||||
|
}
|
||||||
|
b.WriteString("\t\t\t</array>\n\t\t</dict>\n")
|
||||||
|
}
|
||||||
|
b.WriteString("\t</array>\n")
|
||||||
|
}
|
||||||
b.WriteString("\t<key>NSHighResolutionCapable</key>\n\t<true/>\n")
|
b.WriteString("\t<key>NSHighResolutionCapable</key>\n\t<true/>\n")
|
||||||
b.WriteString("</dict>\n</plist>\n")
|
b.WriteString("</dict>\n</plist>\n")
|
||||||
return []byte(b.String())
|
return []byte(b.String())
|
||||||
@@ -92,7 +116,7 @@ func ICNS(src image.Image) ([]byte, error) {
|
|||||||
data, ok := cache[t.size]
|
data, ok := cache[t.size]
|
||||||
if !ok {
|
if !ok {
|
||||||
var err error
|
var err error
|
||||||
if data, err = icon.PNG(icon.Resize(src, t.size, t.size)); err != nil {
|
if data, err = icon.PNG(icon.Sized(src, t.size)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cache[t.size] = data
|
cache[t.size] = data
|
||||||
|
|||||||
@@ -23,3 +23,12 @@ func TestPlistEscapesAndBundleID(t *testing.T) {
|
|||||||
t.Fatal(p)
|
t.Fatal(p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPlistDocumentTypes(t *testing.T) {
|
||||||
|
out := string(Plist(Info{Name: "Aego", Executable: "aego", ID: "ru.akarpov.aego", Documents: []DocType{{Name: "Aego Scene", Extensions: []string{"aescene"}}}}, ""))
|
||||||
|
for _, want := range []string{"<key>CFBundleDocumentTypes</key>", "<string>Aego Scene</string>", "<string>Editor</string>", "<string>aescene</string>"} {
|
||||||
|
if !strings.Contains(out, want) {
|
||||||
|
t.Fatalf("plist has no %s:\n%s", want, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func (j *job) pack() error {
|
|||||||
|
|
||||||
func (j *job) packLinux() error {
|
func (j *job) packLinux() error {
|
||||||
if j.icon != nil {
|
if j.icon != nil {
|
||||||
data, err := icon.PNG(icon.Resize(j.icon, 256, 256))
|
data, err := icon.PNG(icon.Sized(j.icon, 256))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ var webFS embed.FS
|
|||||||
const (
|
const (
|
||||||
CodeWebAsset = "EXP010"
|
CodeWebAsset = "EXP010"
|
||||||
CodeWebNative = "EXP011"
|
CodeWebNative = "EXP011"
|
||||||
|
CodeWebMP3 = "EXP012"
|
||||||
WebWasm = "game.wasm"
|
WebWasm = "game.wasm"
|
||||||
WebFiles = "files.json"
|
WebFiles = "files.json"
|
||||||
WebBudget = 8 << 20
|
WebBudget = 8 << 20
|
||||||
@@ -68,7 +69,11 @@ func CheckWeb(s *Snapshot, native bool) []problems.Diagnostic {
|
|||||||
out = append(out, diag(CodeWebNative, problems.Warning,
|
out = append(out, diag(CodeWebNative, problems.Warning,
|
||||||
"the native Go module is compiled to WebAssembly: it must build without cgo and must not use files, processes or the network", "native/"))
|
"the native Go module is compiled to WebAssembly: it must build without cgo and must not use files, processes or the network", "native/"))
|
||||||
}
|
}
|
||||||
|
_, ffmpegErr := exec.LookPath("ffmpeg")
|
||||||
s.idx.Each(func(e *asset.Entry) {
|
s.idx.Each(func(e *asset.Entry) {
|
||||||
|
if ffmpegErr != nil && strings.EqualFold(filepath.Ext(e.Source), ".mp3") {
|
||||||
|
out = append(out, diag(CodeWebMP3, problems.Warning, e.Source+" is an mp3; it is converted to ogg on import, which needs ffmpeg in PATH", e.Source))
|
||||||
|
}
|
||||||
if e.Missing || exportSkip(e.Kind) || !webUnsupported(e.Kind) {
|
if e.Missing || exportSkip(e.Kind) || !webUnsupported(e.Kind) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -156,7 +161,7 @@ func (j *job) packWeb() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if j.icon != nil {
|
if j.icon != nil {
|
||||||
data, err := icon.PNG(icon.Resize(j.icon, webIconSize, webIconSize))
|
data, err := icon.PNG(icon.Sized(j.icon, webIconSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ canvas { display: block; width: 100%; height: 100%; outline: none; touch-action:
|
|||||||
#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; }
|
#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 { 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; }
|
#fullscreen:hover { opacity: 1; }
|
||||||
|
#mute { position: fixed; top: 10px; right: 52px; width: 36px; height: 36px; border: 0; border-radius: 6px; background: rgba(0, 0, 0, 0.45); color: #fff; font-size: 16px; cursor: pointer; opacity: 0.5; }
|
||||||
|
#mute:hover { opacity: 1; }
|
||||||
</style>
|
</style>
|
||||||
<link rel="preload" href="game.wasm?v={{.Build}}" as="fetch" type="application/wasm" crossorigin>
|
<link rel="preload" href="game.wasm?v={{.Build}}" as="fetch" type="application/wasm" crossorigin>
|
||||||
</head>
|
</head>
|
||||||
@@ -32,6 +34,7 @@ canvas { display: block; width: 100%; height: 100%; outline: none; touch-action:
|
|||||||
<pre id="error" hidden></pre>
|
<pre id="error" hidden></pre>
|
||||||
</div>
|
</div>
|
||||||
<button id="fullscreen" type="button" title="Fullscreen" aria-label="Fullscreen" hidden>⛶</button>
|
<button id="fullscreen" type="button" title="Fullscreen" aria-label="Fullscreen" hidden>⛶</button>
|
||||||
|
<button id="mute" type="button" title="Mute" aria-label="Mute" hidden>🔊</button>
|
||||||
<script src="wasm_exec.js?v={{.Build}}"></script>
|
<script src="wasm_exec.js?v={{.Build}}"></script>
|
||||||
<script src="loader.js?v={{.Build}}"></script>
|
<script src="loader.js?v={{.Build}}"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
const status = document.getElementById("status");
|
const status = document.getElementById("status");
|
||||||
const errorBox = document.getElementById("error");
|
const errorBox = document.getElementById("error");
|
||||||
const full = document.getElementById("fullscreen");
|
const full = document.getElementById("fullscreen");
|
||||||
|
const mute = document.getElementById("mute");
|
||||||
|
|
||||||
|
function syncMute() {
|
||||||
|
const on = globalThis.aegoAudioMute();
|
||||||
|
mute.textContent = on ? "🔇" : "🔊";
|
||||||
|
mute.title = on ? "Unmute" : "Mute";
|
||||||
|
mute.setAttribute("aria-label", mute.title);
|
||||||
|
}
|
||||||
const PARALLEL = 6;
|
const PARALLEL = 6;
|
||||||
const started = performance.now();
|
const started = performance.now();
|
||||||
let total = 0;
|
let total = 0;
|
||||||
@@ -34,6 +42,10 @@
|
|||||||
if (errorBox.hidden) loader.remove();
|
if (errorBox.hidden) loader.remove();
|
||||||
}, 300);
|
}, 300);
|
||||||
full.hidden = !canvas.requestFullscreen;
|
full.hidden = !canvas.requestFullscreen;
|
||||||
|
if (typeof globalThis.aegoAudioMute === "function") {
|
||||||
|
mute.hidden = false;
|
||||||
|
syncMute();
|
||||||
|
}
|
||||||
canvas.focus();
|
canvas.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,6 +54,11 @@
|
|||||||
if (done && done.catch) done.catch(() => {});
|
if (done && done.catch) done.catch(() => {});
|
||||||
canvas.focus();
|
canvas.focus();
|
||||||
});
|
});
|
||||||
|
mute.addEventListener("click", () => {
|
||||||
|
globalThis.aegoAudioMute(!globalThis.aegoAudioMute());
|
||||||
|
syncMute();
|
||||||
|
canvas.focus();
|
||||||
|
});
|
||||||
document.addEventListener("fullscreenchange", () => {
|
document.addEventListener("fullscreenchange", () => {
|
||||||
full.hidden = !!document.fullscreenElement;
|
full.hidden = !!document.fullscreenElement;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ func newGame(cfg GameConfig, types *scene.Types, reg *bind.Registry, man *module
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
e.SetService(loader.ServiceID, g.loader)
|
e.SetService(loader.ServiceID, g.loader)
|
||||||
|
playmods.UseMemorySaves(e)
|
||||||
e.SetService(renderhost.ServiceID, g.renderer)
|
e.SetService(renderhost.ServiceID, g.renderer)
|
||||||
e.SetView(frame.ServiceID, g.runner)
|
e.SetView(frame.ServiceID, g.runner)
|
||||||
e.MainWorld().Recorder = recorder.New(recordDepth)
|
e.MainWorld().Recorder = recorder.New(recordDepth)
|
||||||
|
|||||||