mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2025-07-03 08:25:31 +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;
|
||||||
import com.dfsek.terra.addons.image.config.ColorLoader.ColorString;
|
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.ChannelNoiseSamplerTemplate;
|
||||||
import com.dfsek.terra.addons.image.config.noisesampler.DistanceTransformNoiseSamplerTemplate;
|
import com.dfsek.terra.addons.image.config.noisesampler.DistanceTransformNoiseSamplerTemplate;
|
||||||
import com.dfsek.terra.addons.image.config.image.ImageTemplate;
|
import com.dfsek.terra.addons.image.config.image.ImageTemplate;
|
||||||
@ -52,6 +53,10 @@ public class ImageLibraryAddon implements AddonInitializer {
|
|||||||
.getHandler(FunctionalEventHandler.class)
|
.getHandler(FunctionalEventHandler.class)
|
||||||
.register(addon, ConfigPackPreLoadEvent.class)
|
.register(addon, ConfigPackPreLoadEvent.class)
|
||||||
.priority(10)
|
.priority(10)
|
||||||
|
.then(event -> {
|
||||||
|
ImageLibraryPackConfigTemplate config = event.loadTemplate(new ImageLibraryPackConfigTemplate());
|
||||||
|
event.getPack().getContext().put(config);
|
||||||
|
})
|
||||||
.then(event -> {
|
.then(event -> {
|
||||||
ConfigPack pack = event.getPack();
|
ConfigPack pack = event.getPack();
|
||||||
CheckedRegistry<Supplier<ObjectTemplate<Image>>> imageRegistry = pack.getOrCreateRegistry(IMAGE_REGISTRY_KEY);
|
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 javax.imageio.ImageIO;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
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.BufferedImageWrapper;
|
||||||
import com.dfsek.terra.addons.image.image.Image;
|
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.ConfigPack;
|
||||||
import com.dfsek.terra.api.config.Loader;
|
import com.dfsek.terra.api.config.Loader;
|
||||||
import com.dfsek.terra.api.properties.Properties;
|
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
|
* 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 {
|
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)) {
|
if(!pack.getContext().has(ImageCache.class)) {
|
||||||
cache = new ImageCache(new ConcurrentHashMap<>());
|
System.out.println("Initializing new image cache");
|
||||||
pack.getContext().put(cache);
|
var cacheBuilder = Caffeine.newBuilder();
|
||||||
} else {
|
if (config.unloadOnTimeout()) cacheBuilder.expireAfterAccess(config.getCacheTimeout(), TimeUnit.SECONDS);
|
||||||
cache = pack.getContext().get(ImageCache.class);
|
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 images.cache.get(path);
|
||||||
return cache.map.get(path);
|
}
|
||||||
} else {
|
|
||||||
try {
|
private static Image loadImage(String path, Loader files) throws IOException {
|
||||||
BufferedImageWrapper image = new BufferedImageWrapper(ImageIO.read(files.get(path)));
|
System.out.println("Loading image: " + path);
|
||||||
cache.map.put(path, image);
|
try {
|
||||||
return image;
|
return new BufferedImageWrapper(ImageIO.read(files.get(path)));
|
||||||
} catch(IllegalArgumentException e) {
|
} catch(IllegalArgumentException e) {
|
||||||
throw new IllegalArgumentException("Unable to load image (image might be too large?)", e);
|
throw new IllegalArgumentException("Unable to load image (image might be too large?)", e);
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
if(e instanceof FileNotFoundException) {
|
if(e instanceof FileNotFoundException) {
|
||||||
// Rethrow using nicer message
|
// 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: No such file or directory: " + path, e);
|
||||||
}
|
|
||||||
throw new IOException("Unable to load image", 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