HashCache.java

net.minecraft.data.HashCache

信息

  • 全限定名:net.minecraft.data.HashCache
  • 类型:public class
  • 包:net.minecraft.data
  • 源码路径:src/main/java/net/minecraft/data/HashCache.java
  • 起始行号:L35
  • 职责:

    TODO

字段/常量

  • LOGGER

    • 类型: Logger
    • 修饰符: private static final
    • 源码定位: L36
    • 说明:

      TODO

  • HEADER_MARKER

    • 类型: String
    • 修饰符: private static final
    • 源码定位: L37
    • 说明:

      TODO

  • rootDir

    • 类型: Path
    • 修饰符: private final
    • 源码定位: L38
    • 说明:

      TODO

  • cacheDir

    • 类型: Path
    • 修饰符: private final
    • 源码定位: L39
    • 说明:

      TODO

  • versionId

    • 类型: String
    • 修饰符: private final
    • 源码定位: L40
    • 说明:

      TODO

  • caches

    • 类型: Map<String,HashCache.ProviderCache>
    • 修饰符: private final
    • 源码定位: L41
    • 说明:

      TODO

  • cachesToWrite

    • 类型: Set<String>
    • 修饰符: private final
    • 源码定位: L42
    • 说明:

      TODO

  • cachePaths

    • 类型: Set<Path>
    • 修饰符: private final
    • 源码定位: L43
    • 说明:

      TODO

  • initialCount

    • 类型: int
    • 修饰符: private final
    • 源码定位: L44
    • 说明:

      TODO

  • writes

    • 类型: int
    • 修饰符: private
    • 源码定位: L45
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.data.HashCache.CacheUpdater

    • 类型: class
    • 修饰符: private static
    • 源码定位: L151
    • 说明:

      TODO

  • net.minecraft.data.HashCache.ProviderCache

    • 类型: record
    • 修饰符: private
    • 源码定位: L189
    • 说明:

      TODO

  • net.minecraft.data.HashCache.ProviderCacheBuilder

    • 类型: record
    • 修饰符: private
    • 源码定位: L239
    • 说明:

      TODO

  • net.minecraft.data.HashCache.UpdateFunction

    • 类型: interface
    • 修饰符: public
    • 源码定位: L254
    • 说明:

      TODO

  • net.minecraft.data.HashCache.UpdateResult

    • 类型: record
    • 修饰符: public
    • 源码定位: L258
    • 说明:

      TODO

构造器

public HashCache(Path rootDir, Collection<String> providerIds, WorldVersion version) @ L51

  • 构造器名:HashCache
  • 源码定位:L51
  • 修饰符:public

参数:

  • rootDir: Path
  • providerIds: Collection
  • version: WorldVersion

说明:

TODO

方法

下面的方法块按源码顺序生成。

private Path getProviderCachePath(String provider) @ L47

  • 方法名:getProviderCachePath
  • 源码定位:L47
  • 返回类型:Path
  • 修饰符:private

参数:

  • provider: String

说明:

TODO

private static HashCache.ProviderCache readCache(Path rootDir, Path providerCachePath) @ L71

  • 方法名:readCache
  • 源码定位:L71
  • 返回类型:HashCache.ProviderCache
  • 修饰符:private static

参数:

  • rootDir: Path
  • providerCachePath: Path

说明:

TODO

public boolean shouldRunInThisVersion(String providerId) @ L83

  • 方法名:shouldRunInThisVersion
  • 源码定位:L83
  • 返回类型:boolean
  • 修饰符:public

参数:

  • providerId: String

说明:

TODO

public CompletableFuture<HashCache.UpdateResult> generateUpdate(String providerId, HashCache.UpdateFunction function) @ L88

  • 方法名:generateUpdate
  • 源码定位:L88
  • 返回类型:CompletableFuture<HashCache.UpdateResult>
  • 修饰符:public

参数:

  • providerId: String
  • function: HashCache.UpdateFunction

说明:

TODO

public void applyUpdate(HashCache.UpdateResult result) @ L98

  • 方法名:applyUpdate
  • 源码定位:L98
  • 返回类型:void
  • 修饰符:public

参数:

  • result: HashCache.UpdateResult

说明:

TODO

public void purgeStaleAndWrite() @ L104

  • 方法名:purgeStaleAndWrite
  • 源码定位:L104
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

代码

public class HashCache {
    private static final Logger LOGGER = LogUtils.getLogger();
    private static final String HEADER_MARKER = "// ";
    private final Path rootDir;
    private final Path cacheDir;
    private final String versionId;
    private final Map<String, HashCache.ProviderCache> caches;
    private final Set<String> cachesToWrite = new HashSet<>();
    private final Set<Path> cachePaths = new HashSet<>();
    private final int initialCount;
    private int writes;
 
    private Path getProviderCachePath(String provider) {
        return this.cacheDir.resolve(Hashing.sha1().hashString(provider, StandardCharsets.UTF_8).toString());
    }
 
    public HashCache(Path rootDir, Collection<String> providerIds, WorldVersion version) throws IOException {
        this.versionId = version.id();
        this.rootDir = rootDir;
        this.cacheDir = rootDir.resolve(".cache");
        Files.createDirectories(this.cacheDir);
        Map<String, HashCache.ProviderCache> loadedCaches = new HashMap<>();
        int initialCount = 0;
 
        for (String providerId : providerIds) {
            Path providerCachePath = this.getProviderCachePath(providerId);
            this.cachePaths.add(providerCachePath);
            HashCache.ProviderCache providerCache = readCache(rootDir, providerCachePath);
            loadedCaches.put(providerId, providerCache);
            initialCount += providerCache.count();
        }
 
        this.caches = loadedCaches;
        this.initialCount = initialCount;
    }
 
    private static HashCache.ProviderCache readCache(Path rootDir, Path providerCachePath) {
        if (Files.isReadable(providerCachePath)) {
            try {
                return HashCache.ProviderCache.load(rootDir, providerCachePath);
            } catch (Exception var3) {
                LOGGER.warn("Failed to parse cache {}, discarding", providerCachePath, var3);
            }
        }
 
        return new HashCache.ProviderCache("unknown", ImmutableMap.of());
    }
 
    public boolean shouldRunInThisVersion(String providerId) {
        HashCache.ProviderCache result = this.caches.get(providerId);
        return result == null || !result.version.equals(this.versionId);
    }
 
    public CompletableFuture<HashCache.UpdateResult> generateUpdate(String providerId, HashCache.UpdateFunction function) {
        HashCache.ProviderCache existingCache = this.caches.get(providerId);
        if (existingCache == null) {
            throw new IllegalStateException("Provider not registered: " + providerId);
        } else {
            HashCache.CacheUpdater output = new HashCache.CacheUpdater(providerId, this.versionId, existingCache);
            return function.update(output).thenApply(unused -> output.close());
        }
    }
 
    public void applyUpdate(HashCache.UpdateResult result) {
        this.caches.put(result.providerId(), result.cache());
        this.cachesToWrite.add(result.providerId());
        this.writes = this.writes + result.writes();
    }
 
    public void purgeStaleAndWrite() throws IOException {
        final Set<Path> allowedFiles = new HashSet<>();
        this.caches.forEach((providerId, cache) -> {
            if (this.cachesToWrite.contains(providerId)) {
                Path cachePath = this.getProviderCachePath(providerId);
                cache.save(this.rootDir, cachePath, DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(ZonedDateTime.now()) + "\t" + providerId);
            }
 
            allowedFiles.addAll(cache.data().keySet());
        });
        final MutableInt found = new MutableInt();
        final MutableInt removed = new MutableInt();
        Files.walkFileTree(this.rootDir, new SimpleFileVisitor<Path>() {
            {
                Objects.requireNonNull(HashCache.this);
            }
 
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                if (HashCache.this.cachePaths.contains(file)) {
                    return FileVisitResult.CONTINUE;
                } else {
                    found.increment();
                    if (allowedFiles.contains(file)) {
                        return FileVisitResult.CONTINUE;
                    } else {
                        try {
                            Files.delete(file);
                        } catch (IOException var4) {
                            HashCache.LOGGER.warn("Failed to delete file {}", file, var4);
                        }
 
                        removed.increment();
                        return FileVisitResult.CONTINUE;
                    }
                }
            }
        });
        LOGGER.info(
            "Caching: total files: {}, old count: {}, new count: {}, removed stale: {}, written: {}",
            found,
            this.initialCount,
            allowedFiles.size(),
            removed,
            this.writes
        );
    }
 
    private static class CacheUpdater implements CachedOutput {
        private final String provider;
        private final HashCache.ProviderCache oldCache;
        private final HashCache.ProviderCacheBuilder newCache;
        private final AtomicInteger writes = new AtomicInteger();
        private volatile boolean closed;
 
        private CacheUpdater(String provider, String newVersionId, HashCache.ProviderCache oldCache) {
            this.provider = provider;
            this.oldCache = oldCache;
            this.newCache = new HashCache.ProviderCacheBuilder(newVersionId);
        }
 
        private boolean shouldWrite(Path path, HashCode hash) {
            return !Objects.equals(this.oldCache.get(path), hash) || !Files.exists(path);
        }
 
        @Override
        public void writeIfNeeded(Path path, byte[] input, HashCode hash) throws IOException {
            if (this.closed) {
                throw new IllegalStateException("Cannot write to cache as it has already been closed");
            } else {
                if (this.shouldWrite(path, hash)) {
                    this.writes.incrementAndGet();
                    Files.createDirectories(path.getParent());
                    Files.write(path, input);
                }
 
                this.newCache.put(path, hash);
            }
        }
 
        public HashCache.UpdateResult close() {
            this.closed = true;
            return new HashCache.UpdateResult(this.provider, this.newCache.build(), this.writes.get());
        }
    }
 
    private record ProviderCache(String version, ImmutableMap<Path, HashCode> data) {
        public @Nullable HashCode get(Path path) {
            return this.data.get(path);
        }
 
        public int count() {
            return this.data.size();
        }
 
        public static HashCache.ProviderCache load(Path rootDir, Path cacheFile) throws IOException {
            HashCache.ProviderCache var7;
            try (BufferedReader reader = Files.newBufferedReader(cacheFile, StandardCharsets.UTF_8)) {
                String header = reader.readLine();
                if (!header.startsWith("// ")) {
                    throw new IllegalStateException("Missing cache file header");
                }
 
                String[] headerFields = header.substring("// ".length()).split("\t", 2);
                String savedVersionId = headerFields[0];
                Builder<Path, HashCode> result = ImmutableMap.builder();
                reader.lines().forEach(s -> {
                    int i = s.indexOf(32);
                    result.put(rootDir.resolve(s.substring(i + 1)), HashCode.fromString(s.substring(0, i)));
                });
                var7 = new HashCache.ProviderCache(savedVersionId, result.build());
            }
 
            return var7;
        }
 
        public void save(Path rootDir, Path cacheFile, String extraHeaderInfo) {
            try (BufferedWriter output = Files.newBufferedWriter(cacheFile, StandardCharsets.UTF_8)) {
                output.write("// ");
                output.write(this.version);
                output.write(9);
                output.write(extraHeaderInfo);
                output.newLine();
 
                for (Entry<Path, HashCode> e : this.data.entrySet()) {
                    output.write(e.getValue().toString());
                    output.write(32);
                    output.write(rootDir.relativize(e.getKey()).toString());
                    output.newLine();
                }
            } catch (IOException var9) {
                HashCache.LOGGER.warn("Unable write cachefile {}: {}", cacheFile, var9);
            }
        }
    }
 
    private record ProviderCacheBuilder(String version, ConcurrentMap<Path, HashCode> data) {
        ProviderCacheBuilder(String version) {
            this(version, new ConcurrentHashMap<>());
        }
 
        public void put(Path path, HashCode hash) {
            this.data.put(path, hash);
        }
 
        public HashCache.ProviderCache build() {
            return new HashCache.ProviderCache(this.version, ImmutableMap.copyOf(this.data));
        }
    }
 
    @FunctionalInterface
    public interface UpdateFunction {
        CompletableFuture<?> update(CachedOutput output);
    }
 
    public record UpdateResult(String providerId, HashCache.ProviderCache cache, int writes) {
    }
}

引用的其他类