201 lines
6.6 KiB
Go
201 lines
6.6 KiB
Go
package artsource
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"aego/art"
|
|
"aego/asset"
|
|
"aego/asset/importers/atlas"
|
|
"aego/asset/pipeline"
|
|
"aego/core/ids"
|
|
"aego/core/log"
|
|
"aego/project"
|
|
)
|
|
|
|
func TestLinkedSourceRealPipelinePreservesPNGIDsAndUndo(t *testing.T) {
|
|
p, err := project.Create(t.TempDir(), "Linked PNG", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
img := image.NewNRGBA(image.Rect(0, 0, 4, 2))
|
|
for y := 0; y < 2; y++ {
|
|
for x := 0; x < 4; x++ {
|
|
img.SetNRGBA(x, y, color.NRGBA{R: uint8(20 + x*30), G: uint8(30 + y*50), B: 40, A: 255})
|
|
}
|
|
}
|
|
var pngBuffer bytes.Buffer
|
|
if err := png.Encode(&pngBuffer, img); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const pngPath, artPath = "assets/hero.png", "assets/hero.aeart"
|
|
pngBytes := bytes.Clone(pngBuffer.Bytes())
|
|
meta := pipeline.NewMeta("texture", NewTexture().Version(), asset.TextureDefaults())
|
|
meta.Subs = []pipeline.SubAsset{
|
|
{ID: ids.NewAssetID(), Name: "left", Rect: [4]int{0, 0, 2, 2}},
|
|
{ID: ids.NewAssetID(), Name: "right", Rect: [4]int{2, 0, 2, 2}},
|
|
}
|
|
metaBytes := pipeline.WriteMeta(meta)
|
|
for rel, data := range map[string][]byte{pngPath: pngBytes, pngPath + pipeline.MetaSuffix: metaBytes} {
|
|
if err := os.WriteFile(filepath.Join(p.Root, filepath.FromSlash(rel)), data, 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
registry := pipeline.NewImporterRegistry()
|
|
for _, imp := range []pipeline.Importer{atlas.Wrap(NewTexture()), New()} {
|
|
if err := registry.Register(imp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
pl, err := pipeline.Open(p, registry, log.Nop())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer pl.Close()
|
|
if _, _, err := pl.Scan(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
checkBatch := func(batch *pipeline.Batch, err error) {
|
|
t.Helper()
|
|
if err != nil || batch == nil || len(batch.Failed) != 0 || len(batch.Retry) != 0 {
|
|
t.Fatalf("pipeline batch failed or needs retry: %+v, %v", batch, err)
|
|
}
|
|
}
|
|
checkBatch(pl.ImportAll())
|
|
root, ok := pl.Lookup(pngPath)
|
|
if !ok || root != meta.ID {
|
|
t.Fatal("PNG source identity changed")
|
|
}
|
|
for _, frame := range meta.Subs {
|
|
id, ok := pl.Lookup(pngPath + pipeline.SubSep + frame.Name)
|
|
if !ok || id != frame.ID {
|
|
t.Fatalf("original frame identity missing: %s", frame.Name)
|
|
}
|
|
}
|
|
imageMeta, ok := pl.MetaOf(root)
|
|
if !ok {
|
|
t.Fatal("PNG metadata missing")
|
|
}
|
|
doc, mapping, err := ImportTextureDocument(&pipeline.ImportContext{
|
|
ID: root, Path: pngPath, Source: pngBytes, Meta: &imageMeta,
|
|
Read: pl.SourceBytes, Lookup: pl.Lookup, SourcePath: pl.PathOf,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer doc.Close()
|
|
encoded, err := doc.Encode()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
artMeta, err := pl.NewSourceMeta(artPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := SetTextureLink(&imageMeta, artMeta.ID, pngBytes, mapping); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
create := []pipeline.SourceEdit{
|
|
{Path: artPath, After: encoded},
|
|
{Path: artPath + pipeline.MetaSuffix, After: pipeline.WriteMeta(artMeta)},
|
|
{Path: pngPath + pipeline.MetaSuffix, Before: metaBytes, After: pipeline.WriteMeta(&imageMeta)},
|
|
{Path: pngPath, Before: pngBytes, After: pngBytes, ReadOnlyGuard: true},
|
|
}
|
|
checkBatch(pl.ApplySources(create, false))
|
|
texture := func() []byte {
|
|
t.Helper()
|
|
entry, ok := pl.Entry(root)
|
|
if !ok {
|
|
t.Fatal("PNG entry disappeared")
|
|
}
|
|
for _, artifact := range entry.Artifacts {
|
|
if artifact.Kind != asset.ArtTexture {
|
|
continue
|
|
}
|
|
data, err := pl.Artifacts().ReadFile(artifact.Path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
decoded, err := asset.DecodeTexture(data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return bytes.Clone(decoded.Pixels)
|
|
}
|
|
t.Fatal("PNG texture artifact missing")
|
|
return nil
|
|
}
|
|
before := texture()
|
|
linkedMetaBytes, err := pl.SourceBytes(pngPath + pipeline.MetaSuffix)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
changedMeta, err := pipeline.ParseMeta(linkedMetaBytes, pngPath+pipeline.MetaSuffix)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
changedMeta.Subs[0].Rect, changedMeta.Subs[1].Rect = changedMeta.Subs[1].Rect, changedMeta.Subs[0].Rect
|
|
invalidEdit := []pipeline.SourceEdit{{Path: pngPath + pipeline.MetaSuffix, Before: linkedMetaBytes, After: pipeline.WriteMeta(changedMeta)}}
|
|
if batch, err := pl.ApplySources(invalidEdit, false); err == nil || !strings.Contains(err.Error(), "slicing changed") {
|
|
t.Fatalf("changing linked slicing silently remapped source frames: batch=%+v, error=%v", batch, err)
|
|
}
|
|
if got, err := pl.SourceBytes(pngPath + pipeline.MetaSuffix); err != nil || !bytes.Equal(got, linkedMetaBytes) {
|
|
t.Fatalf("rejected slicing edit did not restore its metadata: %v", err)
|
|
}
|
|
if got, err := pl.SourceBytes(pngPath); err != nil || !bytes.Equal(got, pngBytes) {
|
|
t.Fatalf("rejected slicing edit changed the original PNG: %v", err)
|
|
}
|
|
if got := texture(); !bytes.Equal(got, before) {
|
|
t.Fatal("rejected slicing edit changed the prepared texture")
|
|
}
|
|
session, err := art.NewSession(doc, art.NewJournal(art.DefaultLimits()))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := session.Select("", mapping[1].Frame, art.Albedo); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := session.Execute(context.Background(), art.Operation{Kind: "stroke", Points: []image.Point{{X: 1, Y: 1}}, Brush: art.Brush{Size: 1, Paint: art.Paint{Color: art.Color{R: 255, B: 255, A: 255}}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
painted, err := doc.Encode()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
paint := []pipeline.SourceEdit{{Path: artPath, Before: encoded, After: painted}}
|
|
checkBatch(pl.ApplySources(paint, false))
|
|
if got := texture(); bytes.Equal(got, before) {
|
|
t.Fatal("reimporting the art did not update the existing PNG texture")
|
|
}
|
|
if err := pl.VerifyCurrent(root); err != nil {
|
|
t.Fatalf("linked PNG became stale after art import: %v", err)
|
|
}
|
|
checkBatch(pl.ApplySources(paint, true))
|
|
if got := texture(); !bytes.Equal(got, before) {
|
|
t.Fatal("Undo did not restore the PNG runtime texture")
|
|
}
|
|
checkBatch(pl.ApplySources(create, true))
|
|
if got, err := os.ReadFile(filepath.Join(p.Root, filepath.FromSlash(pngPath))); err != nil || !bytes.Equal(got, pngBytes) {
|
|
t.Fatalf("linking or Undo changed the original PNG: %v", err)
|
|
}
|
|
if got, err := os.ReadFile(filepath.Join(p.Root, filepath.FromSlash(pngPath+pipeline.MetaSuffix))); err != nil || !bytes.Equal(got, metaBytes) {
|
|
t.Fatalf("Undo did not restore the original PNG metadata: %v", err)
|
|
}
|
|
if _, err := os.Lstat(filepath.Join(p.Root, filepath.FromSlash(artPath))); !os.IsNotExist(err) {
|
|
t.Fatalf("Undo left the created source behind: %v", err)
|
|
}
|
|
for _, frame := range meta.Subs {
|
|
id, ok := pl.Lookup(pngPath + pipeline.SubSep + frame.Name)
|
|
if !ok || id != frame.ID {
|
|
t.Fatalf("frame identity changed after Undo: %s", frame.Name)
|
|
}
|
|
}
|
|
}
|