Skip to content

Commit

Permalink
Add multithreaded event support for measurements and dataset proc
Browse files Browse the repository at this point in the history
* Port Measurements and MathDataSets to new event system
* indicators: use and set dirty flags
* event processors: thread and animation timer based
* split trending measurements from dataset measurements
* feedback from @ennerf:
  * changed to fireInvalidated shorthand
  * removed locks from bitstate
  * ValueIndicators: move from bitstate to directly calling postListener
  * YIndicator: fix drag offset
  • Loading branch information
wirew0rm committed Sep 25, 2023
1 parent 5b7eebe commit cfadee2
Show file tree
Hide file tree
Showing 34 changed files with 1,275 additions and 622 deletions.
2 changes: 1 addition & 1 deletion chartfx-chart/src/main/java/io/fair_acc/chartfx/Chart.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import io.fair_acc.chartfx.ui.*;
import io.fair_acc.chartfx.utils.PropUtil;
import io.fair_acc.dataset.AxisDescription;
import io.fair_acc.dataset.event.EventSource;
import io.fair_acc.dataset.events.EventSource;
import io.fair_acc.dataset.events.BitState;
import io.fair_acc.dataset.events.ChartBits;
import javafx.animation.Animation;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.fair_acc.chartfx.events;

import io.fair_acc.dataset.events.BitState;
import io.fair_acc.dataset.events.ChartBits;
import io.fair_acc.dataset.events.EventProcessor;
import javafx.animation.AnimationTimer;
import org.apache.commons.lang3.tuple.Pair;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

/**
* An event processor class which processes dataset events un the UI thread of the chart.
* All datasets added to this processor will be processed whenever they are invalidated.
* <p>
* TODO: check how to ensure that everything gets garbage-collected correctly
*/
public class FxEventProcessor extends AnimationTimer implements EventProcessor {
BitState localState = BitState.initDirty(this, ChartBits.DataSetMask);
BitState stateRoot = BitState.initDirtyMultiThreaded(this, ChartBits.DataSetMask);
List<Pair<BitState, Runnable>> actions = new ArrayList<>();
private static final AtomicReference<FxEventProcessor> INSTANCE = new AtomicReference<>();

Check warning on line 23 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L20-L23

Added lines #L20 - L23 were not covered by tests

public static FxEventProcessor getInstance() {
FxEventProcessor result = INSTANCE.get();

Check warning on line 26 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L26

Added line #L26 was not covered by tests
if (result != null) {
return result;

Check warning on line 28 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L28

Added line #L28 was not covered by tests
}
// probably does not exist yet, but initialise in thread safe way
result = new FxEventProcessor();

Check warning on line 31 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L31

Added line #L31 was not covered by tests
if (INSTANCE.compareAndSet(null, result)) {
return result;

Check warning on line 33 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L33

Added line #L33 was not covered by tests
} else {
return INSTANCE.get();

Check warning on line 35 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L35

Added line #L35 was not covered by tests
}
}

public FxEventProcessor() {
start();
}

Check warning on line 41 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L39-L41

Added lines #L39 - L41 were not covered by tests

@Override
public void handle(final long now) {
localState.setDirty(stateRoot.clear());

Check warning on line 45 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L45

Added line #L45 was not covered by tests
if (localState.isDirty()) {
for (final var action : actions) {
if (action.getLeft().isDirty(ChartBits.DataSetMask)) {
action.getLeft().clear();

Check warning on line 49 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L49

Added line #L49 was not covered by tests
try {
action.getRight().run();
} catch (Exception ignored) {}

Check warning on line 52 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L51-L52

Added lines #L51 - L52 were not covered by tests
}
}

Check warning on line 54 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L54

Added line #L54 was not covered by tests
}
localState.clear();

Check warning on line 56 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L56

Added line #L56 was not covered by tests
// TODO: perform multiple iterations if there are changes to handle wrongly ordered processing chains and break after timeout
}

Check warning on line 58 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L58

Added line #L58 was not covered by tests

public BitState getBitState() {
return stateRoot;

Check warning on line 61 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L61

Added line #L61 was not covered by tests
}

@Override
public void addAction(final BitState obj, final Runnable action) {
obj.addInvalidateListener(stateRoot);
actions.add(Pair.of(obj, action));
}

Check warning on line 68 in chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/events/FxEventProcessor.java#L66-L68

Added lines #L66 - L68 were not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ public abstract class AbstractRangeValueIndicator extends AbstractValueIndicator
private final DoubleProperty lowerBound = new SimpleDoubleProperty(this, "lowerBound") {
@Override
protected void invalidated() {
layoutChildren();
runPostLayout();

Check warning on line 28 in chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractRangeValueIndicator.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractRangeValueIndicator.java#L28

Added line #L28 was not covered by tests
}
};

private final DoubleProperty upperBound = new SimpleDoubleProperty(this, "upperBound") {
@Override
protected void invalidated() {
layoutChildren();
runPostLayout();

Check warning on line 35 in chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractRangeValueIndicator.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractRangeValueIndicator.java#L35

Added line #L35 was not covered by tests
}
};

Expand All @@ -43,7 +43,7 @@ protected void invalidated() {
if (get() < 0 || get() > 1) {
throw new IllegalArgumentException("labelHorizontalPosition must be in rage [0,1]");
}
layoutChildren();
runPostLayout();

Check warning on line 46 in chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractRangeValueIndicator.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractRangeValueIndicator.java#L46

Added line #L46 was not covered by tests
}
};

Expand All @@ -53,7 +53,7 @@ protected void invalidated() {
if (get() < 0 || get() > 1) {
throw new IllegalArgumentException("labelVerticalPosition must be in rage [0,1]");
}
layoutChildren();
runPostLayout();

Check warning on line 56 in chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractRangeValueIndicator.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractRangeValueIndicator.java#L56

Added line #L56 was not covered by tests
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package io.fair_acc.chartfx.plugins;

import io.fair_acc.chartfx.utils.PropUtil;
import io.fair_acc.dataset.events.BitState;
import io.fair_acc.dataset.events.ChartBits;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
Expand All @@ -15,16 +14,14 @@
import javafx.scene.shape.Polygon;

import io.fair_acc.chartfx.axes.Axis;
import io.fair_acc.dataset.event.EventSource;

/**
* Plugin indicating a specific X or Y value as a line drawn on the plot area, with an optional {@link #textProperty()
* text label} describing the value.
*
* @author mhrabia
*/
public abstract class AbstractSingleValueIndicator extends AbstractValueIndicator
implements EventSource, ValueIndicator {
public abstract class AbstractSingleValueIndicator extends AbstractValueIndicator implements ValueIndicator {
/**
* The default distance between the data point coordinates and mouse cursor that triggers shifting the line.
*/
Expand All @@ -34,7 +31,6 @@ public abstract class AbstractSingleValueIndicator extends AbstractValueIndicato
protected static final String STYLE_CLASS_LINE = "value-indicator-line";
protected static final String STYLE_CLASS_MARKER = "value-indicator-marker";
protected static double triangleHalfWidth = 5.0;
private final transient BitState state = BitState.initDirty(this);
private boolean autoRemove = false;

/**
Expand All @@ -60,7 +56,7 @@ protected void invalidated() {
private final DoubleProperty value = new SimpleDoubleProperty(this, "value") {
@Override
protected void invalidated() {
layoutChildren();
runPostLayout();
}
};

Expand All @@ -70,7 +66,7 @@ protected void invalidated() {
if (get() < 0 || get() > 1) {
throw new IllegalArgumentException("labelPosition must be in rage [0,1]");
}
layoutChildren();
runPostLayout();
}
};

Expand Down Expand Up @@ -100,10 +96,9 @@ protected AbstractSingleValueIndicator(Axis axis, final double value, final Stri
});

// Need to add them so that at initialization of the stage the CCS is
// applied and we can calculate label's
// width and height
// applied and we can calculate label's width and height
getChartChildren().addAll(line, label);
PropUtil.runOnChange(state.onAction(ChartBits.ChartPluginState), this.value);
PropUtil.runOnChange(getBitState().onAction(ChartBits.ChartPluginState), this.value);
}

/**
Expand Down Expand Up @@ -149,7 +144,7 @@ private void initLine() {
* identical and for Y indicators start y and end y are identical.
*/
dragDelta.x = pickLine.getStartX() - mouseEvent.getX();
dragDelta.y = pickLine.getStartY() - mouseEvent.getY();
dragDelta.y = -(pickLine.getStartY() - mouseEvent.getY());

Check warning on line 147 in chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractSingleValueIndicator.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractSingleValueIndicator.java#L147

Added line #L147 was not covered by tests
pickLine.setCursor(Cursor.MOVE);
mouseEvent.consume();
}
Expand Down Expand Up @@ -287,11 +282,6 @@ public final void setValue(final double newValue) {
valueProperty().set(newValue);
}

@Override
public BitState getBitState() {
return state;
}

private void updateMouseListener(final boolean state) {
if (state) {
pickLine.setOnMouseReleased(mouseEvent -> pickLine.setCursor(Cursor.HAND));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

package io.fair_acc.chartfx.plugins;

import io.fair_acc.dataset.events.EventSource;
import io.fair_acc.dataset.events.BitState;
import io.fair_acc.dataset.events.ChartBits;
import io.fair_acc.dataset.events.StateListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.geometry.Bounds;
Expand All @@ -32,9 +35,11 @@
*
* @author mhrabia
*/
public abstract class AbstractValueIndicator extends ChartPlugin {
public abstract class AbstractValueIndicator extends ChartPlugin implements EventSource {
private final Axis axis;
private final ChangeListener<? super Number> axisBoundsListener = (obs, oldVal, newVal) -> layoutChildren();
private final StateListener axisBoundsListener = (source, bits) -> runPostLayout();

private final BitState state = BitState.initDirty(this);

private final ListChangeListener<? super ChartPlugin> pluginsListListener = (final Change<? extends ChartPlugin> change) -> updateStyleClass();

Expand All @@ -49,23 +54,23 @@ public abstract class AbstractValueIndicator extends ChartPlugin {
protected final BooleanProperty editableIndicator = new SimpleBooleanProperty(this, "editableIndicator", true) {
@Override
protected void invalidated() {
layoutChildren();
runPostLayout();

Check warning on line 57 in chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractValueIndicator.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractValueIndicator.java#L57

Added line #L57 was not covered by tests
}
};

private final ObjectProperty<HPos> labelHorizontalAnchor = new SimpleObjectProperty<>(this,
"labelHorizontalAnchor", HPos.CENTER) {
@Override
protected void invalidated() {
layoutChildren();
runPostLayout();
}
};

private final ObjectProperty<VPos> labelVerticalAnchor = new SimpleObjectProperty<>(this, "labelVerticalAnchor",
VPos.CENTER) {
@Override
protected void invalidated() {
layoutChildren();
runPostLayout();

Check warning on line 73 in chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractValueIndicator.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractValueIndicator.java#L73

Added line #L73 was not covered by tests
}
};

Expand Down Expand Up @@ -139,13 +144,12 @@ protected AbstractValueIndicator(Axis axis, final String text) {
}
});

textProperty().addListener((obs, oldText, newText) -> layoutChildren());
textProperty().addListener((obs, oldText, newText) -> runPostLayout());
}

private void addAxisListener() {
final Axis valueAxis = getAxis();
valueAxis.minProperty().addListener(axisBoundsListener);
valueAxis.maxProperty().addListener(axisBoundsListener);
valueAxis.getBitState().addChangeListener(ChartBits.AxisRange, axisBoundsListener);
}

protected void addChildNodeIfNotPresent(final Node node) {
Expand Down Expand Up @@ -300,8 +304,7 @@ protected final void layoutLabel(final Bounds bounds, final double hPos, final d

private void removeAxisListener() {
final Axis valueAxis = getAxis();
valueAxis.minProperty().removeListener(axisBoundsListener);
valueAxis.maxProperty().removeListener(axisBoundsListener);
valueAxis.getBitState().removeChangeListener(axisBoundsListener);

Check warning on line 307 in chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractValueIndicator.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/AbstractValueIndicator.java#L307

Added line #L307 was not covered by tests
}

private void removePluginsListListener(final Chart chart) {
Expand Down Expand Up @@ -379,4 +382,9 @@ protected static class Delta {
protected double x;
protected double y;
}

@Override
public BitState getBitState() {
return state;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.fair_acc.chartfx.plugins;

import io.fair_acc.chartfx.plugins.measurements.TrendingMeasurements;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
Expand Down Expand Up @@ -145,6 +146,23 @@ public MenuBar getMenuBar() {
}
}

// loop through TrendingMeasurements categories
for (final TrendingMeasurements.MeasurementCategory category : TrendingMeasurements.MeasurementCategory.values()) {
final Menu newCategory = new Menu(category.getName()); // NOPMD dynamic (but finite) menu generation
measurementMenu.getItems().addAll(newCategory);

// loop through measurements within categories
for (final TrendingMeasurements.MeasurementType measType : TrendingMeasurements.MeasurementType.values()) {
if (measType.getCategory() != category) {
continue;

Check warning on line 157 in chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/ParameterMeasurements.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/ParameterMeasurements.java#L157

Added line #L157 was not covered by tests
}
final MenuItem newMeasurement = new MenuItem(measType.getName()); // NOPMD dynamic (but finite) menu generation
newMeasurement.setId("ParameterMeasurements::newMeasurement::" + measType.toString()); // N.B. not a unique name but for testing this suffices
newMeasurement.setOnAction(evt -> new TrendingMeasurements(this, measType).initialize()); // NOPMD
newCategory.getItems().addAll(newMeasurement);
}
}

// add further miscellaneous items here if needed

parameterMenu.getMenus().addAll(measurementMenu);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.fair_acc.chartfx.plugins;

import io.fair_acc.chartfx.Chart;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;

Expand Down Expand Up @@ -45,11 +46,12 @@ public XRangeIndicator(Axis axis, final double lowerBound, final double upperBou
}

@Override
public void layoutChildren() {
if (getChart() == null) {
public void runPostLayout() {
Chart chart = getChart();

Check warning on line 50 in chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/XRangeIndicator.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/XRangeIndicator.java#L50

Added line #L50 was not covered by tests
if (chart == null) {
return;
}
final Bounds plotAreaBounds = getChart().getCanvas().getBoundsInLocal();
final Bounds plotAreaBounds = chart.getCanvas().getBoundsInLocal();

Check warning on line 54 in chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/XRangeIndicator.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/XRangeIndicator.java#L54

Added line #L54 was not covered by tests
final double minX = plotAreaBounds.getMinX();
final double maxX = plotAreaBounds.getMaxX();
final double minY = plotAreaBounds.getMinY();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import io.fair_acc.chartfx.axes.Axis;
import io.fair_acc.chartfx.ui.geometry.Side;
import io.fair_acc.dataset.event.EventSource;

/**
* A vertical line drawn on the plot area, indicating specified X value, with an optional {@link #textProperty() text
Expand All @@ -24,7 +23,7 @@
*
* @author mhrabia
*/
public class XValueIndicator extends AbstractSingleValueIndicator implements EventSource, ValueIndicator {
public class XValueIndicator extends AbstractSingleValueIndicator implements ValueIndicator {
/**
* Creates a new instance of the indicator.
*
Expand Down Expand Up @@ -62,11 +61,11 @@ protected void handleDragMouseEvent(final MouseEvent mouseEvent) {
}

mouseEvent.consume();
layoutChildren();
runPostLayout();

Check warning on line 64 in chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/XValueIndicator.java

View check run for this annotation

Codecov / codecov/patch

chartfx-chart/src/main/java/io/fair_acc/chartfx/plugins/XValueIndicator.java#L64

Added line #L64 was not covered by tests
}

@Override
public void layoutChildren() {
public void runPostLayout() {
if (getChart() == null) {
return;
}
Expand Down
Loading

0 comments on commit cfadee2

Please sign in to comment.