Bug 518221 - Moving connections in group is very slow
Summary: Moving connections in group is very slow
Status: NEW
Alias: None
Product: GEF
Classification: Tools
Component: GEF MVC (show other bugs)
Version: unspecified   Edit
Hardware: PC Windows 10
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: gef-inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords: greatbug
Depends on:
Blocks:
 
Reported: 2017-06-14 03:46 EDT by Victor Johnsson CLA
Modified: 2019-06-07 06:16 EDT (History)
4 users (show)

See Also:


Attachments
How to reproduce the bug (559.25 KB, image/gif)
2017-06-14 03:46 EDT, Victor Johnsson CLA
no flags Details
hack to speed up connection transforms (4.53 KB, patch)
2018-06-01 05:47 EDT, Victor Johnsson CLA
no flags Details | Diff
Java Mission Control report of dragging connections as shown in the gif (283.99 KB, application/octet-stream)
2018-06-01 05:49 EDT, Victor Johnsson CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Victor Johnsson CLA 2017-06-14 03:46:23 EDT
Created attachment 268894 [details]
How to reproduce the bug

In the GEF logo example, when moving connections selected in group the connection visuals stop being redrawn when dragging after a few seconds. Then when releasing the mouse to stop moving the group, the connection visuals are not redrawn until several seconds later.

How to reproduce:
1. Select a group of connections where none of the connections are attached to anything.
2. Drag them around for a few seconds.
3. Release.

Expected: The connection visuals follow the mouse when dragging. When releasing the visuals are located where mouse was released.
Actual: The connection visuals does not follow the mouse after a few seconds of dragging. Several seconds is needed for the visuals to be refreshed when releasing the mouse.

The attached gif first shows what it looks like when moving a group not containing connections which is problem free and then what it looks like when dragging a group of connections. This was tested with integration release 	5.0.0.201706100201 of GEF.
Comment 1 Matthias Wienand CLA 2017-06-14 11:54:18 EDT
There are multiple reasons for the observed behavior:

1. IBendableContentPart realizes transforming and resizing via changes to the bend points, i.e. not a single transformation is adjusted, but new positions are computed for the individual bend points.

2. ClickDragGesture does not handle events asynchronously, i.e. new events are only handled when processing of previous events is finished. Ideally, the gesture would know that a previous drag event is still being processed, so that intermediary events can be dropped, or processing thereof can be postponed (for the final event of the same type).

3. The final mouse location (when releasing the initially depressed mouse button) is not considered, because the handlers perform changes in response to drag events. When the button is released, the changes are committed.

4. The JavaFX/SWT integration (FXCanvas) is slow w.r.t. redrawing (blit necessary to copy from JavaFX pixel buffer to SWT image data). Moreover, I experienced differences in responsiveness when only using redraw() or also update() within FXCanvasEx.EventDispatcherEx#dispatchEvent() to signal that a new frame should be presented (redraw interval duration exceeded).

Each of which can be tackled individually:

1. Realize visual transformations for bendables using a JavaFX Affine.
 + no anchors need to be changed
 + only a transformation needs to be changed
 + solves the issue locally
 - needs to be done with caution to prevent problems (from the top of my head: redundant data (translation information is incorporated into the bend points) => duplicate transformations, incompatible bend points, possibly wrong Connection positions, etc.)

2. Process events asynchronously, dropping intermediary events of the same type as long as such an event is still processed.
 + event listening is "decoupled" from event processing
 + application remains responsive, even though less updates are carried out (as the corresponding events are dropped due to ongoing processing)
 - some handlers need to be run for all events (would need a mechanism for that)
 - some code needs to be executed on the JavaFX Application thread (would need to spawn that)
 - asynchronous control flow is difficult to reason about and to debug (synchronization mechanisms needed)

3. The gesture could emit a MOUSE_DRAGGED event for the final MOUSE_RELEASED event, reusing the mouse location from the released event, and the button state from the initial MOUSE_PRESSED event. OR: The handler could evaluate the event data given in endDrag() to perform an action similar to the one performed in drag().
 + the final state will match the mouse location

4. We should contribute fixes directly to JavaFX to make FXCanvas perform better and enhance the support for keyboard and touch events, which is currently handled by GEF's FXCanvasEx. In the meantime, you could try to enable/disable the update() call within FXCanvasEx and check if it enhances your situation.

I believe that working with bend points is just too slow in some cases, where it would be awesome if the transformation could be used, at least for updating the visuals during interaction. I will open tickets for the individual tasks after thinking about it again.

As a fix will not be released until Oxygen.1 (although most certainly published way earlier on the master branch), the following workaround can be used in the interim: you can override the setVisualTransform() and getVisualTransform() methods within an IBendableContentPart implementation to replace the slow but consistent default behavior with a fast transformation-based alternative, as well as having to ensure that bend points (corresponding visuals, operations, policies, and handlers) are still working correctly.

I believe that this issue is not critical, because it is 1. less noticeable when running standalone, 2. depending on the performance of the individual system where the application is executed, 3. can be obviated using the outlined workaround, and 4. is only related to performance, not to function ("make it work, make it right, make it fast").
Comment 2 Victor Johnsson CLA 2018-06-01 05:45:19 EDT
This does not seem to be as big of a problem in 5.0.2.201805110202, the connection visuals do not get stuck as long as shown in the gif. However since it is still there I profiled moving the 4 connections for 30 seconds and I found that 27% of the time is spent in org.eclipse.gef.common.collections.ListListenerHelperEx#removeListener and 12% of the time is spent in org.eclipse.gef.common.collections.ListListenerHelperEx#notifyListChangeListeners. This for org.eclipse.gef.fx.nodes.Connection#anchors when calling org.eclipse.gef.fx.nodes.Connection#setAnchor.

Attaching a patch that disables ListListenerHelperEx#removeListener ListListenerHelperEx#notifyListChangeListeners for Connection#anchors. Also attaching the Java Mission Control report.
Comment 3 Victor Johnsson CLA 2018-06-01 05:47:24 EDT
Created attachment 274301 [details]
hack to speed up connection transforms
Comment 4 Victor Johnsson CLA 2018-06-01 05:49:16 EDT
Created attachment 274302 [details]
Java Mission Control report of dragging connections as shown in the gif
Comment 5 Victor Johnsson CLA 2018-06-01 05:52:25 EDT
With the patch applied, the connection visuals do not get stuck as shown in the gif.
Comment 6 Tamas Miklossy CLA 2019-03-02 02:59:35 EST
See also the discussion in the forum thread

https://www.eclipse.org/forums/index.php/t/1096276/

to improve the performance by removing the ListListenerHelperEx#checkCursor method invocations.
Comment 7 Matthias Wienand CLA 2019-03-15 11:56:22 EDT
I removed the checkCursor() calls from ListListenerHelperEx. I will not yet close this ticket, because I want to do further analysis first.
Comment 8 Tamas Miklossy CLA 2019-03-16 03:50:17 EDT
Currently, the ObservableListTests.listenersNotProperlyIterating test cases fails with the following exception: 

ava.lang.ArrayIndexOutOfBoundsException: -1
	at org.eclipse.gef.common.collections.ListListenerHelperEx$AtomicChange.getAddedSubList(ListListenerHelperEx.java:139)
	at org.eclipse.gef.common.collections.ListListenerHelperEx$AtomicChange.wasAdded(ListListenerHelperEx.java:208)
	at javafx.collections.ListChangeListener$Change.wasReplaced(ListChangeListener.java:213)
	at org.eclipse.gef.common.collections.ListListenerHelperEx$AtomicChange.wasReplaced(ListListenerHelperEx.java:223)
	at org.eclipse.gef.common.tests.ObservableListTests$9.onChanged(ObservableListTests.java:664)
	at org.eclipse.gef.common.collections.ListListenerHelperEx.notifyListChangeListeners(ListListenerHelperEx.java:650)
	at org.eclipse.gef.common.collections.ListListenerHelperEx.fireValueChangedEvent(ListListenerHelperEx.java:600)
	at org.eclipse.gef.common.collections.ObservableListWrapperEx.add(ObservableListWrapperEx.java:79)
	at org.eclipse.gef.common.tests.ObservableListTests.listenersNotProperlyIterating(ObservableListTests.java:897)

The reason for the failing test is the change that the checkCursor() method has been removed from the ListListenerHelperEx class. I have set the test case temporary to ignore to make the build process succeed again, but we should analyse this deeper in the near future.
Comment 9 Alexander Nyßen CLA 2019-03-19 13:27:34 EDT
We introduced checkCursor() to prevent violations of the cursor contract, as this is a very likely thing to happen and I still think it is advantageous to have such a check. 

Instead of removing the checkCursor() we could think of introducing the notation of a production mode here as well (we have already done such a thing in AdapterInjectionSupport) and to only evaluate it if not in production mode. That should be fast enough for the production case, while in dev mode we would still have the desired safety.

If we tend to introduce a production mode here as well, we could think of extracting this as a general property of GEF common.