199 lines
5.1 KiB
Go
199 lines
5.1 KiB
Go
package export
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"aego/app/assembly"
|
|
"aego/app/assembly/builtin"
|
|
"aego/app/export/icon"
|
|
"aego/asset"
|
|
scriptImporter "aego/asset/importers/script"
|
|
"aego/asset/pipeline"
|
|
"aego/core/ids"
|
|
"aego/engine/problems"
|
|
"aego/project"
|
|
"aego/render/mesh"
|
|
"aego/render/texture"
|
|
"aego/runtime/bundle"
|
|
"aego/scene/sceneio"
|
|
"aego/script/bind"
|
|
)
|
|
|
|
const (
|
|
CodeStartScene = "EXP001"
|
|
CodeScripts = "EXP002"
|
|
CodeImport = "EXP003"
|
|
CodeMissing = "EXP004"
|
|
CodeIcon = "EXP005"
|
|
CodeMeta = "EXP006"
|
|
CodeScene = "EXP007"
|
|
CodeUnknown = "EXP008"
|
|
CodeInternal = "EXP009"
|
|
)
|
|
|
|
func diag(code string, sev problems.Severity, msg, file string) problems.Diagnostic {
|
|
return problems.Diagnostic{
|
|
Owner: Owner, Code: code, Severity: sev, Message: msg,
|
|
Subject: problems.Subject{Kind: problems.SubjectFile, File: file},
|
|
BlocksPlay: sev == problems.Error,
|
|
}
|
|
}
|
|
|
|
func firstLine(s string) string {
|
|
if i := strings.IndexByte(s, '\n'); i >= 0 {
|
|
return s[:i]
|
|
}
|
|
return s
|
|
}
|
|
|
|
func Blocking(ds []problems.Diagnostic) int {
|
|
n := 0
|
|
for _, d := range ds {
|
|
if d.Severity == problems.Error {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
func resolves(idx *asset.Index, id ids.AssetID) bool {
|
|
if _, ok := asset.LookupBuiltin(id); ok {
|
|
return true
|
|
}
|
|
e, ok := idx.Lookup(id)
|
|
return ok && !e.Missing
|
|
}
|
|
|
|
func Check(p *project.Project, s *Snapshot, profile string, partial bool) []problems.Diagnostic {
|
|
var out []problems.Diagnostic
|
|
seen := map[string]bool{}
|
|
for _, a := range s.assets {
|
|
switch {
|
|
case a.Error == "":
|
|
case a.Importer == scriptImporter.ImporterID:
|
|
if !seen[a.Error] {
|
|
seen[a.Error] = true
|
|
out = append(out, diag(CodeScripts, problems.Error, "scripts do not compile: "+firstLine(a.Error), a.Path))
|
|
}
|
|
default:
|
|
out = append(out, diag(CodeImport, problems.Error, "import failed: "+firstLine(a.Error), a.Path))
|
|
}
|
|
}
|
|
for _, is := range s.issues {
|
|
if is.Kind == pipeline.MetaDuplicate {
|
|
out = append(out, diag(CodeMeta, problems.Error, is.Message(), is.Path))
|
|
}
|
|
}
|
|
switch e, ok := s.idx.Lookup(p.MainScene); {
|
|
case p.MainScene.IsZero():
|
|
out = append(out, diag(CodeStartScene, problems.Error, "project.aego names no mainScene", project.FileName))
|
|
case !ok || e.Missing || e.Kind != asset.ArtScene:
|
|
out = append(out, diag(CodeStartScene, problems.Error, "the start scene "+p.MainScene.String()+" is not an imported scene", project.FileName))
|
|
}
|
|
sev := problems.Warning
|
|
if profile == bundle.Release {
|
|
sev = problems.Error
|
|
}
|
|
s.idx.Each(func(e *asset.Entry) {
|
|
if e.Missing || exportSkip(e.Kind) {
|
|
return
|
|
}
|
|
for _, d := range e.Deps {
|
|
if !resolves(s.idx, d) {
|
|
out = append(out, diag(CodeMissing, sev, e.Source+" references a missing asset "+d.String(), e.Source))
|
|
}
|
|
}
|
|
})
|
|
if p.Icon != "" {
|
|
if _, err := icon.Load(filepath.Join(p.Root, filepath.FromSlash(p.Icon))); err != nil {
|
|
out = append(out, diag(CodeIcon, problems.Error, err.Error(), project.FileName))
|
|
}
|
|
}
|
|
return append(out, validate(s, partial)...)
|
|
}
|
|
|
|
func validate(s *Snapshot, partial bool) []problems.Diagnostic {
|
|
internal := func(err error) []problems.Diagnostic {
|
|
return []problems.Diagnostic{diag(CodeInternal, problems.Error, "cannot run the asset checks: "+err.Error(), "")}
|
|
}
|
|
types, err := assembly.NewTypes()
|
|
if err != nil {
|
|
return internal(err)
|
|
}
|
|
man, err := builtin.Runtime().Load(types, bind.New(), assembly.Options{})
|
|
if err != nil {
|
|
return internal(err)
|
|
}
|
|
if err := types.Schema().Freeze(); err != nil {
|
|
return internal(err)
|
|
}
|
|
out := man.ValidateAssets(types.Schema(), s, partial, nil)
|
|
res := &resolver{s: s}
|
|
s.idx.Each(func(e *asset.Entry) {
|
|
if e.Missing || e.Error != "" || e.Kind != asset.ArtScene {
|
|
return
|
|
}
|
|
src, ok := res.blob(e.ID, asset.ArtScene)
|
|
if !ok {
|
|
out = append(out, diag(CodeScene, problems.Error, e.Source+" has no scene artifact", e.Source))
|
|
return
|
|
}
|
|
_, rep, err := sceneio.Load(src, e.Source, types, res, sceneio.LoadOptions{KeepIDs: true})
|
|
if err != nil {
|
|
out = append(out, diag(CodeScene, problems.Error, e.Source+" cannot be loaded: "+firstLine(err.Error()), e.Source))
|
|
return
|
|
}
|
|
if partial {
|
|
return
|
|
}
|
|
for _, u := range rep.Unknown {
|
|
out = append(out, diag(CodeUnknown, problems.Warning, e.Source+" uses "+u+", which no module registers; it is kept but does nothing", e.Source))
|
|
}
|
|
})
|
|
return out
|
|
}
|
|
|
|
type resolver struct {
|
|
s *Snapshot
|
|
}
|
|
|
|
func (r *resolver) blob(id ids.AssetID, kind asset.ArtifactKind) ([]byte, bool) {
|
|
e, ok := r.s.idx.Lookup(id)
|
|
if !ok || e.Missing || e.Kind != kind {
|
|
return nil, false
|
|
}
|
|
a, ok := e.Artifact(kind)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
data, err := r.s.files.ReadFile(a.Path)
|
|
return data, err == nil
|
|
}
|
|
|
|
func (r *resolver) Texture(ids.AssetID) (texture.Region, bool) {
|
|
return texture.Region{}, false
|
|
}
|
|
|
|
func (r *resolver) Mesh(ids.AssetID) (*mesh.Mesh, bool) {
|
|
return nil, false
|
|
}
|
|
|
|
func (r *resolver) Scene(id ids.AssetID) ([]byte, bool) {
|
|
return r.blob(id, asset.ArtScene)
|
|
}
|
|
|
|
func (r *resolver) Prefab(id ids.AssetID) ([]byte, bool) {
|
|
return r.blob(id, asset.ArtPrefab)
|
|
}
|
|
|
|
func (r *resolver) Generation() uint32 {
|
|
return 1
|
|
}
|
|
|
|
func (r *resolver) Subscribe(func(asset.Reloaded)) func() {
|
|
return func() {}
|
|
}
|
|
|
|
var _ asset.Resolver = (*resolver)(nil)
|