From b4733a0e49bd3e3bcfe710c7cceaf47354c0e537 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Wed, 18 Mar 2026 19:56:52 +0000 Subject: [PATCH] implements PR-027 runtime walkresult and cache integration --- .../messages/assets/OutputFormatCatalog.java | 3 + .../src/main/java/p/packer/Packer.java | 13 ++- .../p/packer/models/PackerRuntimeAsset.java | 15 ++- .../packer/models/PackerRuntimeSnapshot.java | 11 +- .../packer/models/PackerRuntimeWalkFile.java | 30 +++++ .../models/PackerRuntimeWalkProjection.java | 19 +++ .../PackerAbstractBankWalker.java | 10 +- .../repositories/PackerAssetWalker.java | 2 +- .../repositories/PackerFileFingerprint.java | 21 ++++ .../PackerRuntimeAssetMaterializer.java | 110 ++++++++++++++++++ .../repositories/PackerRuntimeLoader.java | 45 +++++-- .../FileSystemPackerWorkspaceService.java | 24 +++- .../services/PackerAssetDetailsService.java | 3 +- .../services/PackerRuntimePatchService.java | 107 ++++++++++++++--- .../FileSystemPackerWorkspaceServiceTest.java | 15 ++- .../PackerAssetDetailsServiceTest.java | 13 ++- .../PackerRuntimePatchServiceTest.java | 80 +++++++++++++ .../services/PackerRuntimeRegistryTest.java | 43 ++++++- 18 files changed, 512 insertions(+), 52 deletions(-) create mode 100644 prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeWalkFile.java create mode 100644 prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeWalkProjection.java create mode 100644 prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerFileFingerprint.java create mode 100644 prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeAssetMaterializer.java create mode 100644 prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerRuntimePatchServiceTest.java diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/assets/OutputFormatCatalog.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/assets/OutputFormatCatalog.java index fe041c62..ef5abfdc 100644 --- a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/assets/OutputFormatCatalog.java +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/messages/assets/OutputFormatCatalog.java @@ -53,6 +53,9 @@ public enum OutputFormatCatalog { return UNKNOWN; } final String normalized = manifestValue.trim().toLowerCase(Locale.ROOT); + if ("sound/bank_v1".equals(normalized)) { + return SOUND_V1; + } for (OutputFormatCatalog candidate : values()) { if (candidate == UNKNOWN) { continue; diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/Packer.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/Packer.java index 60576c2b..3a7d94e1 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/Packer.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/Packer.java @@ -2,7 +2,9 @@ package p.packer; import com.fasterxml.jackson.databind.ObjectMapper; import p.packer.events.PackerEventSink; +import p.packer.repositories.FileSystemPackerCacheRepository; import p.packer.repositories.PackerAssetWalker; +import p.packer.repositories.PackerRuntimeAssetMaterializer; import p.packer.repositories.PackerRuntimeLoader; import p.packer.repositories.PackerRuntimeRegistry; import p.packer.services.*; @@ -29,7 +31,13 @@ public final class Packer implements Closeable { final var workspaceFoundation = new PackerWorkspaceFoundation(mapper); final var declarationParser = new PackerAssetDeclarationParser(mapper); final var assetWalker = new PackerAssetWalker(mapper); - final var runtimeLoader = new PackerRuntimeLoader(workspaceFoundation, declarationParser, assetWalker); + final var cacheRepository = new FileSystemPackerCacheRepository(mapper); + final var assetMaterializer = new PackerRuntimeAssetMaterializer(assetWalker); + final var runtimeLoader = new PackerRuntimeLoader( + workspaceFoundation, + declarationParser, + cacheRepository, + assetMaterializer); final var runtimeRegistry = new PackerRuntimeRegistry(runtimeLoader); final var assetReferenceResolver = new PackerAssetReferenceResolver(workspaceFoundation.lookup()); final var assetDetailsService = new PackerAssetDetailsService(runtimeRegistry, assetReferenceResolver); @@ -37,7 +45,7 @@ public final class Packer implements Closeable { runtimeRegistry, assetReferenceResolver, workspaceFoundation.lookup()); - final var runtimePatchService = new PackerRuntimePatchService(declarationParser); + final var runtimePatchService = new PackerRuntimePatchService(declarationParser, assetMaterializer); final var writeCoordinator = new PackerProjectWriteCoordinator(); return new Packer(new FileSystemPackerWorkspaceService( mapper, @@ -46,6 +54,7 @@ public final class Packer implements Closeable { assetActionReadService, runtimePatchService, runtimeRegistry, + cacheRepository, writeCoordinator, resolvedEventSink), runtimeRegistry, writeCoordinator); } diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeAsset.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeAsset.java index a0e0dfa6..cc8f8499 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeAsset.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeAsset.java @@ -1,6 +1,7 @@ package p.packer.models; import java.nio.file.Path; +import java.util.List; import java.util.Objects; import java.util.Optional; @@ -8,12 +9,24 @@ public record PackerRuntimeAsset( Path assetRoot, Path manifestPath, Optional registryEntry, - PackerAssetDeclarationParseResult parsedDeclaration) { + PackerAssetDeclarationParseResult parsedDeclaration, + PackerRuntimeWalkProjection walkProjection, + List walkDiagnostics) { + + public PackerRuntimeAsset( + Path assetRoot, + Path manifestPath, + Optional registryEntry, + PackerAssetDeclarationParseResult parsedDeclaration) { + this(assetRoot, manifestPath, registryEntry, parsedDeclaration, PackerRuntimeWalkProjection.EMPTY, List.of()); + } public PackerRuntimeAsset { assetRoot = Objects.requireNonNull(assetRoot, "assetRoot").toAbsolutePath().normalize(); manifestPath = Objects.requireNonNull(manifestPath, "manifestPath").toAbsolutePath().normalize(); registryEntry = Objects.requireNonNull(registryEntry, "registryEntry"); parsedDeclaration = Objects.requireNonNull(parsedDeclaration, "parsedDeclaration"); + walkProjection = Objects.requireNonNull(walkProjection, "walkProjection"); + walkDiagnostics = List.copyOf(Objects.requireNonNull(walkDiagnostics, "walkDiagnostics")); } } diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeSnapshot.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeSnapshot.java index 9942ca8a..eceb2090 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeSnapshot.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeSnapshot.java @@ -6,7 +6,15 @@ import java.util.Objects; public record PackerRuntimeSnapshot( long generation, PackerRegistryState registry, - List assets) { + List assets, + PackerWorkspaceCacheState cacheState) { + + public PackerRuntimeSnapshot( + long generation, + PackerRegistryState registry, + List assets) { + this(generation, registry, assets, PackerWorkspaceCacheState.EMPTY); + } public PackerRuntimeSnapshot { if (generation <= 0L) { @@ -14,5 +22,6 @@ public record PackerRuntimeSnapshot( } registry = Objects.requireNonNull(registry, "registry"); assets = List.copyOf(Objects.requireNonNull(assets, "assets")); + cacheState = Objects.requireNonNull(cacheState, "cacheState"); } } diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeWalkFile.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeWalkFile.java new file mode 100644 index 00000000..e9eede0e --- /dev/null +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeWalkFile.java @@ -0,0 +1,30 @@ +package p.packer.models; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public record PackerRuntimeWalkFile( + String relativePath, + String mimeType, + long size, + long lastModified, + String fingerprint, + Map metadata, + List diagnostics) { + + public PackerRuntimeWalkFile { + relativePath = Objects.requireNonNull(relativePath, "relativePath"); + mimeType = mimeType == null || mimeType.isBlank() ? null : mimeType; + if (size < 0L) { + throw new IllegalArgumentException("size must be non-negative"); + } + if (lastModified < 0L) { + throw new IllegalArgumentException("lastModified must be non-negative"); + } + fingerprint = fingerprint == null || fingerprint.isBlank() ? null : fingerprint; + metadata = Map.copyOf(new LinkedHashMap<>(Objects.requireNonNull(metadata, "metadata"))); + diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); + } +} diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeWalkProjection.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeWalkProjection.java new file mode 100644 index 00000000..7bdd6b4d --- /dev/null +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerRuntimeWalkProjection.java @@ -0,0 +1,19 @@ +package p.packer.models; + +import java.util.List; +import java.util.Objects; + +public record PackerRuntimeWalkProjection( + List availableFiles, + List buildCandidateFiles, + long measuredBankSizeBytes) { + public static final PackerRuntimeWalkProjection EMPTY = new PackerRuntimeWalkProjection(List.of(), List.of(), 0L); + + public PackerRuntimeWalkProjection { + availableFiles = List.copyOf(Objects.requireNonNull(availableFiles, "availableFiles")); + buildCandidateFiles = List.copyOf(Objects.requireNonNull(buildCandidateFiles, "buildCandidateFiles")); + if (measuredBankSizeBytes < 0L) { + throw new IllegalArgumentException("measuredBankSizeBytes must be non-negative"); + } + } +} diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAbstractBankWalker.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAbstractBankWalker.java index 5741174b..7136783e 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAbstractBankWalker.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAbstractBankWalker.java @@ -14,9 +14,6 @@ import p.packer.models.PackerWalkResult; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HexFormat; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -132,12 +129,7 @@ public abstract class PackerAbstractBankWalker { } protected String computeFingerprint(final PackerFileProbe fileProbe) { - try { - final MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return HexFormat.of().formatHex(digest.digest(fileProbe.content())); - } catch (NoSuchAlgorithmException exception) { - throw new IllegalStateException("SHA-256 fingerprint is unavailable", exception); - } + return PackerFileFingerprint.sha256(fileProbe); } private Optional resolvePriorFileCache( diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAssetWalker.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAssetWalker.java index 2233b697..bfb6ec55 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAssetWalker.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAssetWalker.java @@ -146,7 +146,7 @@ public class PackerAssetWalker { return RequirementBuildResult.fail("Missing sample rate for sound bank"); } final var sampleRate = Integer.parseInt(sampleRateStr); - final var channelsStr = metadata.get(""); + final var channelsStr = metadata.get("channels"); if (StringUtils.isBlank(channelsStr)) { return RequirementBuildResult.fail("Missing channels for sound bank"); } diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerFileFingerprint.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerFileFingerprint.java new file mode 100644 index 00000000..9736cca7 --- /dev/null +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerFileFingerprint.java @@ -0,0 +1,21 @@ +package p.packer.repositories; + +import p.packer.models.PackerFileProbe; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; + +final class PackerFileFingerprint { + private PackerFileFingerprint() { + } + + static String sha256(final PackerFileProbe fileProbe) { + try { + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return HexFormat.of().formatHex(digest.digest(fileProbe.content())); + } catch (NoSuchAlgorithmException exception) { + throw new IllegalStateException("SHA-256 fingerprint is unavailable", exception); + } + } +} diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeAssetMaterializer.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeAssetMaterializer.java new file mode 100644 index 00000000..8003fbb4 --- /dev/null +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeAssetMaterializer.java @@ -0,0 +1,110 @@ +package p.packer.repositories; + +import p.packer.models.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + +public final class PackerRuntimeAssetMaterializer { + private final PackerAssetWalker assetWalker; + + public PackerRuntimeAssetMaterializer(PackerAssetWalker assetWalker) { + this.assetWalker = Objects.requireNonNull(assetWalker, "assetWalker"); + } + + public PackerRuntimeAssetMaterialization materialize( + Path assetRoot, + Path manifestPath, + Optional registryEntry, + PackerAssetDeclarationParseResult parseResult, + Optional priorAssetCache) { + final Path normalizedAssetRoot = Objects.requireNonNull(assetRoot, "assetRoot").toAbsolutePath().normalize(); + final Path normalizedManifestPath = Objects.requireNonNull(manifestPath, "manifestPath").toAbsolutePath().normalize(); + final Optional safeRegistryEntry = Objects.requireNonNull(registryEntry, "registryEntry"); + final PackerAssetDeclarationParseResult safeParseResult = Objects.requireNonNull(parseResult, "parseResult"); + final Optional safePriorAssetCache = Objects.requireNonNull(priorAssetCache, "priorAssetCache"); + + if (safeRegistryEntry.isEmpty() || !safeParseResult.valid()) { + return new PackerRuntimeAssetMaterialization( + new PackerRuntimeAsset( + normalizedAssetRoot, + normalizedManifestPath, + safeRegistryEntry, + safeParseResult, + PackerRuntimeWalkProjection.EMPTY, + List.of()), + Optional.empty()); + } + + final List availableFiles = listAvailableFiles(normalizedAssetRoot); + final PackerWalkResult walkResult = assetWalker.walk( + normalizedAssetRoot, + safeParseResult.declaration(), + safePriorAssetCache); + final List buildCandidateFiles = walkResult.probeResults().stream() + .map(probeResult -> toRuntimeWalkFile(normalizedAssetRoot, probeResult)) + .sorted(Comparator.comparing(PackerRuntimeWalkFile::relativePath, String.CASE_INSENSITIVE_ORDER)) + .toList(); + final long measuredBankSizeBytes = buildCandidateFiles.stream() + .mapToLong(PackerRuntimeWalkFile::size) + .sum(); + final PackerRuntimeWalkProjection walkProjection = new PackerRuntimeWalkProjection( + availableFiles, + buildCandidateFiles, + measuredBankSizeBytes); + final PackerRuntimeAsset runtimeAsset = new PackerRuntimeAsset( + normalizedAssetRoot, + normalizedManifestPath, + safeRegistryEntry, + safeParseResult, + walkProjection, + walkResult.diagnostics()); + final PackerAssetCacheEntry assetCacheEntry = new PackerAssetCacheEntry( + safeRegistryEntry.get().assetId(), + buildCandidateFiles.stream() + .map(file -> new PackerFileCacheEntry( + file.relativePath(), + file.mimeType(), + file.size(), + file.lastModified(), + file.fingerprint(), + file.metadata())) + .toList()); + return new PackerRuntimeAssetMaterialization(runtimeAsset, Optional.of(assetCacheEntry)); + } + + private List listAvailableFiles(Path assetRoot) { + try (var paths = Files.list(assetRoot) + .filter(Files::isRegularFile) + .filter(path -> !path.getFileName().toString().equalsIgnoreCase("asset.json")) + .map(path -> assetRoot.relativize(path.toAbsolutePath().normalize()).toString().replace('\\', '/')) + .sorted(String.CASE_INSENSITIVE_ORDER)) { + return paths.toList(); + } catch (IOException exception) { + return List.of(); + } + } + + private PackerRuntimeWalkFile toRuntimeWalkFile(Path assetRoot, PackerProbeResult probeResult) { + final PackerFileProbe fileProbe = probeResult.fileProbe(); + return new PackerRuntimeWalkFile( + assetRoot.relativize(fileProbe.path().toAbsolutePath().normalize()).toString().replace('\\', '/'), + fileProbe.mimeType(), + fileProbe.size(), + fileProbe.lastModified(), + PackerFileFingerprint.sha256(fileProbe), + probeResult.metadata(), + probeResult.diagnostics()); + } + + public record PackerRuntimeAssetMaterialization( + PackerRuntimeAsset runtimeAsset, + Optional assetCacheEntry) { + public PackerRuntimeAssetMaterialization { + runtimeAsset = Objects.requireNonNull(runtimeAsset, "runtimeAsset"); + assetCacheEntry = Objects.requireNonNull(assetCacheEntry, "assetCacheEntry"); + } + } +} diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeLoader.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeLoader.java index 92beddeb..e172ec90 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeLoader.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerRuntimeLoader.java @@ -2,8 +2,10 @@ package p.packer.repositories; import p.packer.messages.InitWorkspaceRequest; import p.packer.messages.PackerProjectContext; +import p.packer.models.PackerAssetCacheEntry; import p.packer.models.PackerRuntimeAsset; import p.packer.models.PackerRuntimeSnapshot; +import p.packer.models.PackerWorkspaceCacheState; import p.packer.services.PackerAssetDeclarationParser; import p.packer.services.PackerWorkspaceFoundation; import p.packer.PackerWorkspacePaths; @@ -18,15 +20,18 @@ import java.util.stream.Collectors; public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader { private final PackerWorkspaceFoundation workspaceFoundation; private final PackerAssetDeclarationParser parser; - private final PackerAssetWalker assetWalker; + private final FileSystemPackerCacheRepository cacheRepository; + private final PackerRuntimeAssetMaterializer assetMaterializer; public PackerRuntimeLoader( final PackerWorkspaceFoundation workspaceFoundation, final PackerAssetDeclarationParser parser, - final PackerAssetWalker assetWalker) { + final FileSystemPackerCacheRepository cacheRepository, + final PackerRuntimeAssetMaterializer assetMaterializer) { this.workspaceFoundation = Objects.requireNonNull(workspaceFoundation, "workspaceFoundation"); this.parser = Objects.requireNonNull(parser, "parser"); - this.assetWalker = Objects.requireNonNull(assetWalker, "assetWalker"); + this.cacheRepository = Objects.requireNonNull(cacheRepository, "cacheRepository"); + this.assetMaterializer = Objects.requireNonNull(assetMaterializer, "assetMaterializer"); } private boolean isAssetJson(Path path, BasicFileAttributes attrs) { @@ -39,6 +44,7 @@ public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader { workspaceFoundation.initWorkspace(new InitWorkspaceRequest(safeProject)); final var registry = workspaceFoundation.loadRegistry(safeProject); + final PackerWorkspaceCacheState priorCacheState = loadPriorCacheState(safeProject); final var registryByRoot = registry .assets() .stream() @@ -47,6 +53,7 @@ public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader { entry -> entry)); final List assets = new ArrayList<>(); + final List refreshedCacheEntries = new ArrayList<>(); final var assetsRoot = PackerWorkspacePaths.assetsRoot(safeProject); if (Files.isDirectory(assetsRoot)) { try (final var paths = Files.find(assetsRoot, Integer.MAX_VALUE, this::isAssetJson)) { @@ -58,12 +65,16 @@ public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader { final var assetRoot = manifestPath.getParent(); final var registryEntry = Optional.ofNullable(registryByRoot.get(assetRoot)); final var parseResult = parser.parse(manifestPath); - if (parseResult.valid()) { - final var walkResult = assetWalker.walk(assetRoot, parseResult.declaration()); - - } - final var runtimeAsset = new PackerRuntimeAsset(assetRoot, manifestPath, registryEntry, parseResult); - assets.add(runtimeAsset); + final Optional priorAssetCache = registryEntry + .flatMap(entry -> priorCacheState.findAsset(entry.assetId())); + final var materialized = assetMaterializer.materialize( + assetRoot, + manifestPath, + registryEntry, + parseResult, + priorAssetCache); + assets.add(materialized.runtimeAsset()); + materialized.assetCacheEntry().ifPresent(refreshedCacheEntries::add); } } catch (IOException exception) { throw new p.packer.exceptions.PackerRegistryException( @@ -72,6 +83,20 @@ public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader { } } - return new PackerRuntimeSnapshot(generation, registry, assets); + final PackerWorkspaceCacheState refreshedCacheState = new PackerWorkspaceCacheState( + PackerWorkspaceCacheState.CURRENT_SCHEMA_VERSION, + refreshedCacheEntries.stream() + .sorted(Comparator.comparingInt(PackerAssetCacheEntry::assetId)) + .toList()); + cacheRepository.save(safeProject, refreshedCacheState); + return new PackerRuntimeSnapshot(generation, registry, assets, refreshedCacheState); + } + + private PackerWorkspaceCacheState loadPriorCacheState(PackerProjectContext project) { + try { + return cacheRepository.load(project); + } catch (p.packer.exceptions.PackerRegistryException exception) { + return PackerWorkspaceCacheState.EMPTY; + } } } diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java index 63ed0087..230329cf 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java @@ -13,6 +13,7 @@ import p.packer.messages.assets.*; import p.packer.messages.diagnostics.PackerDiagnosticCategory; import p.packer.messages.diagnostics.PackerDiagnosticSeverity; import p.packer.models.*; +import p.packer.repositories.FileSystemPackerCacheRepository; import p.packer.repositories.PackerRuntimeRegistry; import java.io.IOException; @@ -28,6 +29,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe private final PackerAssetActionReadService actionReadService; private final PackerRuntimePatchService runtimePatchService; private final PackerRuntimeRegistry runtimeRegistry; + private final FileSystemPackerCacheRepository cacheRepository; private final PackerProjectWriteCoordinator writeCoordinator; private final PackerEventSink eventSink; @@ -38,6 +40,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe PackerAssetActionReadService actionReadService, PackerRuntimePatchService runtimePatchService, PackerRuntimeRegistry runtimeRegistry, + FileSystemPackerCacheRepository cacheRepository, PackerProjectWriteCoordinator writeCoordinator, PackerEventSink eventSink) { this.mapper = Objects.requireNonNull(mapper, "mapper"); @@ -46,6 +49,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe this.actionReadService = Objects.requireNonNull(actionReadService, "actionReadService"); this.runtimePatchService = Objects.requireNonNull(runtimePatchService, "runtimePatchService"); this.runtimeRegistry = Objects.requireNonNull(runtimeRegistry, "runtimeRegistry"); + this.cacheRepository = Objects.requireNonNull(cacheRepository, "cacheRepository"); this.writeCoordinator = Objects.requireNonNull(writeCoordinator, "writeCoordinator"); this.eventSink = Objects.requireNonNull(eventSink, "eventSink"); } @@ -84,6 +88,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe final var registryEntry = registryByRoot.get(assetRoot); final var parsed = runtimeAsset.parsedDeclaration(); diagnostics.addAll(parsed.diagnostics()); + diagnostics.addAll(runtimeAsset.walkDiagnostics()); diagnostics.addAll(identityMismatchDiagnostics(registryEntry, parsed, assetManifestPath)); final var summary = buildSummary(project, assetRoot, registryEntry, parsed); assets.add(summary); @@ -220,13 +225,14 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe writeManifest(manifestPath, request, entry.assetUuid()); final PackerRegistryState updated = workspaceFoundation.appendAllocatedEntry(registry, entry); workspaceFoundation.saveRegistry(project, updated); - runtimeRegistry.update(project, (snapshot, generation) -> runtimePatchService.afterCreateAsset( + final var runtime = runtimeRegistry.update(project, (snapshot, generation) -> runtimePatchService.afterCreateAsset( snapshot, generation, updated, entry, assetRoot, manifestPath)); + saveRuntimeCache(project, runtime.snapshot()); final CreateAssetResult result = new CreateAssetResult( PackerOperationStatus.SUCCESS, "Asset created: " + relativeAssetRoot, @@ -270,12 +276,13 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe declaration.assetUuid()); final PackerRegistryState updated = workspaceFoundation.appendAllocatedEntry(registry, entry); workspaceFoundation.saveRegistry(project, updated); - runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterRegisterAsset( + final var runtime = runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterRegisterAsset( currentSnapshot, generation, updated, entry, assetRoot)); + saveRuntimeCache(project, runtime.snapshot()); final RegisterAssetResult result = new RegisterAssetResult( PackerOperationStatus.SUCCESS, "Asset registered: " + relativeAssetRoot, @@ -316,11 +323,12 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe if (!updatedRegistry.equals(registry)) { workspaceFoundation.saveRegistry(project, updatedRegistry); } - runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterDeleteAsset( + final var runtime = runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterDeleteAsset( currentSnapshot, generation, updatedRegistry, assetRoot)); + saveRuntimeCache(project, runtime.snapshot()); final DeleteAssetResult result = new DeleteAssetResult( PackerOperationStatus.SUCCESS, "Asset deleted: " + relativeAssetRoot, @@ -372,7 +380,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe if (!updatedRegistry.equals(registry)) { workspaceFoundation.saveRegistry(project, updatedRegistry); } - runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterMoveAsset( + final var runtime = runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterMoveAsset( currentSnapshot, generation, updatedRegistry, @@ -380,6 +388,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe sourceRoot, targetRoot, targetManifestPath)); + saveRuntimeCache(project, runtime.snapshot()); final AssetReference canonicalReference = updatedEntry .map(packerRegistryEntry -> AssetReference.forAssetId(packerRegistryEntry.assetId())) .orElseGet(() -> AssetReference.forRelativeAssetRoot(targetRelativeRoot)); @@ -558,6 +567,10 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe return PackerWorkspacePaths.relativeAssetRoot(project, assetRoot).replace('\\', '/'); } + private void saveRuntimeCache(PackerProjectContext project, PackerRuntimeSnapshot snapshot) { + cacheRepository.save(project, snapshot.cacheState()); + } + private PackerRegistryState removeRegistryEntry( PackerRegistryState registry, PackerRegistryEntry entry) { @@ -628,12 +641,13 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe try { patchManifestContract(manifest, request); mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest); - runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterUpdateAssetContract( + final var runtime = runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterUpdateAssetContract( currentSnapshot, generation, assetRoot, manifestPath, evaluation.resolved().registryEntry())); + saveRuntimeCache(project, runtime.snapshot()); return new UpdateAssetContractResponse(true, null); } catch (IOException exception) { return new UpdateAssetContractResponse(false, "Unable to update asset contract: " + exception.getMessage()); diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java index a4c9de90..f9031ec0 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java @@ -42,6 +42,7 @@ public final class PackerAssetDetailsService { final var manifestPath = runtimeAsset.manifestPath(); final var parsed = runtimeAsset.parsedDeclaration(); diagnostics.addAll(parsed.diagnostics()); + diagnostics.addAll(runtimeAsset.walkDiagnostics()); if (!parsed.valid()) { return failureResult(project, request.assetReference(), resolved, diagnostics); } @@ -198,4 +199,4 @@ public final class PackerAssetDetailsService { } return requestedReference; } -} \ No newline at end of file +} diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerRuntimePatchService.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerRuntimePatchService.java index c100e7a8..8e9ab178 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerRuntimePatchService.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerRuntimePatchService.java @@ -1,15 +1,21 @@ package p.packer.services; import p.packer.models.*; +import p.packer.repositories.PackerRuntimeAssetMaterializer; import java.nio.file.Path; import java.util.*; +import java.util.stream.Collectors; public final class PackerRuntimePatchService { private final PackerAssetDeclarationParser declarationParser; + private final PackerRuntimeAssetMaterializer assetMaterializer; - public PackerRuntimePatchService(PackerAssetDeclarationParser declarationParser) { + public PackerRuntimePatchService( + PackerAssetDeclarationParser declarationParser, + PackerRuntimeAssetMaterializer assetMaterializer) { this.declarationParser = Objects.requireNonNull(declarationParser, "declarationParser"); + this.assetMaterializer = Objects.requireNonNull(assetMaterializer, "assetMaterializer"); } public PackerRuntimeSnapshot afterCreateAsset( @@ -23,13 +29,19 @@ public final class PackerRuntimePatchService { final List updatedAssets = new ArrayList<>(snapshot.assets().stream() .filter(candidate -> !candidate.assetRoot().equals(assetRoot.toAbsolutePath().normalize())) .toList()); - updatedAssets.add(new PackerRuntimeAsset( + final var materialized = assetMaterializer.materialize( assetRoot, manifestPath, Optional.of(entry), - parsed)); + parsed, + snapshot.cacheState().findAsset(entry.assetId())); + updatedAssets.add(materialized.runtimeAsset()); updatedAssets.sort(Comparator.comparing(asset -> asset.assetRoot().toString(), String.CASE_INSENSITIVE_ORDER)); - return new PackerRuntimeSnapshot(generation, updatedRegistry, updatedAssets); + return new PackerRuntimeSnapshot( + generation, + updatedRegistry, + updatedAssets, + mergeCacheState(snapshot.cacheState(), updatedRegistry, materialized.assetCacheEntry())); } public PackerRuntimeSnapshot afterRegisterAsset( @@ -40,13 +52,17 @@ public final class PackerRuntimePatchService { Path assetRoot) { final List updatedAssets = new ArrayList<>(); boolean patched = false; + Optional refreshedCacheEntry = Optional.empty(); for (PackerRuntimeAsset asset : snapshot.assets()) { if (asset.assetRoot().equals(assetRoot.toAbsolutePath().normalize())) { - updatedAssets.add(new PackerRuntimeAsset( + final var materialized = assetMaterializer.materialize( asset.assetRoot(), asset.manifestPath(), Optional.of(entry), - asset.parsedDeclaration())); + asset.parsedDeclaration(), + snapshot.cacheState().findAsset(entry.assetId())); + updatedAssets.add(materialized.runtimeAsset()); + refreshedCacheEntry = materialized.assetCacheEntry(); patched = true; } else { updatedAssets.add(asset); @@ -55,7 +71,11 @@ public final class PackerRuntimePatchService { if (!patched) { throw new IllegalStateException("Unable to patch runtime snapshot for unregistered asset: " + assetRoot); } - return new PackerRuntimeSnapshot(generation, updatedRegistry, updatedAssets); + return new PackerRuntimeSnapshot( + generation, + updatedRegistry, + updatedAssets, + mergeCacheState(snapshot.cacheState(), updatedRegistry, refreshedCacheEntry)); } public PackerRuntimeSnapshot afterDeleteAsset( @@ -66,7 +86,11 @@ public final class PackerRuntimePatchService { final List updatedAssets = snapshot.assets().stream() .filter(asset -> !asset.assetRoot().equals(assetRoot.toAbsolutePath().normalize())) .toList(); - return new PackerRuntimeSnapshot(generation, updatedRegistry, updatedAssets); + return new PackerRuntimeSnapshot( + generation, + updatedRegistry, + updatedAssets, + pruneCacheState(snapshot.cacheState(), updatedRegistry)); } public PackerRuntimeSnapshot afterMoveAsset( @@ -80,13 +104,17 @@ public final class PackerRuntimePatchService { final PackerAssetDeclarationParseResult parsed = declarationParser.parse(targetManifestPath); final List updatedAssets = new ArrayList<>(); boolean patched = false; + Optional refreshedCacheEntry = Optional.empty(); for (PackerRuntimeAsset asset : snapshot.assets()) { if (asset.assetRoot().equals(sourceRoot.toAbsolutePath().normalize())) { - updatedAssets.add(new PackerRuntimeAsset( + final var materialized = assetMaterializer.materialize( targetRoot, targetManifestPath, updatedRegistryEntry, - parsed)); + parsed, + updatedRegistryEntry.flatMap(entry -> snapshot.cacheState().findAsset(entry.assetId()))); + updatedAssets.add(materialized.runtimeAsset()); + refreshedCacheEntry = materialized.assetCacheEntry(); patched = true; } else { updatedAssets.add(asset); @@ -96,7 +124,11 @@ public final class PackerRuntimePatchService { throw new IllegalStateException("Unable to patch runtime snapshot for moved asset: " + sourceRoot); } updatedAssets.sort(Comparator.comparing(asset -> asset.assetRoot().toString(), String.CASE_INSENSITIVE_ORDER)); - return new PackerRuntimeSnapshot(generation, updatedRegistry, updatedAssets); + return new PackerRuntimeSnapshot( + generation, + updatedRegistry, + updatedAssets, + mergeCacheState(snapshot.cacheState(), updatedRegistry, refreshedCacheEntry)); } public PackerRuntimeSnapshot afterUpdateAssetContract( @@ -108,26 +140,69 @@ public final class PackerRuntimePatchService { final PackerAssetDeclarationParseResult parsed = declarationParser.parse(manifestPath); final List updatedAssets = new ArrayList<>(); boolean patched = false; + Optional refreshedCacheEntry = Optional.empty(); for (PackerRuntimeAsset asset : snapshot.assets()) { if (asset.assetRoot().equals(assetRoot.toAbsolutePath().normalize())) { - updatedAssets.add(new PackerRuntimeAsset( + final var materialized = assetMaterializer.materialize( asset.assetRoot(), asset.manifestPath(), asset.registryEntry(), - parsed)); + parsed, + asset.registryEntry().flatMap(entry -> snapshot.cacheState().findAsset(entry.assetId()))); + updatedAssets.add(materialized.runtimeAsset()); + refreshedCacheEntry = materialized.assetCacheEntry(); patched = true; } else { updatedAssets.add(asset); } } if (!patched) { - updatedAssets.add(new PackerRuntimeAsset( + final var materialized = assetMaterializer.materialize( assetRoot, manifestPath, registryEntry, - parsed)); + parsed, + registryEntry.flatMap(entry -> snapshot.cacheState().findAsset(entry.assetId()))); + updatedAssets.add(materialized.runtimeAsset()); + refreshedCacheEntry = materialized.assetCacheEntry(); updatedAssets.sort(Comparator.comparing(asset -> asset.assetRoot().toString(), String.CASE_INSENSITIVE_ORDER)); } - return new PackerRuntimeSnapshot(generation, snapshot.registry(), updatedAssets); + return new PackerRuntimeSnapshot( + generation, + snapshot.registry(), + updatedAssets, + mergeCacheState(snapshot.cacheState(), snapshot.registry(), refreshedCacheEntry)); + } + + private PackerWorkspaceCacheState mergeCacheState( + PackerWorkspaceCacheState currentCacheState, + PackerRegistryState registry, + Optional refreshedCacheEntry) { + final Map byAssetId = new LinkedHashMap<>(); + currentCacheState.assets().forEach(entry -> byAssetId.put(entry.assetId(), entry)); + refreshedCacheEntry.ifPresent(entry -> byAssetId.put(entry.assetId(), entry)); + return pruneCacheEntries(byAssetId, registry); + } + + private PackerWorkspaceCacheState pruneCacheState( + PackerWorkspaceCacheState currentCacheState, + PackerRegistryState registry) { + final Map byAssetId = new LinkedHashMap<>(); + currentCacheState.assets().forEach(entry -> byAssetId.put(entry.assetId(), entry)); + return pruneCacheEntries(byAssetId, registry); + } + + private PackerWorkspaceCacheState pruneCacheEntries( + Map byAssetId, + PackerRegistryState registry) { + final Set activeAssetIds = registry.assets().stream() + .map(PackerRegistryEntry::assetId) + .collect(Collectors.toSet()); + return new PackerWorkspaceCacheState( + PackerWorkspaceCacheState.CURRENT_SCHEMA_VERSION, + byAssetId.values().stream() + .filter(entry -> activeAssetIds.contains(entry.assetId())) + .sorted(Comparator.comparingInt(PackerAssetCacheEntry::assetId)) + .toList()); } } diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java index a08f6fd7..b2d745b1 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java @@ -7,7 +7,9 @@ import p.packer.events.PackerEvent; import p.packer.events.PackerEventKind; import p.packer.messages.*; import p.packer.messages.assets.*; +import p.packer.repositories.FileSystemPackerCacheRepository; import p.packer.repositories.PackerAssetWalker; +import p.packer.repositories.PackerRuntimeAssetMaterializer; import p.packer.repositories.PackerRuntimeLoader; import p.packer.repositories.PackerRuntimeRegistry; import p.packer.repositories.PackerRuntimeSnapshotLoader; @@ -138,7 +140,8 @@ final class FileSystemPackerWorkspaceServiceTest { assertEquals(PackerAssetState.REGISTERED, detailsResult.details().summary().state()); assertEquals("new_atlas", detailsResult.details().summary().identity().assetName()); assertNotNull(detailsResult.details().summary().identity().assetUuid()); - assertTrue(detailsResult.diagnostics().isEmpty()); + assertTrue(detailsResult.diagnostics().stream().noneMatch(diagnostic -> diagnostic.blocking())); + assertTrue(detailsResult.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("Output metadata for tile bank cannot be empty"))); } @Test @@ -679,11 +682,14 @@ final class FileSystemPackerWorkspaceServiceTest { final var mapper = new ObjectMapper(); final var foundation = new p.packer.services.PackerWorkspaceFoundation(mapper); final var parser = new p.packer.services.PackerAssetDeclarationParser(mapper); + final var assetWalker = new PackerAssetWalker(mapper); + final var cacheRepository = new FileSystemPackerCacheRepository(mapper); + final var assetMaterializer = new PackerRuntimeAssetMaterializer(assetWalker); final var runtimeRegistry = new PackerRuntimeRegistry(loader); final var resolver = new p.packer.services.PackerAssetReferenceResolver(foundation.lookup()); final var detailsService = new p.packer.services.PackerAssetDetailsService(runtimeRegistry, resolver); final var actionReadService = new p.packer.services.PackerAssetActionReadService(runtimeRegistry, resolver, foundation.lookup()); - final var runtimePatchService = new p.packer.services.PackerRuntimePatchService(parser); + final var runtimePatchService = new p.packer.services.PackerRuntimePatchService(parser, assetMaterializer); final var writeCoordinator = new p.packer.services.PackerProjectWriteCoordinator(); return new FileSystemPackerWorkspaceService( new ObjectMapper(), @@ -692,6 +698,7 @@ final class FileSystemPackerWorkspaceServiceTest { actionReadService, runtimePatchService, runtimeRegistry, + cacheRepository, writeCoordinator, eventSink); } @@ -701,7 +708,9 @@ final class FileSystemPackerWorkspaceServiceTest { final var foundation = new p.packer.services.PackerWorkspaceFoundation(mapper); final var parser = new p.packer.services.PackerAssetDeclarationParser(mapper); final var assetWalker = new PackerAssetWalker(mapper); - return new CountingLoader(new PackerRuntimeLoader(foundation, parser, assetWalker)); + final var cacheRepository = new FileSystemPackerCacheRepository(mapper); + final var assetMaterializer = new PackerRuntimeAssetMaterializer(assetWalker); + return new CountingLoader(new PackerRuntimeLoader(foundation, parser, cacheRepository, assetMaterializer)); } private static final class CountingLoader implements PackerRuntimeSnapshotLoader { diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java index e5c9c60e..ad9c8128 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java @@ -11,7 +11,9 @@ import p.packer.messages.PackerProjectContext; import p.packer.messages.assets.OutputCodecCatalog; import p.packer.messages.assets.PackerAssetState; import p.packer.messages.assets.PackerBuildParticipation; +import p.packer.repositories.FileSystemPackerCacheRepository; import p.packer.repositories.PackerAssetWalker; +import p.packer.repositories.PackerRuntimeAssetMaterializer; import p.packer.repositories.PackerRuntimeLoader; import p.packer.repositories.PackerRuntimeRegistry; import p.packer.testing.PackerFixtureLocator; @@ -43,7 +45,8 @@ final class PackerAssetDetailsServiceTest { assertEquals("TILES/indexed_v1", result.details().outputFormat().displayName()); assertEquals(List.of(OutputCodecCatalog.NONE), result.details().availableOutputCodecs()); assertEquals(List.of(), result.details().codecConfigurationFieldsByCodec().get(OutputCodecCatalog.NONE)); - assertTrue(result.diagnostics().isEmpty()); + assertTrue(result.diagnostics().stream().noneMatch(diagnostic -> diagnostic.blocking())); + assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("Output metadata for tile bank cannot be empty"))); } @Test @@ -137,7 +140,13 @@ final class PackerAssetDetailsServiceTest { final var foundation = new PackerWorkspaceFoundation(mapper); final var parser = new PackerAssetDeclarationParser(mapper); final var assetWalker = new PackerAssetWalker(mapper); - final var runtimeRegistry = new PackerRuntimeRegistry(new PackerRuntimeLoader(foundation, parser, assetWalker)); + final var cacheRepository = new FileSystemPackerCacheRepository(mapper); + final var assetMaterializer = new PackerRuntimeAssetMaterializer(assetWalker); + final var runtimeRegistry = new PackerRuntimeRegistry(new PackerRuntimeLoader( + foundation, + parser, + cacheRepository, + assetMaterializer)); final var resolver = new PackerAssetReferenceResolver(foundation.lookup()); return new PackerAssetDetailsService(runtimeRegistry, resolver); } diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerRuntimePatchServiceTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerRuntimePatchServiceTest.java new file mode 100644 index 00000000..e4106b12 --- /dev/null +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerRuntimePatchServiceTest.java @@ -0,0 +1,80 @@ +package p.packer.services; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import p.packer.messages.PackerProjectContext; +import p.packer.repositories.FileSystemPackerCacheRepository; +import p.packer.repositories.PackerAssetWalker; +import p.packer.repositories.PackerRuntimeAssetMaterializer; +import p.packer.repositories.PackerRuntimeLoader; +import p.packer.testing.PackerFixtureLocator; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +final class PackerRuntimePatchServiceTest { + @TempDir + Path tempDir; + + @Test + void afterUpdateAssetContractRefreshesOneAssetProjectionAndCacheState() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("managed")); + writeTilePng(projectRoot.resolve("assets/ui/atlas/tile.png")); + final var foundation = new PackerWorkspaceFoundation(mapper); + final var parser = new PackerAssetDeclarationParser(mapper); + final var assetWalker = new PackerAssetWalker(mapper); + final var cacheRepository = new FileSystemPackerCacheRepository(mapper); + final var assetMaterializer = new PackerRuntimeAssetMaterializer(assetWalker); + final var loader = new PackerRuntimeLoader(foundation, parser, cacheRepository, assetMaterializer); + final var patchService = new PackerRuntimePatchService(parser, assetMaterializer); + final var project = new PackerProjectContext("main", projectRoot); + + final var initialSnapshot = loader.load(project, 1L); + final var registryEntry = initialSnapshot.registry().assets().getFirst(); + final var updatedSnapshot = patchService.afterUpdateAssetContract( + initialSnapshot, + 2L, + projectRoot.resolve("assets/ui/atlas"), + projectRoot.resolve("assets/ui/atlas/asset.json"), + Optional.of(registryEntry)); + + assertTrue(updatedSnapshot.cacheState().findAsset(registryEntry.assetId()).isPresent()); + assertEquals(1, updatedSnapshot.assets().getFirst().walkProjection().buildCandidateFiles().size()); + assertEquals("tile.png", updatedSnapshot.assets().getFirst().walkProjection().buildCandidateFiles().getFirst().relativePath()); + } + + private Path copyFixture(String relativePath, Path targetRoot) throws Exception { + final Path sourceRoot = PackerFixtureLocator.fixtureRoot(relativePath); + try (var stream = Files.walk(sourceRoot)) { + for (Path source : stream.sorted(Comparator.naturalOrder()).toList()) { + final Path target = targetRoot.resolve(sourceRoot.relativize(source).toString()); + if (Files.isDirectory(source)) { + Files.createDirectories(target); + } else { + Files.createDirectories(target.getParent()); + Files.copy(source, target); + } + } + } + return targetRoot; + } + + private void writeTilePng(Path path) throws Exception { + Files.createDirectories(path.getParent()); + final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < 16; y += 1) { + for (int x = 0; x < 16; x += 1) { + image.setRGB(x, y, 0xFFFF0000); + } + } + ImageIO.write(image, "png", path.toFile()); + } +} diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerRuntimeRegistryTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerRuntimeRegistryTest.java index 24ab8722..b8261576 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerRuntimeRegistryTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerRuntimeRegistryTest.java @@ -3,15 +3,21 @@ package p.packer.services; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import p.packer.PackerWorkspacePaths; import p.packer.messages.PackerProjectContext; +import p.packer.repositories.FileSystemPackerCacheRepository; import p.packer.repositories.PackerAssetWalker; +import p.packer.repositories.PackerRuntimeAssetMaterializer; import p.packer.repositories.PackerRuntimeLoader; import p.packer.repositories.PackerRuntimeRegistry; import p.packer.testing.PackerFixtureLocator; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -66,6 +72,24 @@ final class PackerRuntimeRegistryTest { assertEquals(2, refreshed.snapshot().assets().size()); } + @Test + void runtimeSnapshotRetainsWalkProjectionAndPersistsCacheForRegisteredAssets() throws Exception { + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("cache")); + writeTilePng(projectRoot.resolve("assets/ui/atlas/tile.png")); + final PackerRuntimeRegistry registry = runtimeRegistry(); + final PackerProjectContext project = project(projectRoot); + + final PackerProjectRuntime runtime = registry.getOrLoad(project); + + assertTrue(Files.isRegularFile(PackerWorkspacePaths.cachePath(project))); + assertTrue(runtime.snapshot().cacheState().findAsset(1).isPresent()); + final var runtimeAsset = runtime.snapshot().assets().getFirst(); + assertEquals(List.of("tile.png"), runtimeAsset.walkProjection().availableFiles()); + assertEquals(1, runtimeAsset.walkProjection().buildCandidateFiles().size()); + assertEquals("tile.png", runtimeAsset.walkProjection().buildCandidateFiles().getFirst().relativePath()); + assertTrue(runtimeAsset.walkProjection().measuredBankSizeBytes() > 0L); + } + @Test void disposeMarksRuntimeInactiveAndRemovesItFromRegistry() throws Exception { final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("dispose")); @@ -85,7 +109,13 @@ final class PackerRuntimeRegistryTest { final var foundation = new PackerWorkspaceFoundation(mapper); final var parser = new PackerAssetDeclarationParser(mapper); final var assetWalker = new PackerAssetWalker(mapper); - return new PackerRuntimeRegistry(new PackerRuntimeLoader(foundation, parser, assetWalker)); + final var cacheRepository = new FileSystemPackerCacheRepository(mapper); + final var assetMaterializer = new PackerRuntimeAssetMaterializer(assetWalker); + return new PackerRuntimeRegistry(new PackerRuntimeLoader( + foundation, + parser, + cacheRepository, + assetMaterializer)); } private PackerProjectContext project(Path root) { @@ -107,4 +137,15 @@ final class PackerRuntimeRegistryTest { } return targetRoot; } + + private void writeTilePng(Path path) throws Exception { + Files.createDirectories(path.getParent()); + final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < 16; y += 1) { + for (int x = 0; x < 16; x += 1) { + image.setRGB(x, y, 0xFFFF0000); + } + } + ImageIO.write(image, "png", path.toFile()); + } }