mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 23:20:23 +00:00
Optimizations for large connection amounts
This commit is contained in:
parent
1e990389f4
commit
b9ebce0771
8 changed files with 68 additions and 181 deletions
|
@ -3,6 +3,7 @@ package io.xpipe.app.comp.base;
|
|||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ListChangeListener;
|
||||
|
@ -91,8 +92,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
}
|
||||
|
||||
if (!listView.getChildren().equals(newShown)) {
|
||||
listView.getChildren().setAll(newShown);
|
||||
listView.layout();
|
||||
BindingsHelper.setContent(listView.getChildren(), newShown);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.PrettyListView;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ListViewComp<T> extends Comp<CompStructure<ListView<Node>>> {
|
||||
|
||||
private final ObservableList<T> shown;
|
||||
private final ObservableList<T> all;
|
||||
private final Property<T> selected;
|
||||
private final Function<T, Comp<?>> compFunction;
|
||||
|
||||
public ListViewComp(
|
||||
ObservableList<T> shown, ObservableList<T> all, Property<T> selected, Function<T, Comp<?>> compFunction) {
|
||||
this.shown = PlatformThread.sync(shown);
|
||||
this.all = PlatformThread.sync(all);
|
||||
this.selected = selected;
|
||||
this.compFunction = compFunction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<ListView<Node>> createBase() {
|
||||
Map<T, Region> cache = new HashMap<>();
|
||||
|
||||
PrettyListView<Node> listView = new PrettyListView<>();
|
||||
listView.setFocusTraversable(false);
|
||||
if (selected == null) {
|
||||
listView.disableSelection();
|
||||
}
|
||||
|
||||
refresh(listView, shown, cache, false);
|
||||
listView.requestLayout();
|
||||
|
||||
if (selected != null) {
|
||||
if (selected.getValue() != null && shown.contains(selected.getValue())) {
|
||||
listView.getSelectionModel().select(shown.indexOf(selected.getValue()));
|
||||
}
|
||||
|
||||
AtomicBoolean internalSelection = new AtomicBoolean(false);
|
||||
listView.getSelectionModel().selectedItemProperty().addListener((c, o, n) -> {
|
||||
// if (true) return;
|
||||
|
||||
var item = cache.entrySet().stream()
|
||||
.filter(e -> e.getValue().equals(n))
|
||||
.map(e -> e.getKey())
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
internalSelection.set(true);
|
||||
selected.setValue(item);
|
||||
internalSelection.set(false);
|
||||
});
|
||||
|
||||
selected.addListener((c, o, n) -> {
|
||||
if (internalSelection.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedNode = cache.get(n);
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
listView.getSelectionModel().select(selectedNode);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
listView.getSelectionModel().selectedItemProperty().addListener((c, o, n) -> {
|
||||
if (n != null) {
|
||||
listView.getSelectionModel().clearSelection();
|
||||
listView.getScene().getRoot().requestFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
shown.addListener((ListChangeListener<? super T>) (c) -> {
|
||||
refresh(listView, c.getList(), cache, true);
|
||||
});
|
||||
|
||||
all.addListener((ListChangeListener<? super T>) c -> {
|
||||
cache.keySet().retainAll(c.getList());
|
||||
});
|
||||
|
||||
return new SimpleCompStructure<>(listView);
|
||||
}
|
||||
|
||||
private void refresh(ListView<Node> listView, List<? extends T> c, Map<T, Region> cache, boolean asynchronous) {
|
||||
Runnable update = () -> {
|
||||
var newShown = c.stream()
|
||||
.map(v -> {
|
||||
if (!cache.containsKey(v)) {
|
||||
cache.put(v, compFunction.apply(v).createRegion());
|
||||
}
|
||||
|
||||
return cache.get(v);
|
||||
})
|
||||
.toList();
|
||||
|
||||
if (!listView.getItems().equals(newShown)) {
|
||||
BindingsHelper.setContent(listView.getItems(), newShown);
|
||||
listView.layout();
|
||||
}
|
||||
};
|
||||
|
||||
if (asynchronous) {
|
||||
Platform.runLater(update);
|
||||
} else {
|
||||
PlatformThread.runLaterIfNeeded(update);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,12 +24,10 @@ public class MultiContentComp extends SimpleComp {
|
|||
stack.setPickOnBounds(false);
|
||||
for (Map.Entry<Comp<?>, ObservableBooleanValue> entry : content.entrySet()) {
|
||||
var region = entry.getKey().createRegion();
|
||||
stack.getChildren().add(region);
|
||||
SimpleChangeListener.apply(PlatformThread.sync(entry.getValue()), val -> {
|
||||
if (val) {
|
||||
stack.getChildren().add(region);
|
||||
} else {
|
||||
stack.getChildren().remove(region);
|
||||
}
|
||||
region.setManaged(val);
|
||||
region.setVisible(val);
|
||||
});
|
||||
}
|
||||
return stack;
|
||||
|
|
|
@ -136,8 +136,7 @@ public class StoreSection {
|
|||
var filtered = BindingsHelper.filteredContentBinding(
|
||||
ordered,
|
||||
section -> {
|
||||
return category.getValue().contains(section.getWrapper().getEntry())
|
||||
&& section.shouldShow(filterString.get())
|
||||
return section.shouldShow(filterString.get())
|
||||
&& section.anyMatches(entryFilter);
|
||||
},
|
||||
category,
|
||||
|
|
|
@ -37,14 +37,16 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
|
||||
@Override
|
||||
public CompStructure<VBox> createBase() {
|
||||
var root = StandardStoreEntryComp.customSection(section, topLevel).apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
|
||||
var root = StandardStoreEntryComp.customSection(section, topLevel)
|
||||
.apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS));
|
||||
var button = new IconButtonComp(
|
||||
Bindings.createStringBinding(
|
||||
() -> section.getWrapper().getExpanded().get()
|
||||
&& section.getShownChildren().size() > 0
|
||||
? "mdal-keyboard_arrow_down"
|
||||
: "mdal-keyboard_arrow_right",
|
||||
section.getWrapper().getExpanded(), section.getShownChildren()),
|
||||
section.getWrapper().getExpanded(),
|
||||
section.getShownChildren()),
|
||||
() -> {
|
||||
section.getWrapper().toggleExpanded();
|
||||
})
|
||||
|
@ -58,13 +60,27 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
.styleClass("expand-button");
|
||||
List<Comp<?>> topEntryList = List.of(button, root);
|
||||
|
||||
var content = new ListBoxViewComp<>(section.getShownChildren(), section.getAllChildren(), (StoreSection e) -> {
|
||||
// Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the
|
||||
// section is actually expanded
|
||||
var listSections = BindingsHelper.filteredContentBinding(
|
||||
section.getShownChildren(),
|
||||
storeSection -> section.getAllChildren().size() <= 20
|
||||
|| section.getWrapper().getExpanded().get(),
|
||||
section.getWrapper().getExpanded(),
|
||||
section.getAllChildren());
|
||||
var content = new ListBoxViewComp<>(listSections, section.getAllChildren(), (StoreSection e) -> {
|
||||
return StoreSection.customSection(e, false).apply(GrowAugment.create(true, false));
|
||||
}).withLimit(100).hgrow();
|
||||
})
|
||||
.withLimit(100)
|
||||
.hgrow();
|
||||
|
||||
var expanded = Bindings.createBooleanBinding(() -> {
|
||||
return section.getWrapper().getExpanded().get() && section.getShownChildren().size() > 0;
|
||||
}, section.getWrapper().getExpanded(), section.getShownChildren());
|
||||
var expanded = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return section.getWrapper().getExpanded().get()
|
||||
&& section.getShownChildren().size() > 0;
|
||||
},
|
||||
section.getWrapper().getExpanded(),
|
||||
section.getShownChildren());
|
||||
|
||||
return new VerticalComp(List.of(
|
||||
new HorizontalComp(topEntryList)
|
||||
|
@ -84,7 +100,8 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
});
|
||||
struc.get().pseudoClassStateChanged(EVEN, section.getDepth() % 2 == 0);
|
||||
struc.get().pseudoClassStateChanged(ODD, section.getDepth() % 2 != 0);
|
||||
}).apply(struc -> SimpleChangeListener.apply(section.getWrapper().getColor(), val -> {
|
||||
})
|
||||
.apply(struc -> SimpleChangeListener.apply(section.getWrapper().getColor(), val -> {
|
||||
if (!topLevel) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -266,59 +266,43 @@ public class BindingsHelper {
|
|||
return l1;
|
||||
}
|
||||
|
||||
public static <T> void setContent(ObservableList<T> toSet, List<? extends T> newList) {
|
||||
if (toSet.equals(newList)) {
|
||||
public static <T> void setContent(ObservableList<T> target, List<? extends T> newList) {
|
||||
if (target.equals(newList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (toSet.size() == 0) {
|
||||
toSet.setAll(newList);
|
||||
if (target.size() == 0) {
|
||||
target.setAll(newList);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newList.containsAll(toSet)) {
|
||||
var l = new ArrayList<>(newList);
|
||||
l.removeIf(t -> !toSet.contains(t));
|
||||
if (!l.equals(toSet)) {
|
||||
toSet.setAll(newList);
|
||||
return;
|
||||
}
|
||||
|
||||
var start = 0;
|
||||
for (int end = 0; end <= toSet.size(); end++) {
|
||||
var index = end < toSet.size() ? newList.indexOf(toSet.get(end)) : newList.size();
|
||||
for (; start < index; start++) {
|
||||
toSet.add(start, newList.get(start));
|
||||
}
|
||||
start = index + 1;
|
||||
}
|
||||
if (newList.size() == 0) {
|
||||
target.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (toSet.contains(newList)) {
|
||||
var l = new ArrayList<>(newList);
|
||||
l.removeAll(toSet);
|
||||
newList.removeAll(l);
|
||||
var targetSet = new HashSet<>(target);
|
||||
var newSet = new HashSet<>(newList);
|
||||
|
||||
// Only add missing element
|
||||
if (targetSet.size() + 1 == newList.size() && newSet.containsAll(targetSet)) {
|
||||
var l = new HashSet<>(newSet);
|
||||
l.removeAll(targetSet);
|
||||
var found = l.iterator().next();
|
||||
var index = newList.indexOf(found);
|
||||
target.add(index, found);
|
||||
return;
|
||||
}
|
||||
|
||||
toSet.removeIf(e -> !newList.contains(e));
|
||||
|
||||
if (toSet.size() + 1 == newList.size() && newList.containsAll(toSet)) {
|
||||
var l = new ArrayList<>(newList);
|
||||
l.removeAll(toSet);
|
||||
var index = newList.indexOf(l.get(0));
|
||||
toSet.add(index, l.get(0));
|
||||
// Only remove not needed element
|
||||
if (target.size() - 1 == newList.size() && targetSet.containsAll(newSet)) {
|
||||
var l = new HashSet<>(targetSet);
|
||||
l.removeAll(newSet);
|
||||
target.remove(l.iterator().next());
|
||||
return;
|
||||
}
|
||||
|
||||
if (toSet.size() - 1 == newList.size() && toSet.containsAll(newList)) {
|
||||
var l = new ArrayList<>(toSet);
|
||||
l.removeAll(newList);
|
||||
toSet.remove(l.get(0));
|
||||
return;
|
||||
}
|
||||
|
||||
toSet.setAll(newList);
|
||||
// Other cases are more difficult
|
||||
target.setAll(newList);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,6 +162,7 @@ public abstract class DataStorage {
|
|||
},
|
||||
pair.getKey());
|
||||
});
|
||||
e.setChildrenCache(newChildren.stream().map(DataStoreEntryRef::get).toList());
|
||||
saveAsync();
|
||||
return !newChildren.isEmpty();
|
||||
}
|
||||
|
@ -251,6 +252,10 @@ public abstract class DataStorage {
|
|||
return List.of();
|
||||
}
|
||||
|
||||
if (entry.getChildrenCache() != null) {
|
||||
return entry.getChildrenCache();
|
||||
}
|
||||
|
||||
var children = new ArrayList<>(entries.stream()
|
||||
.filter(other -> {
|
||||
if (other.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
|
||||
|
@ -262,6 +267,7 @@ public abstract class DataStorage {
|
|||
&& parent.get().equals(entry);
|
||||
})
|
||||
.toList());
|
||||
entry.setChildrenCache(children);
|
||||
|
||||
if (deep) {
|
||||
for (DataStoreEntry dataStoreEntry : new ArrayList<>(children)) {
|
||||
|
@ -488,6 +494,7 @@ public abstract class DataStorage {
|
|||
if (displayParent != null) {
|
||||
displayParent.setExpanded(true);
|
||||
addStoreEntryIfNotPresent(displayParent);
|
||||
displayParent.setChildrenCache(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,10 @@ public class DataStoreEntry extends StorageElement {
|
|||
@NonFinal
|
||||
DataStoreColor color;
|
||||
|
||||
@NonFinal
|
||||
@Setter
|
||||
List<DataStoreEntry> childrenCache = null;
|
||||
|
||||
private DataStoreEntry(
|
||||
Path directory,
|
||||
UUID uuid,
|
||||
|
@ -318,6 +322,7 @@ public class DataStoreEntry extends StorageElement {
|
|||
lastModified = Instant.now();
|
||||
dirty = true;
|
||||
provider = e.provider;
|
||||
childrenCache = null;
|
||||
refresh();
|
||||
}
|
||||
|
||||
|
@ -327,6 +332,7 @@ public class DataStoreEntry extends StorageElement {
|
|||
if (updateTime) {
|
||||
lastModified = Instant.now();
|
||||
}
|
||||
childrenCache = null;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
|
@ -345,7 +351,7 @@ public class DataStoreEntry extends StorageElement {
|
|||
if (store instanceof ValidatableStore l) {
|
||||
l.validate();
|
||||
} else if (store instanceof FixedHierarchyStore h) {
|
||||
h.listChildren(this);
|
||||
childrenCache = h.listChildren(this).stream().map(DataStoreEntryRef::get).toList();
|
||||
}
|
||||
} finally {
|
||||
setInRefresh(false);
|
||||
|
|
Loading…
Reference in a new issue