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
|
## 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/).
|
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 {
|
dependencies {
|
||||||
implementation(project(":prometeu-infra"))
|
implementation(project(":prometeu-infra"))
|
||||||
implementation(project(":prometeu-packer:prometeu-packer-api"))
|
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 com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import p.packer.events.PackerEventSink;
|
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 p.packer.services.*;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
@ -23,9 +26,11 @@ public final class Packer implements Closeable {
|
|||||||
|
|
||||||
public static Packer bootstrap(final ObjectMapper mapper, final PackerEventSink eventSink) {
|
public static Packer bootstrap(final ObjectMapper mapper, final PackerEventSink eventSink) {
|
||||||
final var resolvedEventSink = Objects.requireNonNull(eventSink, "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 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 assetReferenceResolver = new PackerAssetReferenceResolver(workspaceFoundation.lookup());
|
||||||
final var assetDetailsService = new PackerAssetDetailsService(runtimeRegistry, assetReferenceResolver);
|
final var assetDetailsService = new PackerAssetDetailsService(runtimeRegistry, assetReferenceResolver);
|
||||||
final var assetActionReadService = new PackerAssetActionReadService(
|
final var assetActionReadService = new PackerAssetActionReadService(
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package p.packer.services;
|
package p.packer;
|
||||||
|
|
||||||
import p.packer.messages.PackerProjectContext;
|
import p.packer.messages.PackerProjectContext;
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ public final class PackerWorkspacePaths {
|
|||||||
private static final String ASSETS_DIR = "assets";
|
private static final String ASSETS_DIR = "assets";
|
||||||
private static final String PROMETEU_DIR = ".prometeu";
|
private static final String PROMETEU_DIR = ".prometeu";
|
||||||
private static final String REGISTRY_FILE = "index.json";
|
private static final String REGISTRY_FILE = "index.json";
|
||||||
|
private static final String CACHE_FILE = "cache.json";
|
||||||
|
|
||||||
private PackerWorkspacePaths() {
|
private PackerWorkspacePaths() {
|
||||||
}
|
}
|
||||||
@ -25,6 +26,10 @@ public final class PackerWorkspacePaths {
|
|||||||
return registryDirectory(project).resolve(REGISTRY_FILE).toAbsolutePath().normalize();
|
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) {
|
public static Path assetRoot(PackerProjectContext project, String relativeRoot) {
|
||||||
return assetsRoot(project).resolve(relativeRoot).toAbsolutePath().normalize();
|
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.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
@ -7,6 +7,7 @@ import p.packer.exceptions.PackerRegistryException;
|
|||||||
import p.packer.messages.PackerProjectContext;
|
import p.packer.messages.PackerProjectContext;
|
||||||
import p.packer.models.PackerRegistryEntry;
|
import p.packer.models.PackerRegistryEntry;
|
||||||
import p.packer.models.PackerRegistryState;
|
import p.packer.models.PackerRegistryState;
|
||||||
|
import p.packer.PackerWorkspacePaths;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -14,9 +15,14 @@ import java.nio.file.Path;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public final class FileSystemPackerRegistryRepository implements PackerRegistryRepository {
|
public final class FileSystemPackerRegistryRepository implements PackerRegistryRepository {
|
||||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
|
||||||
private static final int REGISTRY_SCHEMA_VERSION = 1;
|
private static final int REGISTRY_SCHEMA_VERSION = 1;
|
||||||
|
|
||||||
|
private final ObjectMapper mapper;
|
||||||
|
|
||||||
|
public FileSystemPackerRegistryRepository(final ObjectMapper mapper) {
|
||||||
|
this.mapper = Objects.requireNonNull(mapper, "mapper");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PackerRegistryState load(PackerProjectContext project) {
|
public PackerRegistryState load(PackerProjectContext project) {
|
||||||
final Path registryPath = PackerWorkspacePaths.registryPath(project);
|
final Path registryPath = PackerWorkspacePaths.registryPath(project);
|
||||||
@ -25,10 +31,10 @@ public final class FileSystemPackerRegistryRepository implements PackerRegistryR
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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;
|
final int schemaVersion = document.schemaVersion <= 0 ? REGISTRY_SCHEMA_VERSION : document.schemaVersion;
|
||||||
if (schemaVersion != REGISTRY_SCHEMA_VERSION) {
|
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<>();
|
final List<PackerRegistryEntry> entries = new ArrayList<>();
|
||||||
if (document.assets != null) {
|
if (document.assets != null) {
|
||||||
@ -73,7 +79,7 @@ public final class FileSystemPackerRegistryRepository implements PackerRegistryR
|
|||||||
entry.root(),
|
entry.root(),
|
||||||
entry.includedInBuild()))
|
entry.includedInBuild()))
|
||||||
.toList();
|
.toList();
|
||||||
MAPPER.writerWithDefaultPrettyPrinter().writeValue(registryPath.toFile(), document);
|
mapper.writerWithDefaultPrettyPrinter().writeValue(registryPath.toFile(), document);
|
||||||
} catch (IOException exception) {
|
} catch (IOException exception) {
|
||||||
throw new p.packer.exceptions.PackerRegistryException("Unable to save registry: " + registryPath, 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.messages.PackerProjectContext;
|
||||||
import p.packer.models.PackerRegistryState;
|
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.InitWorkspaceRequest;
|
||||||
import p.packer.messages.PackerProjectContext;
|
import p.packer.messages.PackerProjectContext;
|
||||||
import p.packer.models.PackerRegistryEntry;
|
|
||||||
import p.packer.models.PackerRegistryState;
|
|
||||||
import p.packer.models.PackerRuntimeAsset;
|
import p.packer.models.PackerRuntimeAsset;
|
||||||
import p.packer.models.PackerRuntimeSnapshot;
|
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.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -17,12 +18,15 @@ import java.util.stream.Collectors;
|
|||||||
public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader {
|
public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader {
|
||||||
private final PackerWorkspaceFoundation workspaceFoundation;
|
private final PackerWorkspaceFoundation workspaceFoundation;
|
||||||
private final PackerAssetDeclarationParser parser;
|
private final PackerAssetDeclarationParser parser;
|
||||||
|
private final PackerAssetWalker assetWalker;
|
||||||
|
|
||||||
public PackerRuntimeLoader(
|
public PackerRuntimeLoader(
|
||||||
PackerWorkspaceFoundation workspaceFoundation,
|
final PackerWorkspaceFoundation workspaceFoundation,
|
||||||
PackerAssetDeclarationParser parser) {
|
final PackerAssetDeclarationParser parser,
|
||||||
|
final PackerAssetWalker assetWalker) {
|
||||||
this.workspaceFoundation = Objects.requireNonNull(workspaceFoundation, "workspaceFoundation");
|
this.workspaceFoundation = Objects.requireNonNull(workspaceFoundation, "workspaceFoundation");
|
||||||
this.parser = Objects.requireNonNull(parser, "parser");
|
this.parser = Objects.requireNonNull(parser, "parser");
|
||||||
|
this.assetWalker = Objects.requireNonNull(assetWalker, "assetWalker");
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAssetJson(Path path, BasicFileAttributes attrs) {
|
private boolean isAssetJson(Path path, BasicFileAttributes attrs) {
|
||||||
@ -31,11 +35,13 @@ public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PackerRuntimeSnapshot load(PackerProjectContext project, long generation) {
|
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));
|
workspaceFoundation.initWorkspace(new InitWorkspaceRequest(safeProject));
|
||||||
|
|
||||||
final PackerRegistryState registry = workspaceFoundation.loadRegistry(safeProject);
|
final var registry = workspaceFoundation.loadRegistry(safeProject);
|
||||||
final Map<Path, PackerRegistryEntry> registryByRoot = registry.assets().stream()
|
final var registryByRoot = registry
|
||||||
|
.assets()
|
||||||
|
.stream()
|
||||||
.collect(Collectors.toMap(
|
.collect(Collectors.toMap(
|
||||||
entry -> PackerWorkspacePaths.assetRoot(safeProject, entry.root()),
|
entry -> PackerWorkspacePaths.assetRoot(safeProject, entry.root()),
|
||||||
entry -> entry));
|
entry -> entry));
|
||||||
@ -51,11 +57,13 @@ public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader {
|
|||||||
for (final var manifestPath : manifests) {
|
for (final var manifestPath : manifests) {
|
||||||
final var assetRoot = manifestPath.getParent();
|
final var assetRoot = manifestPath.getParent();
|
||||||
final var registryEntry = Optional.ofNullable(registryByRoot.get(assetRoot));
|
final var registryEntry = Optional.ofNullable(registryByRoot.get(assetRoot));
|
||||||
assets.add(new PackerRuntimeAsset(
|
final var parseResult = parser.parse(manifestPath);
|
||||||
assetRoot,
|
if (parseResult.valid()) {
|
||||||
manifestPath,
|
final var walkResult = assetWalker.walk(assetRoot, parseResult.declaration());
|
||||||
registryEntry,
|
|
||||||
parser.parse(manifestPath)));
|
}
|
||||||
|
final var runtimeAsset = new PackerRuntimeAsset(assetRoot, manifestPath, registryEntry, parseResult);
|
||||||
|
assets.add(runtimeAsset);
|
||||||
}
|
}
|
||||||
} catch (IOException exception) {
|
} catch (IOException exception) {
|
||||||
throw new p.packer.exceptions.PackerRegistryException(
|
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.messages.PackerProjectContext;
|
||||||
import p.packer.models.PackerRuntimeSnapshot;
|
import p.packer.models.PackerRuntimeSnapshot;
|
||||||
|
import p.packer.services.PackerProjectRuntime;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package p.packer.services;
|
package p.packer.repositories;
|
||||||
|
|
||||||
import p.packer.messages.PackerProjectContext;
|
import p.packer.messages.PackerProjectContext;
|
||||||
import p.packer.models.PackerRuntimeSnapshot;
|
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.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import p.packer.PackerWorkspacePaths;
|
||||||
import p.packer.PackerWorkspaceService;
|
import p.packer.PackerWorkspaceService;
|
||||||
import p.packer.events.PackerEventKind;
|
import p.packer.events.PackerEventKind;
|
||||||
import p.packer.events.PackerEventSink;
|
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.PackerDiagnosticCategory;
|
||||||
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
||||||
import p.packer.models.*;
|
import p.packer.models.*;
|
||||||
|
import p.packer.repositories.PackerRuntimeRegistry;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -57,14 +59,14 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListAssetsResult listAssets(ListAssetsRequest request) {
|
public ListAssetsResult listAssets(ListAssetsRequest request) {
|
||||||
final PackerProjectContext project = Objects.requireNonNull(request, "request").project();
|
final var project = Objects.requireNonNull(request, "request").project();
|
||||||
final PackerRuntimeSnapshot snapshot = request.deepSync()
|
final var snapshot = request.deepSync()
|
||||||
? runtimeRegistry.refresh(project).snapshot()
|
? runtimeRegistry.refresh(project).snapshot()
|
||||||
: runtimeRegistry.getOrLoad(project).snapshot();
|
: runtimeRegistry.getOrLoad(project).snapshot();
|
||||||
final PackerOperationEventEmitter events = new PackerOperationEventEmitter(project, eventSink);
|
final var events = new PackerOperationEventEmitter(project, eventSink);
|
||||||
final PackerRegistryState registry = snapshot.registry();
|
final var registry = snapshot.registry();
|
||||||
final Map<Path, PackerRegistryEntry> registryByRoot = new HashMap<>();
|
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);
|
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 List<PackerRuntimeAsset> runtimeAssets = snapshot.assets();
|
||||||
final int total = runtimeAssets.size();
|
final int total = runtimeAssets.size();
|
||||||
for (int index = 0; index < runtimeAssets.size(); index += 1) {
|
for (int index = 0; index < runtimeAssets.size(); index += 1) {
|
||||||
final PackerRuntimeAsset runtimeAsset = runtimeAssets.get(index);
|
final var runtimeAsset = runtimeAssets.get(index);
|
||||||
final Path assetRoot = runtimeAsset.assetRoot();
|
final var assetRoot = runtimeAsset.assetRoot();
|
||||||
final Path assetManifestPath = runtimeAsset.manifestPath();
|
final var assetManifestPath = runtimeAsset.manifestPath();
|
||||||
discoveredRoots.add(assetRoot);
|
discoveredRoots.add(assetRoot);
|
||||||
final PackerRegistryEntry registryEntry = registryByRoot.get(assetRoot);
|
final var registryEntry = registryByRoot.get(assetRoot);
|
||||||
final PackerAssetDeclarationParseResult parsed = runtimeAsset.parsedDeclaration();
|
final var parsed = runtimeAsset.parsedDeclaration();
|
||||||
diagnostics.addAll(parsed.diagnostics());
|
diagnostics.addAll(parsed.diagnostics());
|
||||||
diagnostics.addAll(identityMismatchDiagnostics(registryEntry, parsed, assetManifestPath));
|
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);
|
assets.add(summary);
|
||||||
events.emit(
|
events.emit(
|
||||||
PackerEventKind.ASSET_DISCOVERED,
|
PackerEventKind.ASSET_DISCOVERED,
|
||||||
@ -92,8 +94,8 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
List.of(summary.identity().assetName()));
|
List.of(summary.identity().assetName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (PackerRegistryEntry entry : registry.assets()) {
|
for (final var entry : registry.assets()) {
|
||||||
final Path registeredRoot = PackerWorkspacePaths.assetRoot(project, entry.root());
|
final var registeredRoot = PackerWorkspacePaths.assetRoot(project, entry.root());
|
||||||
if (!discoveredRoots.contains(registeredRoot)) {
|
if (!discoveredRoots.contains(registeredRoot)) {
|
||||||
diagnostics.add(new PackerDiagnostic(
|
diagnostics.add(new PackerDiagnostic(
|
||||||
PackerDiagnosticSeverity.ERROR,
|
PackerDiagnosticSeverity.ERROR,
|
||||||
@ -107,7 +109,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
assets.sort(Comparator
|
assets.sort(Comparator
|
||||||
.comparing((PackerAssetSummary asset) -> asset.identity().assetRoot().toString(), String.CASE_INSENSITIVE_ORDER)
|
.comparing((PackerAssetSummary asset) -> asset.identity().assetRoot().toString(), String.CASE_INSENSITIVE_ORDER)
|
||||||
.thenComparing(summary -> summary.identity().assetName(), 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.PARTIAL
|
||||||
: PackerOperationStatus.SUCCESS;
|
: PackerOperationStatus.SUCCESS;
|
||||||
if (!diagnostics.isEmpty()) {
|
if (!diagnostics.isEmpty()) {
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
package p.packer.services;
|
package p.packer.services;
|
||||||
|
|
||||||
|
import p.packer.PackerWorkspacePaths;
|
||||||
import p.packer.messages.*;
|
import p.packer.messages.*;
|
||||||
import p.packer.messages.assets.AssetAction;
|
import p.packer.messages.assets.AssetAction;
|
||||||
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
||||||
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
||||||
import p.packer.models.*;
|
import p.packer.models.*;
|
||||||
|
import p.packer.repositories.PackerRuntimeRegistry;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import java.nio.file.Path;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public final class PackerAssetDeclarationParser {
|
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;
|
private final ObjectMapper mapper;
|
||||||
|
|
||||||
@ -40,17 +40,17 @@ public final class PackerAssetDeclarationParser {
|
|||||||
return new PackerAssetDeclarationParseResult(null, diagnostics);
|
return new PackerAssetDeclarationParseResult(null, diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Integer schemaVersion = requiredInt(root, "schema_version", diagnostics, manifestPath);
|
final var schemaVersion = requiredInt(root, "schema_version", diagnostics, manifestPath);
|
||||||
final String assetUuid = requiredText(root, "asset_uuid", diagnostics, manifestPath);
|
final var assetUuid = requiredText(root, "asset_uuid", diagnostics, manifestPath);
|
||||||
final String name = requiredText(root, "name", diagnostics, manifestPath);
|
final var name = requiredText(root, "name", diagnostics, manifestPath);
|
||||||
final AssetFamilyCatalog assetFamily = requiredAssetFamily(root, diagnostics, manifestPath);
|
final var assetFamily = requiredAssetFamily(root, diagnostics, manifestPath);
|
||||||
final Map<String, List<String>> inputsByRole = requiredInputs(root.path("inputs"), diagnostics, manifestPath);
|
final var inputsByRole = requiredInputs(root.path("inputs"), diagnostics, manifestPath);
|
||||||
final OutputFormatCatalog outputFormat = requiredOutputFormat(root.path("output"), diagnostics, manifestPath);
|
final var outputFormat = requiredOutputFormat(root.path("output"), diagnostics, manifestPath);
|
||||||
final OutputCodecCatalog outputCodec = requiredOutputCodec(root.path("output"), diagnostics, manifestPath);
|
final var outputCodec = requiredOutputCodec(root.path("output"), diagnostics, manifestPath);
|
||||||
final Map<String, String> outputMetadata = optionalOutputMetadata(root.path("output"), diagnostics, manifestPath);
|
final var outputMetadata = optionalOutputMetadata(root.path("output"), diagnostics, manifestPath);
|
||||||
final Boolean preloadEnabled = requiredBoolean(root.path("preload"), "enabled", 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(
|
diagnostics.add(new PackerDiagnostic(
|
||||||
PackerDiagnosticSeverity.ERROR,
|
PackerDiagnosticSeverity.ERROR,
|
||||||
PackerDiagnosticCategory.VERSIONING,
|
PackerDiagnosticCategory.VERSIONING,
|
||||||
@ -77,7 +77,11 @@ public final class PackerAssetDeclarationParser {
|
|||||||
diagnostics);
|
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);
|
final JsonNode field = node.path(fieldName);
|
||||||
if (!field.isInt()) {
|
if (!field.isInt()) {
|
||||||
diagnostics.add(missingOrInvalid(fieldName, "integer", manifestPath));
|
diagnostics.add(missingOrInvalid(fieldName, "integer", manifestPath));
|
||||||
@ -86,7 +90,11 @@ public final class PackerAssetDeclarationParser {
|
|||||||
return field.intValue();
|
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);
|
final JsonNode field = node.path(fieldName);
|
||||||
if (!field.isTextual() || field.asText().isBlank()) {
|
if (!field.isTextual() || field.asText().isBlank()) {
|
||||||
diagnostics.add(missingOrInvalid(fieldName, "non-blank string", manifestPath));
|
diagnostics.add(missingOrInvalid(fieldName, "non-blank string", manifestPath));
|
||||||
@ -95,7 +103,10 @@ public final class PackerAssetDeclarationParser {
|
|||||||
return field.asText().trim();
|
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);
|
final String manifestType = requiredText(node, "type", diagnostics, manifestPath);
|
||||||
if (manifestType == null) {
|
if (manifestType == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -113,7 +124,11 @@ public final class PackerAssetDeclarationParser {
|
|||||||
return assetFamily;
|
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);
|
final JsonNode field = node.path(fieldName);
|
||||||
if (!field.isBoolean()) {
|
if (!field.isBoolean()) {
|
||||||
diagnostics.add(missingOrInvalid(fieldName, "boolean", manifestPath));
|
diagnostics.add(missingOrInvalid(fieldName, "boolean", manifestPath));
|
||||||
@ -122,7 +137,10 @@ public final class PackerAssetDeclarationParser {
|
|||||||
return field.booleanValue();
|
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);
|
final String fmtValue = requiredText(node, "format", diagnostics, manifestPath);
|
||||||
if (fmtValue == null) {
|
if (fmtValue == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -140,7 +158,10 @@ public final class PackerAssetDeclarationParser {
|
|||||||
return outputFormat;
|
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);
|
final String codecValue = requiredText(node, "codec", diagnostics, manifestPath);
|
||||||
if (codecValue == null) {
|
if (codecValue == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -159,10 +180,10 @@ public final class PackerAssetDeclarationParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, String> optionalOutputMetadata(
|
private Map<String, String> optionalOutputMetadata(
|
||||||
JsonNode outputNode,
|
final JsonNode node,
|
||||||
List<PackerDiagnostic> diagnostics,
|
final List<PackerDiagnostic> diagnostics,
|
||||||
Path manifestPath) {
|
final Path manifestPath) {
|
||||||
final JsonNode metadataNode = outputNode.path("metadata");
|
final JsonNode metadataNode = node.path("metadata");
|
||||||
if (metadataNode.isMissingNode() || metadataNode.isNull()) {
|
if (metadataNode.isMissingNode() || metadataNode.isNull()) {
|
||||||
return Map.of();
|
return Map.of();
|
||||||
}
|
}
|
||||||
@ -205,14 +226,17 @@ public final class PackerAssetDeclarationParser {
|
|||||||
return Map.copyOf(metadata);
|
return Map.copyOf(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, List<String>> requiredInputs(JsonNode inputsNode, List<PackerDiagnostic> diagnostics, Path manifestPath) {
|
private Map<String, List<String>> requiredInputs(
|
||||||
if (!inputsNode.isObject()) {
|
final JsonNode node,
|
||||||
|
final List<PackerDiagnostic> diagnostics,
|
||||||
|
final Path manifestPath) {
|
||||||
|
if (!node.isObject()) {
|
||||||
diagnostics.add(missingOrInvalid("inputs", "object of input roles", manifestPath));
|
diagnostics.add(missingOrInvalid("inputs", "object of input roles", manifestPath));
|
||||||
return Map.of();
|
return Map.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, List<String>> result = new LinkedHashMap<>();
|
final Map<String, List<String>> result = new LinkedHashMap<>();
|
||||||
inputsNode.fields().forEachRemaining(entry -> {
|
node.fields().forEachRemaining(entry -> {
|
||||||
if (!entry.getValue().isArray()) {
|
if (!entry.getValue().isArray()) {
|
||||||
diagnostics.add(new PackerDiagnostic(
|
diagnostics.add(new PackerDiagnostic(
|
||||||
PackerDiagnosticSeverity.ERROR,
|
PackerDiagnosticSeverity.ERROR,
|
||||||
@ -250,12 +274,15 @@ public final class PackerAssetDeclarationParser {
|
|||||||
return Map.copyOf(result);
|
return Map.copyOf(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isTrustedRelativePath(String value) {
|
private boolean isTrustedRelativePath(final String value) {
|
||||||
final Path path = Path.of(value).normalize();
|
final Path path = Path.of(value).normalize();
|
||||||
return !path.isAbsolute() && !path.startsWith("..");
|
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(
|
return new PackerDiagnostic(
|
||||||
PackerDiagnosticSeverity.ERROR,
|
PackerDiagnosticSeverity.ERROR,
|
||||||
PackerDiagnosticCategory.STRUCTURAL,
|
PackerDiagnosticCategory.STRUCTURAL,
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
package p.packer.services;
|
package p.packer.services;
|
||||||
|
|
||||||
|
import p.packer.PackerWorkspacePaths;
|
||||||
import p.packer.messages.*;
|
import p.packer.messages.*;
|
||||||
import p.packer.messages.assets.*;
|
import p.packer.messages.assets.*;
|
||||||
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
||||||
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;
|
||||||
import p.packer.models.*;
|
import p.packer.models.*;
|
||||||
|
import p.packer.repositories.PackerRuntimeRegistry;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package p.packer.services;
|
package p.packer.services;
|
||||||
|
|
||||||
|
import p.packer.PackerWorkspacePaths;
|
||||||
import p.packer.messages.AssetReference;
|
import p.packer.messages.AssetReference;
|
||||||
import p.packer.messages.PackerProjectContext;
|
import p.packer.messages.PackerProjectContext;
|
||||||
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package p.packer.services;
|
package p.packer.services;
|
||||||
|
|
||||||
|
import p.packer.PackerWorkspacePaths;
|
||||||
import p.packer.messages.PackerProjectContext;
|
import p.packer.messages.PackerProjectContext;
|
||||||
import p.packer.models.PackerRegistryEntry;
|
import p.packer.models.PackerRegistryEntry;
|
||||||
import p.packer.models.PackerRegistryState;
|
import p.packer.models.PackerRegistryState;
|
||||||
|
|||||||
@ -58,13 +58,13 @@ final class PackerOutputContractCatalog {
|
|||||||
"tile_size",
|
"tile_size",
|
||||||
"TileSize",
|
"TileSize",
|
||||||
PackerCodecConfigurationFieldType.ENUM,
|
PackerCodecConfigurationFieldType.ENUM,
|
||||||
"16",
|
"16x16",
|
||||||
true,
|
true,
|
||||||
List.of("8", "16", "32")));
|
List.of("8x8", "16x16", "32x32")));
|
||||||
case SOUND_V1 -> List.of(
|
case SOUND_V1 -> List.of(
|
||||||
new PackerCodecConfigurationField(
|
new PackerCodecConfigurationField(
|
||||||
"sample_rate",
|
"sample_rate",
|
||||||
"Frame Rate",
|
"Sample Rate",
|
||||||
PackerCodecConfigurationFieldType.ENUM,
|
PackerCodecConfigurationFieldType.ENUM,
|
||||||
"44100",
|
"44100",
|
||||||
true,
|
true,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package p.packer.services;
|
package p.packer.services;
|
||||||
|
|
||||||
|
import p.packer.PackerWorkspacePaths;
|
||||||
import p.packer.messages.PackerProjectContext;
|
import p.packer.messages.PackerProjectContext;
|
||||||
import p.packer.models.PackerRegistryEntry;
|
import p.packer.models.PackerRegistryEntry;
|
||||||
import p.packer.models.PackerRegistryState;
|
import p.packer.models.PackerRegistryState;
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
package p.packer.services;
|
package p.packer.services;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import p.packer.PackerWorkspacePaths;
|
||||||
import p.packer.messages.InitWorkspaceRequest;
|
import p.packer.messages.InitWorkspaceRequest;
|
||||||
import p.packer.messages.InitWorkspaceResult;
|
import p.packer.messages.InitWorkspaceResult;
|
||||||
import p.packer.messages.PackerOperationStatus;
|
import p.packer.messages.PackerOperationStatus;
|
||||||
import p.packer.messages.PackerProjectContext;
|
import p.packer.messages.PackerProjectContext;
|
||||||
import p.packer.models.PackerRegistryEntry;
|
import p.packer.models.PackerRegistryEntry;
|
||||||
import p.packer.models.PackerRegistryState;
|
import p.packer.models.PackerRegistryState;
|
||||||
|
import p.packer.repositories.FileSystemPackerRegistryRepository;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -17,8 +20,8 @@ public final class PackerWorkspaceFoundation {
|
|||||||
private final PackerIdentityAllocator identityAllocator;
|
private final PackerIdentityAllocator identityAllocator;
|
||||||
private final PackerRegistryLookup registryLookup;
|
private final PackerRegistryLookup registryLookup;
|
||||||
|
|
||||||
public PackerWorkspaceFoundation() {
|
public PackerWorkspaceFoundation(final ObjectMapper mapper) {
|
||||||
this(new FileSystemPackerRegistryRepository(), new PackerIdentityAllocator(), new PackerRegistryLookup());
|
this(new FileSystemPackerRegistryRepository(mapper), new PackerIdentityAllocator(), new PackerRegistryLookup());
|
||||||
}
|
}
|
||||||
|
|
||||||
public PackerWorkspaceFoundation(
|
public PackerWorkspaceFoundation(
|
||||||
|
|||||||
@ -7,6 +7,10 @@ import p.packer.events.PackerEvent;
|
|||||||
import p.packer.events.PackerEventKind;
|
import p.packer.events.PackerEventKind;
|
||||||
import p.packer.messages.*;
|
import p.packer.messages.*;
|
||||||
import p.packer.messages.assets.*;
|
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 p.packer.testing.PackerFixtureLocator;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -193,7 +197,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
"NONE:packMode", "tight",
|
"NONE:packMode", "tight",
|
||||||
"NONE:palette", "mono"),
|
"NONE:palette", "mono"),
|
||||||
Map.of(
|
Map.of(
|
||||||
"tile_size", "16",
|
"tile_size", "16x16",
|
||||||
"channels", "1")));
|
"channels", "1")));
|
||||||
|
|
||||||
assertTrue(result.success());
|
assertTrue(result.success());
|
||||||
@ -205,7 +209,7 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
assertEquals("NONE", manifest.path("output").path("codec").asText());
|
assertEquals("NONE", manifest.path("output").path("codec").asText());
|
||||||
assertEquals("tight", manifest.path("output").path("codec_configuration").path("packMode").asText());
|
assertEquals("tight", manifest.path("output").path("codec_configuration").path("packMode").asText());
|
||||||
assertEquals("mono", manifest.path("output").path("codec_configuration").path("palette").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());
|
assertEquals("1", manifest.path("output").path("metadata").path("channels").asText());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,9 +676,10 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
private FileSystemPackerWorkspaceService service(
|
private FileSystemPackerWorkspaceService service(
|
||||||
p.packer.events.PackerEventSink eventSink,
|
p.packer.events.PackerEventSink eventSink,
|
||||||
PackerRuntimeSnapshotLoader loader) {
|
PackerRuntimeSnapshotLoader loader) {
|
||||||
final var foundation = new p.packer.services.PackerWorkspaceFoundation();
|
final var mapper = new ObjectMapper();
|
||||||
final var parser = new p.packer.services.PackerAssetDeclarationParser(new ObjectMapper());
|
final var foundation = new p.packer.services.PackerWorkspaceFoundation(mapper);
|
||||||
final var runtimeRegistry = new p.packer.services.PackerRuntimeRegistry(loader);
|
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 resolver = new p.packer.services.PackerAssetReferenceResolver(foundation.lookup());
|
||||||
final var detailsService = new p.packer.services.PackerAssetDetailsService(runtimeRegistry, resolver);
|
final var detailsService = new p.packer.services.PackerAssetDetailsService(runtimeRegistry, resolver);
|
||||||
final var actionReadService = new p.packer.services.PackerAssetActionReadService(runtimeRegistry, resolver, foundation.lookup());
|
final var actionReadService = new p.packer.services.PackerAssetActionReadService(runtimeRegistry, resolver, foundation.lookup());
|
||||||
@ -692,9 +697,11 @@ final class FileSystemPackerWorkspaceServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CountingLoader countingLoader() {
|
private CountingLoader countingLoader() {
|
||||||
final var foundation = new p.packer.services.PackerWorkspaceFoundation();
|
final var mapper = new ObjectMapper();
|
||||||
final var parser = new p.packer.services.PackerAssetDeclarationParser(new ObjectMapper());
|
final var foundation = new p.packer.services.PackerWorkspaceFoundation(mapper);
|
||||||
return new CountingLoader(new p.packer.services.PackerRuntimeLoader(foundation, parser));
|
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 {
|
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.OutputCodecCatalog;
|
||||||
import p.packer.messages.assets.PackerAssetState;
|
import p.packer.messages.assets.PackerAssetState;
|
||||||
import p.packer.messages.assets.PackerBuildParticipation;
|
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 p.packer.testing.PackerFixtureLocator;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -68,7 +71,7 @@ final class PackerAssetDetailsServiceTest {
|
|||||||
final var manifest = mapper.readTree(manifestPath.toFile());
|
final var manifest = mapper.readTree(manifestPath.toFile());
|
||||||
final ObjectNode output = (ObjectNode) manifest.path("output");
|
final ObjectNode output = (ObjectNode) manifest.path("output");
|
||||||
final ObjectNode metadata = output.putObject("metadata");
|
final ObjectNode metadata = output.putObject("metadata");
|
||||||
metadata.put("tile_size", "16");
|
metadata.put("tile_size", "16x16");
|
||||||
metadata.put("channels", "1");
|
metadata.put("channels", "1");
|
||||||
mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
|
mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
|
||||||
|
|
||||||
@ -77,7 +80,7 @@ final class PackerAssetDetailsServiceTest {
|
|||||||
|
|
||||||
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
assertEquals(PackerOperationStatus.SUCCESS, result.status());
|
||||||
assertEquals(
|
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(
|
result.details().metadataFields().stream().collect(java.util.stream.Collectors.toMap(
|
||||||
field -> field.key(),
|
field -> field.key(),
|
||||||
field -> field.value())));
|
field -> field.value())));
|
||||||
@ -130,9 +133,11 @@ final class PackerAssetDetailsServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PackerAssetDetailsService service() {
|
private PackerAssetDetailsService service() {
|
||||||
final var foundation = new p.packer.services.PackerWorkspaceFoundation();
|
final var mapper = new ObjectMapper();
|
||||||
final var parser = new PackerAssetDeclarationParser(new ObjectMapper());
|
final var foundation = new PackerWorkspaceFoundation(mapper);
|
||||||
final var runtimeRegistry = new PackerRuntimeRegistry(new PackerRuntimeLoader(foundation, parser));
|
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());
|
final var resolver = new PackerAssetReferenceResolver(foundation.lookup());
|
||||||
return new PackerAssetDetailsService(runtimeRegistry, resolver);
|
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.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import p.packer.messages.PackerProjectContext;
|
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 p.packer.testing.PackerFixtureLocator;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -78,9 +81,11 @@ final class PackerRuntimeRegistryTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PackerRuntimeRegistry runtimeRegistry() {
|
private PackerRuntimeRegistry runtimeRegistry() {
|
||||||
final var foundation = new PackerWorkspaceFoundation();
|
final var mapper = new ObjectMapper();
|
||||||
final var parser = new PackerAssetDeclarationParser(new ObjectMapper());
|
final var foundation = new PackerWorkspaceFoundation(mapper);
|
||||||
return new PackerRuntimeRegistry(new PackerRuntimeLoader(foundation, parser));
|
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) {
|
private PackerProjectContext project(Path root) {
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
package p.packer.services;
|
package p.packer.services;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import p.packer.messages.InitWorkspaceRequest;
|
import p.packer.messages.InitWorkspaceRequest;
|
||||||
import p.packer.messages.PackerProjectContext;
|
import p.packer.messages.PackerProjectContext;
|
||||||
import p.packer.models.PackerRegistryEntry;
|
import p.packer.models.PackerRegistryEntry;
|
||||||
import p.packer.models.PackerRegistryState;
|
import p.packer.models.PackerRegistryState;
|
||||||
|
import p.packer.repositories.FileSystemPackerRegistryRepository;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -18,7 +20,8 @@ final class PackerWorkspaceFoundationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void initWorkspaceCreatesAssetsControlStructureAndRegistry() throws Exception {
|
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 PackerProjectContext project = project(tempDir.resolve("main"));
|
||||||
|
|
||||||
final var result = foundation.initWorkspace(new InitWorkspaceRequest(project));
|
final var result = foundation.initWorkspace(new InitWorkspaceRequest(project));
|
||||||
@ -35,7 +38,8 @@ final class PackerWorkspaceFoundationTest {
|
|||||||
@Test
|
@Test
|
||||||
void registryRoundTripPreservesAllocatorAndEntries() throws Exception {
|
void registryRoundTripPreservesAllocatorAndEntries() throws Exception {
|
||||||
final Path projectRoot = tempDir.resolve("main");
|
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);
|
final PackerProjectContext project = project(projectRoot);
|
||||||
foundation.initWorkspace(new InitWorkspaceRequest(project));
|
foundation.initWorkspace(new InitWorkspaceRequest(project));
|
||||||
|
|
||||||
@ -58,7 +62,8 @@ final class PackerWorkspaceFoundationTest {
|
|||||||
@Test
|
@Test
|
||||||
void allocatorIsMonotonicAndPersistedAcrossSaveLoad() throws Exception {
|
void allocatorIsMonotonicAndPersistedAcrossSaveLoad() throws Exception {
|
||||||
final Path projectRoot = tempDir.resolve("main");
|
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);
|
final PackerProjectContext project = project(projectRoot);
|
||||||
foundation.initWorkspace(new InitWorkspaceRequest(project));
|
foundation.initWorkspace(new InitWorkspaceRequest(project));
|
||||||
Files.createDirectories(projectRoot.resolve("assets/ui/atlas"));
|
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)));
|
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");
|
final Path projectRoot = tempDir.resolve("main");
|
||||||
Files.createDirectories(projectRoot.resolve("assets/.prometeu"));
|
Files.createDirectories(projectRoot.resolve("assets/.prometeu"));
|
||||||
Files.writeString(projectRoot.resolve("assets/.prometeu/index.json"), "{ nope ");
|
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)));
|
final p.packer.exceptions.PackerRegistryException exception = assertThrows(p.packer.exceptions.PackerRegistryException.class, () -> repository.load(project(projectRoot)));
|
||||||
|
|
||||||
@ -119,7 +126,8 @@ final class PackerWorkspaceFoundationTest {
|
|||||||
"assets": []
|
"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)));
|
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)));
|
final p.packer.exceptions.PackerRegistryException exception = assertThrows(p.packer.exceptions.PackerRegistryException.class, () -> repository.load(project(projectRoot)));
|
||||||
|
|
||||||
@ -149,7 +158,8 @@ final class PackerWorkspaceFoundationTest {
|
|||||||
@Test
|
@Test
|
||||||
void lookupResolvesByIdUuidAndRootAndFailsOnMissingRoot() throws Exception {
|
void lookupResolvesByIdUuidAndRootAndFailsOnMissingRoot() throws Exception {
|
||||||
final Path projectRoot = tempDir.resolve("main");
|
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);
|
final PackerProjectContext project = project(projectRoot);
|
||||||
foundation.initWorkspace(new InitWorkspaceRequest(project));
|
foundation.initWorkspace(new InitWorkspaceRequest(project));
|
||||||
Files.createDirectories(projectRoot.resolve("assets/ui/atlas"));
|
Files.createDirectories(projectRoot.resolve("assets/ui/atlas"));
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@
|
|||||||
"codec" : "NONE",
|
"codec" : "NONE",
|
||||||
"codec_configuration" : { },
|
"codec_configuration" : { },
|
||||||
"metadata" : {
|
"metadata" : {
|
||||||
"tile_size" : "16"
|
"tile_size" : "16x16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"preload" : {
|
"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",
|
"asset_uuid": "21953cb8-4101-4790-9e5e-d95f5fbc9b5a",
|
||||||
"name": "ui_atlas",
|
"name": "ui_atlas",
|
||||||
"type": "tile_bank",
|
"type": "tile_bank",
|
||||||
"inputs": { "sprites": ["sprites/confirm.png"] },
|
"inputs": { },
|
||||||
"output": { "format": "TILES/indexed_v1", "codec": "NONE" },
|
"output": { "format": "TILES/indexed_v1", "codec": "NONE" },
|
||||||
"preload": { "enabled": true }
|
"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