Hunt down some redundant events

This commit is contained in:
Clément Fournier
2019-01-28 23:28:38 +01:00
parent 3dc99bc160
commit 75da199b06
7 changed files with 53 additions and 43 deletions

View File

@ -8,7 +8,6 @@ import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -23,14 +22,14 @@ import org.reactfx.value.Val;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
import net.sourceforge.pmd.util.fxdesigner.app.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluationException;
import net.sourceforge.pmd.util.fxdesigner.popups.EventLogController;
import net.sourceforge.pmd.util.fxdesigner.app.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.app.CompositeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.app.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluationException;
import net.sourceforge.pmd.util.fxdesigner.popups.EventLogController;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import net.sourceforge.pmd.util.fxdesigner.util.LimitedSizeStack;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.SoftReferenceCache;
import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper;
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil;
@ -148,12 +147,8 @@ public class MainDesignerController extends AbstractController<AbstractControlle
refreshAST(); // initial refreshing
sourceEditorController.moveCaret(0, 0);
// ignore selection events produced in very short delay
// this avoids event handling loops, e.g. an event from
// the xpath panel is forwarded to the treeView, which
// forwards back an event, etc.
getSelectionEvents().thenIgnoreFor(Duration.ofMillis(20))
.subscribe(n -> CompositeSelectionSource.super.bubbleDown(n));
// this is the only place where getSelectionEvents is called
getSelectionEvents().distinct().subscribe(n -> CompositeSelectionSource.super.bubbleDown(n));
}

View File

@ -19,6 +19,7 @@ import java.util.stream.Collectors;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import org.reactfx.SuspendableEventStream;
import org.reactfx.value.Var;
import net.sourceforge.pmd.internal.util.IteratorUtil;
@ -29,9 +30,9 @@ import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
import net.sourceforge.pmd.lang.symboltable.Scope;
import net.sourceforge.pmd.lang.symboltable.ScopedNode;
import net.sourceforge.pmd.util.fxdesigner.model.MetricResult;
import net.sourceforge.pmd.util.fxdesigner.app.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.model.MetricResult;
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty;
import net.sourceforge.pmd.util.fxdesigner.util.controls.ScopeHierarchyTreeCell;
import net.sourceforge.pmd.util.fxdesigner.util.controls.ScopeHierarchyTreeItem;
@ -83,6 +84,9 @@ public class NodeInfoPanelController extends AbstractController<MainDesignerCont
private Node selectedNode;
private SuspendableEventStream<TreeItem<Object>> myScopeItemSelectionEvents;
public NodeInfoPanelController(MainDesignerController mainController) {
super(mainController);
}
@ -101,17 +105,19 @@ public class NodeInfoPanelController extends AbstractController<MainDesignerCont
.distinct()
.subscribe(show -> displayAttributes(selectedNode));
// suppress as early as possible in the pipeline
myScopeItemSelectionEvents = EventStreams.valuesOf(scopeHierarchyTreeView.getSelectionModel().selectedItemProperty()).suppressible();
}
@Override
public EventStream<NodeSelectionEvent> getSelectionEvents() {
return EventStreams.valuesOf(scopeHierarchyTreeView.getSelectionModel().selectedItemProperty())
.filter(Objects::nonNull)
.map(TreeItem::getValue)
.filterMap(o -> o instanceof NameDeclaration, o -> (NameDeclaration) o)
.map(NameDeclaration::getNode)
.map(n -> new NodeSelectionEvent(n, this));
return myScopeItemSelectionEvents.filter(Objects::nonNull)
.map(TreeItem::getValue)
.filterMap(o -> o instanceof NameDeclaration, o -> (NameDeclaration) o)
.map(NameDeclaration::getNode)
.map(n -> new NodeSelectionEvent(n, this));
}
@ -213,7 +219,8 @@ public class NodeInfoPanelController extends AbstractController<MainDesignerCont
// you selected is in its own scope hierarchy so it looks buggy.
int maxDepth = IteratorUtil.count(parentIterator(previousSelection, true));
rootScope.tryFindNode(previousSelection.getValue(), maxDepth)
.ifPresent(scopeHierarchyTreeView.getSelectionModel()::select);
// suspend notifications while selecting
.ifPresent(item -> myScopeItemSelectionEvents.suspendWhile(() -> scopeHierarchyTreeView.getSelectionModel().select(item)));
}
}

View File

@ -19,6 +19,7 @@ import org.controlsfx.validation.ValidationSupport;
import org.controlsfx.validation.Validator;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import org.reactfx.SuspendableEventStream;
import org.reactfx.collection.LiveArrayList;
import org.reactfx.value.Val;
import org.reactfx.value.Var;
@ -27,14 +28,14 @@ import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.XPathRule;
import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery;
import net.sourceforge.pmd.util.fxdesigner.app.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.app.LogEntry.Category;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.model.ObservableXPathRuleBuilder;
import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluationException;
import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluator;
import net.sourceforge.pmd.util.fxdesigner.popups.ExportXPathWizardController;
import net.sourceforge.pmd.util.fxdesigner.app.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper;
import net.sourceforge.pmd.util.fxdesigner.util.autocomplete.CompletionResultSource;
import net.sourceforge.pmd.util.fxdesigner.util.autocomplete.XPathAutocompleteProvider;
@ -100,6 +101,7 @@ public class XPathPanelController extends AbstractController<MainDesignerControl
// ui property
private Var<String> xpathVersionUIProperty = Var.newSimpleVar(XPathRuleQuery.XPATH_2_0);
private SuspendableEventStream<TextAwareNodeWrapper> selectionEvents;
public XPathPanelController(MainDesignerController mainController) {
super(mainController);
@ -127,7 +129,7 @@ public class XPathPanelController extends AbstractController<MainDesignerControl
.or(xpathVersionProperty().changes())
.subscribe(tick -> parent.refreshXPathResults());
selectionEvents = EventStreams.valuesOf(xpathResultListView.getSelectionModel().selectedItemProperty()).suppressible();
}
@ -219,11 +221,9 @@ public class XPathPanelController extends AbstractController<MainDesignerControl
@Override
public EventStream<NodeSelectionEvent> getSelectionEvents() {
return EventStreams.valuesOf(xpathResultListView.getSelectionModel().selectedItemProperty())
.conditionOn(xpathResultListView.focusedProperty())
.filter(Objects::nonNull)
.map(TextAwareNodeWrapper::getNode)
.map(n -> new NodeSelectionEvent(n, this));
return selectionEvents.filter(Objects::nonNull)
.map(TextAwareNodeWrapper::getNode)
.map(n -> new NodeSelectionEvent(n, this));
}
@ -232,7 +232,7 @@ public class XPathPanelController extends AbstractController<MainDesignerControl
xpathResultListView.getItems().stream()
.filter(wrapper -> wrapper.getNode().equals(node))
.findFirst()
.ifPresent(xpathResultListView.getSelectionModel()::select);
.ifPresent(item -> selectionEvents.suspendWhile(() -> xpathResultListView.getSelectionModel().select(item)));
}

View File

@ -30,6 +30,11 @@ public interface CompositeSelectionSource extends NodeSelectionSource {
}
default boolean isRoot() {
return false;
}
@Override
default void setFocusNode(Node node) {
// by default do nothing,

View File

@ -23,13 +23,10 @@ import org.reactfx.collection.LiveArrayList;
import org.reactfx.collection.LiveList;
import org.reactfx.value.Val;
import net.sourceforge.pmd.util.fxdesigner.app.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.app.LogEntry;
import net.sourceforge.pmd.util.fxdesigner.app.LogEntry.Category;
import net.sourceforge.pmd.util.fxdesigner.app.LogEntry.LogEntryWithData;
import net.sourceforge.pmd.util.fxdesigner.app.ApplicationComponent;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource.NodeSelectionEvent;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
/**
@ -55,6 +52,7 @@ public class EventLogger implements ApplicationComponent {
this.designerRoot = designerRoot; // we have to be careful with initialization order here
EventStream<LogEntryWithData<NodeSelectionEvent>> eventTraces =
// none of this is done if developer mode isn't enabled because then those events aren't even pushed in the first place
reduceEntangledIfPossible(filterOnCategory(latestEvent, false, SELECTION_EVENT_TRACING).map(t -> (LogEntryWithData<NodeSelectionEvent>) t),
// the user data for those is the event
// if they're the same event we reduce them together

View File

@ -33,7 +33,12 @@ public interface NodeSelectionSource extends ApplicationComponent {
/**
* Returns a stream of events that should push an event each time
* this source or one of its sub components records a change in node
* selection.
* selection. This one needs to be implemented in sub classes.
*
* <p>You can't trust that this method will return the same stream
* when called several times. In fact it's just called one time.
* That's why you can't abstract the suppressible behaviour here.
* You'd need Scala traits.
*/
EventStream<NodeSelectionEvent> getSelectionEvents();

View File

@ -12,7 +12,6 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.reactfx.EventSource;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import org.reactfx.SuspendableEventStream;
import org.reactfx.value.Var;
@ -37,25 +36,25 @@ public class AstTreeView extends TreeView<Node> implements NodeSelectionSource {
private final TreeViewWrapper<Node> myWrapper = new TreeViewWrapper<>(this);
private ASTTreeItem selectedTreeItem;
private final SuspendableEventStream<Node> pausableEvents;
private final SuspendableEventStream<Node> selectionEvents;
private DesignerRoot designerRoot;
public AstTreeView() {
EventSource<Node> selectionEvents = new EventSource<>();
pausableEvents = selectionEvents.pausable();
EventSource<Node> eventSink = new EventSource<>();
selectionEvents = eventSink.suppressible();
// push a node selection event whenever...
// * The selection changes
EventStreams.valuesOf(getSelectionModel().selectedItemProperty())
.filterMap(Objects::nonNull, TreeItem::getValue)
.subscribe(selectionEvents::push);
.subscribe(eventSink::push);
// * a cell is explicitly clicked. This catches the case where the cell was already selected
// * the currently selected cell is explicitly clicked
setCellFactory(tv -> new ASTTreeCell(n -> {
// only push an event if the node was already selected
if (selectedTreeItem != null && selectedTreeItem.getValue() != null && selectedTreeItem.getValue().equals(n)) {
selectionEvents.push(n);
eventSink.push(n);
}
}));
@ -63,8 +62,8 @@ public class AstTreeView extends TreeView<Node> implements NodeSelectionSource {
@Override
public EventStream<NodeSelectionEvent> getSelectionEvents() {
return pausableEvents.map(n -> new NodeSelectionEvent(n, this));
public SuspendableEventStream<NodeSelectionEvent> getSelectionEvents() {
return selectionEvents.map(n -> new NodeSelectionEvent(n, this)).suppressible();
}
@ -82,7 +81,8 @@ public class AstTreeView extends TreeView<Node> implements NodeSelectionSource {
ASTTreeItem found = ((ASTTreeItem) getRoot()).findItem(node);
if (found != null) {
pausableEvents.suspendWhile(() -> selectionModel.select(found));
// don't fire any selection event while itself setting the selected item
selectionEvents.suspendWhile(() -> selectionModel.select(found));
}
highlightFocusNodeParents(selectedTreeItem, found);