implements PR-35
This commit is contained in:
parent
99d1070c46
commit
fa527720a4
@ -0,0 +1,22 @@
|
|||||||
|
package p.packer.models;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public record PackerRuntimeMaterializationConfig(
|
||||||
|
PackerWalkMode mode,
|
||||||
|
Predicate<PackerProbeResult> projectionFilter) {
|
||||||
|
|
||||||
|
public PackerRuntimeMaterializationConfig {
|
||||||
|
mode = Objects.requireNonNull(mode, "mode");
|
||||||
|
projectionFilter = Objects.requireNonNull(projectionFilter, "projectionFilter");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PackerRuntimeMaterializationConfig runtimeDefault() {
|
||||||
|
return new PackerRuntimeMaterializationConfig(PackerWalkMode.RUNTIME, probe -> true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PackerRuntimeMaterializationConfig packingBuild() {
|
||||||
|
return new PackerRuntimeMaterializationConfig(PackerWalkMode.PACKING, probe -> true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public record PackerRuntimeWalkFile(
|
public record PackerRuntimeWalkFile(
|
||||||
String relativePath,
|
String relativePath,
|
||||||
@ -11,6 +12,7 @@ public record PackerRuntimeWalkFile(
|
|||||||
long size,
|
long size,
|
||||||
long lastModified,
|
long lastModified,
|
||||||
String fingerprint,
|
String fingerprint,
|
||||||
|
Optional<byte[]> contentBytes,
|
||||||
Map<String, Object> metadata,
|
Map<String, Object> metadata,
|
||||||
List<PackerDiagnostic> diagnostics) {
|
List<PackerDiagnostic> diagnostics) {
|
||||||
|
|
||||||
@ -24,6 +26,8 @@ public record PackerRuntimeWalkFile(
|
|||||||
throw new IllegalArgumentException("lastModified must be non-negative");
|
throw new IllegalArgumentException("lastModified must be non-negative");
|
||||||
}
|
}
|
||||||
fingerprint = fingerprint == null || fingerprint.isBlank() ? null : fingerprint;
|
fingerprint = fingerprint == null || fingerprint.isBlank() ? null : fingerprint;
|
||||||
|
final Optional<byte[]> safeContentBytes = Objects.requireNonNull(contentBytes, "contentBytes");
|
||||||
|
contentBytes = safeContentBytes.map(byte[]::clone);
|
||||||
metadata = Map.copyOf(new LinkedHashMap<>(Objects.requireNonNull(metadata, "metadata")));
|
metadata = Map.copyOf(new LinkedHashMap<>(Objects.requireNonNull(metadata, "metadata")));
|
||||||
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
package p.packer.models;
|
||||||
|
|
||||||
|
public enum PackerWalkMode {
|
||||||
|
RUNTIME,
|
||||||
|
PACKING
|
||||||
|
}
|
||||||
@ -20,11 +20,28 @@ public final class PackerRuntimeAssetMaterializer {
|
|||||||
Optional<PackerRegistryEntry> registryEntry,
|
Optional<PackerRegistryEntry> registryEntry,
|
||||||
PackerAssetDeclarationParseResult parseResult,
|
PackerAssetDeclarationParseResult parseResult,
|
||||||
Optional<PackerAssetCacheEntry> priorAssetCache) {
|
Optional<PackerAssetCacheEntry> priorAssetCache) {
|
||||||
|
return materialize(
|
||||||
|
assetRoot,
|
||||||
|
manifestPath,
|
||||||
|
registryEntry,
|
||||||
|
parseResult,
|
||||||
|
priorAssetCache,
|
||||||
|
PackerRuntimeMaterializationConfig.runtimeDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PackerRuntimeAssetMaterialization materialize(
|
||||||
|
Path assetRoot,
|
||||||
|
Path manifestPath,
|
||||||
|
Optional<PackerRegistryEntry> registryEntry,
|
||||||
|
PackerAssetDeclarationParseResult parseResult,
|
||||||
|
Optional<PackerAssetCacheEntry> priorAssetCache,
|
||||||
|
PackerRuntimeMaterializationConfig materializationConfig) {
|
||||||
final Path normalizedAssetRoot = Objects.requireNonNull(assetRoot, "assetRoot").toAbsolutePath().normalize();
|
final Path normalizedAssetRoot = Objects.requireNonNull(assetRoot, "assetRoot").toAbsolutePath().normalize();
|
||||||
final Path normalizedManifestPath = Objects.requireNonNull(manifestPath, "manifestPath").toAbsolutePath().normalize();
|
final Path normalizedManifestPath = Objects.requireNonNull(manifestPath, "manifestPath").toAbsolutePath().normalize();
|
||||||
final Optional<PackerRegistryEntry> safeRegistryEntry = Objects.requireNonNull(registryEntry, "registryEntry");
|
final Optional<PackerRegistryEntry> safeRegistryEntry = Objects.requireNonNull(registryEntry, "registryEntry");
|
||||||
final PackerAssetDeclarationParseResult safeParseResult = Objects.requireNonNull(parseResult, "parseResult");
|
final PackerAssetDeclarationParseResult safeParseResult = Objects.requireNonNull(parseResult, "parseResult");
|
||||||
final Optional<PackerAssetCacheEntry> safePriorAssetCache = Objects.requireNonNull(priorAssetCache, "priorAssetCache");
|
final Optional<PackerAssetCacheEntry> safePriorAssetCache = Objects.requireNonNull(priorAssetCache, "priorAssetCache");
|
||||||
|
final PackerRuntimeMaterializationConfig safeMaterializationConfig = Objects.requireNonNull(materializationConfig, "materializationConfig");
|
||||||
final String currentContractFingerprint = safeParseResult.valid()
|
final String currentContractFingerprint = safeParseResult.valid()
|
||||||
? PackerContractFingerprint.metadataFingerprint(safeParseResult.declaration().outputMetadata())
|
? PackerContractFingerprint.metadataFingerprint(safeParseResult.declaration().outputMetadata())
|
||||||
: null;
|
: null;
|
||||||
@ -47,7 +64,8 @@ public final class PackerRuntimeAssetMaterializer {
|
|||||||
safeParseResult.declaration(),
|
safeParseResult.declaration(),
|
||||||
reusablePriorAssetCache(safePriorAssetCache, currentContractFingerprint));
|
reusablePriorAssetCache(safePriorAssetCache, currentContractFingerprint));
|
||||||
final List<PackerRuntimeWalkFile> buildCandidateFiles = walkResult.probeResults().stream()
|
final List<PackerRuntimeWalkFile> buildCandidateFiles = walkResult.probeResults().stream()
|
||||||
.map(probeResult -> toRuntimeWalkFile(normalizedAssetRoot, probeResult))
|
.filter(probeResult -> shouldIncludeProbe(safeParseResult.declaration(), normalizedAssetRoot, probeResult, safeMaterializationConfig))
|
||||||
|
.map(probeResult -> toRuntimeWalkFile(normalizedAssetRoot, probeResult, safeMaterializationConfig))
|
||||||
.sorted(Comparator.comparing(PackerRuntimeWalkFile::relativePath, String.CASE_INSENSITIVE_ORDER))
|
.sorted(Comparator.comparing(PackerRuntimeWalkFile::relativePath, String.CASE_INSENSITIVE_ORDER))
|
||||||
.toList();
|
.toList();
|
||||||
final long measuredBankSizeBytes = buildCandidateFiles.stream()
|
final long measuredBankSizeBytes = buildCandidateFiles.stream()
|
||||||
@ -105,18 +123,47 @@ public final class PackerRuntimeAssetMaterializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PackerRuntimeWalkFile toRuntimeWalkFile(Path assetRoot, PackerProbeResult probeResult) {
|
private PackerRuntimeWalkFile toRuntimeWalkFile(
|
||||||
|
Path assetRoot,
|
||||||
|
PackerProbeResult probeResult,
|
||||||
|
PackerRuntimeMaterializationConfig materializationConfig) {
|
||||||
final PackerFileProbe fileProbe = probeResult.fileProbe();
|
final PackerFileProbe fileProbe = probeResult.fileProbe();
|
||||||
|
final String relativePath = assetRoot.relativize(fileProbe.path().toAbsolutePath().normalize()).toString().replace('\\', '/');
|
||||||
return new PackerRuntimeWalkFile(
|
return new PackerRuntimeWalkFile(
|
||||||
assetRoot.relativize(fileProbe.path().toAbsolutePath().normalize()).toString().replace('\\', '/'),
|
relativePath,
|
||||||
fileProbe.mimeType(),
|
fileProbe.mimeType(),
|
||||||
fileProbe.size(),
|
fileProbe.size(),
|
||||||
fileProbe.lastModified(),
|
fileProbe.lastModified(),
|
||||||
PackerFileFingerprint.sha256(fileProbe),
|
PackerFileFingerprint.sha256(fileProbe),
|
||||||
|
materializationConfig.mode() == PackerWalkMode.PACKING
|
||||||
|
? Optional.of(fileProbe.content())
|
||||||
|
: Optional.empty(),
|
||||||
probeResult.metadata(),
|
probeResult.metadata(),
|
||||||
probeResult.diagnostics());
|
probeResult.diagnostics());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldIncludeProbe(
|
||||||
|
PackerAssetDeclaration declaration,
|
||||||
|
Path assetRoot,
|
||||||
|
PackerProbeResult probeResult,
|
||||||
|
PackerRuntimeMaterializationConfig materializationConfig) {
|
||||||
|
if (!materializationConfig.projectionFilter().test(probeResult)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (materializationConfig.mode() != PackerWalkMode.PACKING) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (declaration.artifacts().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final String relativePath = assetRoot.relativize(probeResult.fileProbe().path().toAbsolutePath().normalize())
|
||||||
|
.toString()
|
||||||
|
.replace('\\', '/');
|
||||||
|
return declaration.artifacts().stream()
|
||||||
|
.map(PackerAssetArtifactSelection::file)
|
||||||
|
.anyMatch(relativePath::equals);
|
||||||
|
}
|
||||||
|
|
||||||
public record PackerRuntimeAssetMaterialization(
|
public record PackerRuntimeAssetMaterialization(
|
||||||
PackerRuntimeAsset runtimeAsset,
|
PackerRuntimeAsset runtimeAsset,
|
||||||
Optional<PackerAssetCacheEntry> assetCacheEntry) {
|
Optional<PackerAssetCacheEntry> assetCacheEntry) {
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import p.packer.messages.assets.OutputCodecCatalog;
|
|||||||
import p.packer.messages.assets.OutputFormatCatalog;
|
import p.packer.messages.assets.OutputFormatCatalog;
|
||||||
import p.packer.models.*;
|
import p.packer.models.*;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -68,20 +70,102 @@ final class PackerRuntimeAssetMaterializerTest {
|
|||||||
assertTrue(walker.lastPriorAssetCache().isEmpty());
|
assertTrue(walker.lastPriorAssetCache().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runtimeDefaultProjectionSuppressesContentBytes() throws Exception {
|
||||||
|
final var materializer = new PackerRuntimeAssetMaterializer(new PackerAssetWalker(MAPPER));
|
||||||
|
final Path assetRoot = Files.createDirectories(tempDir.resolve("runtime-default"));
|
||||||
|
final Path manifestPath = assetRoot.resolve("asset.json");
|
||||||
|
Files.writeString(manifestPath, "{}");
|
||||||
|
writeTile(assetRoot.resolve("selected.png"), 16);
|
||||||
|
writeTile(assetRoot.resolve("extra.png"), 16);
|
||||||
|
|
||||||
|
final PackerAssetDeclaration declaration = declaration(
|
||||||
|
Map.of("tile_size", "16x16"),
|
||||||
|
List.of(new PackerAssetArtifactSelection("selected.png", 0)),
|
||||||
|
palettePipeline());
|
||||||
|
final var materialized = materializer.materialize(
|
||||||
|
assetRoot,
|
||||||
|
manifestPath,
|
||||||
|
Optional.of(new PackerRegistryEntry(1, "uuid", "asset", true)),
|
||||||
|
new PackerAssetDeclarationParseResult(declaration, List.of()),
|
||||||
|
Optional.empty());
|
||||||
|
|
||||||
|
assertEquals(2, materialized.runtimeAsset().walkProjection().buildCandidateFiles().size());
|
||||||
|
assertTrue(materialized.runtimeAsset().walkProjection().buildCandidateFiles().stream()
|
||||||
|
.allMatch(file -> file.contentBytes().isEmpty()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void packingProjectionKeepsSelectedFilesAndInjectsContentBytes() throws Exception {
|
||||||
|
final var materializer = new PackerRuntimeAssetMaterializer(new PackerAssetWalker(MAPPER));
|
||||||
|
final Path assetRoot = Files.createDirectories(tempDir.resolve("packing"));
|
||||||
|
final Path manifestPath = assetRoot.resolve("asset.json");
|
||||||
|
Files.writeString(manifestPath, "{}");
|
||||||
|
writeTile(assetRoot.resolve("selected.png"), 16);
|
||||||
|
writeTile(assetRoot.resolve("extra.png"), 16);
|
||||||
|
|
||||||
|
final PackerAssetDeclaration declaration = declaration(
|
||||||
|
Map.of("tile_size", "16x16"),
|
||||||
|
List.of(new PackerAssetArtifactSelection("selected.png", 0)),
|
||||||
|
palettePipeline());
|
||||||
|
final var materialized = materializer.materialize(
|
||||||
|
assetRoot,
|
||||||
|
manifestPath,
|
||||||
|
Optional.of(new PackerRegistryEntry(1, "uuid", "asset", true)),
|
||||||
|
new PackerAssetDeclarationParseResult(declaration, List.of()),
|
||||||
|
Optional.empty(),
|
||||||
|
PackerRuntimeMaterializationConfig.packingBuild());
|
||||||
|
|
||||||
|
assertEquals(1, materialized.runtimeAsset().walkProjection().buildCandidateFiles().size());
|
||||||
|
final var file = materialized.runtimeAsset().walkProjection().buildCandidateFiles().getFirst();
|
||||||
|
assertEquals("selected.png", file.relativePath());
|
||||||
|
assertTrue(file.contentBytes().isPresent());
|
||||||
|
assertTrue(file.contentBytes().get().length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
private static PackerAssetDeclaration declaration(Map<String, String> metadata) {
|
private static PackerAssetDeclaration declaration(Map<String, String> metadata) {
|
||||||
|
return declaration(metadata, List.of(), Map.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PackerAssetDeclaration declaration(
|
||||||
|
Map<String, String> metadata,
|
||||||
|
List<PackerAssetArtifactSelection> artifacts,
|
||||||
|
Map<String, JsonNode> pipelineMetadata) {
|
||||||
return new PackerAssetDeclaration(
|
return new PackerAssetDeclaration(
|
||||||
1,
|
1,
|
||||||
"uuid",
|
"uuid",
|
||||||
"asset",
|
"asset",
|
||||||
AssetFamilyCatalog.TILE_BANK,
|
AssetFamilyCatalog.TILE_BANK,
|
||||||
List.of(),
|
artifacts,
|
||||||
OutputFormatCatalog.TILES_INDEXED_V1,
|
OutputFormatCatalog.TILES_INDEXED_V1,
|
||||||
OutputCodecCatalog.NONE,
|
OutputCodecCatalog.NONE,
|
||||||
metadata,
|
metadata,
|
||||||
Map.<String, JsonNode>of(),
|
pipelineMetadata,
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Map<String, JsonNode> palettePipeline() {
|
||||||
|
final var payload = MAPPER.createObjectNode();
|
||||||
|
payload.putArray("originalArgb8888").add(0xFFFF0000);
|
||||||
|
payload.putArray("convertedRgb565").add(0xF800);
|
||||||
|
final var declaration = MAPPER.createObjectNode();
|
||||||
|
declaration.put("index", 0);
|
||||||
|
declaration.set("palette", payload);
|
||||||
|
final var palettes = MAPPER.createArrayNode();
|
||||||
|
palettes.add(declaration);
|
||||||
|
return Map.of("palettes", palettes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeTile(Path path, int tileSize) throws Exception {
|
||||||
|
final BufferedImage image = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
for (int y = 0; y < tileSize; y += 1) {
|
||||||
|
for (int x = 0; x < tileSize; x += 1) {
|
||||||
|
image.setRGB(x, y, 0xFFFF0000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImageIO.write(image, "png", path.toFile());
|
||||||
|
}
|
||||||
|
|
||||||
private static final class TrackingWalker extends PackerAssetWalker {
|
private static final class TrackingWalker extends PackerAssetWalker {
|
||||||
private Optional<PackerAssetCacheEntry> lastPriorAssetCache = Optional.empty();
|
private Optional<PackerAssetCacheEntry> lastPriorAssetCache = Optional.empty();
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user