Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -1,8 +0,0 @@
|
||||
# Aego
|
||||
|
||||
A 2D and 3D game engine with an editor, a scripting language and a web export, written in Go.
|
||||
|
||||
- Documentation and the playground: <https://aego.akarpov.ru>
|
||||
- Installing and building from source: [engine/res/docs/install.md](engine/res/docs/install.md)
|
||||
- Contributing, check levels and rules for engine code: [docs/contributing.md](docs/contributing.md)
|
||||
- Architecture and decisions: [ARCHITECTURE.md](ARCHITECTURE.md)
|
||||
@@ -2,7 +2,6 @@ package agent
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"maps"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
@@ -66,8 +65,9 @@ func sceneCommands() []*Command {
|
||||
{Name: "scene.rename", Doc: "rename a node", Write: true, Args: []Arg{argFile, argNode, {Name: "name", Type: "string", Doc: "new name", Required: true, Pos: true}}, Run: func(s *Session, a Args) (*Output, error) {
|
||||
return s.sceneTx(a, "Rename", mutate.Rename(a.Str("node"), a.Str("name")))
|
||||
}},
|
||||
{Name: "scene.add-comp", Doc: "add a component with its defaults and the components it requires", Write: true, Args: []Arg{argFile, argNode, {Name: "comp", Type: "string", Doc: "component name, full or a unique suffix (Body2D)", Required: true, Pos: true},
|
||||
{Name: "no-required", Type: "boolean", Doc: "add only this component; missing requirements stay as problems"}}, Run: (*Session).sceneAddComp},
|
||||
{Name: "scene.add-comp", Doc: "add a component with its defaults", Write: true, Args: []Arg{argFile, argNode, {Name: "comp", Type: "string", Doc: "component name, full or a unique suffix (Body2D)", Required: true, Pos: true}}, Run: func(s *Session, a Args) (*Output, error) {
|
||||
return s.sceneTx(a, "Add Component", mutate.AddComp(a.Str("node"), a.Str("comp")))
|
||||
}},
|
||||
{Name: "scene.remove-comp", Doc: "remove a component", Write: true, Args: []Arg{argFile, argNode, {Name: "comp", Type: "string", Doc: "component name", Required: true, Pos: true}}, Run: func(s *Session, a Args) (*Output, error) {
|
||||
return s.sceneTx(a, "Remove Component", mutate.RemoveComp(a.Str("node"), a.Str("comp")))
|
||||
}},
|
||||
@@ -93,49 +93,6 @@ func (s *Session) scene(a Args) (string, []byte, *mutate.Document, error) {
|
||||
return rel, src, doc, err
|
||||
}
|
||||
|
||||
// sceneAddComp adds a component and, unless no-required is set, the components it requires first, in one transaction (D144).
|
||||
func (s *Session) sceneAddComp(a Args) (*Output, error) {
|
||||
rel, src, doc, err := s.scene(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, comp := a.Str("node"), a.Str("comp")
|
||||
ops := []mutate.Op{mutate.AddComp(node, comp)}
|
||||
var notes []string
|
||||
if !a.Bool("no-required") {
|
||||
td, err := doc.Component(comp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, err := doc.Node(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := mutate.Required(doc.Scene, td, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops = ops[:0]
|
||||
for _, r := range req {
|
||||
ops = append(ops, mutate.AddComp(node, r.FullName))
|
||||
notes = append(notes, "added "+label(r)+" (required by "+label(td)+")")
|
||||
}
|
||||
ops = append(ops, mutate.AddComp(node, td.FullName))
|
||||
}
|
||||
out, err := s.applyDoc(a, rel, src, doc, mutate.Tx{Label: "Add Component", Ops: ops})
|
||||
if out != nil {
|
||||
out.Notes = append(notes, out.Notes...)
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
func label(td *schema.TypeDescriptor) string {
|
||||
if td.DisplayName != "" {
|
||||
return td.DisplayName
|
||||
}
|
||||
return td.FullName
|
||||
}
|
||||
|
||||
func (s *Session) sceneTx(a Args, label string, ops ...mutate.Op) (*Output, error) {
|
||||
rel, src, doc, err := s.scene(a)
|
||||
if err != nil {
|
||||
@@ -288,9 +245,6 @@ func (s *Session) sceneRun(a Args) (*Output, error) {
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteString("frames: " + strconv.Itoa(rep.Frames) + "\nhash: " + rep.Hash + "\n")
|
||||
for _, k := range slices.Sorted(maps.Keys(rep.Counters)) {
|
||||
b.WriteString(k + ": " + strconv.FormatUint(rep.Counters[k], 10) + "\n")
|
||||
}
|
||||
for _, l := range rep.Log {
|
||||
b.WriteString(l + "\n")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"aego/script/abi"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
@@ -35,14 +34,6 @@ type TypeView struct {
|
||||
Requires []string `json:"requires,omitempty"`
|
||||
Doc string `json:"doc,omitempty"`
|
||||
Fields []FieldView `json:"fields,omitempty"`
|
||||
Methods []CallView `json:"methods,omitempty"`
|
||||
Signals []CallView `json:"signals,omitempty"`
|
||||
}
|
||||
|
||||
type CallView struct {
|
||||
Name string `json:"name"`
|
||||
Sig string `json:"sig"`
|
||||
Doc string `json:"doc,omitempty"`
|
||||
}
|
||||
|
||||
func schemaCommands() []*Command {
|
||||
@@ -99,7 +90,6 @@ func (s *Session) lookupType(name string) (*schema.TypeDescriptor, error) {
|
||||
found = append(found, td)
|
||||
}
|
||||
}
|
||||
found = schema.PreferProject(found)
|
||||
switch len(found) {
|
||||
case 1:
|
||||
return found[0], nil
|
||||
@@ -172,51 +162,5 @@ func (s *Session) schemaShow(a Args) (*Output, error) {
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
if s.kit != nil {
|
||||
for _, cm := range s.kit.methods(td.FullName) {
|
||||
v.Methods = append(v.Methods, CallView{Name: cm.Name, Sig: methodSig(cm.Sig, cm.Params), Doc: summary(cm.Doc)})
|
||||
}
|
||||
}
|
||||
for _, sg := range td.Signals {
|
||||
params := make([]string, len(sg.Params))
|
||||
for i, p := range sg.Params {
|
||||
params[i] = p
|
||||
if i < len(sg.Names) && sg.Names[i] != "" {
|
||||
params[i] = sg.Names[i] + " " + p
|
||||
}
|
||||
}
|
||||
v.Signals = append(v.Signals, CallView{Name: sg.Name, Sig: "(" + strings.Join(params, ", ") + ")", Doc: summary(sg.Doc)})
|
||||
}
|
||||
for _, sec := range []struct {
|
||||
title string
|
||||
list []CallView
|
||||
}{{"methods", v.Methods}, {"signals", v.Signals}} {
|
||||
if len(sec.list) == 0 {
|
||||
continue
|
||||
}
|
||||
b.WriteString(sec.title + ":\n")
|
||||
for _, c := range sec.list {
|
||||
b.WriteString(" " + c.Name + c.Sig)
|
||||
if c.Doc != "" {
|
||||
b.WriteString(" — " + c.Doc)
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
}
|
||||
return &Output{Value: v, Text: b.String()}, nil
|
||||
}
|
||||
|
||||
func methodSig(sig abi.Signature, names []string) string {
|
||||
params := make([]string, len(sig.Params))
|
||||
for i, p := range sig.Params {
|
||||
params[i] = p.String()
|
||||
if i < len(names) && names[i] != "" {
|
||||
params[i] = names[i] + " " + params[i]
|
||||
}
|
||||
}
|
||||
out := "(" + strings.Join(params, ", ") + ")"
|
||||
if sig.Result.Kind != abi.Void {
|
||||
out += " " + sig.Result.String()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"aego/scene/sceneio"
|
||||
"aego/schema"
|
||||
"aego/script/bind"
|
||||
"aego/script/bind/manifest"
|
||||
"aego/script/reload"
|
||||
"aego/script/runtime"
|
||||
"aego/script/workspace"
|
||||
@@ -47,11 +46,10 @@ type RunRequest struct {
|
||||
}
|
||||
|
||||
type RunReport struct {
|
||||
Frames int `json:"frames"`
|
||||
Hash string `json:"hash"`
|
||||
Counters map[string]uint64 `json:"counters,omitempty"`
|
||||
Log []string `json:"log,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Frames int `json:"frames"`
|
||||
Hash string `json:"hash"`
|
||||
Log []string `json:"log,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type Runner func(p *project.Project, r RunRequest) (RunReport, error)
|
||||
@@ -81,25 +79,6 @@ type Kit struct {
|
||||
ws *workspace.Workspace
|
||||
binds *bind.Registry
|
||||
opt Options
|
||||
man *manifest.Manifest
|
||||
}
|
||||
|
||||
// methods lists the script methods the bindings give a component type, built once per kit.
|
||||
func (k *Kit) methods(comp string) []manifest.CompMethod {
|
||||
if k.man == nil {
|
||||
m, err := manifest.FromRegistry(k.binds, k.Types.Schema())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
k.man = m
|
||||
}
|
||||
var out []manifest.CompMethod
|
||||
for _, cm := range k.man.CompMethods {
|
||||
if cm.Comp == comp {
|
||||
out = append(out, cm)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func NewKit(p *project.Project, opt Options) (*Kit, error) {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"aego/modules/dataasset"
|
||||
"aego/modules/debug"
|
||||
"aego/modules/display"
|
||||
"aego/modules/gameplay"
|
||||
inputmod "aego/modules/input"
|
||||
"aego/modules/physics"
|
||||
"aego/modules/render2d"
|
||||
@@ -77,10 +76,6 @@ func cameraEntry() assembly.Entry {
|
||||
return assembly.Entry{ID: camera.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return camera.New() }}
|
||||
}
|
||||
|
||||
func gameplayEntry() assembly.Entry {
|
||||
return assembly.Entry{ID: gameplay.ID, Requires: []string{physics.ID, scripting.ID}, New: func(assembly.Options) module.Module { return gameplay.New() }}
|
||||
}
|
||||
|
||||
func dataEntry() assembly.Entry {
|
||||
return assembly.Entry{ID: dataasset.ID, New: func(assembly.Options) module.Module { return dataasset.New() }}
|
||||
}
|
||||
@@ -95,5 +90,5 @@ func savegameEntry() assembly.Entry {
|
||||
|
||||
func Default() assembly.Catalog {
|
||||
return assembly.Catalog{core(), dataEntry(), inputEntry(), render2dEntry(), render3dEntry(), scriptEntry(), physicsEntry(), debugEntry(),
|
||||
displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), cameraEntry(), gameplayEntry(), spawnEntry(), savegameEntry()}
|
||||
displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), cameraEntry(), spawnEntry(), savegameEntry()}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
cameraeditor "aego/modules/camera/editor"
|
||||
"aego/modules/debug"
|
||||
debugeditor "aego/modules/debug/editor"
|
||||
"aego/modules/gameplay"
|
||||
gameplayeditor "aego/modules/gameplay/editor"
|
||||
inputmod "aego/modules/input"
|
||||
inputeditor "aego/modules/input/editor"
|
||||
"aego/modules/physics"
|
||||
@@ -27,7 +25,6 @@ func With(c assembly.Catalog) assembly.Catalog {
|
||||
With(render3d.ID, render3deditor.Hook).
|
||||
With(physics.ID, physicseditor.Hook).
|
||||
With(camera.ID, cameraeditor.Hook).
|
||||
With(gameplay.ID, gameplayeditor.Hook).
|
||||
With(audio.ID, audioeditor.Hook).
|
||||
With(debug.ID, debugeditor.Hook)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"aego/modules/audio"
|
||||
"aego/modules/camera"
|
||||
"aego/modules/debug"
|
||||
"aego/modules/gameplay"
|
||||
inputmod "aego/modules/input"
|
||||
"aego/modules/physics"
|
||||
"aego/modules/render2d"
|
||||
@@ -32,7 +31,6 @@ func Catalog() assembly.Catalog {
|
||||
{ID: debug.ID, New: func(assembly.Options) module.Module { return debug.New() }},
|
||||
{ID: tween.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return tween.New() }},
|
||||
{ID: camera.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return camera.New() }},
|
||||
{ID: gameplay.ID, Requires: []string{physics.ID, scripting.ID}, New: func(assembly.Options) module.Module { return gameplay.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() }},
|
||||
|
||||
@@ -41,7 +41,6 @@ type Config struct {
|
||||
Editor bool
|
||||
FailOnScriptErrors bool
|
||||
Catalog assembly.Catalog
|
||||
Platform platform.Platform
|
||||
Profile string
|
||||
NoProfile bool
|
||||
}
|
||||
@@ -99,9 +98,6 @@ func (h *Host) build(cfg Config, mods []module.Module) error {
|
||||
ecfg.LoopScope = cfg.LoopScope
|
||||
}
|
||||
builder := engine.NewBuilder(ecfg).WithLogger(lg).Use(rmod)
|
||||
if cfg.Platform != nil {
|
||||
builder.WithPlatform(cfg.Platform)
|
||||
}
|
||||
if h.Types, err = assembly.NewTypes(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -191,7 +187,6 @@ func (h *Host) build(cfg Config, mods []module.Module) error {
|
||||
}
|
||||
h.Settings.SetNames(h.Sections.Names)
|
||||
h.Engine.SetService(settings.ServiceID, h.Settings)
|
||||
h.Engine.OnClose(h.Modules.Shutdown)
|
||||
if err = h.Modules.Init(&module.InitContext{Engine: h.Engine, Assets: h.Loader, Log: lg, Editor: cfg.Editor, Sections: h.Sections, Problems: h.Problems, Settings: h.Settings}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -232,6 +227,9 @@ func (h *Host) Close() {
|
||||
return
|
||||
}
|
||||
h.closed = true
|
||||
if h.Modules != nil {
|
||||
h.Modules.Shutdown()
|
||||
}
|
||||
if h.Engine != nil {
|
||||
h.Engine.Close()
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
package docstool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"aego/app/playground"
|
||||
"aego/engine/docs"
|
||||
"aego/engine/module"
|
||||
)
|
||||
|
||||
// CheckExamples loads the example scene of every module code, validates it and runs it headless; the code must be among the problems it reports (D146).
|
||||
func CheckExamples(reg *docs.Registry, w io.Writer) int {
|
||||
pg, err := playground.New()
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, "examples: 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()
|
||||
}
|
||||
}()
|
||||
bad := 0
|
||||
for _, e := range reg.All() {
|
||||
if e.Kind != docs.Code || e.Diag == nil || e.Diag.Example == "" {
|
||||
continue
|
||||
}
|
||||
found, why := false, ""
|
||||
code := ""
|
||||
if e.Diag.ExampleScript != "" {
|
||||
c, err := pg.TryCode(e.Diag.ExampleScript)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "%s: error: example script does not compile: %v\n", e.ID, err)
|
||||
bad++
|
||||
continue
|
||||
}
|
||||
code = c
|
||||
}
|
||||
if res := pg.PlayScene("example-"+e.ID, []byte(e.Diag.Example), code, nil); !res.Started {
|
||||
why = "does not load: " + res.Error
|
||||
} else {
|
||||
for _, d := range module.ValidateScene(pg.Game().Scene(), nil) {
|
||||
found = found || d.Code == e.ID
|
||||
}
|
||||
if err := pg.Game().Step(tryFrames); err != nil {
|
||||
why = "stops the engine: " + err.Error()
|
||||
}
|
||||
}
|
||||
poll := pg.Poll()
|
||||
for _, f := range poll.Faults {
|
||||
found = found || f.Code == e.ID
|
||||
}
|
||||
for _, l := range poll.Log {
|
||||
found = found || strings.Contains(l.Text, e.ID)
|
||||
}
|
||||
switch {
|
||||
case why != "":
|
||||
fmt.Fprintf(w, "%s: error: example %s %s\n", e.ID, e.Diag.ExampleFile, why)
|
||||
bad++
|
||||
case !found:
|
||||
fmt.Fprintf(w, "%s: error: example of %s does not produce %s\n", e.ID, e.ID, e.ID)
|
||||
bad++
|
||||
}
|
||||
}
|
||||
pg.Stop()
|
||||
return bad
|
||||
}
|
||||
@@ -76,9 +76,6 @@ func (e *Env) Check(strict bool) int {
|
||||
if CheckTry(reg, e.Stdout) > 0 && code == 0 {
|
||||
code = 1
|
||||
}
|
||||
if CheckExamples(reg, e.Stdout) > 0 && code == 0 {
|
||||
code = 1
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ type request struct {
|
||||
Component string `json:"component"`
|
||||
Field string `json:"field"`
|
||||
Value float64 `json:"value"`
|
||||
Node string `json:"node"`
|
||||
}
|
||||
|
||||
type Reply struct {
|
||||
@@ -28,7 +27,6 @@ type Reply struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
State *docsession.State `json:"state,omitempty"`
|
||||
Log []Line `json:"log,omitempty"`
|
||||
Pose []float64 `json:"pose,omitempty"`
|
||||
}
|
||||
|
||||
func (a *App) Call(method string, payload []byte) Reply {
|
||||
@@ -66,17 +64,6 @@ func (a *App) Call(method string, payload []byte) Reply {
|
||||
case "state":
|
||||
st := s.State()
|
||||
return Reply{State: &st}
|
||||
case "pose":
|
||||
sc := s.Controller().Scene()
|
||||
if sc == nil {
|
||||
return Reply{Error: "the scene is not running"}
|
||||
}
|
||||
h := sc.Find(sc.Root(), req.Node)
|
||||
if h.IsZero() || !sc.IsAlive(h) {
|
||||
return Reply{Error: "no node " + strconv.Quote(req.Node)}
|
||||
}
|
||||
p := sc.WorldPosition(h)
|
||||
return Reply{Pose: []float64{float64(p.X), float64(p.Y)}}
|
||||
case "log":
|
||||
out := Reply{Log: a.lines}
|
||||
a.lines = nil
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
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,
|
||||
)
|
||||
}
|
||||
@@ -61,8 +61,7 @@ func Main(mods ...sdk.Module) {
|
||||
|
||||
func fail(err error) {
|
||||
os.Stderr.WriteString("aego-editor: " + err.Error() + "\n")
|
||||
platform.Boot("fatal", err.Error())
|
||||
if !platform.ShowFatal("Aego", platform.FatalText("Aego", err.Error())) && !interactive() {
|
||||
if !interactive() {
|
||||
platform.Alert("Aego", err.Error())
|
||||
}
|
||||
os.Exit(1)
|
||||
@@ -113,8 +112,6 @@ func relInside(root, file string) (string, bool) {
|
||||
}
|
||||
|
||||
func Start(e Entry) error {
|
||||
defer platform.RecoverFatal("Aego")
|
||||
startBoot("edit")
|
||||
platform.SetAppIcon(brand.Window()...)
|
||||
markEntry(e.Verb)
|
||||
cfg := e.Config
|
||||
@@ -148,7 +145,6 @@ func Start(e Entry) error {
|
||||
cfg.OpenPath = rel
|
||||
}
|
||||
}
|
||||
platform.Boot("editor", p.Root)
|
||||
if bootstrap.NeedsNative(p) && !cfg.Headless && cfg.Frames == 0 && !cfg.Supervised {
|
||||
if goFound() {
|
||||
return supervise.Run(p, supervise.Options{
|
||||
@@ -157,7 +153,6 @@ func Start(e Entry) error {
|
||||
})
|
||||
}
|
||||
cfg.NativeNote = NoGoNote
|
||||
platform.Boot("native", NoGoNote)
|
||||
}
|
||||
return Run(p, cfg, e.Mods...)
|
||||
}
|
||||
|
||||
@@ -176,7 +176,6 @@ func OpenLauncher(cfg Config) (*LaunchSession, error) {
|
||||
}
|
||||
ls := &LaunchSession{Engine: e}
|
||||
d := &launchDriver{s: ls, renderer: mod.Renderer(), log: lg.With(shell.ChannelEditor), images: map[string]launchImage{}}
|
||||
platform.Boot("launcher", "open")
|
||||
ls.UI = launcher.New(launcher.Options{
|
||||
Templates: launcherTemplates(),
|
||||
Examples: examples,
|
||||
|
||||
@@ -179,7 +179,7 @@ func TestProfilerSectionsFollowPlay(t *testing.T) {
|
||||
s.Step()
|
||||
}
|
||||
drawPanel(s, prof)
|
||||
expectSections(t, prof.Sections(), "Frame", "Memory", "Game", "Scripts", "Physics", "Audio", "Camera", "Gameplay")
|
||||
expectSections(t, prof.Sections(), "Frame", "Memory", "Game", "Scripts", "Physics", "Audio")
|
||||
if _, err := ctx.Docs.Open("scenes/other.aescene"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -7,10 +7,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"aego/core/ids"
|
||||
"aego/editor/core/command"
|
||||
"aego/editor/core/context"
|
||||
"aego/editor/core/vectors"
|
||||
"aego/editor/core/workspace"
|
||||
"aego/editor/panels/gameview"
|
||||
"aego/editor/shell"
|
||||
@@ -58,60 +56,7 @@ func (s *Session) shots(dir string) error {
|
||||
}
|
||||
return selectNamed(ctx, "Player")
|
||||
}},
|
||||
{name: "hitbox-gizmo", frames: 60, setup: func(s *Session) error {
|
||||
sc := ctx.Scene()
|
||||
var span []ids.NodeID
|
||||
for _, n := range []string{"EnemyLeft", "PatrolA1", "PatrolB1"} {
|
||||
span = append(span, sc.ID(sc.Find(sc.Root(), n)))
|
||||
}
|
||||
ctx.Select(span...)
|
||||
if err := ctx.Commands.Run(command.SceneFrame); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < 30; i++ {
|
||||
s.Step()
|
||||
}
|
||||
return selectNamed(ctx, "EnemyLeft")
|
||||
}},
|
||||
{name: "camera-follow", frames: 60, setup: func(s *Session) error {
|
||||
sc := ctx.Scene()
|
||||
ctx.Select(sc.ID(sc.Find(sc.Root(), "Camera")), sc.ID(sc.Find(sc.Root(), "Player")))
|
||||
if err := ctx.Commands.Run(command.SceneFrame); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < 30; i++ {
|
||||
s.Step()
|
||||
}
|
||||
return selectNamed(ctx, "Camera")
|
||||
}},
|
||||
{name: "camera-zone", frames: 60, setup: func(s *Session) error {
|
||||
sc := ctx.Scene()
|
||||
ctx.Select(sc.ID(sc.Find(sc.Root(), "ArenaZone")), sc.ID(sc.Find(sc.Root(), "PatrolA2")), sc.ID(sc.Find(sc.Root(), "PatrolB2")), sc.ID(sc.Find(sc.Root(), "GroundRight")))
|
||||
if err := ctx.Commands.Run(command.SceneFrame); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < 30; i++ {
|
||||
s.Step()
|
||||
}
|
||||
return selectNamed(ctx, "ArenaZone")
|
||||
}},
|
||||
{name: "clip-inspector", frames: 60, setup: func(s *Session) error {
|
||||
id, ok := s.Pipeline.Lookup("assets/audio/music.ogg")
|
||||
if !ok {
|
||||
return fmt.Errorf("no assets/audio/music.ogg")
|
||||
}
|
||||
s.App.Data().Inspect(id)
|
||||
return nil
|
||||
}},
|
||||
{name: "add-required", frames: 30, setup: func(s *Session) error {
|
||||
if err := selectNamed(ctx, "PatrolA1"); err != nil {
|
||||
return err
|
||||
}
|
||||
s.App.Inspector().OpenAdd("follow")
|
||||
return nil
|
||||
}},
|
||||
{name: "tags", frames: 60, setup: func(s *Session) error {
|
||||
ctx.UI.CloseAllPopups()
|
||||
return selectNamed(ctx, "Player")
|
||||
}},
|
||||
{name: "prefab", frames: 60, setup: func(s *Session) error {
|
||||
@@ -138,23 +83,12 @@ func (s *Session) shots(dir string) error {
|
||||
scene()
|
||||
return ctx.Commands.Run(command.PlayStart)
|
||||
}},
|
||||
{name: "motion-play", frames: 30, setup: func(s *Session) error {
|
||||
s.App.Workspace().Show(workspace.Inspector)
|
||||
s.App.Inspector().ScrollTo("gameplay.Motion2D")
|
||||
return selectNamed(ctx, "Player")
|
||||
}},
|
||||
{name: "mixer", frames: 120, setup: func(s *Session) error {
|
||||
s.App.Workspace().Switch(workspace.Audio)
|
||||
s.App.Workspace().Show(workspace.Mixer)
|
||||
return nil
|
||||
}},
|
||||
{name: "physics-debug", frames: 30, setup: func(s *Session) error {
|
||||
s.App.Workspace().Switch(workspace.Design)
|
||||
ctx.Overlay.Set("physics.debug.contacts", true)
|
||||
return ctx.Commands.Run(gameview.CommandOverlays)
|
||||
}},
|
||||
{name: "demo", frames: 60, inset: 2, bottom: 28, setup: func(s *Session) error {
|
||||
for _, f := range []string{gameview.CommandOverlays, vectors.CommandToggle} {
|
||||
for _, f := range []string{"physics.debug", "physics.debug.shapes", "physics.debug.contacts", "physics.debug.joints"} {
|
||||
ctx.Overlay.Set(f, false)
|
||||
}
|
||||
return ctx.Commands.Run(shell.CommandFullscreenPlay)
|
||||
|
||||
@@ -18,14 +18,12 @@ canvas { display: block; width: 100%; height: 100%; outline: none; touch-action:
|
||||
#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; padding: 0; 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; }
|
||||
#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; padding: 0; cursor: pointer; opacity: 0.5; }
|
||||
#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; }
|
||||
#fullscreen svg, #mute svg { display: block; width: 18px; height: 18px; margin: auto; }
|
||||
#mute .off, #mute.muted .on { display: none; }
|
||||
#mute.muted .off { display: inline; }
|
||||
</style>
|
||||
<link rel="preload" href="game.wasm?v={{.Build}}" as="fetch" type="application/wasm" crossorigin>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="game" data-aego tabindex="0" aria-label="{{.Title}}"></canvas>
|
||||
@@ -35,8 +33,8 @@ canvas { display: block; width: 100%; height: 100%; outline: none; touch-action:
|
||||
<div id="status">Loading…</div>
|
||||
<pre id="error" hidden></pre>
|
||||
</div>
|
||||
<button id="fullscreen" type="button" title="Fullscreen" aria-label="Fullscreen" hidden><svg viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M9.92 13.44L10.25 13.38L13.38 13.38L13.38 10.25L13.44 9.92L13.76 9.52L14.08 9.39L14.58 9.44L14.98 9.76L15.12 10.25L15.12 14.25L15.06 14.58L14.74 14.98L14.25 15.12L10.25 15.12L9.92 15.06L9.52 14.74L9.38 14.25L9.52 13.76ZM13.44 6.08L13.38 5.75L13.38 2.62L10.25 2.62L9.92 2.56L9.52 2.24L9.39 1.92L9.44 1.42L9.76 1.02L10.25 0.88L14.25 0.88L14.58 0.94L14.98 1.26L15.12 1.75L15.12 5.75L15.06 6.08L14.74 6.48L14.25 6.62L13.76 6.48ZM2.56 9.92L2.62 10.25L2.62 13.38L5.75 13.38L6.08 13.44L6.48 13.76L6.62 14.25L6.56 14.58L6.24 14.98L5.75 15.12L1.75 15.12L1.42 15.06L1.02 14.74L0.88 14.25L0.88 10.25L0.94 9.92L1.26 9.52L1.75 9.38L2.24 9.52ZM6.08 2.56L5.75 2.62L2.62 2.62L2.62 5.75L2.56 6.08L2.24 6.48L1.75 6.62L1.42 6.56L1.02 6.24L0.88 5.75L0.88 1.75L0.94 1.42L1.26 1.02L1.75 0.88L5.75 0.88L6.08 0.94L6.48 1.26L6.62 1.75L6.48 2.24Z"/></svg></button>
|
||||
<button id="mute" type="button" title="Mute" aria-label="Mute" hidden><svg viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M1.81 6.04L4 6L6.74 3.47L7.11 3.41L7.36 3.55L7.5 3.9L7.5 12.1L7.36 12.45L7.11 12.59L6.74 12.53L4 10L2 10L1.65 9.85L1.5 9.5L1.5 6.5L1.58 6.22Z"/><path class="on" fill="currentColor" d="M12.53 5.89L11.97 4.96L11.01 3.9L10.95 3.62L10.99 3.33L11.15 3.08L11.39 2.91L11.82 2.86L12.21 3.05L12.72 3.54L13.53 4.59L13.91 5.29L14.17 5.95L14.35 6.63L14.5 7.96L14.46 8.75L14.34 9.45L14.14 10.13L13.58 11.34L13.18 11.92L12.67 12.52L12.09 13.04L11.67 13.15L11.39 13.09L11.15 12.92L10.99 12.67L10.95 12.38L11.09 11.97L11.61 11.46L12.27 10.59L12.73 9.61L12.88 9.08L12.97 8.54L12.97 7.46L12.73 6.39ZM10.05 7.09L9.82 6.68L9.38 6.19L9.26 5.77L9.31 5.48L9.48 5.24L9.72 5.08L10.01 5.02L10.29 5.08L10.54 5.24L11.01 5.76L11.27 6.16L11.66 7.18L11.75 8.04L11.58 9.12L11.23 9.91L10.54 10.76L10.29 10.92L10.01 10.98L9.72 10.92L9.48 10.76L9.31 10.52L9.26 10.23L9.38 9.81L9.95 9.12L10.2 8.47L10.24 7.76Z"/><path class="off" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" d="M10.25 5.75l4.5 4.5M14.75 5.75l-4.5 4.5"/></svg></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="loader.js?v={{.Build}}"></script>
|
||||
</body>
|
||||
|
||||
@@ -198,11 +198,6 @@ func (g *Game) Engine() *engine.Engine {
|
||||
return g.e
|
||||
}
|
||||
|
||||
// Scene is the scene Play loaded, nil before the first Play or after Stop.
|
||||
func (g *Game) Scene() *scene.Scene {
|
||||
return g.scene
|
||||
}
|
||||
|
||||
func (g *Game) Play(name string, src []byte, mods []*bytecode.Module) error {
|
||||
g.Stop()
|
||||
rt := scriptruntime.New(scriptruntime.Options{Bindings: g.reg, Schema: g.types.Schema(), Log: g.log, VM: playmods.SandboxVM()})
|
||||
|
||||
@@ -9,10 +9,6 @@ import (
|
||||
"aego/app/agent"
|
||||
"aego/core/log"
|
||||
"aego/core/rewind"
|
||||
"aego/engine/recorder"
|
||||
"aego/modules/audio"
|
||||
"aego/modules/camera"
|
||||
"aego/modules/gameplay"
|
||||
"aego/project"
|
||||
"aego/sdk"
|
||||
)
|
||||
@@ -49,7 +45,6 @@ func AgentRunner(mods ...sdk.Module) agent.Runner {
|
||||
}
|
||||
sum, _, _ := WorldHash(a.Engine.MainWorld(), rewind.NewHasher())
|
||||
rep.Hash = hex.EncodeToString(sum[:])
|
||||
rep.Counters = counters(a)
|
||||
a.Close()
|
||||
rep.Log = logLines(buf.String())
|
||||
return rep, nil
|
||||
@@ -68,27 +63,3 @@ func logLines(s string) []string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// counters sums what the modules counted during the run, for scene run and run_headless.
|
||||
func counters(a *App) map[string]uint64 {
|
||||
out := map[string]uint64{}
|
||||
w := a.Engine.MainWorld()
|
||||
if s := gameplay.Of(w); s != nil {
|
||||
st := s.Stats()
|
||||
out["gameplay.steps"], out["gameplay.casts"], out["gameplay.hits"] = st.Steps, st.Casts, st.Hits
|
||||
out["gameplay.deaths"], out["gameplay.respawns"], out["gameplay.pickups"] = st.Deaths, st.Respawns, st.Pickups
|
||||
}
|
||||
if c := camera.Of(w); c != nil {
|
||||
c.Outputs(func(camera.OutputInfo) { out["camera.outputs"]++ })
|
||||
}
|
||||
if m := audio.ModuleOf(a.Engine); m != nil {
|
||||
c, st := m.Counts(), m.Stats()
|
||||
out["audio.finished"], out["audio.stolen"], out["audio.rejected"], out["audio.starved"] = c.Finished, c.Stolen, c.Rejected, c.Starved
|
||||
out["audio.underruns"] = st.Underruns
|
||||
}
|
||||
if r := w.Recorder; r != nil {
|
||||
counts := r.Counts()
|
||||
out["events.audio"], out["events.gameplay"] = counts[recorder.Audio], counts[recorder.Gameplay]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -80,7 +80,6 @@ type bus struct {
|
||||
target float32
|
||||
delta float32
|
||||
ramp int
|
||||
live bool
|
||||
acc []float32
|
||||
peak atomic.Uint32
|
||||
rms atomic.Uint32
|
||||
|
||||
@@ -441,87 +441,3 @@ func TestVoicePositionAndKill(t *testing.T) {
|
||||
t.Fatalf("kill: %+v, live %v", ev, m.Live(id))
|
||||
}
|
||||
}
|
||||
|
||||
func directPair(tb testing.TB, mem *Memory, loop bool) (*Mixer, voice, voice) {
|
||||
m := newMixer(tb)
|
||||
m.Play(PlayParams{Memory: mem, Gain: 0.7, Pan: 0.3, Loop: loop, FadeIn: 0.005})
|
||||
buf := make([]float32, 64*2)
|
||||
m.Mix(buf)
|
||||
for i := range m.voices {
|
||||
if m.voices[i].state == voicePlaying {
|
||||
return m, m.voices[i], m.voices[i]
|
||||
}
|
||||
}
|
||||
tb.Fatal("no playing voice")
|
||||
return nil, voice{}, voice{}
|
||||
}
|
||||
|
||||
// The direct path for pitch 1 gives the same samples, position, gain and end as Hermite, through fades, loops and the stop ramp.
|
||||
func TestDirectPathMatchesHermite(t *testing.T) {
|
||||
for _, ch := range []int{1, 2} {
|
||||
for _, loop := range []bool{false, true} {
|
||||
mem := sine(440, rate/20, ch, 0.5)
|
||||
if loop {
|
||||
mem.LoopStart, mem.LoopEnd = 100, mem.Frames()-50
|
||||
}
|
||||
m, a, b := directPair(t, mem, loop)
|
||||
if b.step != 1 {
|
||||
t.Fatalf("step %v, the direct path needs 1", b.step)
|
||||
}
|
||||
const n = 256
|
||||
accA, accB := make([]float32, n*2), make([]float32, n*2)
|
||||
for block := 0; block < 40; block++ {
|
||||
if block == 20 {
|
||||
a.rampTo(0, 300)
|
||||
b.rampTo(0, 300)
|
||||
a.stopping, b.stopping = true, true
|
||||
}
|
||||
clear(accA)
|
||||
clear(accB)
|
||||
da := m.renderMemory(&a, accA, n)
|
||||
db := m.renderMemoryDirect(&b, accB, n)
|
||||
if da != db || a.pos != b.pos || a.gain != b.gain {
|
||||
t.Fatalf("ch=%d loop=%v block %d: done %v/%v pos %v/%v gain %v/%v", ch, loop, block, da, db, a.pos, b.pos, a.gain, b.gain)
|
||||
}
|
||||
for i := range accA {
|
||||
if accA[i] != accB[i] {
|
||||
t.Fatalf("ch=%d loop=%v block %d sample %d: %v != %v", ch, loop, block, i, accA[i], accB[i])
|
||||
}
|
||||
}
|
||||
if da {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadedPitch1(tb testing.TB) (*Mixer, []float32) {
|
||||
m := newMixer(tb)
|
||||
mem := sine(440, rate/10, 1, 0.5)
|
||||
for i := 0; i < 64; i++ {
|
||||
m.Play(PlayParams{Memory: mem, Gain: 0.1, Pan: float32(i%3 - 1), Pitch: 1, Loop: true})
|
||||
}
|
||||
buf := make([]float32, 512*2)
|
||||
m.Mix(buf)
|
||||
if m.Active() != 64 {
|
||||
tb.Fatalf("%d active voices", m.Active())
|
||||
}
|
||||
return m, buf
|
||||
}
|
||||
|
||||
func TestMixPitch1Allocs(t *testing.T) {
|
||||
m, buf := loadedPitch1(t)
|
||||
if n := testing.AllocsPerRun(200, func() { m.Mix(buf) }); n != 0 {
|
||||
t.Fatalf("Mix allocates %v times per call", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMix64Pitch1(b *testing.B) {
|
||||
m, buf := loadedPitch1(b)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
m.Mix(buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ func (p *Pump) Start() {
|
||||
|
||||
func (p *Pump) run(stop, done chan struct{}) {
|
||||
defer close(done)
|
||||
period := time.Duration(float64(p.cfg.Block*p.cfg.Target) / float64(p.cfg.SampleRate) / 6 * float64(time.Second))
|
||||
period := time.Duration(float64(p.cfg.Block) / float64(p.cfg.SampleRate) / 2 * float64(time.Second))
|
||||
t := time.NewTicker(max(period, time.Millisecond))
|
||||
defer t.Stop()
|
||||
for {
|
||||
|
||||
@@ -102,33 +102,3 @@ func TestAgentSceneTreeJSON(t *testing.T) {
|
||||
t.Fatalf("output %v:\n%s", err, out)
|
||||
}
|
||||
}
|
||||
|
||||
// The top-down example needs no native module, so the CLI can run it from a copy.
|
||||
func TestAgentSceneRunCounters(t *testing.T) {
|
||||
dir := copyProject(t, "topdown")
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdout := os.Stdout
|
||||
os.Stdout = w
|
||||
code := dispatch([]string{"scene", "run", "scenes/main.aescene", "--frames", "300", "--json", "--project", dir})
|
||||
os.Stdout = stdout
|
||||
w.Close()
|
||||
out, _ := io.ReadAll(r)
|
||||
if code != 0 {
|
||||
t.Fatalf("exit %d:\n%s", code, out)
|
||||
}
|
||||
var v struct {
|
||||
Value struct {
|
||||
Counters map[string]uint64 `json:"counters"`
|
||||
} `json:"value"`
|
||||
}
|
||||
if err := json.Unmarshal(out, &v); err != nil {
|
||||
t.Fatalf("output %v:\n%s", err, out)
|
||||
}
|
||||
c := v.Value.Counters
|
||||
if c["gameplay.steps"] == 0 || c["camera.outputs"] != 1 {
|
||||
t.Fatalf("counters %v, want gameplay steps and one camera output", c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,7 @@ const ServiceID = "physics2d.queries"
|
||||
type QueryFilter struct {
|
||||
LayerMask uint32
|
||||
IncludeTriggers bool
|
||||
SkipDynamic bool
|
||||
ExcludeNode scene.NodeHandle
|
||||
ExcludeBody scene.NodeHandle
|
||||
}
|
||||
|
||||
type RayHit struct {
|
||||
@@ -23,16 +21,6 @@ type RayHit struct {
|
||||
ColliderNode scene.NodeHandle
|
||||
}
|
||||
|
||||
type CastHit struct {
|
||||
Point mathx.Vec2
|
||||
Normal mathx.Vec2
|
||||
Fraction float32
|
||||
Depth float32
|
||||
Node scene.NodeHandle
|
||||
ColliderNode scene.NodeHandle
|
||||
Initial bool
|
||||
}
|
||||
|
||||
type Queries interface {
|
||||
Raycast(origin, dir mathx.Vec2, maxDist float32, f QueryFilter) (RayHit, bool)
|
||||
RaycastAll(origin, dir mathx.Vec2, maxDist float32, f QueryFilter, out []RayHit) int
|
||||
@@ -40,15 +28,4 @@ type Queries interface {
|
||||
OverlapCircle(c mathx.Vec2, r float32, f QueryFilter, out []scene.NodeHandle) int
|
||||
OverlapBox(c, half mathx.Vec2, angle float32, f QueryFilter, out []scene.NodeHandle) int
|
||||
BodyAt(p mathx.Vec2, f QueryFilter) (scene.NodeHandle, bool)
|
||||
CastCircle(c mathx.Vec2, r float32, delta mathx.Vec2, f QueryFilter) (CastHit, bool)
|
||||
CastBox(c, half mathx.Vec2, angle float32, delta mathx.Vec2, f QueryFilter) (CastHit, bool)
|
||||
CastCapsule(a, b mathx.Vec2, r float32, delta mathx.Vec2, f QueryFilter) (CastHit, bool)
|
||||
CastBody(body scene.NodeHandle, offset, delta mathx.Vec2, f QueryFilter) (CastHit, bool)
|
||||
Penetration(body scene.NodeHandle, f QueryFilter, out []CastHit) int
|
||||
BodyPose(body scene.NodeHandle) (mathx.Vec2, float32, bool)
|
||||
BodyVelocity(body scene.NodeHandle) (mathx.Vec2, float32, bool)
|
||||
MoveKinematic(body scene.NodeHandle, p mathx.Vec2, angle, dt float32) error
|
||||
Teleport(body scene.NodeHandle, p mathx.Vec2, angle float32) error
|
||||
ApplyImpulse(body scene.NodeHandle, j mathx.Vec2) error
|
||||
Gravity() mathx.Vec2
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package scriptcall
|
||||
|
||||
import (
|
||||
"aego/scene"
|
||||
"aego/script/vm"
|
||||
)
|
||||
|
||||
const ServiceID = "script.call"
|
||||
|
||||
// Ref names a plain method of one script instance; a new program generation makes it stale.
|
||||
type Ref struct {
|
||||
Node scene.NodeHandle
|
||||
Instance vm.InstanceID
|
||||
Index int32
|
||||
Gen uint64
|
||||
}
|
||||
|
||||
type Caller interface {
|
||||
Method(node scene.NodeHandle, name string, params int) (Ref, bool)
|
||||
Call(ref *Ref, args []vm.Value) bool
|
||||
Generation() uint64
|
||||
}
|
||||
@@ -30,11 +30,6 @@ map $uri $aego_edge_coep {
|
||||
default "";
|
||||
}
|
||||
|
||||
map $sent_http_content_type $aego_edge_page_cache {
|
||||
"~^text/html" "public, max-age=60";
|
||||
default $aego_edge_cache;
|
||||
}
|
||||
|
||||
server {
|
||||
include snippets/maintenance.conf;
|
||||
listen 127.0.0.1:18443 ssl proxy_protocol;
|
||||
@@ -76,14 +71,7 @@ server {
|
||||
try_files $uri @aego_origin;
|
||||
}
|
||||
|
||||
# Pages live at clean URLs; /a/index.html -> /a/, /a/b.html -> /a/b.
|
||||
location ~ \.(html|json)$ {
|
||||
if ($request_uri ~ "^(.*/)index\.html(\?.*)?$") {
|
||||
return 301 $1$is_args$args;
|
||||
}
|
||||
if ($request_uri ~ "^(.+)\.html(\?.*)?$") {
|
||||
return 301 $1$is_args$args;
|
||||
}
|
||||
add_header Cache-Control "public, max-age=60" always;
|
||||
add_header X-Cache-Status EDGE always;
|
||||
add_header Cross-Origin-Opener-Policy $aego_edge_coop always;
|
||||
@@ -101,12 +89,8 @@ server {
|
||||
return 404;
|
||||
}
|
||||
|
||||
location ~ ^/(play|demo|try)$ {
|
||||
return 301 $uri/$is_args$args;
|
||||
}
|
||||
|
||||
location / {
|
||||
add_header Cache-Control $aego_edge_page_cache always;
|
||||
add_header Cache-Control $aego_edge_cache always;
|
||||
add_header Cross-Origin-Opener-Policy $aego_edge_coop always;
|
||||
add_header Cross-Origin-Embedder-Policy $aego_edge_coep always;
|
||||
add_header X-Cache-Status EDGE always;
|
||||
|
||||
@@ -23,11 +23,6 @@ map $uri $aego_coep {
|
||||
default "";
|
||||
}
|
||||
|
||||
map $sent_http_content_type $aego_page_cache {
|
||||
"~^text/html" "public, max-age=60";
|
||||
default $aego_cache;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
root /usr/share/nginx/html;
|
||||
@@ -56,21 +51,7 @@ server {
|
||||
add_header Cache-Control "no-cache";
|
||||
}
|
||||
|
||||
# Pages live at clean URLs; /a/index.html -> /a/, /a/b.html -> /a/b.
|
||||
location ~ \.html$ {
|
||||
if ($request_uri ~ "^(.*/)index\.html(\?.*)?$") {
|
||||
return 301 $1$is_args$args;
|
||||
}
|
||||
if ($request_uri ~ "^(.+)\.html(\?.*)?$") {
|
||||
return 301 $1$is_args$args;
|
||||
}
|
||||
add_header Cache-Control "public, max-age=60";
|
||||
add_header Cross-Origin-Opener-Policy $aego_coop;
|
||||
add_header Cross-Origin-Embedder-Policy $aego_coep;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location ~ \.json$ {
|
||||
location ~ \.(html|json)$ {
|
||||
add_header Cache-Control "public, max-age=60";
|
||||
add_header Cross-Origin-Opener-Policy $aego_coop;
|
||||
add_header Cross-Origin-Embedder-Policy $aego_coep;
|
||||
@@ -83,7 +64,7 @@ server {
|
||||
}
|
||||
|
||||
location / {
|
||||
add_header Cache-Control $aego_page_cache;
|
||||
add_header Cache-Control $aego_cache;
|
||||
add_header Cross-Origin-Opener-Policy $aego_coop;
|
||||
add_header Cross-Origin-Embedder-Policy $aego_coep;
|
||||
try_files $uri $uri.html $uri/ =404;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
## Build and run
|
||||
|
||||
```sh
|
||||
bash tools/bootstrap-native.sh
|
||||
make cli
|
||||
.cache/bin/aego edit examples/platformer
|
||||
```
|
||||
|
||||
`make res` regenerates icons, previews and ES shaders; `make golden-update` rewrites golden images after an intended UI change.
|
||||
|
||||
## Check levels
|
||||
|
||||
| Level | When | Command |
|
||||
|---|---|---|
|
||||
| 1 | after an edit | `make quick`: build and short tests of the packages your change touches and everything that imports them |
|
||||
| 2 | before a commit | `make affected`, plus `make check-camera`, `check-audio`, `check-gameplay`, `check-mutate`, `check-editor`, `check-web` or `check-docs` for the areas you touched, and `make golden` after UI changes |
|
||||
| 3 | before a push or a tag | `make ci` |
|
||||
|
||||
`tools/affected.sh [base]` lists the affected packages (default base `origin/master`, uncommitted files included). The CI server always runs the full `make ci`.
|
||||
|
||||
## Rules for engine code
|
||||
|
||||
- **Modules talk through contracts.** A module never imports another module: shared types and services live in `contracts/` (`physics2d`, `camera2d`, `scriptcall`, `signals`, `debugdraw`, …). `tools/depcheck` enforces the import rules; an exception goes into `tools/depcheck/exceptions.txt` with its file and reason.
|
||||
- **Kernel stays neutral.** Kernel, editor and `sdk` never name a built-in module, its components or its diagnostic codes in string literals; names come from the module catalog.
|
||||
- **Every component has a Try it scene.** It lives at `res/docs/try/<Component>.aescene` (with `<Component>.aes` and prefabs next to it when needed), shows the component's own effect and runs without warnings; `aego docs check` fails without it.
|
||||
- **Internal state is not a component.** Controller state lives in unexported fields of the component it belongs to or in a world service. A component that exists only to carry state for a service is registered `Hidden`: it is not in Create, Add Component, Try it or the docs.
|
||||
- **Requirements add, they do not block.** Declare `Requires`; Add Component, `scene add-comp`, Paste and presets add the missing components in the same undo step.
|
||||
- **Every diagnostic code has a page with an example.** Component codes (`CAM`, `AUD`, `GMP`, `PHY`, …) also have an example scene that produces the code; `aego docs check` fails without one. Asset codes say `scope: asset`, codes that only an edit during Play produces say `scope: play`.
|
||||
- **Nodes are labelled, not pathed.** UI shows nodes with `props.NodeLabel` (icon and name, the path in the tooltip).
|
||||
- **Editor edits go through history.** Every edit is a history command, normally `commands.Mutation`, so Undo, the Play changes diff and agents see it.
|
||||
- **Hot paths do not allocate.** Systems, mixers and controllers allocate nothing per frame; add `testing.AllocsPerRun` checks and a benchmark next to new hot code.
|
||||
|
||||
## Commits
|
||||
|
||||
One commit per sub-stage, named `Stage <n><x>: <what>`; decisions go into `ARCHITECTURE.md` as `D<number>` in the same commit.
|
||||
@@ -97,16 +97,6 @@ sdk.Component[Wind](r, sdk.Name("weather.Wind")).
|
||||
|
||||
`engine/problems` collects `sdk.Diagnostic{Owner, Code, Severity, Message, Subject, BlocksPlay}` from sources with a revision; the service rebuilds the list only when a revision changes. `sdk.Diagnostics(ctx)` in `Init` returns the service; a module adds runtime diagnostics as a `sdk.ProblemSource` from `AttachWorld` and removes it in `DetachWorld`. The Problems panel shows them with the owner as category; an error with `BlocksPlay` stops Play (D210).
|
||||
|
||||
Every code a module reports is listed in `Info.Codes` and has a page `res/docs/diagnostics/<CODE>.md` with front matter `code`, `title`, `severity` (`error`, `warning` or `info`) and either `example: <CODE>.aescene`, a scene next to the page that produces the code (with `<CODE>.aes` next to it when a script has to act, as for PHY007), `scope: asset` for codes that come from importing or checking an asset, or `scope: play` for codes that only an edit during Play produces (PHY006). `aego docs check` fails on a declared code without a page, a page whose code is not declared, a component code without an example, and an example that does not produce its code after `module.ValidateScene` and 60 headless frames. The pages appear under Reference → Diagnostics next to the script codes (D146, D316).
|
||||
|
||||
### Contracts between modules
|
||||
|
||||
A module never imports another module. What two modules share lives in `contracts/<name>` as plain types, a service ID and an interface; the provider registers the service in its world, the consumer looks it up with `engine.ServiceOf` and works without it.
|
||||
|
||||
- **Provisions.** A value handed over at load time, keyed by a kind. `camera2d.AccessibilityKind` (`"camera2d.accessibility"`) carries the IDs of the Screen Shake and Reduce Motion settings: the settings module provides them with `r.Provide(camera2d.AccessibilityKind, camera2d.Accessibility{...})`, the camera module reads `ctx.Provided(camera2d.AccessibilityKind)` in `Init` and scales shake by them every frame.
|
||||
- **`physics2d.Queries`** (`physics2d.ServiceID`). Ray, point, circle and box queries; shape casts `CastCircle`, `CastBox`, `CastCapsule` and `CastBody`, which sweeps every non-trigger collider of a body and returns the first hit with its fraction, normal and, when the shapes already overlap, `Initial` and `Depth`; `Penetration` lists the overlaps of a body for depenetration; `BodyPose`, `BodyVelocity`, `MoveKinematic` (moves a kinematic body so contacts see its velocity), `Teleport`, `ApplyImpulse` and `Gravity`. `QueryFilter` selects layers and triggers; `SkipDynamic` ignores dynamic bodies, and `ExcludeNode` and `ExcludeBody` skip the caller's own collider or body. The gameplay controller is written only against this interface.
|
||||
- **`scriptcall.Caller`** (`scriptcall.ServiceID`). `Method(node, name, params)` finds a plain method by name in the scripts on a node and returns a `Ref`; `Call(&ref, args)` runs it. A hot reload starts a new `Generation`, which makes every older `Ref` stale: look the method up again when `Ref.Gen` differs. The State Machine finds its hooks this way.
|
||||
|
||||
### Resources
|
||||
|
||||
Module icons are named `<owner>.*` or `cat.<owner>`; a clash with the engine atlas or another module is a build error. Built-in modules list their icon sources through `IconDir()`. Docs registered with `Registry.Docs` carry the module as owner and are left out of runtime builds (D211).
|
||||
@@ -179,6 +169,4 @@ A panel gets a fresh `Frame` every draw (`UI`, drag and drop, time, focus). Anyt
|
||||
|
||||
A module can give any of its components a live example on the documentation site: put a scene in `res/docs/try/<Component>.aescene`, named after the last part of the component ID (`try/Body2D.aescene` for `physics.Body2D`). The page of that component gets a Try it block that runs the scene in the browser with the Inspector, selecting the first node that has the component; a guide page can show a scene with `try: try/name.aescene` and `try_select: module.Component` in its front matter.
|
||||
|
||||
Try scenes run on the docs runtime catalog (core, input, render2d, render3d, scripting, physics, debug, tween, cameras, gameplay, spawn, savegame and audio) with builtin assets only. A script `<Component>.aes` and prefabs `<Component>.<name>.aeprefab` (with their `.meta`) next to the scene are compiled and mounted with it. `aego docs check` loads each one, runs it for 60 frames and fails on a load error, a warning or a fault, and on a scene of a module outside the catalog. Keep the scene to what the component needs, put the documented node first, and add a camera.
|
||||
|
||||
Every visible component needs one. A component registered `Hidden`, which only carries state for a service, has none and is left out of Create, Add Component, Try it and the docs. The rules for engine code, including these, are in [contributing.md](contributing.md).
|
||||
Try scenes run on the sandbox catalog (core, input, render2d, scripting, physics, debug) with builtin assets only and no scripts. `aego docs check` loads each one, runs it for 60 frames and fails on a load error, a warning or a fault, and on a scene of a module outside the sandbox. Keep the scene to what the component needs, put the documented node first, and add a camera.
|
||||
|
||||
@@ -17,7 +17,6 @@ Sources are `<id>.svg` files in `engine/res/icons/src` and `modules/<id>/res/ico
|
||||
| `cat.audio` | engine | 16 | Documentation: Audio group and module | ok |
|
||||
| `cat.camera` | engine | 24 | New Project: template 2.5d | ok |
|
||||
| `cat.game` | engine | 16 | Inspector and Add Component: components of a project module without an icon of their own | ok |
|
||||
| `cat.gameplay` | engine | 16 | template menu Gameplay | ok |
|
||||
| `cat.node` | engine | 24 | New Project: template empty; Search: node results | ok |
|
||||
| `cat.physics` | engine | 16 | template menu Physics | ok |
|
||||
| `cat.rendering` | engine | 24 | New Project: template 2d; Search: mesh assets | ok |
|
||||
@@ -61,10 +60,8 @@ Sources are `<id>.svg` files in `engine/res/icons/src` and `modules/<id>/res/ico
|
||||
| `edit.paste` | engine | 16 | Paste (nodes, assets, inspector field) | ok |
|
||||
| `edit.rename` | engine | 16 | Rename (nodes, assets, workspace) | ok |
|
||||
| `edit.select_all` | engine | 16 | Edit → Select All | ok |
|
||||
| `file.audio` | engine | 16 | Documentation: sound clips page | ok |
|
||||
| `file.autosave` | engine | 16 | File → Autosave Scripts | ok |
|
||||
| `file.data` | engine | 24 | Assets: data asset tiles | ok |
|
||||
| `file.download` | engine | 16 | Documentation: Install and run page | ok |
|
||||
| `file.folder` | engine | 24 | Assets: folders | ok |
|
||||
| `file.mesh` | engine | 24 | Assets: mesh tiles | ok |
|
||||
| `file.open` | engine | 16 | Assets menu: Open | ok |
|
||||
@@ -78,16 +75,6 @@ Sources are `<id>.svg` files in `engine/res/icons/src` and `modules/<id>/res/ico
|
||||
| `file.table` | engine | 24 | Assets: table tiles | ok |
|
||||
| `file.texture` | engine | 24 | Assets: texture tiles | ok |
|
||||
| `file.unknown` | engine | 24 | Assets: tiles of other kinds | ok |
|
||||
| `gameplay.checkpoint` | engine | 16 | component gameplay.Checkpoint2D; template gameplay.checkpoint | ok |
|
||||
| `gameplay.fsm` | engine | 16 | component gameplay.StateMachine | ok |
|
||||
| `gameplay.health` | engine | 16 | component gameplay.Health | ok |
|
||||
| `gameplay.hitbox` | engine | 16 | component gameplay.Hitbox2D; template gameplay.enemy | ok |
|
||||
| `gameplay.hurtbox` | engine | 16 | component gameplay.Hurtbox2D | ok |
|
||||
| `gameplay.killzone` | engine | 16 | component gameplay.KillZone2D; template gameplay.killzone | ok |
|
||||
| `gameplay.lifetime` | engine | 16 | component gameplay.Lifetime | ok |
|
||||
| `gameplay.motion` | engine | 16 | component gameplay.Motion2D; template gameplay.platform; template gameplay.platformer; template gameplay.topdown | ok |
|
||||
| `gameplay.pickup` | engine | 16 | component gameplay.Pickup2D; template gameplay.pickup | ok |
|
||||
| `gameplay.respawn` | engine | 16 | component gameplay.Respawn | ok |
|
||||
| `help.about` | engine | 16 | Help → About Aego | ok |
|
||||
| `help.book` | engine | 16 | Documentation tab | ok |
|
||||
| `log.error` | engine | 16 | Console: errors | ok |
|
||||
@@ -136,7 +123,6 @@ Sources are `<id>.svg` files in `engine/res/icons/src` and `modules/<id>/res/ico
|
||||
| `prefab.variant` | engine | 16 | Assets menu: Create Variant | ok |
|
||||
| `shape.cube` | engine | 16 | template render3d.cube | ok |
|
||||
| `shape.cylinder` | engine | 16 | template render3d.cylinder | ok |
|
||||
| `shape.dice` | engine | 16 | Documentation: random numbers module | ok |
|
||||
| `shape.plane` | engine | 16 | template render3d.plane | ok |
|
||||
| `shape.sphere` | engine | 16 | template render3d.sphere | ok |
|
||||
| `status.busy` | engine | 14 | Task Center: running task | ok |
|
||||
@@ -154,7 +140,6 @@ Sources are `<id>.svg` files in `engine/res/icons/src` and `modules/<id>/res/ico
|
||||
| `ui.chevron_down` | engine | 16 | trees, combos and drop-downs | ok |
|
||||
| `ui.chevron_right` | engine | 16 | trees and submenus | ok |
|
||||
| `ui.close` | engine | 16 | tabs, chips and dialogs | ok |
|
||||
| `ui.curve` | engine | 16 | Documentation: curves module | ok |
|
||||
| `ui.lock` | engine | 16 | Inspector: locked | ok |
|
||||
| `ui.minus` | engine | 16 | list editors: remove | ok |
|
||||
| `ui.more` | engine | 16 | card menus | ok |
|
||||
|
||||
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 203 KiB |