]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/ParentNode.java
Performance improvements for ParentNode in scene graph
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / ParentNode.java
index 866e8d36cba6718fe20b5eebb926fe9698228e2a..e9e0b4be3e1f5beb2107b89733a1ac72ba04bc2d 100644 (file)
  *******************************************************************************/
 package org.simantics.scenegraph;
 
-import gnu.trove.map.hash.THashMap;
-
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.util.Collection;
-import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 
+import gnu.trove.map.hash.TLongObjectHashMap;
+
 /**
  * Base class of all scene graph nodes which can have a set of sub-nodes
  * (children). This class only provides support for unordered children.
@@ -33,11 +33,6 @@ public abstract class ParentNode<T extends INode> extends Node {
     public static final String                 UNLINK               = "#UNLINK#";
     public static final String                 NULL                 = "#NULL#";
 
-    protected static final String[]            EMPTY_STRING_ARRAY   = {};
-
-    @SuppressWarnings("rawtypes")
-    private static final Map                   DISPOSED_CHILDREN    = Collections.emptyMap();
-
     /**
      * A value used for {@link #rootNodeCache} to indicate the node has been
      * disposed and the root node cache is to be considered indefinitely
@@ -51,7 +46,8 @@ public abstract class ParentNode<T extends INode> extends Node {
         }
     };
 
-    protected transient Map<String, T>         children             = createChildMap();
+    protected transient Map<String, INode>       children      = createChildMap();
+    private transient TLongObjectHashMap<String> childrenIdMap = new TLongObjectHashMap<>();
 
     /**
      * A cached value for the root node of this parent node. This makes it
@@ -64,50 +60,57 @@ public abstract class ParentNode<T extends INode> extends Node {
      */
     protected transient volatile ParentNode<?> rootNodeCache;
 
-    protected Map<String, T> createChildMap() {
-       return new THashMap<String, T>(1);
+    protected Map<String, INode> createChildMap() {
+        return createChildMap(1);
     }
-    
+
+    protected Map<String, INode> createChildMap(int initialCapacity) {
+        // With JDK 1.8 HashMap is faster than Trove
+        return new HashMap<>(initialCapacity);
+    }
+
     public final <TC> TC addNode(Class<TC> a) {
         return addNode(java.util.UUID.randomUUID().toString(), a);
     }
 
-    @SuppressWarnings("unchecked")
     public <TC extends INode> TC addNode(String id, TC child) {
+        return addNodeInternal(id, child, true, true);
+    }
 
+    private <TC extends INode> TC addNodeInternal(String id, TC child, boolean init, boolean addToChildren) {
         child.setParent(this);
-        children.put(id, (T)child);
+        if (addToChildren)
+            children.put(id, child);
+
+        childrenIdMap.put(child.getId(), id);
 
-        child.init();
+        if (init)
+            child.init();
         childrenChanged();
         return (TC)child;
-       
     }
 
-    @SuppressWarnings("unchecked")
     public <TC extends INode> TC attachNode(String id, TC child) {
-
-        child.setParent(this);
-        children.put(id, (T)child);
-
-        child.attach();
-        childrenChanged();
-        return (TC)child;
-       
+        return addNodeInternal(id, child, false, true);
     }
 
     @SuppressWarnings("unchecked")
     public <TC extends INode> TC detachNode(String id) {
-        T child = children.remove(id);
+        INode child = children.remove(id);
         if (child == null)
             return null;
+        childrenIdMap.remove(child.getId());
         child.setParent(null);
         childrenChanged();
         return (TC) child;
     }
 
-    @SuppressWarnings("unchecked")
     public <TC> TC addNode(String id, Class<TC> a) {
+        return addNodeInternal0(id, a, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    private <TC> TC addNodeInternal0(String id, Class<TC> a, boolean addToChildren) {
         // a must be extended from Node
         if(!Node.class.isAssignableFrom(a)) {
             throw new IllegalArgumentException(a + " is not extended from org.simantics.scenegraph.Node");
@@ -120,75 +123,32 @@ public abstract class ParentNode<T extends INode> extends Node {
         } catch (IllegalAccessException e) {
             throw new NodeException("Node " + Node.getSimpleClassName(a) + " instantiation failed, see exception for details.", e);
         }
-
-        child.setParent(this);
-        children.put(id, (T)child);
-
-        child.init();
-        childrenChanged();
-        return (TC)child;
+        return (TC) addNodeInternal(id, child, true, addToChildren);
     }
 
     @SuppressWarnings("unchecked")
     public final <TC extends INode> TC getOrAttachNode(String id, TC a) {
-        synchronized (children) {
-            if (children.containsKey(id))
-                return (TC) children.get(id);
-        }
-        return attachNode(id, a);
+        return (TC) children.computeIfAbsent(id, key -> {
+            return (T) addNodeInternal(id, a, false, false);
+        });
     }
     
     @SuppressWarnings("unchecked")
     public final <TC> TC getOrCreateNode(String id, Class<TC> a) {
-        synchronized (children) {
-            if (children.containsKey(id))
-                return (TC) children.get(id);
-        }
-        return addNode(id, a);
+        return (TC) children.computeIfAbsent(id, key -> {
+            return (T) addNodeInternal0(id, a, false);
+        });
     }
 
     public final void removeNode(String id) {
-        INode child = null;
-        synchronized (children) {
-            child = children.remove(id);
-        }
-        if (child != null) {
-            if (child instanceof ParentNode<?>) {
-                ((ParentNode<?>) child).removeNodes();
-            }
-            child.cleanup();
-            child.setParent(null);
-            childrenChanged();
-            if (propertyChangeListener != null) {
-                propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), NULL)); // "children" is a special field name
-            }
-        }
+        INode child = children.remove(id);
+        if (child != null)
+            removeNodeInternal(child, true);
     }
 
     public final void removeNode(INode child) {
-        synchronized (children) {
-            String id = null;
-            // FIXME: damn slow, needs more data structure to be supported well
-            // One option would be to store the id<->node mappings in a BidiMap.
-            for (String tmp : children.keySet()) {
-                if (children.get(tmp).equals(child)) {
-                    id = tmp;
-                    break;
-                }
-            }
-            if(id == null) return;
-            children.remove(id);
-            childrenChanged();
-        }
-        if (child instanceof ParentNode<?>) {
-            ((ParentNode<?>) child).removeNodes();
-        }
-        child.cleanup();
-        child.setParent(null);
-
-        if (propertyChangeListener != null) {
-            propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), NULL)); // "children" is a special field name
-        }
+        String key = childrenIdMap.get(child.getId());
+        removeNode(key);
     }
 
     /**
@@ -196,25 +156,25 @@ public abstract class ParentNode<T extends INode> extends Node {
      * children).
      */
     public final void removeNodes() {
-        synchronized (children) {
-            boolean changed = false;
-            String[] keys = children.keySet().toArray(EMPTY_STRING_ARRAY);
-            for (String key : keys) {
-                INode child = children.remove(key);
-                if (child != null) {
-                    changed = true;
-                    if (child instanceof ParentNode<?>) {
-                        ((ParentNode<?>) child).removeNodes();
-                    }
-                    child.cleanup();
-                    child.setParent(null);
-                    if (propertyChangeListener != null) {
-                        propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), NULL)); // "children" is a special field name
-                    }
-                }
+        boolean changed = children.size() > 0;
+        children.forEach((id, child) -> removeNodeInternal(child, false));
+        children.clear();
+        childrenIdMap.clear();
+
+        if (changed)
+            childrenChanged();
+    }
+
+    private void removeNodeInternal(INode child, boolean triggerChildrenChanged) {
+        if (child != null) {
+            if (child instanceof ParentNode<?>) {
+                ((ParentNode<?>) child).removeNodes();
             }
-            if (changed)
+            child.cleanup();
+            child.setParent(null);
+            if (triggerChildrenChanged)
                 childrenChanged();
+            triggerPropertyChangeEvent(child);
         }
     }
 
@@ -228,8 +188,9 @@ public abstract class ParentNode<T extends INode> extends Node {
 
     public abstract void asyncRemoveNode(INode node);
 
+    @SuppressWarnings("unchecked")
     public T getNode(String id) {
-        return children.get(id);
+        return (T) children.get(id);
     }
 
     /**
@@ -243,8 +204,9 @@ public abstract class ParentNode<T extends INode> extends Node {
     /**
      * @return the collection of this node's children in an unspecified order
      */
+    @SuppressWarnings("unchecked")
     public Collection<T> getNodes() {
-        return children.values();
+        return (Collection<T>) children.values();
     }
 
     public int getNodeCount() {
@@ -258,32 +220,28 @@ public abstract class ParentNode<T extends INode> extends Node {
      */
     public void setPropertyChangeListener(PropertyChangeListener propertyChangeListener) {
         this.propertyChangeListener = propertyChangeListener;
-        synchronized(children) {
-            for(T t : children.values()) {
-                INode child = t;
-                if(child instanceof ParentNode<?>) {
-                    ((ParentNode<?>)child).setPropertyChangeListener(propertyChangeListener);
-                } else {
-                    ((Node)child).propertyChangeListener = propertyChangeListener; // FIXME
-                }
+        children.forEach((id, child) -> {
+            if(child instanceof ParentNode<?>) {
+                ((ParentNode<?>)child).setPropertyChangeListener(propertyChangeListener);
+            } else {
+                ((Node)child).propertyChangeListener = propertyChangeListener; // FIXME
             }
-        }
+        });
     }
 
-    @SuppressWarnings("unchecked")
     @Override
     public void cleanup() {
         retractMapping();
-        if (children != DISPOSED_CHILDREN) { 
-            synchronized(children) {
-                for(T child : children.values()) {
-                    ((INode)child).cleanup();
-                    ((INode)child).setParent(null);
-                }
-                children.clear();
-                children = DISPOSED_CHILDREN;
-                childrenChanged();
-            }
+        if (children != null) { 
+            children.forEach((id, child) -> {
+                child.cleanup();
+                child.setParent(null);
+            });
+            children.clear();
+            childrenIdMap.clear();
+            children = null;
+            childrenIdMap = null;
+            childrenChanged();
             rootNodeCache = DISPOSED;
         }
     }
@@ -294,13 +252,12 @@ public abstract class ParentNode<T extends INode> extends Node {
             return;
         }
 
-        synchronized (children) {
-            // 1. Add children under parent
-            parent.appendChildren(children);
+        // 1. Add children under parent
+        parent.appendChildren(children);
 
-            // 2. Clear children
-            children.clear();
-        }
+        // 2. Clear child maps to prevent cleanup from deleting them in step 4. 
+        children.clear();
+        childrenIdMap.clear();
 
         // 3. Remove this node from parent
         parent.unlinkChild(this);
@@ -313,30 +270,21 @@ public abstract class ParentNode<T extends INode> extends Node {
      * Helper method for delete()
      * @param children
      */
-    @SuppressWarnings("unchecked")
-    protected void appendChildren(Map<String, ?> children) {
-        synchronized(this.children) {
-            for(String id : children.keySet()) {
-                INode child = (INode)children.get(id);
-                this.children.put(id, (T)child); // Hopefully cast works
-                child.setParent(this);
-
-                // Send notify only if we are on server side (or standalone)
-                if (propertyChangeListener != null && location.equals(Location.LOCAL)) {
-                    propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", null, EXISTING)); // "children" is a special field name
-                }
-            }
-        }
+    protected void appendChildren(Map<String, INode> children) {
+        children.forEach((key, value) -> {
+            appendChildInternal(key, value);
+        });
     }
 
-    @SuppressWarnings("unchecked")
     protected void appendChild(String id, INode child) {
-        children.put(id, (T)child);
+        appendChildInternal(id, child);
+    }
+    
+    private void appendChildInternal(String id, INode child) {
+        children.put(id, child);
+        childrenIdMap.put(child.getId(), id);
         child.setParent(this);
-        // Send notify only if we are on server side (or standalone)
-        if (propertyChangeListener != null && location.equals(Location.LOCAL)) {
-            propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+(child).getId()+"]", null, EXISTING)); // "children" is a special field name
-        }
+        triggerPropertyChangeEvent(child);
     }
 
     /**
@@ -344,22 +292,17 @@ public abstract class ParentNode<T extends INode> extends Node {
      * @param child
      */
     protected void unlinkChild(INode child) {
-        synchronized(children) {
-            String id = null;
-            // FIXME: damn slow, needs more data structure to be supported well
-            // One option would be to store the id<->node mappings in a BidiMap.
-            for(String tmp : children.keySet()) {
-                if(children.get(tmp).equals(child)) {
-                    id = tmp;
-                    break;
-                }
-            }
-            if(id == null) return;
+        String id = childrenIdMap.remove(child.getId());
+        if(id != null) {
             children.remove(id);
             childrenChanged();
+            triggerPropertyChangeEvent(child);
         }
+    }
 
-        if(propertyChangeListener != null && location.equals(Location.LOCAL)) {
+    private void triggerPropertyChangeEvent(INode child) {
+        // Send notify only if we are on server side (or standalone)
+        if (propertyChangeListener != null && location.equals(Location.LOCAL)) {
             propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), UNLINK)); // "children" is a special field name
         }
     }