RichTextArea alternative FastCache suggestions

Jurgen Doll jurgen at ivoryemr.co.za
Tue Sep 2 10:45:51 UTC 2025


Hi Andy

I noticed in VFlow.onContentChange (line 1051) that the whole cellCache is cleared, instead of just the relevant cells that were changed being reset. On reviewing the FastCache implementation I can see the difficulty in why this is the case, so after giving it some thought I have two simple variants of FastCache, that would enable resetting of cells in the cache, for you to consider using instead.
First some background. The current FastCache is great for random data content, however if I'm not mistaken the RichTextArea data that it holds isn't random but rather consists of a range (window) of consecutive cells (paragraphs) from some start index to an end index. These ranges usually move linearly forwards or backwards a single cell at a time (for scrolling), or can jump to either the immediately preceeding or proceeding range (for paging), and sometimes jump to another range completely. It's with this type of data content and behavior that the following two variants are based on.
In the first variant the HashMap is simply dropped and the cache is based solely on the Entry<T> array using the modulo of the index and cache size. This then becomes a circular or ring cache:
public class FastCircularCache<T> {
private static record Entry<T>(int index, T cell) { }

private final Entry<T>[] data;
private final int capacity;

public FastCircularCache(int capacity) {
data = new Entry[capacity];
this.capacity = capacity;
}

public T get(int row) {
Entry<T> entry = data[row % capacity];
if (entry != null && entry.index() == row) {
return entry.cell();
}
return null;
}

/**
* Adds a new cell to the cache. NOTE: this method does not check whether
* another cell for the given row is present, so this call must be preceded by a
* {@link #get(int)}.
*/
public void add(int index, T cell) {
data[index % capacity] = new Entry<>(index, cell);
}

public void reset(int start, int end) {
for (int i = start; i < end; i++) {
data[i % capacity] = null;
}
}

public void clear() {
Arrays.fill(data, null);
}
}

In the second variant the array is dropped instead and the cache is based solely on the HashMap together with min and max int variables, which are the minium and maximum indices present at a particular moment in the HashMap. These are used as eviction indices, where min is evicted from the HashMap if the new index is increasing otherwise max is evicted if the new index is decreasing. This is then a window or sliding cache:
public class FastSlidingCache<T> {

private int min = Integer.MAX_VALUE, max = 0;
private final HashMap<Integer, T> data;
private final int capacity;

public FastSlidingCache(int capacity) {
data = new HashMap<>(capacity);
this.capacity = capacity;
}

public T get(int row) {
if (row < min || row > max) return null;
return data.get(row);
}

/**
* Adds a new cell to the cache. When the cache is full, this method evicts a
* cell from the cache first. NOTE: this method does not check whether
* another cell for the given row is present, so this call must be preceded by a
* {@link #get(int)}.
*/
public void add(int index, T cell) {
if (data.size() >= capacity) {
if (index < min) while (!evict(max--));
if (index > max) while (!evict(min++));
}

if (index < min) min = index;
if (index > max) max = index;

data.put(index, cell);
}

private boolean evict(int index) {
return data.remove(index) != null;
}

public void reset(int start, int end) {
for (int i = start; i < end; i++) {
data.remove(i);
}
}

public void clear() {
min = Integer.MAX_VALUE;
data.clear();
max = 0;
}
}
Each variant has it's strengths and weaknesses. I hope you'll find one of them useful or at least inspiring in improving the current FastCache implementation.

Kind regards
Jurgen

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-discuss/attachments/20250902/a4f5500a/attachment-0001.htm>


More information about the openjfx-discuss mailing list