changeset 4289:8091df24a8e5

RT-31644 Reimplement dirty layout processing to use flags
author Martin Sladecek <martin.sladecek@oracle.com>
date Mon, 15 Jul 2013 12:19:04 +0200
parents f6c0aafef1a8
children 233be46d9596 0318a5d3d917
files modules/controls/src/main/java/com/sun/javafx/scene/control/skin/VirtualFlow.java modules/graphics/src/main/java/com/sun/javafx/scene/LayoutFlags.java modules/graphics/src/main/java/javafx/scene/Node.java modules/graphics/src/main/java/javafx/scene/Parent.java modules/graphics/src/main/java/javafx/scene/Scene.java
diffstat 5 files changed, 115 insertions(+), 146 deletions(-) [+]
line wrap: on
line diff
--- a/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/VirtualFlow.java	Mon Jul 15 10:39:31 2013 +0200
+++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/VirtualFlow.java	Mon Jul 15 12:19:04 2013 +0200
@@ -793,10 +793,7 @@
         // isNeedsLayout() is commented out due to RT-21417. This does not
         // appear to impact performance (indeed, it may help), and resolves the
         // issue identified in RT-21417.
-        if (getScene() != null/* && !isNeedsLayout()*/) {
-            getScene().addToDirtyLayoutList(this);
-            setNeedsLayout(true);
-        }
+        setNeedsLayout(true);
     }
     
     @Override protected void layoutChildren() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/javafx/scene/LayoutFlags.java	Mon Jul 15 12:19:04 2013 +0200
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.javafx.scene;
+
+public enum LayoutFlags {
+    CLEAN,
+    DIRTY_BRANCH,
+    NEEDS_LAYOUT
+}
--- a/modules/graphics/src/main/java/javafx/scene/Node.java	Mon Jul 15 10:39:31 2013 +0200
+++ b/modules/graphics/src/main/java/javafx/scene/Node.java	Mon Jul 15 12:19:04 2013 +0200
@@ -138,6 +138,7 @@
 import com.sun.javafx.scene.CssFlags;
 import com.sun.javafx.scene.DirtyBits;
 import com.sun.javafx.scene.EventHandlerProperties;
+import com.sun.javafx.scene.LayoutFlags;
 import com.sun.javafx.scene.NodeEventDispatcher;
 import com.sun.javafx.scene.NodeHelper;
 import com.sun.javafx.scene.SceneHelper;
@@ -4812,7 +4813,7 @@
 
         return tmin;
     }
-
+    
 
     // Good to find a home for commonly use util. code such as EPS.
     // and almostZero. This code currently defined in multiple places,
@@ -8380,14 +8381,14 @@
              styleHelper.setObservableStyleMap(styleMap);
          }
      }
-
+     
     /**
      * Flags used to indicate in which way this node is dirty (or whether it
      * is clean) and what must happen during the next CSS cycle on the
      * scenegraph.
      */
     CssFlags cssFlag = CssFlags.CLEAN;
-
+    
     /**
      * Needed for testing.
      */
--- a/modules/graphics/src/main/java/javafx/scene/Parent.java	Mon Jul 15 10:39:31 2013 +0200
+++ b/modules/graphics/src/main/java/javafx/scene/Parent.java	Mon Jul 15 12:19:04 2013 +0200
@@ -59,6 +59,7 @@
 import com.sun.javafx.sg.prism.NGGroup;
 import com.sun.javafx.sg.prism.NGNode;
 import com.sun.javafx.tk.Toolkit;
+import com.sun.javafx.scene.LayoutFlags;
 import sun.util.logging.PlatformLogger;
 import sun.util.logging.PlatformLogger.Level;
 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;
@@ -637,29 +638,20 @@
             children.get(i).setScenes(newScene, newSubScene);
         }
 
-        // If this node was in the old scene's dirty layout
-        // list, then remove it from that list so that it is
-        // not processed on the next pulse
-        final boolean awaitingLayout = isNeedsLayout();
+        final boolean awaitingLayout = layoutFlag != LayoutFlags.CLEAN;
 
         sceneRoot = (newSubScene != null && newSubScene.getRoot() == this) ||
                     (newScene != null && newScene.getRoot() == this);
         layoutRoot = !isManaged() || sceneRoot;
 
+
         if (awaitingLayout) {
-            boolean sceneChanged = oldScene != newScene;
-            if (oldScene != null && sceneChanged) {
-                oldScene.removeFromDirtyLayoutList(this);
-            }
             // If this node is dirty and the new scene or subScene is not null
             // then add this node to the new scene's dirty list
             if (newScene != null && layoutRoot) {
                 if (newSubScene != null) {
                     newSubScene.setDirtyLayout(this);
                 }
-                if (sceneChanged) {
-                    newScene.addToDirtyLayoutList(this);
-                }
             }
         }
     }
@@ -773,18 +765,37 @@
      * Indicates that this Node and its subnodes requires a layout pass on
      * the next pulse.
      */
-    private ReadOnlyBooleanWrapper needsLayout = new ReadOnlyBooleanWrapper(this, "needsLayout", true);
+    private ReadOnlyBooleanWrapper needsLayout;
+    LayoutFlags layoutFlag = LayoutFlags.CLEAN;
 
     protected final void setNeedsLayout(boolean value) {
-        needsLayout.set(value);
+        if (value) {
+            markDirtyLayout(true);
+        } else if (layoutFlag == LayoutFlags.NEEDS_LAYOUT) {
+            boolean hasBranch = false;
+            for (int i = 0, max = children.size(); i < max; i++) {
+                final Node child = children.get(i);
+                if (child instanceof Parent) {
+                    if (((Parent)child).layoutFlag != LayoutFlags.CLEAN) {
+                        hasBranch = true;
+                        break;
+                    }
+
+                }
+            }
+            setLayoutFlag(hasBranch ? LayoutFlags.DIRTY_BRANCH : LayoutFlags.CLEAN);
+        }
     }
 
     public final boolean isNeedsLayout() {
-        return needsLayout.get();
+        return layoutFlag == LayoutFlags.NEEDS_LAYOUT;
     }
 
     public final ReadOnlyBooleanProperty needsLayoutProperty() {
-        return needsLayout.getReadOnlyProperty();
+        if (needsLayout == null) {
+            needsLayout = new ReadOnlyBooleanWrapper(this, "needsLayout", layoutFlag == LayoutFlags.NEEDS_LAYOUT);
+        }
+        return needsLayout;
     }
 
     /**
@@ -801,6 +812,30 @@
     private double minWidthCache = -1;
     private double minHeightCache = -1;
 
+    private void setLayoutFlag(LayoutFlags flag) {
+        if (needsLayout != null) {
+            needsLayout.set(flag == LayoutFlags.NEEDS_LAYOUT);
+        }
+        layoutFlag = flag;
+    }
+
+    private void markDirtyLayoutBranch() {
+        Parent parent = getParent();
+        while (parent != null && parent.layoutFlag == LayoutFlags.CLEAN) {
+            parent.setLayoutFlag(LayoutFlags.DIRTY_BRANCH);
+            parent = parent.getParent();
+        }
+    }
+
+    private void markDirtyLayout(boolean local) {
+        setLayoutFlag(LayoutFlags.NEEDS_LAYOUT);
+        if (local || layoutRoot) {
+            markDirtyLayoutBranch();
+        } else {
+            requestParentLayout();
+        }
+    }
+
     /**
      * Requests a layout pass to be performed before the next scene is
      * rendered. This is batched up asynchronously to happen once per
@@ -812,18 +847,12 @@
      * @since JavaFX 8.0
      */
     public void requestLayout() {
-        if (!isNeedsLayout()) {
+        if (layoutFlag != LayoutFlags.NEEDS_LAYOUT) {
             prefWidthCache = -1;
             prefHeightCache = -1;
             minWidthCache = -1;
             minHeightCache = -1;
-            PlatformLogger logger = Logging.getLayoutLogger();
-            if (logger.isLoggable(Level.FINER)) {
-                logger.finer(this.toString());
-            }
-
-            setNeedsLayout(true);
-            requestParentLayout();
+            markDirtyLayout(false);
         } else {
             clearSizeCache();
         }
@@ -839,16 +868,7 @@
      * when it's parent recomputes the layout with the new hints.
      */
     protected final void requestParentLayout() {
-        if (layoutRoot) {
-            final Scene scene = getScene();
-            final SubScene subScene = getSubScene();
-            if (subScene != null) {
-                subScene.setDirtyLayout(this);
-            }
-            if (scene != null) {
-                scene.addToDirtyLayoutList(this);
-            }
-        } else {
+        if (!layoutRoot) {
             final Parent parent = getParent();
             if (parent != null && !parent.performingLayout) {
                 parent.requestLayout();
@@ -1019,31 +1039,23 @@
      * Executes a top-down layout pass on the scene graph under this parent.
      */
     public final void layout() {
-        if (isNeedsLayout()) {
-            if (PULSE_LOGGING_ENABLED) PULSE_LOGGER.fxIncrementCounter("Parent#layout() on dirty Node");
-            performingLayout = true;
-
-            PlatformLogger logger = Logging.getLayoutLogger();
-            if (logger.isLoggable(Level.FINE)) {
-                logger.fine(this+" size: "+
-                        getLayoutBounds().getWidth()+" x "+getLayoutBounds().getHeight());
-            }
-
-            // layout the children in this parent.
-            layoutChildren();
-
-            // Perform layout on each child, hoping it has random access performance!
-            for (int i=0, max=children.size(); i<max; i++) {
-                final Node child = children.get(i);
-                if (child instanceof Parent) {
-                    ((Parent) child).layout();
+        switch(layoutFlag) {
+            case CLEAN:
+                break;
+            case NEEDS_LAYOUT:
+                performingLayout = true;
+                layoutChildren();
+                // Intended fall-through
+            case DIRTY_BRANCH:
+                for (int i = 0, max = children.size(); i < max; i++) {
+                    final Node child = children.get(i);
+                    if (child instanceof Parent) {
+                        ((Parent)child).layout();
+                    }
                 }
-            }
-            setNeedsLayout(false);
-
-            performingLayout = false;
-        } else {
-            if (PULSE_LOGGING_ENABLED) PULSE_LOGGER.fxIncrementCounter("Parent#layout() on clean Node");
+                setLayoutFlag(LayoutFlags.CLEAN);
+                performingLayout = false;
+                break;
         }
     }
 
@@ -1075,7 +1087,7 @@
      * whenever the sceneRoot field changes, or whenever the managed
      * property changes.
      */
-    private boolean layoutRoot = false;
+    boolean layoutRoot = false;
     @Override final void notifyManagedChanged() {
         layoutRoot = !isManaged() || sceneRoot;
     }
@@ -1218,6 +1230,7 @@
      * Constructs a new {@code Parent}.
      */
     protected Parent() {
+        layoutFlag = LayoutFlags.NEEDS_LAYOUT;
     }
 
     /**
--- a/modules/graphics/src/main/java/javafx/scene/Scene.java	Mon Jul 15 10:39:31 2013 +0200
+++ b/modules/graphics/src/main/java/javafx/scene/Scene.java	Mon Jul 15 12:19:04 2013 +0200
@@ -136,6 +136,7 @@
 import com.sun.javafx.tk.TKScenePaintListener;
 import com.sun.javafx.tk.TKStage;
 import com.sun.javafx.tk.Toolkit;
+import com.sun.javafx.scene.LayoutFlags;
 
 import sun.util.logging.PlatformLogger;
 import sun.util.logging.PlatformLogger.Level;
@@ -538,79 +539,10 @@
         }
     }
 
-    /**
-     * List of dirty layout roots.
-     * When a parent is either marked as a layout root or is unmanaged and it
-     * has its needsLayout flag set to true, then that node is added to this set
-     * so that it can be laid out on the next pulse without requiring its
-     * ancestors to be laid out.
-     */
-    private Set<Parent> dirtyLayoutRootsA = new LinkedHashSet<Parent>(10);
-    private Set<Parent> dirtyLayoutRootsB = new LinkedHashSet<Parent>(10);
-    private Set<Parent> dirtyLayoutRoots = dirtyLayoutRootsA;
-
-    /**
-     * Add the specified parent to this scene's dirty layout list.
-     * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
-     */
-    @Deprecated
-    public void addToDirtyLayoutList(Parent p) {
-        // If the current size of the list is 0 then we will need to schedule
-        // a pulse event because a layout pass is needed.
-        if (dirtyLayoutRoots.isEmpty()) {
-            Toolkit.getToolkit().requestNextPulse();
-        }
-        // Add the node.
-        dirtyLayoutRoots.add(p);
-    }
-
-    /**
-     * Remove the specified parent from this scene's dirty layout list.
-     */
-    void removeFromDirtyLayoutList(Parent p) {
-        dirtyLayoutRoots.remove(p);
-    }
-
     private void doLayoutPass() {
-        // sometimes a layout pass with cause scene-graph changes (bounds/structure)
-        // that leave some branches needing further layout, so pass through roots twice
-        layoutDirtyRoots();
-        layoutDirtyRoots();
-
-        // we don't want to spin too long in layout, so if there are still dirty
-        // roots, we'll leave those for next pulse.
-        if (dirtyLayoutRoots.size() > 0) {
-            PlatformLogger logger = Logging.getLayoutLogger();
-            if (logger.isLoggable(Level.FINER)) {
-                logger.finer("after layout pass, "+dirtyLayoutRoots.size()+" layout root nodes still dirty");
-            }
-            Toolkit.getToolkit().requestNextPulse();
-        }
-    }
-
-    private void layoutDirtyRoots() {
-        if (dirtyLayoutRoots.size() > 0) {
-            PlatformLogger logger = Logging.getLayoutLogger();
-            Set<Parent> temp = dirtyLayoutRoots;
-            if (dirtyLayoutRoots == dirtyLayoutRootsA) {
-                dirtyLayoutRoots = dirtyLayoutRootsB;
-            } else {
-                dirtyLayoutRoots = dirtyLayoutRootsA;
-            }
-
-            for (Parent parent : temp) {
-                if (parent.getScene() == this && parent.isNeedsLayout()) {
-                    if (logger.isLoggable(Level.FINE)) {
-                        logger.fine("<<< START >>> root = "+parent.toString());
-                    }
-                    parent.layout();
-                    if (logger.isLoggable(Level.FINE)) {
-                        logger.fine("<<<  END  >>> root = "+parent.toString());
-                    }
-                }
-            }
-            temp.clear();
+        final Parent r = getRoot();
+        if (r != null) {
+            r.layout();
         }
     }
 
@@ -1178,12 +1110,6 @@
         return root;
     }
 
-    private void doLayoutPassWithoutPulse(int maxAttempts) {
-        for (int i = 0; dirtyLayoutRoots.size() > 0 && i != maxAttempts; ++i) {
-            layoutDirtyRoots();
-        }
-    }
-
     void setNeedsRepaint() {
         if (this.impl_peer != null) {
             impl_peer.entireSceneNeedsRepaint();
@@ -1202,7 +1128,7 @@
 
         // we do not need pulse in the snapshot code
         // because this scene can be stage-less
-        doLayoutPassWithoutPulse(3);
+        doLayoutPass();
 
         if (!paused) {
             getRoot().updateBounds();
@@ -2116,9 +2042,10 @@
      * @return boolean indicating whether the scene is quiescent
      */
     boolean isQuiescent() {
+        final Parent r = getRoot();
         return !isFocusDirty()
-               && (getRoot().cssFlag == CssFlags.CLEAN)
-               && dirtyLayoutRoots.isEmpty();
+               && (r == null || (r.cssFlag == CssFlags.CLEAN &&
+                r.layoutFlag == LayoutFlags.CLEAN));
     }
 
     /**