All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.palexdev.virtualizedfx.utils.VFXCellsCache Maven / Gradle / Ivy

There is a newer version: 21.6.4
Show newest version
/*
 * Copyright (C) 2024 Parisi Alessandro - [email protected]
 * This file is part of VirtualizedFX (https://github.com/palexdev/VirtualizedFX)
 *
 * VirtualizedFX is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 3 of the License,
 * or (at your option) any later version.
 *
 * VirtualizedFX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with VirtualizedFX. If not, see .
 */

package io.github.palexdev.virtualizedfx.utils;

import io.github.palexdev.virtualizedfx.cells.base.VFXCell;
import io.github.palexdev.virtualizedfx.list.VFXList;

import java.util.Collection;
import java.util.LinkedList;
import java.util.Optional;
import java.util.function.Function;

/**
 * Simple cache implementation for virtualized containers that produce cells of type {@link  VFXCell}.
 * Cells are stored in a {@link CellsQueue}, a special extension of {@link LinkedList}.
 * The cache can be limited in the maximum number of cells to keep by setting its capacity, see {@link VFXList#cacheCapacityProperty()}.
 * Cells won't be added if the capacity is reached and will be disposed immediately instead.
 * 

* Aside from the typical functions of a cache (take/store), you are also allowed to 'populate' it at will, see {@link #populate()}. *

* Note however that to generate cells, the cache must know the function to produce them. The function must be * the same used by the container, therefore, it's set by the container itself. *

* This is mostly useful to 'pre-populate' it, filling the cache before the container is even laid out, * so that at init cells are already built and just need to be updated. *

* Beware, in order for this to work, the cells you are using must allow {@code null} items! *

* Dev Notes *

* I often thought about optimizing the cache by using a {@code Map} instead of a {@code Queue} to store the cached cells. * However, doing so poses a variety of issues that make such an improvement not possible. *

* Pros *

* The enhancement would make the cache more efficient in theory because when a cell is de-cached it could be extracted by * the needed item. Which means that if a cell was previously built for that item, the only update needed would be the index. * The current implementation, polls the first available cell in the cache and updates both the item and the index. *

* Cons *

*

- The {@link #populate()} method could not work because the cells are produced with {@code null} items. So, these * cells would require a separate collection. This would also make controlling the cache capacity a bit harder, as well as * add and poll operations. *

- The mapping [Item -> VFXCell] raises the problem of updating the cache when the items list changes. * If an item is removed from the list, and a mapping is present in the cache, then it becomes invalid. Which means that * the cell associated with that item would need to be disposed or moved to the other collection. *

- T items come from "outside". In this new version of {@code VirtualizedFX}, to simplify the handling of changes in * the items' list, mappings of type [Item -> VFXCell] are used in the state. But to avoid memory leaks, old maps are always * cleaned, so that there is not any reference to old items. So, again, we would have to manage this aspect too for the cache. *

* Ultimately, the number of cons and the implementation complexity they pose make me think that the refactor would not * be worth the effort. Especially considering that I have no empiric proof that it would be more efficient, JMH tests * would be required, which is even more work and thus adds up to the cons. */ public class VFXCellsCache> { //================================================================================ // Properties //================================================================================ private Function cellFactory; private final CellsQueue queue = new CellsQueue<>(0); //================================================================================ // Constructors //================================================================================ public VFXCellsCache(Function cellFactory) {this.cellFactory = cellFactory;} public VFXCellsCache(Function cellFactory, int capacity) { this.cellFactory = cellFactory; queue.setCapacity(capacity); } //================================================================================ // Methods //================================================================================ /** * Fills the cache to its limit. Can be useful to 'pre-populate' the cache before init time, making cells already * available and just in need of an update ({@link VFXCell#updateIndex(int)}, {@link VFXCell#updateItem(Object)}). * * @throws NullPointerException if {@link #getCellFactory()} returns {@code null} */ public VFXCellsCache populate() { if (queue.size() == queue.getCapacity()) return this; // Already at capacity if (cellFactory == null) throw new NullPointerException("Cannot populate cache as the cell factory is null"); do { C c = cellFactory.apply(null); queue.add(c); } while (queue.size() != queue.getCapacity()); return this; } /** * Adds the given cells to the queue. For successfully cached cells, {@link VFXCell#onCache()} will automatically be invoked. */ @SafeVarargs public final VFXCellsCache cache(C... cells) { for (C c : cells) { if (queue.add(c)) c.onCache(); } return this; } /** * Adds the given cells to the queue. For successfully cached cells, {@link VFXCell#onCache()} will automatically be invoked. */ public VFXCellsCache cache(Collection cells) { for (C c : cells) { if (queue.add(c)) c.onCache(); } return this; } /** * Removes one cell from the cache, specifically from the queue's head, so the oldest cached cell. * Beware this can return a {@code null} value, if it's not, {@link VFXCell#onDeCache()} is automatically invoked. */ public C take() { C c = queue.poll(); if (c != null) c.onDeCache(); return c; } /** * Wraps the result of {@link #take()} in an {@link Optional} instance. */ public Optional tryTake() { return Optional.ofNullable(take()); } /** * Removed the specified cell from the cache's queue. The removed cell is also disposed. */ public VFXCellsCache remove(C cell) { boolean removed = queue.remove(cell); if (removed) cell.dispose(); return this; } /** * Disposes and removes all the cells from the cache. */ public VFXCellsCache clear() { queue.forEach(VFXCell::dispose); queue.clear(); return this; } /** * @return the number of cached cells */ public int size() { return queue.size(); } /** * Sets the cache's capacity. */ public VFXCellsCache setCapacity(int capacity) { queue.setCapacity(capacity); return this; } public Function getCellFactory() { return cellFactory; } public void setCellFactory(Function cellFactory) { this.cellFactory = cellFactory; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy