[Enhancement] 延后加载并缓存资源包图标 (#6730)
This commit is contained in:
1 parent
450504ceda
commit
886a25a935
8 files changed
+219
-136
No files matched your search
@@ -27,7 +27,6 @@ import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
@@ -46,7 +45,6 @@ import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.addon.datapack.DataPack;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -55,15 +53,13 @@ import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ItemPropertyAsyncCache;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.IntStream;
|
||||
@@ -263,11 +259,13 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
private final BooleanProperty activeProperty;
|
||||
private final DataPack.Pack packInfo;
|
||||
|
||||
private SoftReference<CompletableFuture<Image>> iconCache;
|
||||
private final ItemPropertyAsyncCache<Image, DataPackInfoObject> iconCache;
|
||||
|
||||
DataPackInfoObject(DataPack.Pack packInfo) {
|
||||
this.packInfo = packInfo;
|
||||
this.activeProperty = packInfo.activeProperty();
|
||||
|
||||
this.iconCache = new ItemPropertyAsyncCache.Soft<>(this, this::loadIcon, this::getDefaultIcon);
|
||||
}
|
||||
|
||||
String getTitle() {
|
||||
@@ -282,7 +280,11 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
return packInfo;
|
||||
}
|
||||
|
||||
Image loadIcon() {
|
||||
private Image getDefaultIcon() {
|
||||
return FXUtils.newBuiltinImage("/assets/img/unknown_pack.png");
|
||||
}
|
||||
|
||||
private Image loadIcon() {
|
||||
Image image = null;
|
||||
Path imagePath;
|
||||
if (this.getPackInfo().isDirectory()) {
|
||||
@@ -291,7 +293,6 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
image = FXUtils.loadImage(imagePath, 64, 64, true, true);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("fail to load image, datapack path: " + getPackInfo().getPath(), e);
|
||||
return FXUtils.newBuiltinImage("/assets/img/unknown_pack.png");
|
||||
}
|
||||
} else {
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(getPackInfo().getPath())) {
|
||||
@@ -301,7 +302,6 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warning("fail to load image, datapack path: " + getPackInfo().getPath(), e);
|
||||
return FXUtils.newBuiltinImage("/assets/img/unknown_pack.png");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,36 +309,9 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
Math.abs(image.getWidth() - image.getHeight()) < 1) {
|
||||
return image;
|
||||
} else {
|
||||
return FXUtils.newBuiltinImage("/assets/img/unknown_pack.png");
|
||||
return getDefaultIcon();
|
||||
}
|
||||
}
|
||||
|
||||
public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference<ObjectProperty<DataPackInfoObject>> current) {
|
||||
SoftReference<CompletableFuture<Image>> iconCache = this.iconCache;
|
||||
CompletableFuture<Image> imageFuture;
|
||||
if (iconCache != null && (imageFuture = iconCache.get()) != null) {
|
||||
Image image = imageFuture.getNow(null);
|
||||
if (image != null) {
|
||||
imageContainer.setImage(image);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
imageFuture = CompletableFuture.supplyAsync(this::loadIcon, Schedulers.io());
|
||||
this.iconCache = new SoftReference<>(imageFuture);
|
||||
}
|
||||
imageContainer.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"));
|
||||
imageFuture.thenAcceptAsync(image -> {
|
||||
if (current != null) {
|
||||
ObjectProperty<DataPackInfoObject> infoObjectProperty = current.get();
|
||||
if (infoObjectProperty == null || infoObjectProperty.get() != this) {
|
||||
// The current ListCell has already switched to another object
|
||||
return;
|
||||
}
|
||||
}
|
||||
imageContainer.setImage(image);
|
||||
}, Schedulers.javafx());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private final class DataPackInfoListCell extends MDListCell<DataPackInfoObject> {
|
||||
@@ -377,7 +350,7 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
|
||||
}
|
||||
checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.activeProperty);
|
||||
dataItem.loadIcon(imageContainer, new WeakReference<>(this.itemProperty()));
|
||||
dataItem.iconCache.attachValue(imageContainer.imageProperty(), new WeakReference<>(this.itemProperty()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
@@ -64,13 +63,13 @@ import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ItemPropertyAsyncCache;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
@@ -599,13 +598,15 @@ public final class ModListPage extends ListPageBase<ModListPage.ModInfoObject> i
|
||||
private final LocalModFile localModFile;
|
||||
private final @Nullable ModTranslations.Mod modTranslations;
|
||||
|
||||
private SoftReference<CompletableFuture<Image>> iconCache;
|
||||
private final ItemPropertyAsyncCache<Image, ModInfoObject> iconCache;
|
||||
|
||||
ModInfoObject(LocalModFile localModFile) {
|
||||
this.localModFile = localModFile;
|
||||
this.active = localModFile.activeProperty();
|
||||
|
||||
this.modTranslations = ModTranslations.MOD.getMod(localModFile.getId(), localModFile.getName());
|
||||
|
||||
this.iconCache = new ItemPropertyAsyncCache.Soft<>(this, this::loadIcon, this::getDefaultIcon);
|
||||
}
|
||||
|
||||
public LocalModFile getModInfo() {
|
||||
@@ -616,7 +617,10 @@ public final class ModListPage extends ListPageBase<ModListPage.ModInfoObject> i
|
||||
return modTranslations;
|
||||
}
|
||||
|
||||
@FXThread
|
||||
private Image getDefaultIcon() {
|
||||
return GameInstanceIconType.getIconType(this.localModFile.getModLoaderType()).getIcon();
|
||||
}
|
||||
|
||||
private Image loadIcon() {
|
||||
List<String> iconPaths = new ArrayList<>();
|
||||
|
||||
@@ -639,34 +643,7 @@ public final class ModListPage extends ListPageBase<ModListPage.ModInfoObject> i
|
||||
LOG.warning("Failed to load mod icons", e);
|
||||
}
|
||||
|
||||
return GameInstanceIconType.getIconType(this.localModFile.getModLoaderType()).getIcon();
|
||||
}
|
||||
|
||||
public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference<ObjectProperty<ModInfoObject>> current) {
|
||||
SoftReference<CompletableFuture<Image>> iconCache = this.iconCache;
|
||||
CompletableFuture<Image> imageFuture;
|
||||
if (iconCache != null && (imageFuture = iconCache.get()) != null) {
|
||||
Image image = imageFuture.getNow(null);
|
||||
if (image != null) {
|
||||
imageContainer.setImage(image);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
imageFuture = CompletableFuture.supplyAsync(this::loadIcon, Schedulers.io());
|
||||
this.iconCache = new SoftReference<>(imageFuture);
|
||||
}
|
||||
imageContainer.setImage(GameInstanceIconType.getIconType(localModFile.getModLoaderType()).getIcon());
|
||||
imageFuture.thenAcceptAsync(image -> {
|
||||
if (current != null) {
|
||||
ObjectProperty<ModInfoObject> infoObjectProperty = current.get();
|
||||
if (infoObjectProperty == null || infoObjectProperty.get() != this) {
|
||||
// The current ListCell has already switched to another object
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
imageContainer.setImage(image);
|
||||
}, Schedulers.javafx());
|
||||
return getDefaultIcon();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,7 +660,7 @@ public final class ModListPage extends ListPageBase<ModListPage.ModInfoObject> i
|
||||
|
||||
var imageContainer = new ImageContainer(40);
|
||||
titleContainer.setAlignment(Pos.CENTER_LEFT);
|
||||
modInfo.loadIcon(imageContainer, null);
|
||||
modInfo.iconCache.attachValue(imageContainer.imageProperty(), null);
|
||||
|
||||
TwoLineListItem title = new TwoLineListItem();
|
||||
title.getTitleLabel().setWrapText(true);
|
||||
@@ -845,7 +822,7 @@ public final class ModListPage extends ListPageBase<ModListPage.ModInfoObject> i
|
||||
|
||||
ModLoaderType modLoaderType = modInfo.getModLoaderType();
|
||||
|
||||
dataItem.loadIcon(imageContainer, new WeakReference<>(this.itemProperty()));
|
||||
dataItem.iconCache.attachValue(imageContainer.imageProperty(), new WeakReference<>(this.itemProperty()));
|
||||
|
||||
String displayName = modInfo.getName();
|
||||
if (modTranslations != null && I18n.isUseChinese()) {
|
||||
|
||||
@@ -57,14 +57,14 @@ import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.javafx.ItemPropertyAsyncCache;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.Objects;
|
||||
@@ -494,13 +494,18 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResourcePackInfoObject {
|
||||
public static final class ResourcePackInfoObject {
|
||||
|
||||
private final ResourcePackFile file;
|
||||
private final BooleanProperty enabled;
|
||||
|
||||
private final ItemPropertyAsyncCache<Image, ResourcePackInfoObject> iconCache;
|
||||
|
||||
public ResourcePackInfoObject(Pair<ResourcePackFile, Boolean> pair) {
|
||||
this.file = pair.key();
|
||||
this.enabled = new SimpleBooleanProperty(this, "enabled", pair.value());
|
||||
|
||||
this.iconCache = new ItemPropertyAsyncCache.Soft<>(this, this::loadIcon, this::getDefaultIcon);
|
||||
}
|
||||
|
||||
public ResourcePackFile getFile() {
|
||||
@@ -511,13 +516,16 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
return enabled;
|
||||
}
|
||||
|
||||
Image getIcon() {
|
||||
Image image = file.getIcon();
|
||||
if (image == null || image.isError() || image.getWidth() <= 0 || image.getHeight() <= 0 ||
|
||||
(Math.abs(image.getWidth() - image.getHeight()) >= 1)) {
|
||||
image = FXUtils.newBuiltinImage("/assets/img/unknown_pack.png");
|
||||
private Image getDefaultIcon() {
|
||||
return FXUtils.newBuiltinImage("/assets/img/unknown_pack.png");
|
||||
}
|
||||
|
||||
private Image loadIcon() {
|
||||
Image icon = file.loadIcon();
|
||||
if (icon != null && !icon.isError() && icon.getWidth() > 0 && icon.getHeight() > 0 && Math.abs(icon.getWidth() - icon.getHeight()) < 1) {
|
||||
return icon;
|
||||
}
|
||||
return image;
|
||||
return getDefaultIcon();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,7 +591,7 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
if (empty || item == null) return;
|
||||
|
||||
ResourcePackFile file = item.getFile();
|
||||
imageContainer.setImage(item.getIcon());
|
||||
item.iconCache.attachValue(imageContainer.imageProperty(), new WeakReference<>(itemProperty()));
|
||||
|
||||
content.getTags().clear();
|
||||
content.setTitle(file.getFileName());
|
||||
@@ -623,7 +631,7 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
maxWidthProperty().bind(Controllers.getDecorator().contentWidthProperty().multiply(0.7));
|
||||
|
||||
ImageContainer imageContainer = new ImageContainer(40);
|
||||
imageContainer.setImage(packInfoObject.getIcon());
|
||||
packInfoObject.iconCache.attachValue(imageContainer.imageProperty(), null);
|
||||
|
||||
TwoLineListItem title = new TwoLineListItem();
|
||||
title.setTitle(pack.getFileName());
|
||||
|
||||
@@ -114,16 +114,16 @@ public sealed abstract class ResourcePackFile extends LocalAddonFile implements
|
||||
|
||||
@Nullable
|
||||
@Contract(pure = true)
|
||||
public abstract PackMcMeta getMeta();
|
||||
public abstract PackMcMeta.PackInfo getPackInfo();
|
||||
|
||||
@Nullable
|
||||
public LocalAddonFile.Description getDescription() {
|
||||
if (getMeta() == null || getMeta().pack() == null) return null;
|
||||
return getMeta().pack().description();
|
||||
if (getPackInfo() == null) return null;
|
||||
return getPackInfo().description();
|
||||
}
|
||||
|
||||
// 64*64
|
||||
public abstract @Nullable Image getIcon();
|
||||
public abstract @Nullable Image loadIcon();
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ResourcePackFile other) {
|
||||
|
||||
+16
-19
@@ -30,8 +30,7 @@ import java.nio.file.Path;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
final class ResourcePackFolder extends ResourcePackFile {
|
||||
private final PackMcMeta meta;
|
||||
private final @Nullable Image icon;
|
||||
private final PackMcMeta.PackInfo info;
|
||||
|
||||
public ResourcePackFolder(ResourcePackManager manager, Path path) {
|
||||
super(manager, path);
|
||||
@@ -42,33 +41,31 @@ final class ResourcePackFolder extends ResourcePackFile {
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to parse resource pack meta", e);
|
||||
}
|
||||
this.meta = meta;
|
||||
this.info = meta != null ? meta.pack() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackMcMeta.PackInfo getPackInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Image loadIcon() {
|
||||
byte[] iconData = null;
|
||||
Image iconTemp = null;
|
||||
try {
|
||||
iconData = Files.readAllBytes(path.resolve("pack.png"));
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read resource pack icon", e);
|
||||
iconData = Files.readAllBytes(getFile().resolve("pack.png"));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resource pack icon", e);
|
||||
}
|
||||
|
||||
if (iconData != null) {
|
||||
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) {
|
||||
iconTemp = new Image(inputStream, 64, 64, true, true);
|
||||
return new Image(inputStream, 64, 64, true, true);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resource pack icon", e);
|
||||
}
|
||||
}
|
||||
this.icon = iconTemp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackMcMeta getMeta() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Image getIcon() {
|
||||
return icon;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-4
@@ -522,18 +522,18 @@ public final class ResourcePackManager extends LocalAddonManager<ResourcePackFil
|
||||
}
|
||||
|
||||
public ResourcePackFile.Compatibility getCompatibility(@NotNull ResourcePackFile resourcePack) {
|
||||
if (resourcePack.getMeta() == null || resourcePack.getMeta().pack() == null)
|
||||
if (resourcePack.getPackInfo() == null)
|
||||
return ResourcePackFile.Compatibility.MISSING_PACK_META;
|
||||
if (this.getRequiredVersion().isUnspecified())
|
||||
return ResourcePackFile.Compatibility.MISSING_GAME_META;
|
||||
int requiredMajor = getRequiredVersion().majorVersion();
|
||||
VersionRange<PackMcMeta.PackVersion> versionRange;
|
||||
if (requiredMajor > 64)
|
||||
versionRange = getResourcePackVersionRangeNew(resourcePack.getMeta().pack());
|
||||
versionRange = getResourcePackVersionRangeNew(resourcePack.getPackInfo());
|
||||
else if (requiredMajor > 15)
|
||||
versionRange = getResourcePackVersionRangeOld(resourcePack.getMeta().pack());
|
||||
versionRange = getResourcePackVersionRangeOld(resourcePack.getPackInfo());
|
||||
else
|
||||
versionRange = getResourcePackVersionRangeOldest(resourcePack.getMeta().pack());
|
||||
versionRange = getResourcePackVersionRangeOldest(resourcePack.getPackInfo());
|
||||
if (versionRange.isEmpty())
|
||||
return ResourcePackFile.Compatibility.INVALID;
|
||||
if (versionRange.getMaximum().compareTo(this.getRequiredVersion()) < 0)
|
||||
|
||||
+20
-25
@@ -36,52 +36,47 @@ import java.util.Optional;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
final class ResourcePackZipFile extends ResourcePackFile {
|
||||
private final PackMcMeta meta;
|
||||
private final @Nullable Image icon;
|
||||
private final PackMcMeta.PackInfo info;
|
||||
|
||||
public ResourcePackZipFile(ResourcePackManager manager, Path path) throws IOException {
|
||||
super(manager, path);
|
||||
|
||||
PackMcMeta metaTemp = null;
|
||||
byte[] iconData = null;
|
||||
|
||||
PackMcMeta meta = null;
|
||||
try (var zipFileTree = CompressingUtils.openZipTree(path)) {
|
||||
try {
|
||||
metaTemp = PackMcMeta.fromNonNullJson(zipFileTree.readTextEntry("/pack.mcmeta"));
|
||||
meta = PackMcMeta.fromNonNullJson(zipFileTree.readTextEntry("/pack.mcmeta"));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to parse resource pack meta", e);
|
||||
}
|
||||
}
|
||||
this.info = meta != null ? meta.pack() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackMcMeta.PackInfo getPackInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Image loadIcon() {
|
||||
byte[] iconData = null;
|
||||
try (var zipFileTree = CompressingUtils.openZipTree(getFile())) {
|
||||
var iconEntry = zipFileTree.getEntry("/pack.png");
|
||||
if (iconEntry != null) {
|
||||
try {
|
||||
iconData = zipFileTree.readBinaryEntry(iconEntry);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resource pack icon", e);
|
||||
}
|
||||
iconData = zipFileTree.readBinaryEntry(iconEntry);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resource pack icon", e);
|
||||
}
|
||||
this.meta = metaTemp;
|
||||
|
||||
Image iconTemp = null;
|
||||
if (iconData != null) {
|
||||
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) {
|
||||
iconTemp = new Image(inputStream, 64, 64, true, true);
|
||||
return new Image(inputStream, 64, 64, true, true);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resource pack icon", e);
|
||||
}
|
||||
}
|
||||
this.icon = iconTemp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackMcMeta getMeta() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Image getIcon() {
|
||||
return icon;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.javafx;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/// Cache for a property of an item of [javafx.scene.control.ListCell].
|
||||
///
|
||||
/// @param <T> type of the property value
|
||||
/// @param <B> type of the bean, aka. the item
|
||||
public abstract class ItemPropertyAsyncCache<T, B> {
|
||||
|
||||
private final B bean;
|
||||
|
||||
public ItemPropertyAsyncCache(B bean) {
|
||||
this.bean = Objects.requireNonNull(bean);
|
||||
}
|
||||
|
||||
protected abstract @Nullable T getValue();
|
||||
|
||||
protected abstract @Nullable T getDefault();
|
||||
|
||||
/// @return the completable future that loads the value, or null if not set or already garbage-collected
|
||||
@Nullable
|
||||
protected abstract CompletableFuture<T> getFuture();
|
||||
|
||||
protected abstract void setFuture(@NotNull CompletableFuture<T> imageFuture);
|
||||
|
||||
public final void attachValue(ObjectProperty<T> property, @Nullable WeakReference<ObjectProperty<B>> current) {
|
||||
CompletableFuture<T> future = getFuture();
|
||||
if (future != null) {
|
||||
T value = future.getNow(null);
|
||||
if (value != null) {
|
||||
property.set(value);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
future = CompletableFuture.supplyAsync(this::getValue, Schedulers.io());
|
||||
setFuture(future);
|
||||
}
|
||||
property.set(getDefault());
|
||||
future.thenAcceptAsync(image -> {
|
||||
if (current != null) {
|
||||
ObjectProperty<B> thisProperty = current.get();
|
||||
if (thisProperty == null || thisProperty.get() != bean) {
|
||||
// The current ListCell has already switched to another object
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
property.set(image);
|
||||
}, Schedulers.javafx());
|
||||
}
|
||||
|
||||
///
|
||||
/// @param <T> {@inheritDoc}
|
||||
/// @param <B> {@inheritDoc}
|
||||
private static abstract class Base<T, B> extends ItemPropertyAsyncCache<T, B> {
|
||||
|
||||
private final Supplier<T> valueSupplier, defaultSupplier;
|
||||
|
||||
///
|
||||
/// @param bean the item of list cell
|
||||
/// @param valueSupplier supplier of the value
|
||||
/// @param defaultSupplier supplier of the placeholder value
|
||||
public Base(B bean, Supplier<T> valueSupplier, @Nullable Supplier<T> defaultSupplier) {
|
||||
super(bean);
|
||||
this.valueSupplier = Objects.requireNonNull(valueSupplier);
|
||||
this.defaultSupplier = Objects.requireNonNullElse(defaultSupplier, () -> null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable T getValue() {
|
||||
return valueSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable T getDefault() {
|
||||
return defaultSupplier.get();
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of [ItemPropertyAsyncCache], using a [SoftReference] to balance the cost of time and memory.
|
||||
///
|
||||
/// @param <T> {@inheritDoc}
|
||||
/// @param <B> {@inheritDoc}
|
||||
public static final class Soft<T, B> extends Base<T, B> {
|
||||
|
||||
private SoftReference<@Nullable CompletableFuture<T>> cache = null;
|
||||
|
||||
///
|
||||
/// @param bean the item of list cell
|
||||
/// @param valueSupplier supplier of the value
|
||||
/// @param defaultSupplier supplier of the placeholder value
|
||||
public Soft(B bean, Supplier<T> valueSupplier, @Nullable Supplier<T> defaultSupplier) {
|
||||
super(bean, valueSupplier, defaultSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable CompletableFuture<T> getFuture() {
|
||||
return cache != null ? cache.get() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setFuture(@NotNull CompletableFuture<T> future) {
|
||||
this.cache = new SoftReference<>(future);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user