Optimizations for large connection amounts

This commit is contained in:
crschnick 2023-10-15 00:04:20 +00:00
parent 1e990389f4
commit b9ebce0771
8 changed files with 68 additions and 181 deletions

View file

@ -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);
}
};

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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,

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);