Files
2026-09-26 16:32:40 +03:00

206 lines
4.6 KiB
Go

package integration
import (
"os"
"path/filepath"
"testing"
"aego/app/run"
"aego/core/log"
"aego/project"
"aego/scene"
"aego/scene/components"
"aego/scene/sceneio"
"aego/sdk"
)
const rotatorScene = `scene version=1 {
node "1a2b3c4d5e6f708192a3b4c5d6e7f809" name="Camera" enabled=true layers=1 {
transform { position=[0, 0] rotation=0 scale=[1, 1] }
component "engine.Camera" version=1 {
zoom=64
viewport=[0, 0, 1, 1]
mask=-1
order=0
clear=true
clearColor=#0a0d12ff
scale=free
virtual=[0, 0]
active=true
}
}
node "2b3c4d5e6f708192a3b4c5d6e7f8091a" name="Player" enabled=true layers=1 {
transform { position=[0, 0] rotation=0 scale=[1, 1] }
component "game.Rotator" version=1 {
speed=1.5
}
}
}
`
type Rotator struct {
Speed float32 `aego:"speed"`
}
type rotatorSystem struct{}
func (rotatorSystem) Run(ctx *sdk.Context) {
s := sdk.SceneOf(ctx)
if s == nil {
return
}
dt := float32(ctx.Dt)
sdk.Query1[Rotator](s).Each(func(n sdk.NodeHandle, r *Rotator) {
s.SetRotation(n, s.Local(n).Rotation+r.Speed*dt)
})
}
type rotatorModule struct{}
func (rotatorModule) Info() sdk.ModuleInfo {
return sdk.ModuleInfo{ID: "game.rotator", Version: "0.1.0"}
}
func (rotatorModule) Register(r *sdk.Registry) error {
if err := sdk.RegisterComponent[Rotator](r, sdk.Name("game.Rotator")); err != nil {
return err
}
return sdk.RegisterSystem(r, sdk.SystemDesc{
ID: "game.rotator",
Phase: sdk.PhaseUpdate,
System: rotatorSystem{},
})
}
func newRotatorProject(t *testing.T) *project.Project {
t.Helper()
p, err := project.Create(t.TempDir(), "Rotator", false)
if err != nil {
t.Fatal(err)
}
scenePath := filepath.Join(p.ScenesDir(), "main.aescene")
if err := os.WriteFile(scenePath, []byte(rotatorScene), 0o644); err != nil {
t.Fatal(err)
}
return p
}
func TestNativeComponentDrivesTheScene(t *testing.T) {
p := newRotatorProject(t)
app, err := run.Open(p, run.Config{
Headless: true,
Frames: 120,
Logger: log.Nop(),
ScenePath: filepath.Join(p.ScenesDir(), "main.aescene"),
}, rotatorModule{})
if err != nil {
t.Fatal(err)
}
defer app.Close()
player := app.Scene.Find(app.Scene.Root(), "Player")
if player.IsZero() {
t.Fatal("player node missing")
}
if _, ok := scene.Get[Rotator](app.Scene, player); !ok {
t.Fatal("rotator component was not loaded")
}
before := app.Scene.Local(player).Rotation
driver := app.Driver()
for i := 0; i < 120; i++ {
if !app.Engine.Step(driver) {
t.Fatalf("engine stopped at frame %d: %v", i, app.Engine.Err())
}
}
after := app.Scene.Local(player).Rotation
if after <= before {
t.Fatalf("rotation did not advance: %v -> %v", before, after)
}
}
func TestSceneLoadsWithoutTheNativeModule(t *testing.T) {
p := newRotatorProject(t)
app, err := run.Open(p, run.Config{
Headless: true,
Logger: log.Nop(),
ScenePath: filepath.Join(p.ScenesDir(), "main.aescene"),
})
if err != nil {
t.Fatal(err)
}
defer app.Close()
player := app.Scene.Find(app.Scene.Root(), "Player")
if player.IsZero() {
t.Fatal("player node missing")
}
if _, ok := scene.Get[components.SpriteRenderer](app.Scene, player); ok {
t.Fatal("unexpected component")
}
out := sceneioSave(t, app)
if !containsString(out, "game.Rotator") || !containsString(out, "speed=1.5") {
t.Fatalf("unknown component data was lost:\n%s", out)
}
}
func TestCamerasProduceViews(t *testing.T) {
p := newRotatorProject(t)
app, err := run.Open(p, run.Config{
Headless: true,
Logger: log.Nop(),
ScenePath: filepath.Join(p.ScenesDir(), "main.aescene"),
}, rotatorModule{})
if err != nil {
t.Fatal(err)
}
defer app.Close()
driver := app.Driver()
for i := 0; i < 5; i++ {
app.Engine.Step(driver)
}
if st := app.Runner.Stats(); st.Views != 1 {
t.Fatalf("views = %d", st.Views)
}
}
func TestNoAllocationsPerFrameWithScene(t *testing.T) {
p := newRotatorProject(t)
app, err := run.Open(p, run.Config{
Headless: true,
Logger: log.Nop(),
ScenePath: filepath.Join(p.ScenesDir(), "main.aescene"),
}, rotatorModule{})
if err != nil {
t.Fatal(err)
}
defer app.Close()
driver := app.Driver()
for i := 0; i < 60; i++ {
app.Engine.Step(driver)
}
allocs := testing.AllocsPerRun(240, func() {
app.Engine.Step(driver)
})
if allocs != 0 {
t.Fatalf("frame allocates %.1f objects with a scene loaded", allocs)
}
}
func sceneioSave(t *testing.T, app *run.App) string {
t.Helper()
return string(sceneio.Save(app.Scene))
}
func containsString(s, sub string) bool {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
return false
}