asset walker (WIP)
This commit is contained in:
parent
5b2e2b023f
commit
92c74c7bd0
@ -4,7 +4,7 @@ This directory contains active packer discussion agendas.
|
||||
|
||||
## Active Agendas
|
||||
|
||||
There are currently no active packer agendas.
|
||||
1. [`Tilemap and Metatile Runtime Binary Layout Agenda.md`](./Tilemap%20and%20Metatile%20Runtime%20Binary%20Layout%20Agenda.md)
|
||||
|
||||
The first packer agenda wave was consolidated into normative specs under [`../specs/`](../specs/) and didactic material under [`../learn/`](../learn/).
|
||||
|
||||
|
||||
@ -0,0 +1,152 @@
|
||||
# Tilemap and Metatile Runtime Binary Layout Agenda
|
||||
|
||||
## Status
|
||||
|
||||
Open
|
||||
|
||||
## Purpose
|
||||
|
||||
Convergir a discussão sobre o contrato de mapa para handheld em duas camadas:
|
||||
|
||||
- formato de autoria/edição (JSON legível),
|
||||
- formato de execução (layout binário compacto em runtime).
|
||||
|
||||
O objetivo é fechar qual estrutura será adotada para `tilemap bank`, `tileset bank` e dados de colisão/flags, com foco em previsibilidade de memória e streaming de mapas.
|
||||
|
||||
## Domain Owner
|
||||
|
||||
`docs/packer`
|
||||
|
||||
Este tema é owner de packer porque envolve transformação de artefatos (`JSON -> BIN`), contrato de build e layout de saída para consumo do runtime.
|
||||
|
||||
Domínios impactados (cross-domain):
|
||||
|
||||
- `docs/vm-arch` (contrato de leitura em runtime e limites de memória),
|
||||
- `docs/studio` (edição/preview de mapas no workspace),
|
||||
- `docs/compiler/pbs` (integração futura com referências de assets em código, quando aplicável).
|
||||
|
||||
## Problem
|
||||
|
||||
Hoje existe alinhamento conceitual de que:
|
||||
|
||||
1. visual e colisão devem ser separados por responsabilidade;
|
||||
2. o mapa não deve repetir metadados extensos por célula;
|
||||
3. apenas uma janela ativa de até `9` mapas deve ficar residente em memória.
|
||||
|
||||
Mas ainda não existe decisão formal sobre:
|
||||
|
||||
- layout binário final por célula em runtime,
|
||||
- orçamento por mapa e por janela ativa,
|
||||
- responsabilidade exata entre `map bank` e `tileset bank`.
|
||||
|
||||
Sem esse contrato, o packer não fecha especificação de saída e o runtime/studio ficam sem baseline único.
|
||||
|
||||
## Context
|
||||
|
||||
Premissas atuais da discussão:
|
||||
|
||||
- `tileset bank` pode ter tamanho diferente dos demais banks;
|
||||
- `map bank` não precisa seguir o mesmo tamanho de `tileset bank`;
|
||||
- mapa deve referenciar IDs compactos (visual, colisão e flags), em vez de duplicar estrutura completa por célula;
|
||||
- paletas por bank continuam sendo opção válida para preservar decisões artísticas locais;
|
||||
- orçamento alvo foi discutido no contexto de `64 KiB` por map bank e janela ativa de `3x3` mapas (`9` residentes).
|
||||
|
||||
## Options
|
||||
|
||||
### Option A — Célula `u8` (mapa ultra-compacto)
|
||||
|
||||
- Cada célula armazena apenas `metatileId` (`0..255`).
|
||||
- Colisão/flags vêm de tabela auxiliar por `metatileId`.
|
||||
|
||||
### Option B — Célula `u16` bit-packed (recomendação inicial)
|
||||
|
||||
- Cada célula usa `16 bits` com divisão sugerida:
|
||||
- `visualId`: `10 bits` (`0..1023`),
|
||||
- `collisionId`: `5 bits` (`0..31`),
|
||||
- `flags`: `1 bit` (`0..1`) ou reservado para evolução.
|
||||
- Permite desacoplamento visual/lógico sem custo de `u32`.
|
||||
|
||||
### Option C — Célula `u32` (maior flexibilidade)
|
||||
|
||||
- Exemplo de divisão:
|
||||
- `visualId`: `12 bits`,
|
||||
- `collisionId`: `8 bits`,
|
||||
- `flags/event`: `12 bits`.
|
||||
- Ganho de expressividade para triggers/eventos inline; custo de memória dobra vs `u16`.
|
||||
|
||||
## Tradeoffs
|
||||
|
||||
- Option A minimiza RAM e I/O, mas limita variedade visual e desloca muita semântica para tabelas externas.
|
||||
- Option B oferece bom equilíbrio para handheld: compacta, previsível e com espaço suficiente para muitos casos de mapa.
|
||||
- Option C simplifica evolução funcional (eventos por célula), mas pressiona memória e banda de streaming sem necessidade comprovada agora.
|
||||
|
||||
## Runtime Binary Structure (focus)
|
||||
|
||||
Estrutura sugerida para runtime (baseada em Option B):
|
||||
|
||||
1. **Map Header (fixo)**
|
||||
- `magic` (`4 bytes`)
|
||||
- `version` (`u16`)
|
||||
- `width` (`u16`)
|
||||
- `height` (`u16`)
|
||||
- `cellEncoding` (`u8`) — ex.: `1 = U16_PACKED_V1`
|
||||
- `visualBankId` (`u16`)
|
||||
- `collisionBankId` (`u16`)
|
||||
- `reserved` / `checksum` (conforme decisão posterior)
|
||||
|
||||
2. **Cell Stream**
|
||||
- vetor contínuo com `width * height` células `u16`, little-endian;
|
||||
- leitura linear favorece cache e descompressão simples.
|
||||
|
||||
3. **Optional Chunks (future-proof)**
|
||||
- bloco opcional de `eventTriggers`;
|
||||
- bloco opcional de `spawnPoints`;
|
||||
- bloco opcional de `navHints`.
|
||||
|
||||
Packing de célula (`U16_PACKED_V1`):
|
||||
|
||||
- `bits 0..9` => `visualId`
|
||||
- `bits 10..14` => `collisionId`
|
||||
- `bit 15` => `flag0`
|
||||
|
||||
Decodificação de referência:
|
||||
|
||||
- `visualId -> metatile visual table -> 4 subtiles (8x8) + palette/flip/priority`
|
||||
- `collisionId -> collision/material table -> walk/solid/swim/damage/etc.`
|
||||
|
||||
## Memory Notes for Active Window (`9` maps)
|
||||
|
||||
Assumindo `64 KiB` por map bank:
|
||||
|
||||
- `1` mapa residente: `64 KiB`
|
||||
- `9` mapas residentes: `576 KiB`
|
||||
|
||||
Capacidade por encoding dentro de `64 KiB`:
|
||||
|
||||
- `u8`: `65,536` células (`256x256` máximo quadrado)
|
||||
- `u16`: `32,768` células (`~181x181` máximo quadrado, ou retângulos equivalentes)
|
||||
- `u32`: `16,384` células (`128x128` máximo quadrado)
|
||||
|
||||
## Recommendation
|
||||
|
||||
Adotar `Option B` como baseline para decisão:
|
||||
|
||||
1. autoria em JSON orientada a IDs (`visualId`, `collisionId`, `flags`);
|
||||
2. empacotamento determinístico para `U16_PACKED_V1` no build;
|
||||
3. janela ativa de runtime limitada a `9` mapas com budget explícito;
|
||||
4. extensão para `u32` somente via nova versão de encoding e evidência de necessidade.
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. `collisionId` com `5 bits` (`32` classes) é suficiente para os biomas/projetos previstos?
|
||||
2. `flag0` deve ser reservado para trigger rápido ou para variação visual contextual?
|
||||
3. Quais chunks opcionais entram já em `V1` e quais ficam para `V2`?
|
||||
4. O `map bank` seguirá estritamente `64 KiB` ou terá tamanho variável com metadado de capacidade?
|
||||
5. Qual política de compressão do stream (`none`, `LZ4`, etc.) será padrão no packer?
|
||||
|
||||
## Expected Follow-up
|
||||
|
||||
1. Abrir `decision` em `docs/packer/decisions` fechando o encoding `U16_PACKED_V1`.
|
||||
2. Propagar contrato de leitura para `docs/vm-arch`.
|
||||
3. Definir no `docs/studio/specs` o schema de edição JSON correspondente.
|
||||
4. Planejar PR de implementação (`packer` + `runtime`) com testes de roundtrip (`JSON -> BIN -> decode`).
|
||||
@ -5,4 +5,5 @@ plugins {
|
||||
dependencies {
|
||||
implementation(project(":prometeu-infra"))
|
||||
implementation(project(":prometeu-packer:prometeu-packer-api"))
|
||||
implementation("org.apache.tika:tika-core:3.2.1")
|
||||
}
|
||||
|
||||
@ -2,6 +2,9 @@ package p.packer;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import p.packer.events.PackerEventSink;
|
||||
import p.packer.repositories.PackerAssetWalker;
|
||||
import p.packer.repositories.PackerRuntimeLoader;
|
||||
import p.packer.repositories.PackerRuntimeRegistry;
|
||||
import p.packer.services.*;
|
||||
|
||||
import java.io.Closeable;
|
||||
@ -23,9 +26,11 @@ public final class Packer implements Closeable {
|
||||
|
||||
public static Packer bootstrap(final ObjectMapper mapper, final PackerEventSink eventSink) {
|
||||
final var resolvedEventSink = Objects.requireNonNull(eventSink, "eventSink");
|
||||
final var workspaceFoundation = new PackerWorkspaceFoundation();
|
||||
final var workspaceFoundation = new PackerWorkspaceFoundation(mapper);
|
||||
final var declarationParser = new PackerAssetDeclarationParser(mapper);
|
||||
final var runtimeRegistry = new PackerRuntimeRegistry(new PackerRuntimeLoader(workspaceFoundation, declarationParser));
|
||||
final var assetWalker = new PackerAssetWalker(mapper);
|
||||
final var runtimeLoader = new PackerRuntimeLoader(workspaceFoundation, declarationParser, assetWalker);
|
||||
final var runtimeRegistry = new PackerRuntimeRegistry(runtimeLoader);
|
||||
final var assetReferenceResolver = new PackerAssetReferenceResolver(workspaceFoundation.lookup());
|
||||
final var assetDetailsService = new PackerAssetDetailsService(runtimeRegistry, assetReferenceResolver);
|
||||
final var assetActionReadService = new PackerAssetActionReadService(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package p.packer.services;
|
||||
package p.packer;
|
||||
|
||||
import p.packer.messages.PackerProjectContext;
|
||||
|
||||
@ -9,6 +9,7 @@ public final class PackerWorkspacePaths {
|
||||
private static final String ASSETS_DIR = "assets";
|
||||
private static final String PROMETEU_DIR = ".prometeu";
|
||||
private static final String REGISTRY_FILE = "index.json";
|
||||
private static final String CACHE_FILE = "cache.json";
|
||||
|
||||
private PackerWorkspacePaths() {
|
||||
}
|
||||
@ -25,6 +26,10 @@ public final class PackerWorkspacePaths {
|
||||
return registryDirectory(project).resolve(REGISTRY_FILE).toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
public static Path cachePath(PackerProjectContext project) {
|
||||
return registryDirectory(project).resolve(CACHE_FILE).toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
public static Path assetRoot(PackerProjectContext project, String relativeRoot) {
|
||||
return assetsRoot(project).resolve(relativeRoot).toAbsolutePath().normalize();
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package p.packer.models;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public record PackerFileProbe(
|
||||
Path path,
|
||||
String mimeType,
|
||||
long lastModified,
|
||||
byte[] content) {
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package p.packer.models;
|
||||
|
||||
public record PackerPaletteV1(
|
||||
int[] originalArgb8888,
|
||||
short[] convertedRgb565) {
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package p.packer.models;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public record PackerProbeResult(
|
||||
PackerFileProbe fileProbe,
|
||||
Map<String, Object> metadata,
|
||||
List<PackerDiagnostic> diagnostics) {
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
package p.packer.models;
|
||||
|
||||
public record PackerSoundBankRequirements(int sampleRate, int channels) {
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
package p.packer.models;
|
||||
|
||||
public record PackerTileBankRequirements(int tileSize) {
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package p.packer.models;
|
||||
|
||||
public record PackerTileIndexedV1(
|
||||
int width,
|
||||
int height,
|
||||
byte[] paletteIndices) {
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package p.packer.models;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public record PackerWalkResult(
|
||||
List<PackerProbeResult> probeResults,
|
||||
List<PackerDiagnostic> diagnostics) {
|
||||
|
||||
public static final PackerWalkResult EMPTY = new PackerWalkResult(List.of(), List.of());
|
||||
|
||||
public PackerWalkResult {
|
||||
probeResults = List.copyOf(Objects.requireNonNull(probeResults, "probeResults"));
|
||||
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package p.packer.services;
|
||||
package p.packer.repositories;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
@ -7,6 +7,7 @@ import p.packer.exceptions.PackerRegistryException;
|
||||
import p.packer.messages.PackerProjectContext;
|
||||
import p.packer.models.PackerRegistryEntry;
|
||||
import p.packer.models.PackerRegistryState;
|
||||
import p.packer.PackerWorkspacePaths;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@ -14,9 +15,14 @@ import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public final class FileSystemPackerRegistryRepository implements PackerRegistryRepository {
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
private static final int REGISTRY_SCHEMA_VERSION = 1;
|
||||
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
public FileSystemPackerRegistryRepository(final ObjectMapper mapper) {
|
||||
this.mapper = Objects.requireNonNull(mapper, "mapper");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackerRegistryState load(PackerProjectContext project) {
|
||||
final Path registryPath = PackerWorkspacePaths.registryPath(project);
|
||||
@ -25,10 +31,10 @@ public final class FileSystemPackerRegistryRepository implements PackerRegistryR
|
||||
}
|
||||
|
||||
try {
|
||||
final RegistryDocument document = MAPPER.readValue(registryPath.toFile(), RegistryDocument.class);
|
||||
final RegistryDocument document = mapper.readValue(registryPath.toFile(), RegistryDocument.class);
|
||||
final int schemaVersion = document.schemaVersion <= 0 ? REGISTRY_SCHEMA_VERSION : document.schemaVersion;
|
||||
if (schemaVersion != REGISTRY_SCHEMA_VERSION) {
|
||||
throw new p.packer.exceptions.PackerRegistryException("Unsupported registry schema_version: " + schemaVersion);
|
||||
throw new PackerRegistryException("Unsupported registry schema_version: " + schemaVersion);
|
||||
}
|
||||
final List<PackerRegistryEntry> entries = new ArrayList<>();
|
||||
if (document.assets != null) {
|
||||
@ -73,7 +79,7 @@ public final class FileSystemPackerRegistryRepository implements PackerRegistryR
|
||||
entry.root(),
|
||||
entry.includedInBuild()))
|
||||
.toList();
|
||||
MAPPER.writerWithDefaultPrettyPrinter().writeValue(registryPath.toFile(), document);
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(registryPath.toFile(), document);
|
||||
} catch (IOException exception) {
|
||||
throw new p.packer.exceptions.PackerRegistryException("Unable to save registry: " + registryPath, exception);
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
package p.packer.repositories;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.tika.Tika;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
||||
import p.packer.models.PackerDiagnostic;
|
||||
import p.packer.models.PackerFileProbe;
|
||||
import p.packer.models.PackerProbeResult;
|
||||
import p.packer.models.PackerWalkResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class PackerAbstractBankWalker<R> {
|
||||
private final Tika tika = new Tika();
|
||||
protected final ObjectMapper mapper;
|
||||
|
||||
protected PackerAbstractBankWalker(ObjectMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
protected abstract Set<String> getSupportedMimeTypes();
|
||||
protected abstract PackerProbeResult processFileProbe(PackerFileProbe fileProbe, R requirements);
|
||||
|
||||
private Optional<PackerFileProbe> getFileProbeIfSupported(
|
||||
final Path filePath,
|
||||
final Set<String> supportedMimeTypes) throws IOException {
|
||||
final var bytes = Files.readAllBytes(filePath);
|
||||
final var mimeType = tika.detect(bytes);
|
||||
if (!supportedMimeTypes.contains(mimeType)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
final var lastModified = filePath.toFile().lastModified();
|
||||
final var fileProbe = new PackerFileProbe(filePath, mimeType, lastModified, bytes);
|
||||
return Optional.of(fileProbe);
|
||||
}
|
||||
|
||||
private List<Path> retrieveValidPaths(final Path assetRoot) throws IOException {
|
||||
try (final var paths = Files.list(assetRoot)
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(path -> !isAssetManifest(path))) {
|
||||
return paths.toList();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAssetManifest(final Path path) {
|
||||
return path.getFileName().toString().equalsIgnoreCase("asset.json");
|
||||
}
|
||||
|
||||
private List<PackerFileProbe> processFileProbeWhenSupported(
|
||||
final Path assetRoot,
|
||||
final Set<String> supportedMimeTypes,
|
||||
final List<PackerDiagnostic> diagnostics) {
|
||||
final List<PackerFileProbe> supportedFileProbes = new ArrayList<>();
|
||||
try {
|
||||
final var validPaths = retrieveValidPaths(assetRoot);
|
||||
for (final var filePath : validPaths) {
|
||||
final var fileProbeMaybe = getFileProbeIfSupported(filePath, supportedMimeTypes);
|
||||
if (fileProbeMaybe.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
final var fileProbe = fileProbeMaybe.get();
|
||||
supportedFileProbes.add(fileProbe);
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Unable to walk tile asset root: " + exception.getMessage(),
|
||||
assetRoot,
|
||||
true
|
||||
));
|
||||
}
|
||||
return supportedFileProbes;
|
||||
}
|
||||
|
||||
public PackerWalkResult walk(
|
||||
final Path assetRoot,
|
||||
final R requirements) {
|
||||
final List<PackerDiagnostic> rootDiagnostics = new ArrayList<>();
|
||||
final List<PackerProbeResult> probeResults = new ArrayList<>();
|
||||
processFileProbeWhenSupported(assetRoot, getSupportedMimeTypes(), rootDiagnostics)
|
||||
.forEach(fileProbe -> probeResults.add(processFileProbe(fileProbe, requirements)));
|
||||
return new PackerWalkResult(probeResults, rootDiagnostics);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,164 @@
|
||||
package p.packer.repositories;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import p.packer.messages.assets.OutputFormatCatalog;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
||||
import p.packer.models.*;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class PackerAssetWalker {
|
||||
private static final Set<OutputFormatCatalog> TILE_BANK_SUPPORTED_FORMATS = Set.of(
|
||||
OutputFormatCatalog.TILES_INDEXED_V1
|
||||
);
|
||||
|
||||
private static final Set<OutputFormatCatalog> SOUND_BANK_SUPPORTED_FORMATS = Set.of(
|
||||
OutputFormatCatalog.SOUND_V1
|
||||
);
|
||||
|
||||
private final PackerTileBankWalker tileBankWalker;
|
||||
private final PackerSoundBankWalker soundBankWalker;
|
||||
|
||||
public PackerAssetWalker(final ObjectMapper mapper) {
|
||||
this.tileBankWalker = new PackerTileBankWalker(mapper);
|
||||
this.soundBankWalker = new PackerSoundBankWalker(mapper);
|
||||
}
|
||||
|
||||
public PackerWalkResult walk(
|
||||
final Path assetRoot,
|
||||
final PackerAssetDeclaration declaration) {
|
||||
final List<PackerDiagnostic> diagnostics = new ArrayList<>();
|
||||
switch (declaration.assetFamily()) {
|
||||
case TILE_BANK -> {
|
||||
var metadata = declaration.outputMetadata();
|
||||
if (MapUtils.isEmpty(metadata)) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.WARNING,
|
||||
PackerDiagnosticCategory.HYGIENE,
|
||||
"Output metadata for tile bank cannot be empty, using default values",
|
||||
assetRoot,
|
||||
false));
|
||||
metadata = Map.of("tile_size", "16x16");
|
||||
}
|
||||
final var requirementBuildResult = buildTileBankRequirements(declaration, metadata);
|
||||
if (requirementBuildResult.hasError()) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
requirementBuildResult.errorMessage(),
|
||||
assetRoot,
|
||||
true));
|
||||
return new PackerWalkResult(List.of(), diagnostics);
|
||||
}
|
||||
final var walkResult = tileBankWalker.walk(assetRoot, requirementBuildResult.requirements);
|
||||
diagnostics.addAll(walkResult.diagnostics());
|
||||
return new PackerWalkResult(walkResult.probeResults(), diagnostics);
|
||||
}
|
||||
case SOUND_BANK -> {
|
||||
var metadata = declaration.outputMetadata();
|
||||
if (MapUtils.isEmpty(metadata)) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.WARNING,
|
||||
PackerDiagnosticCategory.HYGIENE,
|
||||
"Output metadata for tile bank cannot be empty, using default values",
|
||||
assetRoot,
|
||||
false));
|
||||
metadata = Map.of("sample_rate", "44100", "channels", "1");
|
||||
}
|
||||
final var result = buildSoundBankRequirements(declaration, metadata);
|
||||
if (result.hasError()) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
result.errorMessage(),
|
||||
assetRoot,
|
||||
true));
|
||||
return new PackerWalkResult(List.of(), diagnostics);
|
||||
}
|
||||
final var walkResult = soundBankWalker.walk(assetRoot, result.requirements);
|
||||
diagnostics.addAll(walkResult.diagnostics());
|
||||
return new PackerWalkResult(walkResult.probeResults(), diagnostics);
|
||||
}
|
||||
case UNKNOWN -> {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.WARNING,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Unknown asset family for declaration, skipping content walk",
|
||||
assetRoot,
|
||||
false));
|
||||
return new PackerWalkResult(List.of(), diagnostics);
|
||||
}
|
||||
}
|
||||
return PackerWalkResult.EMPTY;
|
||||
}
|
||||
|
||||
private RequirementBuildResult<PackerTileBankRequirements> buildTileBankRequirements(
|
||||
final PackerAssetDeclaration declaration,
|
||||
final Map<String, String> metadata) {
|
||||
if (!TILE_BANK_SUPPORTED_FORMATS.contains(declaration.outputFormat())) {
|
||||
return RequirementBuildResult.fail("Unsupported output format for tile bank: " + declaration.outputFormat());
|
||||
}
|
||||
final var tileSizeStr = metadata.get("tile_size");
|
||||
if (StringUtils.isBlank(tileSizeStr)) {
|
||||
return RequirementBuildResult.fail("Tile size metadata for tile bank cannot be empty");
|
||||
}
|
||||
final int tileSize;
|
||||
switch (tileSizeStr) {
|
||||
case "8x8": {
|
||||
tileSize = 8;
|
||||
} break;
|
||||
case "16x16": {
|
||||
tileSize = 16;
|
||||
} break;
|
||||
case "32x32": {
|
||||
tileSize = 32;
|
||||
} break;
|
||||
default: {
|
||||
return RequirementBuildResult.fail("Unsupported tile size for tile bank: " + tileSizeStr);
|
||||
}
|
||||
}
|
||||
return RequirementBuildResult.success(new PackerTileBankRequirements(tileSize));
|
||||
}
|
||||
|
||||
private RequirementBuildResult<PackerSoundBankRequirements> buildSoundBankRequirements(
|
||||
final PackerAssetDeclaration declaration,
|
||||
final Map<String, String> metadata) {
|
||||
if (!SOUND_BANK_SUPPORTED_FORMATS.contains(declaration.outputFormat())) {
|
||||
return RequirementBuildResult.fail("Unsupported output format for sound bank: " + declaration.outputFormat());
|
||||
}
|
||||
final var sampleRateStr = metadata.get("sample_rate");
|
||||
if (StringUtils.isBlank(sampleRateStr)) {
|
||||
return RequirementBuildResult.fail("Missing sample rate for sound bank");
|
||||
}
|
||||
final var sampleRate = Integer.parseInt(sampleRateStr);
|
||||
final var channelsStr = metadata.get("");
|
||||
if (StringUtils.isBlank(channelsStr)) {
|
||||
return RequirementBuildResult.fail("Missing channels for sound bank");
|
||||
}
|
||||
final var channels = Integer.parseInt(channelsStr);
|
||||
return RequirementBuildResult.success(new PackerSoundBankRequirements(sampleRate, channels));
|
||||
}
|
||||
|
||||
private record RequirementBuildResult<T>(
|
||||
T requirements,
|
||||
String errorMessage) {
|
||||
static <T> RequirementBuildResult<T> success(T requirements) {
|
||||
return new RequirementBuildResult<>(requirements, null);
|
||||
}
|
||||
|
||||
static <T> RequirementBuildResult<T> fail(String errorMessage) {
|
||||
return new RequirementBuildResult<>(null, errorMessage);
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return StringUtils.isNotBlank(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package p.packer.services;
|
||||
package p.packer.repositories;
|
||||
|
||||
import p.packer.messages.PackerProjectContext;
|
||||
import p.packer.models.PackerRegistryState;
|
||||
@ -1,11 +1,12 @@
|
||||
package p.packer.services;
|
||||
package p.packer.repositories;
|
||||
|
||||
import p.packer.messages.InitWorkspaceRequest;
|
||||
import p.packer.messages.PackerProjectContext;
|
||||
import p.packer.models.PackerRegistryEntry;
|
||||
import p.packer.models.PackerRegistryState;
|
||||
import p.packer.models.PackerRuntimeAsset;
|
||||
import p.packer.models.PackerRuntimeSnapshot;
|
||||
import p.packer.services.PackerAssetDeclarationParser;
|
||||
import p.packer.services.PackerWorkspaceFoundation;
|
||||
import p.packer.PackerWorkspacePaths;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@ -17,12 +18,15 @@ import java.util.stream.Collectors;
|
||||
public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader {
|
||||
private final PackerWorkspaceFoundation workspaceFoundation;
|
||||
private final PackerAssetDeclarationParser parser;
|
||||
private final PackerAssetWalker assetWalker;
|
||||
|
||||
public PackerRuntimeLoader(
|
||||
PackerWorkspaceFoundation workspaceFoundation,
|
||||
PackerAssetDeclarationParser parser) {
|
||||
final PackerWorkspaceFoundation workspaceFoundation,
|
||||
final PackerAssetDeclarationParser parser,
|
||||
final PackerAssetWalker assetWalker) {
|
||||
this.workspaceFoundation = Objects.requireNonNull(workspaceFoundation, "workspaceFoundation");
|
||||
this.parser = Objects.requireNonNull(parser, "parser");
|
||||
this.assetWalker = Objects.requireNonNull(assetWalker, "assetWalker");
|
||||
}
|
||||
|
||||
private boolean isAssetJson(Path path, BasicFileAttributes attrs) {
|
||||
@ -31,11 +35,13 @@ public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader {
|
||||
|
||||
@Override
|
||||
public PackerRuntimeSnapshot load(PackerProjectContext project, long generation) {
|
||||
final PackerProjectContext safeProject = Objects.requireNonNull(project, "project");
|
||||
final var safeProject = Objects.requireNonNull(project, "project");
|
||||
workspaceFoundation.initWorkspace(new InitWorkspaceRequest(safeProject));
|
||||
|
||||
final PackerRegistryState registry = workspaceFoundation.loadRegistry(safeProject);
|
||||
final Map<Path, PackerRegistryEntry> registryByRoot = registry.assets().stream()
|
||||
final var registry = workspaceFoundation.loadRegistry(safeProject);
|
||||
final var registryByRoot = registry
|
||||
.assets()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(
|
||||
entry -> PackerWorkspacePaths.assetRoot(safeProject, entry.root()),
|
||||
entry -> entry));
|
||||
@ -51,11 +57,13 @@ public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader {
|
||||
for (final var manifestPath : manifests) {
|
||||
final var assetRoot = manifestPath.getParent();
|
||||
final var registryEntry = Optional.ofNullable(registryByRoot.get(assetRoot));
|
||||
assets.add(new PackerRuntimeAsset(
|
||||
assetRoot,
|
||||
manifestPath,
|
||||
registryEntry,
|
||||
parser.parse(manifestPath)));
|
||||
final var parseResult = parser.parse(manifestPath);
|
||||
if (parseResult.valid()) {
|
||||
final var walkResult = assetWalker.walk(assetRoot, parseResult.declaration());
|
||||
|
||||
}
|
||||
final var runtimeAsset = new PackerRuntimeAsset(assetRoot, manifestPath, registryEntry, parseResult);
|
||||
assets.add(runtimeAsset);
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
throw new p.packer.exceptions.PackerRegistryException(
|
||||
@ -1,7 +1,8 @@
|
||||
package p.packer.services;
|
||||
package p.packer.repositories;
|
||||
|
||||
import p.packer.messages.PackerProjectContext;
|
||||
import p.packer.models.PackerRuntimeSnapshot;
|
||||
import p.packer.services.PackerProjectRuntime;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
@ -1,4 +1,4 @@
|
||||
package p.packer.services;
|
||||
package p.packer.repositories;
|
||||
|
||||
import p.packer.messages.PackerProjectContext;
|
||||
import p.packer.models.PackerRuntimeSnapshot;
|
||||
@ -0,0 +1,33 @@
|
||||
package p.packer.repositories;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import p.packer.models.PackerDiagnostic;
|
||||
import p.packer.models.PackerFileProbe;
|
||||
import p.packer.models.PackerProbeResult;
|
||||
import p.packer.models.PackerSoundBankRequirements;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class PackerSoundBankWalker extends PackerAbstractBankWalker<PackerSoundBankRequirements> {
|
||||
private static final Set<String> SUPPORTED_MIME_TYPES = Set.of("audio/wav");
|
||||
|
||||
public PackerSoundBankWalker(ObjectMapper mapper) {
|
||||
super(mapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getSupportedMimeTypes() {
|
||||
return SUPPORTED_MIME_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PackerProbeResult processFileProbe(
|
||||
final PackerFileProbe fileProbe,
|
||||
final PackerSoundBankRequirements requirements) {
|
||||
final List<PackerDiagnostic> diagnostics = new ArrayList<>();
|
||||
return new PackerProbeResult(fileProbe, Map.of(), diagnostics);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,231 @@
|
||||
package p.packer.repositories;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
||||
import p.packer.models.*;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class PackerTileBankWalker extends PackerAbstractBankWalker<PackerTileBankRequirements> {
|
||||
private static final int MAX_COLORS_PER_PALETTE = 15;
|
||||
private static final int COLOR_KEY_RGB = 0x00FF00FF;
|
||||
private static final Set<String> SUPPORTED_MIME_TYPES = Set.of(
|
||||
"image/png"
|
||||
);
|
||||
|
||||
public PackerTileBankWalker(ObjectMapper mapper) {
|
||||
super(mapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getSupportedMimeTypes() {
|
||||
return SUPPORTED_MIME_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PackerProbeResult processFileProbe(
|
||||
final PackerFileProbe fileProbe,
|
||||
final PackerTileBankRequirements requirements) {
|
||||
final List<PackerDiagnostic> diagnostics = new ArrayList<>();
|
||||
|
||||
final var image = readImage(fileProbe, diagnostics);
|
||||
if (image == null) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Unable to decode tile image content: " + fileProbe.path().getFileName(),
|
||||
fileProbe.path(),
|
||||
true
|
||||
));
|
||||
return new PackerProbeResult(fileProbe, Map.of(), diagnostics);
|
||||
}
|
||||
|
||||
final var width = image.getWidth();
|
||||
final var height = image.getHeight();
|
||||
if (width != requirements.tileSize() || height != requirements.tileSize()) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Invalid tile dimensions for " + fileProbe.path().getFileName()
|
||||
+ ": expected " + requirements.tileSize() + "x" + requirements.tileSize()
|
||||
+ " but got " + width + "x" + height,
|
||||
fileProbe.path(),
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
final Map<Integer, Integer> paletteIndexByRgb = new LinkedHashMap<>();
|
||||
final byte[] paletteIndices = new byte[image.getWidth() * image.getHeight()];
|
||||
boolean partialAlphaFound = false;
|
||||
boolean exceededPaletteLimit = false;
|
||||
|
||||
int offset = 0;
|
||||
for (var y = 0; y < image.getHeight(); y++) {
|
||||
for (var x = 0; x < image.getWidth(); x++) {
|
||||
final int argb = image.getRGB(x, y);
|
||||
final int alpha = (argb >>> 24) & 0xFF;
|
||||
final int rgb = argb & 0x00FFFFFF;
|
||||
|
||||
if (alpha == 0 || rgb == COLOR_KEY_RGB) {
|
||||
paletteIndices[offset++] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (alpha < 0xFF) {
|
||||
partialAlphaFound = true;
|
||||
}
|
||||
|
||||
Integer paletteIndex = paletteIndexByRgb.get(rgb);
|
||||
if (paletteIndex == null) {
|
||||
if (paletteIndexByRgb.size() >= MAX_COLORS_PER_PALETTE) {
|
||||
exceededPaletteLimit = true;
|
||||
paletteIndices[offset++] = 0;
|
||||
continue;
|
||||
}
|
||||
paletteIndex = paletteIndexByRgb.size() + 1;
|
||||
paletteIndexByRgb.put(rgb, paletteIndex);
|
||||
}
|
||||
|
||||
paletteIndices[offset++] = paletteIndex.byteValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (partialAlphaFound) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.WARNING,
|
||||
PackerDiagnosticCategory.HYGIENE,
|
||||
"Tile contains partial alpha; flattening to RGB and ignoring alpha channel",
|
||||
fileProbe.path(),
|
||||
false
|
||||
));
|
||||
}
|
||||
|
||||
if (exceededPaletteLimit) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Tile image exceeds color limit for " + fileProbe.path().getFileName()
|
||||
+ ": expected at most " + MAX_COLORS_PER_PALETTE + " colors for indices 1..15",
|
||||
fileProbe.path(),
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
if (diagnostics.stream().anyMatch(PackerDiagnostic::blocking)) {
|
||||
return new PackerProbeResult(fileProbe, Map.of(), diagnostics);
|
||||
}
|
||||
|
||||
final var tile = new PackerTileIndexedV1(image.getWidth(), image.getHeight(), paletteIndices);
|
||||
final var palette = buildPalette(paletteIndexByRgb);
|
||||
serializeProbeArtifacts(fileProbe, tile, palette, diagnostics);
|
||||
return new PackerProbeResult(fileProbe, Map.of("tile", tile, "palette", palette), diagnostics);
|
||||
}
|
||||
|
||||
private void serializeProbeArtifacts(
|
||||
final PackerFileProbe fileProbe,
|
||||
final PackerTileIndexedV1 tile,
|
||||
final PackerPaletteV1 palette,
|
||||
final List<PackerDiagnostic> diagnostics) {
|
||||
final Path basePath = baseOutputPath(fileProbe.path());
|
||||
final Path tileOutputPath = Path.of(basePath + ".tile.json");
|
||||
final Path paletteOutputPath = Path.of(basePath + ".palette.json");
|
||||
|
||||
try {
|
||||
Files.createDirectories(Objects.requireNonNull(tileOutputPath.getParent()));
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(tileOutputPath.toFile(), toTilePayload(tile));
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(paletteOutputPath.toFile(), toPalettePayload(palette));
|
||||
} catch (IOException exception) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Unable to serialize tile/palette artifacts for " + fileProbe.path().getFileName()
|
||||
+ ": " + exception.getMessage(),
|
||||
fileProbe.path(),
|
||||
true
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private Path baseOutputPath(final Path sourcePath) {
|
||||
final String fileName = sourcePath.getFileName().toString();
|
||||
final int extensionStart = fileName.lastIndexOf('.');
|
||||
final String outputBaseName = extensionStart > 0 ? fileName.substring(0, extensionStart) : fileName;
|
||||
final Path parent = sourcePath.getParent();
|
||||
if (parent == null) {
|
||||
return Path.of(outputBaseName);
|
||||
}
|
||||
return parent.resolve(outputBaseName);
|
||||
}
|
||||
|
||||
private Map<String, Object> toTilePayload(final PackerTileIndexedV1 tile) {
|
||||
final List<Integer> indices = new ArrayList<>(tile.paletteIndices().length);
|
||||
for (final byte value : tile.paletteIndices()) {
|
||||
indices.add(Byte.toUnsignedInt(value));
|
||||
}
|
||||
|
||||
return Map.of(
|
||||
"width", tile.width(),
|
||||
"height", tile.height(),
|
||||
"paletteIndices", indices
|
||||
);
|
||||
}
|
||||
|
||||
private Map<String, Object> toPalettePayload(final PackerPaletteV1 palette) {
|
||||
final List<Integer> convertedRgb565 = new ArrayList<>(palette.convertedRgb565().length);
|
||||
for (final short value : palette.convertedRgb565()) {
|
||||
convertedRgb565.add(Short.toUnsignedInt(value));
|
||||
}
|
||||
|
||||
return Map.of(
|
||||
"originalArgb8888", palette.originalArgb8888(),
|
||||
"convertedRgb565", convertedRgb565
|
||||
);
|
||||
}
|
||||
|
||||
private PackerPaletteV1 buildPalette(final Map<Integer, Integer> paletteIndexByRgb) {
|
||||
final int[] originalArgb8888 = new int[paletteIndexByRgb.size()];
|
||||
final short[] convertedRgb565 = new short[paletteIndexByRgb.size()];
|
||||
for (final var entry : paletteIndexByRgb.entrySet()) {
|
||||
final int rgb = entry.getKey();
|
||||
final int index = entry.getValue() - 1;
|
||||
final int argb = 0xFF000000 | rgb;
|
||||
originalArgb8888[index] = argb;
|
||||
convertedRgb565[index] = convertToRgb565(argb);
|
||||
}
|
||||
return new PackerPaletteV1(originalArgb8888, convertedRgb565);
|
||||
}
|
||||
|
||||
private short convertToRgb565(final int argb) {
|
||||
final int red = (argb >> 16) & 0xFF;
|
||||
final int green = (argb >> 8) & 0xFF;
|
||||
final int blue = argb & 0xFF;
|
||||
|
||||
final int r5 = (red >> 3) & 0x1F;
|
||||
final int g6 = (green >> 2) & 0x3F;
|
||||
final int b5 = (blue >> 3) & 0x1F;
|
||||
return (short) ((r5 << 11) | (g6 << 5) | b5);
|
||||
}
|
||||
|
||||
private java.awt.image.BufferedImage readImage(
|
||||
final PackerFileProbe fileProbe,
|
||||
final List<PackerDiagnostic> diagnostics) {
|
||||
try {
|
||||
return ImageIO.read(new ByteArrayInputStream(fileProbe.content()));
|
||||
} catch (Exception exception) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
"Unable to decode tile image content: " + exception.getMessage(),
|
||||
fileProbe.path(),
|
||||
true
|
||||
));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ package p.packer.services;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import p.packer.PackerWorkspacePaths;
|
||||
import p.packer.PackerWorkspaceService;
|
||||
import p.packer.events.PackerEventKind;
|
||||
import p.packer.events.PackerEventSink;
|
||||
@ -12,6 +13,7 @@ import p.packer.messages.assets.*;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
||||
import p.packer.models.*;
|
||||
import p.packer.repositories.PackerRuntimeRegistry;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@ -57,14 +59,14 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
|
||||
@Override
|
||||
public ListAssetsResult listAssets(ListAssetsRequest request) {
|
||||
final PackerProjectContext project = Objects.requireNonNull(request, "request").project();
|
||||
final PackerRuntimeSnapshot snapshot = request.deepSync()
|
||||
final var project = Objects.requireNonNull(request, "request").project();
|
||||
final var snapshot = request.deepSync()
|
||||
? runtimeRegistry.refresh(project).snapshot()
|
||||
: runtimeRegistry.getOrLoad(project).snapshot();
|
||||
final PackerOperationEventEmitter events = new PackerOperationEventEmitter(project, eventSink);
|
||||
final PackerRegistryState registry = snapshot.registry();
|
||||
final var events = new PackerOperationEventEmitter(project, eventSink);
|
||||
final var registry = snapshot.registry();
|
||||
final Map<Path, PackerRegistryEntry> registryByRoot = new HashMap<>();
|
||||
for (PackerRegistryEntry entry : registry.assets()) {
|
||||
for (final var entry : registry.assets()) {
|
||||
registryByRoot.put(PackerWorkspacePaths.assetRoot(project, entry.root()), entry);
|
||||
}
|
||||
|
||||
@ -75,15 +77,15 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
final List<PackerRuntimeAsset> runtimeAssets = snapshot.assets();
|
||||
final int total = runtimeAssets.size();
|
||||
for (int index = 0; index < runtimeAssets.size(); index += 1) {
|
||||
final PackerRuntimeAsset runtimeAsset = runtimeAssets.get(index);
|
||||
final Path assetRoot = runtimeAsset.assetRoot();
|
||||
final Path assetManifestPath = runtimeAsset.manifestPath();
|
||||
final var runtimeAsset = runtimeAssets.get(index);
|
||||
final var assetRoot = runtimeAsset.assetRoot();
|
||||
final var assetManifestPath = runtimeAsset.manifestPath();
|
||||
discoveredRoots.add(assetRoot);
|
||||
final PackerRegistryEntry registryEntry = registryByRoot.get(assetRoot);
|
||||
final PackerAssetDeclarationParseResult parsed = runtimeAsset.parsedDeclaration();
|
||||
final var registryEntry = registryByRoot.get(assetRoot);
|
||||
final var parsed = runtimeAsset.parsedDeclaration();
|
||||
diagnostics.addAll(parsed.diagnostics());
|
||||
diagnostics.addAll(identityMismatchDiagnostics(registryEntry, parsed, assetManifestPath));
|
||||
final PackerAssetSummary summary = buildSummary(project, assetRoot, registryEntry, parsed);
|
||||
final var summary = buildSummary(project, assetRoot, registryEntry, parsed);
|
||||
assets.add(summary);
|
||||
events.emit(
|
||||
PackerEventKind.ASSET_DISCOVERED,
|
||||
@ -92,8 +94,8 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
List.of(summary.identity().assetName()));
|
||||
}
|
||||
|
||||
for (PackerRegistryEntry entry : registry.assets()) {
|
||||
final Path registeredRoot = PackerWorkspacePaths.assetRoot(project, entry.root());
|
||||
for (final var entry : registry.assets()) {
|
||||
final var registeredRoot = PackerWorkspacePaths.assetRoot(project, entry.root());
|
||||
if (!discoveredRoots.contains(registeredRoot)) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
@ -107,7 +109,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
||||
assets.sort(Comparator
|
||||
.comparing((PackerAssetSummary asset) -> asset.identity().assetRoot().toString(), String.CASE_INSENSITIVE_ORDER)
|
||||
.thenComparing(summary -> summary.identity().assetName(), String.CASE_INSENSITIVE_ORDER));
|
||||
final PackerOperationStatus status = diagnostics.stream().anyMatch(PackerDiagnostic::blocking)
|
||||
final var status = diagnostics.stream().anyMatch(PackerDiagnostic::blocking)
|
||||
? PackerOperationStatus.PARTIAL
|
||||
: PackerOperationStatus.SUCCESS;
|
||||
if (!diagnostics.isEmpty()) {
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
package p.packer.services;
|
||||
|
||||
import p.packer.PackerWorkspacePaths;
|
||||
import p.packer.messages.*;
|
||||
import p.packer.messages.assets.AssetAction;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
||||
import p.packer.models.*;
|
||||
import p.packer.repositories.PackerRuntimeRegistry;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@ -16,7 +16,7 @@ import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public final class PackerAssetDeclarationParser {
|
||||
private static final int SUPPORTED_SCHEMA_VERSION = 1;
|
||||
private static final Set<Integer> SUPPORTED_SCHEMA_VERSIONS = Set.of(1);
|
||||
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
@ -40,17 +40,17 @@ public final class PackerAssetDeclarationParser {
|
||||
return new PackerAssetDeclarationParseResult(null, diagnostics);
|
||||
}
|
||||
|
||||
final Integer schemaVersion = requiredInt(root, "schema_version", diagnostics, manifestPath);
|
||||
final String assetUuid = requiredText(root, "asset_uuid", diagnostics, manifestPath);
|
||||
final String name = requiredText(root, "name", diagnostics, manifestPath);
|
||||
final AssetFamilyCatalog assetFamily = requiredAssetFamily(root, diagnostics, manifestPath);
|
||||
final Map<String, List<String>> inputsByRole = requiredInputs(root.path("inputs"), diagnostics, manifestPath);
|
||||
final OutputFormatCatalog outputFormat = requiredOutputFormat(root.path("output"), diagnostics, manifestPath);
|
||||
final OutputCodecCatalog outputCodec = requiredOutputCodec(root.path("output"), diagnostics, manifestPath);
|
||||
final Map<String, String> outputMetadata = optionalOutputMetadata(root.path("output"), diagnostics, manifestPath);
|
||||
final Boolean preloadEnabled = requiredBoolean(root.path("preload"), "enabled", diagnostics, manifestPath);
|
||||
final var schemaVersion = requiredInt(root, "schema_version", diagnostics, manifestPath);
|
||||
final var assetUuid = requiredText(root, "asset_uuid", diagnostics, manifestPath);
|
||||
final var name = requiredText(root, "name", diagnostics, manifestPath);
|
||||
final var assetFamily = requiredAssetFamily(root, diagnostics, manifestPath);
|
||||
final var inputsByRole = requiredInputs(root.path("inputs"), diagnostics, manifestPath);
|
||||
final var outputFormat = requiredOutputFormat(root.path("output"), diagnostics, manifestPath);
|
||||
final var outputCodec = requiredOutputCodec(root.path("output"), diagnostics, manifestPath);
|
||||
final var outputMetadata = optionalOutputMetadata(root.path("output"), diagnostics, manifestPath);
|
||||
final var preloadEnabled = requiredBoolean(root.path("preload"), "enabled", diagnostics, manifestPath);
|
||||
|
||||
if (schemaVersion != null && schemaVersion != SUPPORTED_SCHEMA_VERSION) {
|
||||
if (schemaVersion != null && !SUPPORTED_SCHEMA_VERSIONS.contains(schemaVersion)) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.VERSIONING,
|
||||
@ -77,7 +77,11 @@ public final class PackerAssetDeclarationParser {
|
||||
diagnostics);
|
||||
}
|
||||
|
||||
private Integer requiredInt(JsonNode node, String fieldName, List<PackerDiagnostic> diagnostics, Path manifestPath) {
|
||||
private Integer requiredInt(
|
||||
final JsonNode node,
|
||||
final String fieldName,
|
||||
final List<PackerDiagnostic> diagnostics,
|
||||
final Path manifestPath) {
|
||||
final JsonNode field = node.path(fieldName);
|
||||
if (!field.isInt()) {
|
||||
diagnostics.add(missingOrInvalid(fieldName, "integer", manifestPath));
|
||||
@ -86,7 +90,11 @@ public final class PackerAssetDeclarationParser {
|
||||
return field.intValue();
|
||||
}
|
||||
|
||||
private String requiredText(JsonNode node, String fieldName, List<PackerDiagnostic> diagnostics, Path manifestPath) {
|
||||
private String requiredText(
|
||||
final JsonNode node,
|
||||
final String fieldName,
|
||||
final List<PackerDiagnostic> diagnostics,
|
||||
final Path manifestPath) {
|
||||
final JsonNode field = node.path(fieldName);
|
||||
if (!field.isTextual() || field.asText().isBlank()) {
|
||||
diagnostics.add(missingOrInvalid(fieldName, "non-blank string", manifestPath));
|
||||
@ -95,7 +103,10 @@ public final class PackerAssetDeclarationParser {
|
||||
return field.asText().trim();
|
||||
}
|
||||
|
||||
private AssetFamilyCatalog requiredAssetFamily(JsonNode node, List<PackerDiagnostic> diagnostics, Path manifestPath) {
|
||||
private AssetFamilyCatalog requiredAssetFamily(
|
||||
final JsonNode node,
|
||||
final List<PackerDiagnostic> diagnostics,
|
||||
final Path manifestPath) {
|
||||
final String manifestType = requiredText(node, "type", diagnostics, manifestPath);
|
||||
if (manifestType == null) {
|
||||
return null;
|
||||
@ -113,7 +124,11 @@ public final class PackerAssetDeclarationParser {
|
||||
return assetFamily;
|
||||
}
|
||||
|
||||
private Boolean requiredBoolean(JsonNode node, String fieldName, List<PackerDiagnostic> diagnostics, Path manifestPath) {
|
||||
private Boolean requiredBoolean(
|
||||
final JsonNode node,
|
||||
final String fieldName,
|
||||
final List<PackerDiagnostic> diagnostics,
|
||||
final Path manifestPath) {
|
||||
final JsonNode field = node.path(fieldName);
|
||||
if (!field.isBoolean()) {
|
||||
diagnostics.add(missingOrInvalid(fieldName, "boolean", manifestPath));
|
||||
@ -122,7 +137,10 @@ public final class PackerAssetDeclarationParser {
|
||||
return field.booleanValue();
|
||||
}
|
||||
|
||||
private OutputFormatCatalog requiredOutputFormat(JsonNode node, List<PackerDiagnostic> diagnostics, Path manifestPath) {
|
||||
private OutputFormatCatalog requiredOutputFormat(
|
||||
final JsonNode node,
|
||||
final List<PackerDiagnostic> diagnostics,
|
||||
final Path manifestPath) {
|
||||
final String fmtValue = requiredText(node, "format", diagnostics, manifestPath);
|
||||
if (fmtValue == null) {
|
||||
return null;
|
||||
@ -140,7 +158,10 @@ public final class PackerAssetDeclarationParser {
|
||||
return outputFormat;
|
||||
}
|
||||
|
||||
private OutputCodecCatalog requiredOutputCodec(JsonNode node, List<PackerDiagnostic> diagnostics, Path manifestPath) {
|
||||
private OutputCodecCatalog requiredOutputCodec(
|
||||
final JsonNode node,
|
||||
final List<PackerDiagnostic> diagnostics,
|
||||
final Path manifestPath) {
|
||||
final String codecValue = requiredText(node, "codec", diagnostics, manifestPath);
|
||||
if (codecValue == null) {
|
||||
return null;
|
||||
@ -159,10 +180,10 @@ public final class PackerAssetDeclarationParser {
|
||||
}
|
||||
|
||||
private Map<String, String> optionalOutputMetadata(
|
||||
JsonNode outputNode,
|
||||
List<PackerDiagnostic> diagnostics,
|
||||
Path manifestPath) {
|
||||
final JsonNode metadataNode = outputNode.path("metadata");
|
||||
final JsonNode node,
|
||||
final List<PackerDiagnostic> diagnostics,
|
||||
final Path manifestPath) {
|
||||
final JsonNode metadataNode = node.path("metadata");
|
||||
if (metadataNode.isMissingNode() || metadataNode.isNull()) {
|
||||
return Map.of();
|
||||
}
|
||||
@ -205,14 +226,17 @@ public final class PackerAssetDeclarationParser {
|
||||
return Map.copyOf(metadata);
|
||||
}
|
||||
|
||||
private Map<String, List<String>> requiredInputs(JsonNode inputsNode, List<PackerDiagnostic> diagnostics, Path manifestPath) {
|
||||
if (!inputsNode.isObject()) {
|
||||
private Map<String, List<String>> requiredInputs(
|
||||
final JsonNode node,
|
||||
final List<PackerDiagnostic> diagnostics,
|
||||
final Path manifestPath) {
|
||||
if (!node.isObject()) {
|
||||
diagnostics.add(missingOrInvalid("inputs", "object of input roles", manifestPath));
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
final Map<String, List<String>> result = new LinkedHashMap<>();
|
||||
inputsNode.fields().forEachRemaining(entry -> {
|
||||
node.fields().forEachRemaining(entry -> {
|
||||
if (!entry.getValue().isArray()) {
|
||||
diagnostics.add(new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
@ -250,12 +274,15 @@ public final class PackerAssetDeclarationParser {
|
||||
return Map.copyOf(result);
|
||||
}
|
||||
|
||||
private boolean isTrustedRelativePath(String value) {
|
||||
private boolean isTrustedRelativePath(final String value) {
|
||||
final Path path = Path.of(value).normalize();
|
||||
return !path.isAbsolute() && !path.startsWith("..");
|
||||
}
|
||||
|
||||
private PackerDiagnostic missingOrInvalid(String fieldName, String expected, Path manifestPath) {
|
||||
private PackerDiagnostic missingOrInvalid(
|
||||
final String fieldName,
|
||||
final String expected,
|
||||
final Path manifestPath) {
|
||||
return new PackerDiagnostic(
|
||||
PackerDiagnosticSeverity.ERROR,
|
||||
PackerDiagnosticCategory.STRUCTURAL,
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
package p.packer.services;
|
||||
|
||||
import p.packer.PackerWorkspacePaths;
|
||||
import p.packer.messages.*;
|
||||
import p.packer.messages.assets.*;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
||||
import p.packer.models.*;
|
||||
import p.packer.repositories.PackerRuntimeRegistry;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package p.packer.services;
|
||||
|
||||
import p.packer.PackerWorkspacePaths;
|
||||
import p.packer.messages.AssetReference;
|
||||
import p.packer.messages.PackerProjectContext;
|
||||
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package p.packer.services;
|
||||
|
||||
import p.packer.PackerWorkspacePaths;
|
||||
import p.packer.messages.PackerProjectContext;
|
||||
import p.packer.models.PackerRegistryEntry;
|
||||
import p.packer.models.PackerRegistryState;
|
||||
|
||||
@ -58,13 +58,13 @@ final class PackerOutputContractCatalog {
|
||||
"tile_size",
|
||||
"TileSize",
|
||||
PackerCodecConfigurationFieldType.ENUM,
|
||||
"16",
|
||||
"16x16",
|
||||
true,
|
||||
List.of("8", "16", "32")));
|
||||
List.of("8x8", "16x16", "32x32")));
|
||||
case SOUND_V1 -> List.of(
|
||||
new PackerCodecConfigurationField(
|
||||
"sample_rate",
|
||||
"Frame Rate",
|
||||
"Sample Rate",
|
||||
PackerCodecConfigurationFieldType.ENUM,
|
||||
"44100",
|
||||
true,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package p.packer.services;
|
||||
|
||||
import p.packer.PackerWorkspacePaths;
|
||||
import p.packer.messages.PackerProjectContext;
|
||||
import p.packer.models.PackerRegistryEntry;
|
||||
import p.packer.models.PackerRegistryState;
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
package p.packer.services;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import p.packer.PackerWorkspacePaths;
|
||||
import p.packer.messages.InitWorkspaceRequest;
|
||||
import p.packer.messages.InitWorkspaceResult;
|
||||
import p.packer.messages.PackerOperationStatus;
|
||||
import p.packer.messages.PackerProjectContext;
|
||||
import p.packer.models.PackerRegistryEntry;
|
||||
import p.packer.models.PackerRegistryState;
|
||||
import p.packer.repositories.FileSystemPackerRegistryRepository;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@ -17,8 +20,8 @@ public final class PackerWorkspaceFoundation {
|
||||
private final PackerIdentityAllocator identityAllocator;
|
||||
private final PackerRegistryLookup registryLookup;
|
||||
|
||||
public PackerWorkspaceFoundation() {
|
||||
this(new FileSystemPackerRegistryRepository(), new PackerIdentityAllocator(), new PackerRegistryLookup());
|
||||
public PackerWorkspaceFoundation(final ObjectMapper mapper) {
|
||||
this(new FileSystemPackerRegistryRepository(mapper), new PackerIdentityAllocator(), new PackerRegistryLookup());
|
||||
}
|
||||
|
||||
public PackerWorkspaceFoundation(
|
||||
|
||||
@ -7,6 +7,10 @@ import p.packer.events.PackerEvent;
|
||||
import p.packer.events.PackerEventKind;
|
||||
import p.packer.messages.*;
|
||||
import p.packer.messages.assets.*;
|
||||
import p.packer.repositories.PackerAssetWalker;
|
||||
import p.packer.repositories.PackerRuntimeLoader;
|
||||
import p.packer.repositories.PackerRuntimeRegistry;
|
||||
import p.packer.repositories.PackerRuntimeSnapshotLoader;
|
||||
import p.packer.testing.PackerFixtureLocator;
|
||||
|
||||
import java.nio.file.Files;
|
||||
@ -193,7 +197,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
"NONE:packMode", "tight",
|
||||
"NONE:palette", "mono"),
|
||||
Map.of(
|
||||
"tile_size", "16",
|
||||
"tile_size", "16x16",
|
||||
"channels", "1")));
|
||||
|
||||
assertTrue(result.success());
|
||||
@ -205,7 +209,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
assertEquals("NONE", manifest.path("output").path("codec").asText());
|
||||
assertEquals("tight", manifest.path("output").path("codec_configuration").path("packMode").asText());
|
||||
assertEquals("mono", manifest.path("output").path("codec_configuration").path("palette").asText());
|
||||
assertEquals("16", manifest.path("output").path("metadata").path("tile_size").asText());
|
||||
assertEquals("16x16", manifest.path("output").path("metadata").path("tile_size").asText());
|
||||
assertEquals("1", manifest.path("output").path("metadata").path("channels").asText());
|
||||
}
|
||||
|
||||
@ -672,9 +676,10 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
private FileSystemPackerWorkspaceService service(
|
||||
p.packer.events.PackerEventSink eventSink,
|
||||
PackerRuntimeSnapshotLoader loader) {
|
||||
final var foundation = new p.packer.services.PackerWorkspaceFoundation();
|
||||
final var parser = new p.packer.services.PackerAssetDeclarationParser(new ObjectMapper());
|
||||
final var runtimeRegistry = new p.packer.services.PackerRuntimeRegistry(loader);
|
||||
final var mapper = new ObjectMapper();
|
||||
final var foundation = new p.packer.services.PackerWorkspaceFoundation(mapper);
|
||||
final var parser = new p.packer.services.PackerAssetDeclarationParser(mapper);
|
||||
final var runtimeRegistry = new PackerRuntimeRegistry(loader);
|
||||
final var resolver = new p.packer.services.PackerAssetReferenceResolver(foundation.lookup());
|
||||
final var detailsService = new p.packer.services.PackerAssetDetailsService(runtimeRegistry, resolver);
|
||||
final var actionReadService = new p.packer.services.PackerAssetActionReadService(runtimeRegistry, resolver, foundation.lookup());
|
||||
@ -692,9 +697,11 @@ final class FileSystemPackerWorkspaceServiceTest {
|
||||
}
|
||||
|
||||
private CountingLoader countingLoader() {
|
||||
final var foundation = new p.packer.services.PackerWorkspaceFoundation();
|
||||
final var parser = new p.packer.services.PackerAssetDeclarationParser(new ObjectMapper());
|
||||
return new CountingLoader(new p.packer.services.PackerRuntimeLoader(foundation, parser));
|
||||
final var mapper = new ObjectMapper();
|
||||
final var foundation = new p.packer.services.PackerWorkspaceFoundation(mapper);
|
||||
final var parser = new p.packer.services.PackerAssetDeclarationParser(mapper);
|
||||
final var assetWalker = new PackerAssetWalker(mapper);
|
||||
return new CountingLoader(new PackerRuntimeLoader(foundation, parser, assetWalker));
|
||||
}
|
||||
|
||||
private static final class CountingLoader implements PackerRuntimeSnapshotLoader {
|
||||
|
||||
@ -11,6 +11,9 @@ import p.packer.messages.PackerProjectContext;
|
||||
import p.packer.messages.assets.OutputCodecCatalog;
|
||||
import p.packer.messages.assets.PackerAssetState;
|
||||
import p.packer.messages.assets.PackerBuildParticipation;
|
||||
import p.packer.repositories.PackerAssetWalker;
|
||||
import p.packer.repositories.PackerRuntimeLoader;
|
||||
import p.packer.repositories.PackerRuntimeRegistry;
|
||||
import p.packer.testing.PackerFixtureLocator;
|
||||
|
||||
import java.nio.file.Files;
|
||||
@ -68,7 +71,7 @@ final class PackerAssetDetailsServiceTest {
|
||||
final var manifest = mapper.readTree(manifestPath.toFile());
|
||||
final ObjectNode output = (ObjectNode) manifest.path("output");
|
||||
final ObjectNode metadata = output.putObject("metadata");
|
||||
metadata.put("tile_size", "16");
|
||||
metadata.put("tile_size", "16x16");
|
||||
metadata.put("channels", "1");
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
|
||||
|
||||
@ -77,7 +80,7 @@ final class PackerAssetDetailsServiceTest {
|
||||
|
||||
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
||||
assertEquals(
|
||||
Map.of("tile_size", "16", "channels", "1"),
|
||||
Map.of("tile_size", "16x16", "channels", "1"),
|
||||
result.details().metadataFields().stream().collect(java.util.stream.Collectors.toMap(
|
||||
field -> field.key(),
|
||||
field -> field.value())));
|
||||
@ -130,9 +133,11 @@ final class PackerAssetDetailsServiceTest {
|
||||
}
|
||||
|
||||
private PackerAssetDetailsService service() {
|
||||
final var foundation = new p.packer.services.PackerWorkspaceFoundation();
|
||||
final var parser = new PackerAssetDeclarationParser(new ObjectMapper());
|
||||
final var runtimeRegistry = new PackerRuntimeRegistry(new PackerRuntimeLoader(foundation, parser));
|
||||
final var mapper = new ObjectMapper();
|
||||
final var foundation = new PackerWorkspaceFoundation(mapper);
|
||||
final var parser = new PackerAssetDeclarationParser(mapper);
|
||||
final var assetWalker = new PackerAssetWalker(mapper);
|
||||
final var runtimeRegistry = new PackerRuntimeRegistry(new PackerRuntimeLoader(foundation, parser, assetWalker));
|
||||
final var resolver = new PackerAssetReferenceResolver(foundation.lookup());
|
||||
return new PackerAssetDetailsService(runtimeRegistry, resolver);
|
||||
}
|
||||
|
||||
@ -4,6 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import p.packer.messages.PackerProjectContext;
|
||||
import p.packer.repositories.PackerAssetWalker;
|
||||
import p.packer.repositories.PackerRuntimeLoader;
|
||||
import p.packer.repositories.PackerRuntimeRegistry;
|
||||
import p.packer.testing.PackerFixtureLocator;
|
||||
|
||||
import java.nio.file.Files;
|
||||
@ -78,9 +81,11 @@ final class PackerRuntimeRegistryTest {
|
||||
}
|
||||
|
||||
private PackerRuntimeRegistry runtimeRegistry() {
|
||||
final var foundation = new PackerWorkspaceFoundation();
|
||||
final var parser = new PackerAssetDeclarationParser(new ObjectMapper());
|
||||
return new PackerRuntimeRegistry(new PackerRuntimeLoader(foundation, parser));
|
||||
final var mapper = new ObjectMapper();
|
||||
final var foundation = new PackerWorkspaceFoundation(mapper);
|
||||
final var parser = new PackerAssetDeclarationParser(mapper);
|
||||
final var assetWalker = new PackerAssetWalker(mapper);
|
||||
return new PackerRuntimeRegistry(new PackerRuntimeLoader(foundation, parser, assetWalker));
|
||||
}
|
||||
|
||||
private PackerProjectContext project(Path root) {
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
package p.packer.services;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import p.packer.messages.InitWorkspaceRequest;
|
||||
import p.packer.messages.PackerProjectContext;
|
||||
import p.packer.models.PackerRegistryEntry;
|
||||
import p.packer.models.PackerRegistryState;
|
||||
import p.packer.repositories.FileSystemPackerRegistryRepository;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@ -18,7 +20,8 @@ final class PackerWorkspaceFoundationTest {
|
||||
|
||||
@Test
|
||||
void initWorkspaceCreatesAssetsControlStructureAndRegistry() throws Exception {
|
||||
final PackerWorkspaceFoundation foundation = new PackerWorkspaceFoundation();
|
||||
final var mapper = new ObjectMapper();
|
||||
final PackerWorkspaceFoundation foundation = new PackerWorkspaceFoundation(mapper);
|
||||
final PackerProjectContext project = project(tempDir.resolve("main"));
|
||||
|
||||
final var result = foundation.initWorkspace(new InitWorkspaceRequest(project));
|
||||
@ -35,7 +38,8 @@ final class PackerWorkspaceFoundationTest {
|
||||
@Test
|
||||
void registryRoundTripPreservesAllocatorAndEntries() throws Exception {
|
||||
final Path projectRoot = tempDir.resolve("main");
|
||||
final PackerWorkspaceFoundation foundation = new PackerWorkspaceFoundation();
|
||||
final var mapper = new ObjectMapper();
|
||||
final PackerWorkspaceFoundation foundation = new PackerWorkspaceFoundation(mapper);
|
||||
final PackerProjectContext project = project(projectRoot);
|
||||
foundation.initWorkspace(new InitWorkspaceRequest(project));
|
||||
|
||||
@ -58,7 +62,8 @@ final class PackerWorkspaceFoundationTest {
|
||||
@Test
|
||||
void allocatorIsMonotonicAndPersistedAcrossSaveLoad() throws Exception {
|
||||
final Path projectRoot = tempDir.resolve("main");
|
||||
final PackerWorkspaceFoundation foundation = new PackerWorkspaceFoundation();
|
||||
final var mapper = new ObjectMapper();
|
||||
final PackerWorkspaceFoundation foundation = new PackerWorkspaceFoundation(mapper);
|
||||
final PackerProjectContext project = project(projectRoot);
|
||||
foundation.initWorkspace(new InitWorkspaceRequest(project));
|
||||
Files.createDirectories(projectRoot.resolve("assets/ui/atlas"));
|
||||
@ -89,7 +94,8 @@ final class PackerWorkspaceFoundationTest {
|
||||
]
|
||||
}
|
||||
""");
|
||||
final FileSystemPackerRegistryRepository repository = new FileSystemPackerRegistryRepository();
|
||||
final var mapper = new ObjectMapper();
|
||||
final FileSystemPackerRegistryRepository repository = new FileSystemPackerRegistryRepository(mapper);
|
||||
|
||||
final p.packer.exceptions.PackerRegistryException exception = assertThrows(p.packer.exceptions.PackerRegistryException.class, () -> repository.load(project(projectRoot)));
|
||||
|
||||
@ -101,7 +107,8 @@ final class PackerWorkspaceFoundationTest {
|
||||
final Path projectRoot = tempDir.resolve("main");
|
||||
Files.createDirectories(projectRoot.resolve("assets/.prometeu"));
|
||||
Files.writeString(projectRoot.resolve("assets/.prometeu/index.json"), "{ nope ");
|
||||
final FileSystemPackerRegistryRepository repository = new FileSystemPackerRegistryRepository();
|
||||
final var mapper = new ObjectMapper();
|
||||
final FileSystemPackerRegistryRepository repository = new FileSystemPackerRegistryRepository(mapper);
|
||||
|
||||
final p.packer.exceptions.PackerRegistryException exception = assertThrows(p.packer.exceptions.PackerRegistryException.class, () -> repository.load(project(projectRoot)));
|
||||
|
||||
@ -119,7 +126,8 @@ final class PackerWorkspaceFoundationTest {
|
||||
"assets": []
|
||||
}
|
||||
""");
|
||||
final FileSystemPackerRegistryRepository repository = new FileSystemPackerRegistryRepository();
|
||||
final var mapper = new ObjectMapper();
|
||||
final FileSystemPackerRegistryRepository repository = new FileSystemPackerRegistryRepository(mapper);
|
||||
|
||||
final p.packer.exceptions.PackerRegistryException exception = assertThrows(p.packer.exceptions.PackerRegistryException.class, () -> repository.load(project(projectRoot)));
|
||||
|
||||
@ -139,7 +147,8 @@ final class PackerWorkspaceFoundationTest {
|
||||
]
|
||||
}
|
||||
""");
|
||||
final FileSystemPackerRegistryRepository repository = new FileSystemPackerRegistryRepository();
|
||||
final var mapper = new ObjectMapper();
|
||||
final FileSystemPackerRegistryRepository repository = new FileSystemPackerRegistryRepository(mapper);
|
||||
|
||||
final p.packer.exceptions.PackerRegistryException exception = assertThrows(p.packer.exceptions.PackerRegistryException.class, () -> repository.load(project(projectRoot)));
|
||||
|
||||
@ -149,7 +158,8 @@ final class PackerWorkspaceFoundationTest {
|
||||
@Test
|
||||
void lookupResolvesByIdUuidAndRootAndFailsOnMissingRoot() throws Exception {
|
||||
final Path projectRoot = tempDir.resolve("main");
|
||||
final PackerWorkspaceFoundation foundation = new PackerWorkspaceFoundation();
|
||||
final var mapper = new ObjectMapper();
|
||||
final PackerWorkspaceFoundation foundation = new PackerWorkspaceFoundation(mapper);
|
||||
final PackerProjectContext project = project(projectRoot);
|
||||
foundation.initWorkspace(new InitWorkspaceRequest(project));
|
||||
Files.createDirectories(projectRoot.resolve("assets/ui/atlas"));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@
|
||||
"codec" : "NONE",
|
||||
"codec_configuration" : { },
|
||||
"metadata" : {
|
||||
"tile_size" : "16"
|
||||
"tile_size" : "16x16"
|
||||
}
|
||||
},
|
||||
"preload" : {
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"convertedRgb565" : [ 65414 ],
|
||||
"originalArgb8888" : [ -265674 ]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 137 B After Width: | Height: | Size: 137 B |
@ -0,0 +1,5 @@
|
||||
{
|
||||
"height" : 16,
|
||||
"width" : 16,
|
||||
"paletteIndices" : [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 ]
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
"asset_uuid": "21953cb8-4101-4790-9e5e-d95f5fbc9b5a",
|
||||
"name": "ui_atlas",
|
||||
"type": "tile_bank",
|
||||
"inputs": { "sprites": ["sprites/confirm.png"] },
|
||||
"inputs": { },
|
||||
"output": { "format": "TILES/indexed_v1", "codec": "NONE" },
|
||||
"preload": { "enabled": true }
|
||||
}
|
||||
|
||||
4
test-projects/main/assets/ui/atlas2/confirm.palette.json
Normal file
4
test-projects/main/assets/ui/atlas2/confirm.palette.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"convertedRgb565" : [ 65414 ],
|
||||
"originalArgb8888" : [ -265674 ]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 137 B After Width: | Height: | Size: 137 B |
5
test-projects/main/assets/ui/atlas2/confirm.tile.json
Normal file
5
test-projects/main/assets/ui/atlas2/confirm.tile.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"height" : 16,
|
||||
"width" : 16,
|
||||
"paletteIndices" : [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 ]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user