package com.hypixel.hytale.server.worldgen.util.cache; import com.hypixel.hytale.server.core.HytaleServer; import java.lang.ref.WeakReference; import java.lang.ref.Cleaner.Cleanable; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class TimeoutCache implements Cache { private final Map> map = new ConcurrentHashMap<>(); private final long timeout; @Nonnull private final Function func; @Nullable private final BiConsumer destroyer; @Nonnull private final ScheduledFuture future; @Nonnull private final Cleanable cleanable; public TimeoutCache(long expire, @Nonnull TimeUnit unit, @Nonnull Function func, @Nullable BiConsumer destroyer) { this.timeout = unit.toNanos(expire); this.func = func; this.destroyer = destroyer; this.future = HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(new CleanupRunnable(new WeakReference<>(this)), expire, expire, unit); this.cleanable = CleanupFutureAction.CLEANER.register(this, new CleanupFutureAction(this.future)); } @Override public void cleanup() { long expire = System.nanoTime() - this.timeout; for (Entry> entry : this.map.entrySet()) { TimeoutCache.CacheEntry cacheEntry = entry.getValue(); if (cacheEntry.timestamp < expire) { K key = entry.getKey(); if (this.map.remove(key, entry.getValue()) && this.destroyer != null) { this.destroyer.accept(key, cacheEntry.value); } } } } @Override public void shutdown() { this.cleanable.clean(); Iterator>> iterator = this.map.entrySet().iterator(); while (iterator.hasNext()) { Entry> entry = iterator.next(); K key = entry.getKey(); TimeoutCache.CacheEntry cacheEntry = entry.getValue(); if (this.map.remove(key, cacheEntry)) { iterator.remove(); if (this.destroyer != null) { this.destroyer.accept(key, cacheEntry.value); } } } } @Override public V get(K key) { if (this.future.isCancelled()) { throw new IllegalStateException("Cache has been shutdown!"); } else { TimeoutCache.CacheEntry cacheEntry = this.map.compute(key, (k, v) -> { if (v != null) { v.timestamp = System.nanoTime(); return v; } else { return new TimeoutCache.CacheEntry<>(this.func.apply((K)k)); } }); return cacheEntry.value; } } private static class CacheEntry { private final V value; private long timestamp; public CacheEntry(V value) { this.value = value; this.timestamp = System.nanoTime(); } } }