mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2025-07-02 16:05:29 +00:00
Improve image caching options
This commit is contained in:
parent
805f99f57a
commit
e30bcbf1ba
@ -6,6 +6,7 @@ import java.util.function.Supplier;
|
||||
|
||||
import com.dfsek.terra.addons.image.config.ColorLoader;
|
||||
import com.dfsek.terra.addons.image.config.ColorLoader.ColorString;
|
||||
import com.dfsek.terra.addons.image.config.ImageLibraryPackConfigTemplate;
|
||||
import com.dfsek.terra.addons.image.config.noisesampler.ChannelNoiseSamplerTemplate;
|
||||
import com.dfsek.terra.addons.image.config.noisesampler.DistanceTransformNoiseSamplerTemplate;
|
||||
import com.dfsek.terra.addons.image.config.image.ImageTemplate;
|
||||
@ -52,6 +53,10 @@ public class ImageLibraryAddon implements AddonInitializer {
|
||||
.getHandler(FunctionalEventHandler.class)
|
||||
.register(addon, ConfigPackPreLoadEvent.class)
|
||||
.priority(10)
|
||||
.then(event -> {
|
||||
ImageLibraryPackConfigTemplate config = event.loadTemplate(new ImageLibraryPackConfigTemplate());
|
||||
event.getPack().getContext().put(config);
|
||||
})
|
||||
.then(event -> {
|
||||
ConfigPack pack = event.getPack();
|
||||
CheckedRegistry<Supplier<ObjectTemplate<Image>>> imageRegistry = pack.getOrCreateRegistry(IMAGE_REGISTRY_KEY);
|
||||
|
@ -0,0 +1,41 @@
|
||||
package com.dfsek.terra.addons.image.config;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.ConfigTemplate;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Default;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Description;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.api.properties.Properties;
|
||||
|
||||
public class ImageLibraryPackConfigTemplate implements ConfigTemplate, Properties {
|
||||
// TODO - These would be better as plugin wide config parameters in config.yml
|
||||
|
||||
@Value("images.cache.load-on-use")
|
||||
@Description("If set to true, images will load into memory upon use rather than on pack load.")
|
||||
@Default
|
||||
private boolean loadOnUse = false;
|
||||
|
||||
@Value("images.cache.unload-on-timeout")
|
||||
@Description("If set to true, images will be removed from memory if not used after a timeout, otherwise images will stay loaded in memory. " +
|
||||
"Trades decreased memory consumption when not performing image reads for extra processing time required to perform cache lookups.")
|
||||
@Default
|
||||
private boolean unloadOnTimeout = false;
|
||||
|
||||
@Value("images.cache.timeout")
|
||||
@Description("How many seconds to keep images loaded in the image cache for if images.cache.unload-on-timeout is enabled.")
|
||||
@Default
|
||||
private int cacheTimeout = 300;
|
||||
|
||||
public boolean loadOnUse() {
|
||||
return loadOnUse;
|
||||
}
|
||||
|
||||
public boolean unloadOnTimeout() {
|
||||
return unloadOnTimeout;
|
||||
}
|
||||
|
||||
public int getCacheTimeout() {
|
||||
return cacheTimeout;
|
||||
}
|
||||
}
|
@ -3,44 +3,62 @@ package com.dfsek.terra.addons.image.config.image;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.dfsek.terra.addons.image.config.ImageLibraryPackConfigTemplate;
|
||||
import com.dfsek.terra.addons.image.image.BufferedImageWrapper;
|
||||
import com.dfsek.terra.addons.image.image.Image;
|
||||
import com.dfsek.terra.addons.image.image.SuppliedImage;
|
||||
import com.dfsek.terra.api.config.ConfigPack;
|
||||
import com.dfsek.terra.api.config.Loader;
|
||||
import com.dfsek.terra.api.properties.Properties;
|
||||
|
||||
import com.dfsek.terra.api.util.generic.Lazy;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
|
||||
|
||||
/*
|
||||
* Cache prevents configs from loading the same image multiple times into memory
|
||||
*/
|
||||
record ImageCache(ConcurrentHashMap<String, Image> map) implements Properties {
|
||||
record ImageCache(LoadingCache<String, Image> cache) implements Properties {
|
||||
public static Image load(String path, ConfigPack pack, Loader files) throws IOException {
|
||||
ImageCache cache;
|
||||
ImageLibraryPackConfigTemplate config = pack.getContext().get(ImageLibraryPackConfigTemplate.class);
|
||||
ImageCache images;
|
||||
if(!pack.getContext().has(ImageCache.class)) {
|
||||
cache = new ImageCache(new ConcurrentHashMap<>());
|
||||
pack.getContext().put(cache);
|
||||
} else {
|
||||
cache = pack.getContext().get(ImageCache.class);
|
||||
System.out.println("Initializing new image cache");
|
||||
var cacheBuilder = Caffeine.newBuilder();
|
||||
if (config.unloadOnTimeout()) cacheBuilder.expireAfterAccess(config.getCacheTimeout(), TimeUnit.SECONDS);
|
||||
images = new ImageCache(cacheBuilder.build(s -> loadImage(path, files)));
|
||||
pack.getContext().put(images);
|
||||
} else images = pack.getContext().get(ImageCache.class);
|
||||
|
||||
if (config.loadOnUse()) {
|
||||
if(config.unloadOnTimeout()) { // Grab directly from cache if images are to unload on timeout
|
||||
return new SuppliedImage(() -> images.cache.get(path));
|
||||
} else {
|
||||
// If images do not time out, image can be lazily loaded once instead of performing cache lookups for each image operation
|
||||
Lazy<Image> lazyImage = Lazy.lazy(() -> images.cache.get(path));
|
||||
return new SuppliedImage(lazyImage::value);
|
||||
}
|
||||
}
|
||||
|
||||
if(cache.map.containsKey(path)) {
|
||||
return cache.map.get(path);
|
||||
} else {
|
||||
try {
|
||||
BufferedImageWrapper image = new BufferedImageWrapper(ImageIO.read(files.get(path)));
|
||||
cache.map.put(path, image);
|
||||
return image;
|
||||
} catch(IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Unable to load image (image might be too large?)", e);
|
||||
} catch(IOException e) {
|
||||
if(e instanceof FileNotFoundException) {
|
||||
// Rethrow using nicer message
|
||||
throw new IOException("Unable to load image: No such file or directory: " + path, e);
|
||||
}
|
||||
throw new IOException("Unable to load image", e);
|
||||
return images.cache.get(path);
|
||||
}
|
||||
|
||||
private static Image loadImage(String path, Loader files) throws IOException {
|
||||
System.out.println("Loading image: " + path);
|
||||
try {
|
||||
return new BufferedImageWrapper(ImageIO.read(files.get(path)));
|
||||
} catch(IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Unable to load image (image might be too large?)", e);
|
||||
} catch(IOException e) {
|
||||
if(e instanceof FileNotFoundException) {
|
||||
// Rethrow using nicer message
|
||||
throw new IOException("Unable to load image: No such file or directory: " + path, e);
|
||||
}
|
||||
throw new IOException("Unable to load image", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package com.dfsek.terra.addons.image.image;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class SuppliedImage implements Image {
|
||||
|
||||
private final Supplier<Image> imageSupplier;
|
||||
|
||||
public SuppliedImage(Supplier<Image> imageSupplier) {
|
||||
this.imageSupplier = imageSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRGB(int x, int y) {
|
||||
return imageSupplier.get().getRGB(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return imageSupplier.get().getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return imageSupplier.get().getHeight();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user