Add caching for streaming texts
This commit is contained in:
parent
2ea148c30a
commit
708f9fc6d5
@ -0,0 +1,143 @@
|
||||
package io.noties.markwon.ext.latex;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.LruCache;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import ru.noties.jlatexmath.JLatexMathDrawable;
|
||||
|
||||
/**
|
||||
* Cache for JLatexMathDrawable instances to improve performance
|
||||
* by avoiding redundant rendering of the same LaTeX formulas.
|
||||
*
|
||||
*/
|
||||
public class JLatexMathDrawableCache {
|
||||
|
||||
private static final int DEFAULT_CACHE_SIZE = 32;
|
||||
|
||||
// Singleton instance
|
||||
private static volatile JLatexMathDrawableCache instance;
|
||||
|
||||
// LRU cache to store rendered LaTeX drawables
|
||||
private final LruCache<String, Object> cache;
|
||||
|
||||
// Whether to enable the cache
|
||||
private final boolean enabled;
|
||||
|
||||
private JLatexMathDrawableCache(int maxSize, boolean enabled) {
|
||||
this.cache = new LruCache<>(maxSize);
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of the cache
|
||||
*/
|
||||
@NonNull
|
||||
public static JLatexMathDrawableCache getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (JLatexMathDrawableCache.class) {
|
||||
if (instance == null) {
|
||||
instance = new JLatexMathDrawableCache(DEFAULT_CACHE_SIZE, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance with custom configuration
|
||||
*
|
||||
* @param maxSize maximum number of entries in the cache
|
||||
* @param enabled whether the cache is enabled
|
||||
* @return a new cache instance
|
||||
*/
|
||||
@NonNull
|
||||
public static JLatexMathDrawableCache create(int maxSize, boolean enabled) {
|
||||
return new JLatexMathDrawableCache(maxSize, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a drawable from the cache
|
||||
*
|
||||
* @param key the LaTeX formula string
|
||||
* @return the cached drawable or null if not found
|
||||
*/
|
||||
/**
|
||||
* Cache entry containing both the drawable and its configuration
|
||||
*/
|
||||
private static class CacheEntry {
|
||||
final Drawable drawable;
|
||||
final JLatexMathDrawable.Builder builder;
|
||||
|
||||
CacheEntry(Drawable drawable, JLatexMathDrawable.Builder builder) {
|
||||
this.drawable = drawable;
|
||||
this.builder = builder;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Drawable get(@NonNull String key) {
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
synchronized (cache) {
|
||||
final CacheEntry entry = (CacheEntry) cache.get(key);
|
||||
if (entry != null) {
|
||||
if (entry.drawable instanceof JLatexMathDrawable && entry.builder != null) {
|
||||
// Recreate the drawable with the same configuration
|
||||
return entry.builder.build();
|
||||
}
|
||||
return entry.drawable;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a drawable in the cache
|
||||
*
|
||||
* @param key the LaTeX formula string
|
||||
* @param drawable the drawable to cache
|
||||
*/
|
||||
public void put(@NonNull String key, @NonNull Drawable drawable) {
|
||||
put(key, drawable, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a drawable in the cache with its builder for recreation
|
||||
*
|
||||
* @param key the LaTeX formula string
|
||||
* @param drawable the drawable to cache
|
||||
* @param builder the builder used to create the drawable
|
||||
*/
|
||||
public void put(@NonNull String key, @NonNull Drawable drawable, @Nullable JLatexMathDrawable.Builder builder) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (cache) {
|
||||
cache.put(key, new CacheEntry(drawable, builder));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache
|
||||
*/
|
||||
public void clear() {
|
||||
synchronized (cache) {
|
||||
cache.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current size of the cache
|
||||
*/
|
||||
public int size() {
|
||||
synchronized (cache) {
|
||||
return cache.size();
|
||||
}
|
||||
}
|
||||
}
|
@ -359,16 +359,51 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
|
||||
private final Config config;
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
private final Map<AsyncDrawable, Future<?>> cache = new HashMap<>(3);
|
||||
|
||||
private final JLatexMathDrawableCache drawableCache;
|
||||
JLatextAsyncDrawableLoader(@NonNull Config config) {
|
||||
this.config = config;
|
||||
this.drawableCache = config.cacheEnabled
|
||||
? JLatexMathDrawableCache.create(config.cacheSize, true)
|
||||
: JLatexMathDrawableCache.create(0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(@NonNull final AsyncDrawable drawable) {
|
||||
|
||||
// this method must be called from main-thread only (thus synchronization can be skipped)
|
||||
|
||||
final String latex = drawable.getDestination();
|
||||
|
||||
// Check the cache first
|
||||
final Drawable cachedDrawable = drawableCache.get(latex);
|
||||
if (cachedDrawable != null) {
|
||||
// Use cached drawable immediately
|
||||
drawable.setResult(cachedDrawable);
|
||||
return;
|
||||
}
|
||||
|
||||
// If synchronous rendering is enabled, render immediately on the main thread
|
||||
if (config.syncRendering) {
|
||||
try {
|
||||
final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable;
|
||||
final JLatexMathDrawable result;
|
||||
|
||||
if (jLatextAsyncDrawable.isBlock()) {
|
||||
result = createBlockDrawable(jLatextAsyncDrawable);
|
||||
} else {
|
||||
result = createInlineDrawable(jLatextAsyncDrawable);
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
drawableCache.put(latex, result);
|
||||
|
||||
// Set the result immediately
|
||||
drawable.setResult(result);
|
||||
} catch (Throwable t) {
|
||||
handleRenderingError(drawable, t);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// check for currently running tasks associated with provided drawable
|
||||
final Future<?> future = cache.get(drawable);
|
||||
|
||||
@ -376,7 +411,6 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
|
||||
// as asyncDrawable is immutable, it won't have destination changed (so there is no need
|
||||
// to cancel any started tasks)
|
||||
if (future == null) {
|
||||
|
||||
cache.put(drawable, config.executorService.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -384,32 +418,12 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
|
||||
try {
|
||||
execute();
|
||||
} catch (Throwable t) {
|
||||
// @since 4.3.0 add error handling
|
||||
final ErrorHandler errorHandler = config.errorHandler;
|
||||
if (errorHandler == null) {
|
||||
// as before
|
||||
Log.e(
|
||||
"JLatexMathPlugin",
|
||||
"Error displaying latex: `" + drawable.getDestination() + "`",
|
||||
t);
|
||||
} else {
|
||||
// just call `getDestination` without casts and checks
|
||||
final Drawable errorDrawable = errorHandler.handleError(
|
||||
drawable.getDestination(),
|
||||
t
|
||||
);
|
||||
if (errorDrawable != null) {
|
||||
DrawableUtils.applyIntrinsicBoundsIfEmpty(errorDrawable);
|
||||
setResult(drawable, errorDrawable);
|
||||
}
|
||||
}
|
||||
handleRenderingError(drawable, t);
|
||||
}
|
||||
}
|
||||
|
||||
private void execute() {
|
||||
|
||||
final JLatexMathDrawable jLatexMathDrawable;
|
||||
|
||||
final JLatextAsyncDrawable jLatextAsyncDrawable = (JLatextAsyncDrawable) drawable;
|
||||
|
||||
if (jLatextAsyncDrawable.isBlock()) {
|
||||
@ -418,12 +432,38 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
|
||||
jLatexMathDrawable = createInlineDrawable(jLatextAsyncDrawable);
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
drawableCache.put(latex, jLatexMathDrawable);
|
||||
|
||||
setResult(drawable, jLatexMathDrawable);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRenderingError(@NonNull AsyncDrawable drawable, @NonNull Throwable t) {
|
||||
// @since 4.3.0 add error handling
|
||||
final ErrorHandler errorHandler = config.errorHandler;
|
||||
if (errorHandler == null) {
|
||||
// as before
|
||||
Log.e(
|
||||
"JLatexMathPlugin",
|
||||
"Error displaying latex: `" + drawable.getDestination() + "`",
|
||||
t);
|
||||
} else {
|
||||
// just call `getDestination` without casts and checks
|
||||
final Drawable errorDrawable = errorHandler.handleError(
|
||||
drawable.getDestination(),
|
||||
t
|
||||
);
|
||||
if (errorDrawable != null) {
|
||||
DrawableUtils.applyIntrinsicBoundsIfEmpty(errorDrawable);
|
||||
setResult(drawable, errorDrawable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void cancel(@NonNull AsyncDrawable drawable) {
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user