commit 11845a9ed9e2d7122cd74182385d628d9f6d4a78
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Wed May 1 22:52:07 2019 +0100

    Carve the 'children list' from DOutputSurface and DNode into a separate class of its own, DistortedChildrenList.

diff --git a/src/main/java/org/distorted/library/main/DistortedChildrenList.java b/src/main/java/org/distorted/library/main/DistortedChildrenList.java
new file mode 100644
index 0000000..16ea928
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/DistortedChildrenList.java
@@ -0,0 +1,254 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted 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 for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+package org.distorted.library.main;
+
+import org.distorted.library.mesh.MeshBase;
+
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class DistortedChildrenList implements DistortedMaster.Slave
+  {
+  private static final int ATTACH = 0;
+  private static final int DETACH = 1;
+  private static final int DETALL = 2;
+  private static final int SORT   = 3;
+
+  private class Job
+    {
+    int type;
+    DistortedNode node;
+
+    Job(int t, DistortedNode n)
+      {
+      type = t;
+      node = n;
+      }
+    }
+
+  private ArrayList<Job> mJobs;
+  private ArrayList<DistortedNode> mChildren;
+  private DistortedChildrenList.Parent mParent;
+  private int mNumChildren;
+
+  public interface Parent
+    {
+    void adjustIsomorphism();
+    DistortedChildrenList getChildren();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  DistortedChildrenList(DistortedChildrenList.Parent parent)
+    {
+    mParent = parent;
+    mJobs = new ArrayList<>();
+    mChildren = null;
+    mNumChildren = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumChildren()
+    {
+    return mNumChildren;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  DistortedNode getChild(int index)
+    {
+    return mChildren.get(index);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void removeChild(DistortedNode node)
+    {
+    if( mChildren.remove(node) )
+      {
+      mNumChildren--;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Can make this logarithmic but the typical number of children is very small anyway.
+//
+// We want to keep same buckets next to each other, while avoiding changes in order of the children
+// (if possible!) We want to keep bucket=0 (i.e. the non-postprocessed children) at the beginning.
+
+  void addSortingByBuckets(DistortedNode newChild)
+    {
+    int i;
+    long bucket = newChild.getPostprocessQueue().getID();
+    boolean sameBucket = false;
+
+    for(i=0; i<mNumChildren; i++)
+      {
+      if( mChildren.get(i).getPostprocessQueue().getID() == bucket )
+        {
+        sameBucket=true;
+        }
+      else if( sameBucket || bucket==0 )
+        {
+        break;
+        }
+      }
+
+    mChildren.add(i,newChild);
+    mNumChildren++;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void attach(DistortedNode node)
+    {
+    mJobs.add(new Job(ATTACH,node));
+    DistortedMaster.newSlave(this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  DistortedNode attach(DistortedSurface surface, DistortedEffects effects, MeshBase mesh)
+    {
+    DistortedNode node = new DistortedNode(surface,effects,mesh);
+    mJobs.add(new Job(ATTACH,node));
+    DistortedMaster.newSlave(this);
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void detach(DistortedNode node)
+    {
+    mJobs.add(new Job(DETACH,node));
+    DistortedMaster.newSlave(this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void detach(DistortedEffects effects)
+    {
+    long id = effects.getID();
+    DistortedNode node;
+    boolean detached = false;
+
+    for(int i=0; i<mNumChildren; i++)
+      {
+      node = mChildren.get(i);
+
+      if( node.getEffects().getID()==id )
+        {
+        detached = true;
+        mJobs.add(new Job(DETACH,node));
+        DistortedMaster.newSlave(this);
+        break;
+        }
+      }
+
+    if( !detached )
+      {
+      // if we failed to detach any, it still might be the case that
+      // there's an ATTACH job that we need to cancel.
+      int num = mJobs.size();
+      Job job;
+
+      for(int i=0; i<num; i++)
+        {
+        job = mJobs.get(i);
+
+        if( job.type==ATTACH && job.node.getEffects()==effects )
+          {
+          mJobs.remove(i);
+          break;
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void detachAll()
+    {
+    mJobs.add(new Job(DETALL,null));
+    DistortedMaster.newSlave(this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * This is not really part of the public API. Has to be public only because it is a part of the
+ * DistortedSlave interface, which should really be a class that we extend here instead but
+ * Java has no multiple inheritance.
+ *
+ * @y.exclude
+ */
+  public void doWork()
+    {
+    int num = mJobs.size();
+
+    if( num>0 )
+      {
+      Job job;
+      int numChanges=0;
+
+      for(int i=0; i<num; i++)
+        {
+        job = mJobs.remove(0);
+
+        switch(job.type)
+          {
+          case ATTACH: numChanges++;
+                       if( mChildren==null ) mChildren = new ArrayList<>(2);
+                       job.node.setParent(mParent);
+                       addSortingByBuckets(job.node);
+                       break;
+          case DETACH: numChanges++;
+                       if( mNumChildren>0 && mChildren.remove(job.node) )
+                         {
+                         job.node.setParent(null);
+                         mNumChildren--;
+                         }
+                       break;
+          case DETALL: numChanges++;
+                       if( mNumChildren>0 )
+                         {
+                         DistortedNode tmp;
+
+                         for(int j=mNumChildren-1; j>=0; j--)
+                           {
+                           tmp = mChildren.remove(j);
+                           tmp.setParent(null);
+                           }
+
+                         mNumChildren = 0;
+                         }
+                       break;
+          case SORT  : mChildren.remove(job.node);
+                       addSortingByBuckets(job.node);
+                       break;
+          }
+        }
+      if( numChanges>0 ) mParent.adjustIsomorphism();
+      }
+    }
+  }
+
diff --git a/src/main/java/org/distorted/library/main/DistortedMaster.java b/src/main/java/org/distorted/library/main/DistortedMaster.java
index 5cad657..fb3ad22 100644
--- a/src/main/java/org/distorted/library/main/DistortedMaster.java
+++ b/src/main/java/org/distorted/library/main/DistortedMaster.java
@@ -99,35 +99,6 @@ public class DistortedMaster
     if( !found ) mSlaves.add(s);
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Can make this logarithmic but the typical number of children is very small anyway.
-//
-// We want to keep same buckets next to each other, while avoiding changes in order of the children
-// (if possible!) We want to keep bucket=0 (i.e. the non-postprocessed children) at the beginning.
-
-  static void addSortingByBuckets(ArrayList<DistortedNode> mChildren, DistortedNode newChild)
-    {
-    int i,num = mChildren.size();
-    long bucket = newChild.getPostprocessQueue().getID();
-    boolean sameBucket = false;
-
-    for(i=0; i<num; i++)
-      {
-      if( mChildren.get(i).getPostprocessQueue().getID() == bucket )
-        {
-        sameBucket=true;
-        }
-      else if( sameBucket || bucket==0 )
-        {
-        break;
-        }
-      }
-
-    mChildren.add(i,newChild);
-
-    //android.util.Log.e("newChild", "newBucket="+bucket+" new child at "+i+" total num ="+num);
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   static void onDestroy()
diff --git a/src/main/java/org/distorted/library/main/DistortedNode.java b/src/main/java/org/distorted/library/main/DistortedNode.java
index 703a088..5b1bee9 100644
--- a/src/main/java/org/distorted/library/main/DistortedNode.java
+++ b/src/main/java/org/distorted/library/main/DistortedNode.java
@@ -37,39 +37,18 @@ import java.util.Collections;
  * to sub-class 'NodeData'. Two identical sub-trees attached at different points of the main tree
  * will point to the same NodeData; only the first of this is rendered (mData.numRender!).
  */
-public class DistortedNode implements DistortedMaster.Slave
+public class DistortedNode implements DistortedChildrenList.Parent
   {
-  private static final int ATTACH = 0;
-  private static final int DETACH = 1;
-  private static final int DETALL = 2;
-  private static final int SORT   = 3;
-
-  private class Job
-    {
-    int type;
-    DistortedNode node;
-
-    Job(int t, DistortedNode n)
-      {
-      type = t;
-      node = n;
-      }
-    }
-
-  private ArrayList<Job> mJobs = new ArrayList<>();
-
-  private ArrayList<DistortedNode> mChildren;
-  private int[] mNumChildren;  // ==mChildren.length(), but we only create mChildren if the first one gets added
-
-  private boolean mRenderWayOIT;
-  private DistortedNode mParent;
-  private DistortedOutputSurface mSurfaceParent;
   private MeshBase mMesh;
   private DistortedEffects mEffects;
   private DistortedSurface mSurface;
   private DistortedRenderState mState;
   private DistortedNodeData mData;
+  private DistortedChildrenList mChildren;
+  private DistortedChildrenList.Parent mParent;
+
   private int mFboW, mFboH, mFboDepthStencil;
+  private boolean mRenderWayOIT;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -89,8 +68,9 @@ public class DistortedNode implements DistortedMaster.Slave
   private ArrayList<Long> generateIDList()
     {
     ArrayList<Long> ret = new ArrayList<>();
+    int numChildren = mChildren.getNumChildren();
 
-    if( mNumChildren[0]==0 )
+    if( numChildren==0 )
       {
       // add a negative number so this leaf never gets confused with a internal node
       // with a single child that happens to have ID identical to some leaf's Effects ID.
@@ -100,9 +80,9 @@ public class DistortedNode implements DistortedMaster.Slave
       {
       DistortedNode node;
    
-      for(int i=0; i<mNumChildren[0]; i++)
+      for(int i=0; i<numChildren; i++)
         {
-        node = mChildren.get(i);
+        node = mChildren.getChild(i);
         ret.add(node.mData.ID);
         }
 
@@ -129,13 +109,17 @@ public class DistortedNode implements DistortedMaster.Slave
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// tree isomorphism algorithm
-
-  private void adjustIsomorphism()
+/**
+ * This is not really part of the public API. Has to be public only because it is a part of the
+ * DistortedChildrenList.Parent interface.
+ *
+ * @y.exclude
+ */
+  public void adjustIsomorphism()
     {
     DistortedNodeData newData = DistortedNodeData.returnData(generateIDList());
     boolean deleteOldFBO = mData.removeData();
-    boolean createNewFBO = (mNumChildren[0]>0 && newData.mFBO==null);
+    boolean createNewFBO = (mChildren.getNumChildren()>0 && newData.mFBO==null);
 
     if( deleteOldFBO && createNewFBO )
       {
@@ -148,9 +132,7 @@ public class DistortedNode implements DistortedMaster.Slave
       }
     else if( createNewFBO )
       {
-      int width  = mFboW <= 0 ? mSurface.getWidth()  : mFboW;
-      int height = mFboH <= 0 ? mSurface.getHeight() : mFboH;
-      newData.mFBO = new DistortedFramebuffer(1,mFboDepthStencil, DistortedSurface.TYPE_TREE, width, height);
+      newData.mFBO = allocateNewFBO();
       }
 
     mData = newData;
@@ -158,12 +140,24 @@ public class DistortedNode implements DistortedMaster.Slave
     if( mParent!=null ) mParent.adjustIsomorphism();
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * This is not really part of the public API. Has to be public only because it is a part of the
+ * DistortedChildrenList.Parent interface.
+ *
+ * @y.exclude
+ */
+  public DistortedChildrenList getChildren()
+    {
+    return mChildren;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // return the total number of render calls issued
 
   int drawNoBlend(long currTime, DistortedOutputSurface surface)
     {
-    DistortedSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
+    DistortedSurface input = getInternalSurface();
 
     if( input.setAsInput() )
       {
@@ -182,7 +176,7 @@ public class DistortedNode implements DistortedMaster.Slave
 
   int drawOIT(long currTime, DistortedOutputSurface surface)
     {
-    DistortedSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
+    DistortedSurface input = getInternalSurface();
 
     if( input.setAsInput() )
       {
@@ -199,7 +193,7 @@ public class DistortedNode implements DistortedMaster.Slave
 
   int draw(long currTime, DistortedOutputSurface surface)
     {
-    DistortedSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
+    DistortedSurface input = getInternalSurface();
 
     if( input.setAsInput() )
       {
@@ -217,21 +211,16 @@ public class DistortedNode implements DistortedMaster.Slave
   int renderRecursive(long currTime)
     {
     int numRenders = 0;
+    int numChildren = mChildren.getNumChildren();
 
-    if( mNumChildren[0]>0 && mData.notRenderedYetAtThisTime(currTime) )
+    if( numChildren>0 && mData.notRenderedYetAtThisTime(currTime) )
       {
-      for (int i=0; i<mNumChildren[0]; i++)
-        {
-        numRenders += mChildren.get(i).renderRecursive(currTime);
-        }
-
-      if( mData.mFBO==null )
+      for (int i=0; i<numChildren; i++)
         {
-        int width  = mFboW <= 0 ? mSurface.getWidth()  : mFboW;
-        int height = mFboH <= 0 ? mSurface.getHeight() : mFboH;
-        mData.mFBO = new DistortedFramebuffer(1,mFboDepthStencil, DistortedSurface.TYPE_TREE, width, height);
+        numRenders += mChildren.getChild(i).renderRecursive(currTime);
         }
 
+      if( mData.mFBO==null ) mData.mFBO = allocateNewFBO();
       mData.mFBO.setAsOutput(currTime);
 
       if( mSurface.setAsInput() )
@@ -240,7 +229,7 @@ public class DistortedNode implements DistortedMaster.Slave
         DistortedEffects.blitPriv(mData.mFBO);
         }
 
-      numRenders += mData.mFBO.renderChildren(currTime,mNumChildren[0],mChildren,0, mRenderWayOIT);
+      numRenders += mData.mFBO.renderChildren(currTime,numChildren,mChildren,0, mRenderWayOIT);
       }
 
     return numRenders;
@@ -248,10 +237,18 @@ public class DistortedNode implements DistortedMaster.Slave
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  void setSurfaceParent(DistortedOutputSurface dep)
+  private DistortedFramebuffer allocateNewFBO()
     {
-    mSurfaceParent = dep;
-    mParent = null;
+    int width  = mFboW <= 0 ? mSurface.getWidth()  : mFboW;
+    int height = mFboH <= 0 ? mSurface.getHeight() : mFboH;
+    return new DistortedFramebuffer(1,mFboDepthStencil, DistortedSurface.TYPE_TREE, width, height);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setParent(DistortedChildrenList.Parent parent)
+    {
+    mParent = parent;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -260,14 +257,9 @@ public class DistortedNode implements DistortedMaster.Slave
     {
     if( mParent!=null )
       {
-      mParent.mChildren.remove(this);
-      DistortedMaster.addSortingByBuckets(mParent.mChildren,this);
-      }
-    else if( mSurfaceParent!=null )
-      {
-      ArrayList<DistortedNode> children = mSurfaceParent.getChildren();
-      children.remove(this);
-      DistortedMaster.addSortingByBuckets(children,this);
+      DistortedChildrenList siblings = mParent.getChildren();
+      siblings.removeChild(this);
+      siblings.addSortingByBuckets(this);
       }
     }
 
@@ -294,10 +286,8 @@ public class DistortedNode implements DistortedMaster.Slave
     mEffects       = effects;
     mMesh          = mesh;
     mState         = new DistortedRenderState();
-    mChildren      = null;
-    mNumChildren   = new int[1];
+    mChildren      = new DistortedChildrenList(this);
     mParent        = null;
-    mSurfaceParent = null;
     mRenderWayOIT  = false;
 
     mFboW            = 0;  // i.e. take this from
@@ -324,7 +314,6 @@ public class DistortedNode implements DistortedMaster.Slave
     mMesh         = node.mMesh;
     mState        = new DistortedRenderState();
     mParent       = null;
-    mSurfaceParent= null;
     mRenderWayOIT = false;
 
     mFboW            = node.mFboW;
@@ -357,20 +346,14 @@ public class DistortedNode implements DistortedMaster.Slave
         mSurface = new DistortedFramebuffer(1,depthStencil,DistortedSurface.TYPE_TREE,w,h);
         }
       }
+
     if( (flags & Distorted.CLONE_CHILDREN) != 0 )
       {
-      if( node.mChildren==null )     // do NOT copy over the NULL!
-        {
-        node.mChildren = new ArrayList<>(2);
-        }
-
       mChildren = node.mChildren;
-      mNumChildren = node.mNumChildren;
       }
     else
       {
-      mChildren = null;
-      mNumChildren = new int[1];
+      mChildren = new DistortedChildrenList(this);
       }
 
     mData = DistortedNodeData.returnData(generateIDList());
@@ -427,8 +410,7 @@ public class DistortedNode implements DistortedMaster.Slave
  */
   public void attach(DistortedNode node)
     {
-    mJobs.add(new Job(ATTACH,node));
-    DistortedMaster.newSlave(this);
+    mChildren.attach(node);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -445,10 +427,7 @@ public class DistortedNode implements DistortedMaster.Slave
  */
   public DistortedNode attach(DistortedSurface surface, DistortedEffects effects, MeshBase mesh)
     {
-    DistortedNode node = new DistortedNode(surface,effects,mesh);
-    mJobs.add(new Job(ATTACH,node));
-    DistortedMaster.newSlave(this);
-    return node;
+    return mChildren.attach(surface,effects,mesh);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -462,8 +441,7 @@ public class DistortedNode implements DistortedMaster.Slave
  */
   public void detach(DistortedNode node)
     {
-    mJobs.add(new Job(DETACH,node));
-    DistortedMaster.newSlave(this);
+    mChildren.detach(node);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -480,41 +458,7 @@ public class DistortedNode implements DistortedMaster.Slave
  */
   public void detach(DistortedEffects effects)
     {
-    long id = effects.getID();
-    DistortedNode node;
-    boolean detached = false;
-
-    for(int i=0; i<mNumChildren[0]; i++)
-      {
-      node = mChildren.get(i);
-
-      if( node.getEffects().getID()==id )
-        {
-        detached = true;
-        mJobs.add(new Job(DETACH,node));
-        DistortedMaster.newSlave(this);
-        break;
-        }
-      }
-
-    if( !detached )
-      {
-      // if we failed to detach any, it still might be the case that
-      // there's an ATTACH job that we need to cancel.
-      int num = mJobs.size();
-      Job job;
-
-      for(int i=0; i<num; i++)
-        {
-        job = mJobs.get(i);
-
-        if( job.type==ATTACH && job.node.getEffects()==effects )
-          {
-          mJobs.remove(i);
-          break;
-          }
-        }
-      }
+    mChildren.detach(effects);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -526,70 +470,7 @@ public class DistortedNode implements DistortedMaster.Slave
  */
   public void detachAll()
     {
-    mJobs.add(new Job(DETALL,null));
-    DistortedMaster.newSlave(this);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * This is not really part of the public API. Has to be public only because it is a part of the
- * DistortedSlave interface, which should really be a class that we extend here instead but
- * Java has no multiple inheritance.
- *
- * @y.exclude
- */
-  public void doWork()
-    {
-    int num = mJobs.size();
-
-    if( num>0 )
-      {
-      Job job;
-      int numChanges=0;
-
-      for(int i=0; i<num; i++)
-        {
-        job = mJobs.remove(0);
-
-        switch(job.type)
-          {
-          case ATTACH: numChanges++;
-                       if( mChildren==null ) mChildren = new ArrayList<>(2);
-                       job.node.mParent = this;
-                       job.node.mSurfaceParent = null;
-                       DistortedMaster.addSortingByBuckets(mChildren,job.node);
-                       mNumChildren[0]++;
-                       break;
-          case DETACH: numChanges++;
-                       if( mNumChildren[0]>0 && mChildren.remove(job.node) )
-                         {
-                         job.node.mParent = null;
-                         job.node.mSurfaceParent = null;
-                         mNumChildren[0]--;
-                         }
-                       break;
-          case DETALL: numChanges++;
-                       if( mNumChildren[0]>0 )
-                         {
-                         DistortedNode tmp;
-
-                         for(int j=mNumChildren[0]-1; j>=0; j--)
-                           {
-                           tmp = mChildren.remove(j);
-                           tmp.mParent = null;
-                           tmp.mSurfaceParent = null;
-                           }
-
-                         mNumChildren[0] = 0;
-                         }
-                       break;
-          case SORT  : mChildren.remove(job.node);
-                       DistortedMaster.addSortingByBuckets(mChildren,job.node);
-                       break;
-          }
-        }
-      if( numChanges>0 ) adjustIsomorphism();
-      }
+    mChildren.detachAll();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -622,7 +503,7 @@ public class DistortedNode implements DistortedMaster.Slave
    */
   public DistortedSurface getInternalSurface()
     {
-    return mNumChildren[0]==0 ? mSurface : mData.mFBO;
+    return mChildren.getNumChildren()==0 ? mSurface : mData.mFBO;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/main/DistortedOutputSurface.java b/src/main/java/org/distorted/library/main/DistortedOutputSurface.java
index 6164313..ac05b52 100644
--- a/src/main/java/org/distorted/library/main/DistortedOutputSurface.java
+++ b/src/main/java/org/distorted/library/main/DistortedOutputSurface.java
@@ -25,69 +25,26 @@ import android.opengl.Matrix;
 import org.distorted.library.effect.EffectQuality;
 import org.distorted.library.mesh.MeshBase;
 
-import java.util.ArrayList;
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * This is not really part of the public API.
  *
  * @y.exclude
  */
-public abstract class DistortedOutputSurface extends DistortedSurface implements DistortedMaster.Slave
+public abstract class DistortedOutputSurface extends DistortedSurface implements DistortedChildrenList.Parent
 {
-/**
- * Do not create DEPTH or STENCIL attachment
- */
   public static final int NO_DEPTH_NO_STENCIL = 0;
-/**
- * Create DEPTH, but not STENCIL
- */
   public static final int DEPTH_NO_STENCIL    = 1;
-/**
- * Create both DEPTH and STENCIL
- */
   public static final int BOTH_DEPTH_STENCIL  = 2;
 
-  private static final int ATTACH = 0;
-  private static final int DETACH = 1;
-  private static final int DETALL = 2;
-  private static final int SORT   = 3;
-
-  private ArrayList<DistortedNode> mChildren;
-  private int mNumChildren;   // ==mChildren.length(), but we only create mChildren if the first one gets added
-  private boolean mRenderWayOIT;
-
-  private class Job
-    {
-    int type;
-    DistortedNode node;
-
-    Job(int t, DistortedNode n)
-      {
-      type = t;
-      node = n;
-      }
-    }
-
-  private ArrayList<Job> mJobs = new ArrayList<>();
-
-  // Global buffers used for postprocessing.
-  private static DistortedFramebuffer[] mBuffer=null;
-
-  float mFOV;
-  float mDistance, mNear;
+  float mFOV, mDistance, mNear;
   float[] mProjectionMatrix;
 
   int mDepthStencilCreated;
   int mDepthStencil;
   int[] mDepthStencilH;
   int[] mFBOH;
-  private long[] mTime;
 
-  private float mClearR, mClearG, mClearB, mClearA;
-  private float mClearDepth;
-  private int mClearStencil;
-  private int mClear;
   float mMipmap;
 
   int mRealWidth;   // the Surface can be backed up with a texture that is
@@ -97,6 +54,13 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
 
   int mCurrFBO;     // internal current FBO (see Distorted.FBO_QUEUE_SIZE)
 
+  private static DistortedFramebuffer[] mBuffer=null; // Global buffers used for postprocessing.
+  private long[] mTime;
+  private float mClearR, mClearG, mClearB, mClearA, mClearDepth;
+  private int mClear, mClearStencil;
+  private boolean mRenderWayOIT;
+  private DistortedChildrenList mChildren;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   DistortedOutputSurface(int width, int height, int createColor, int numfbos, int numcolors, int depthStencil, int fbo, int type)
@@ -137,6 +101,8 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
 
     mMipmap = 1.0f;
 
+    mChildren = new DistortedChildrenList(this);
+
     createProjection();
     }
 
@@ -409,7 +375,7 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
 // to a whole buffer (lastQueue.postprocess) and merge it (this.oitBuild or blitWithDepth - depending
 // on the type of rendering)
 
-  int renderChildren(long time, int numChildren, ArrayList<DistortedNode> children, int fbo, boolean oit)
+  int renderChildren(long time, int numChildren, DistortedChildrenList children, int fbo, boolean oit)
     {
     int quality=0, numRenders=0, bucketChange=0;
     DistortedNode child;
@@ -431,7 +397,7 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
 
     for(int i=0; i<numChildren; i++)
       {
-      child = children.get(i);
+      child = children.getChild(i);
       currQueue = child.getPostprocessQueue();
       currBucket= currQueue.getID();
 
@@ -465,7 +431,7 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
             }
           else
             {
-            for(int j=bucketChange; j<i; j++) numRenders += lastQueue.preprocess( mBuffer[quality],children.get(j) );
+            for(int j=bucketChange; j<i; j++) numRenders += lastQueue.preprocess( mBuffer[quality],children.getChild(j) );
             numRenders += lastQueue.postprocess(mBuffer[quality]);
 
             if( oit )
@@ -507,7 +473,7 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
 
         if( i==numChildren-1 )
           {
-          for(int j=bucketChange; j<numChildren; j++) numRenders += currQueue.preprocess( mBuffer[quality],children.get(j) );
+          for(int j=bucketChange; j<numChildren; j++) numRenders += currQueue.preprocess( mBuffer[quality],children.getChild(j) );
           numRenders += currQueue.postprocess(mBuffer[quality]);
 
           if( oit )
@@ -536,12 +502,29 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  ArrayList<DistortedNode> getChildren()
+/**
+ * This is not really part of the public API. Has to be public only because it is a part of the
+ * DistortedChildrenList.Parent interface.
+ *
+ * @y.exclude
+ */
+  public DistortedChildrenList getChildren()
     {
     return mChildren;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * This is not really part of the public API. Has to be public only because it is a part of the
+ * DistortedChildrenList.Parent interface.
+ *
+ * @y.exclude
+ */
+  public void adjustIsomorphism()
+    {
+
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Not part of the Public API.
@@ -611,48 +594,18 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
  */
   public int render(long time, int fbo)
     {
-    // change tree topology (attach and detach children)
-/*
-    boolean changed1 =
-*/
     DistortedMaster.toDo();
-/*
-    if( changed1 )
-      {
-      for(int i=0; i<mNumChildren; i++)
-        {
-        mChildren.get(i).debug(0);
-        }
-
-      DistortedNode.debugMap();
-      }
-*/
-    // create and delete all underlying OpenGL resources
-    // Watch out: FIRST change topology, only then deal
-    // with OpenGL resources. That's because changing Tree
-    // can result in additional Framebuffers that would need
-    // to be created immediately, before the calls to drawRecursive()
-/*
-    boolean changed2 =
-*/
     toDo();
-/*
-    if( changed2 )
-      {
-      DistortedObject.debugLists();
-      }
-*/
-    // mark OpenGL state as unknown
     DistortedRenderState.reset();
 
-    int numRenders=0;
+    int numRenders=0, numChildren = mChildren.getNumChildren();
 
-    for(int i=0; i<mNumChildren; i++)
+    for(int i=0; i<numChildren; i++)
       {
-      numRenders += mChildren.get(i).renderRecursive(time);
+      numRenders += mChildren.getChild(i).renderRecursive(time);
       }
 
-    numRenders += renderChildren(time,mNumChildren,mChildren,fbo, mRenderWayOIT);
+    numRenders += renderChildren(time,numChildren,mChildren,fbo, mRenderWayOIT);
 
     return numRenders;
     }
@@ -927,8 +880,7 @@ public void setOrderIndependentTransparency(boolean oit, float initialSize)
  */
   public void attach(DistortedNode node)
     {
-    mJobs.add(new Job(ATTACH,node));
-    DistortedMaster.newSlave(this);
+    mChildren.attach(node);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -945,10 +897,7 @@ public void setOrderIndependentTransparency(boolean oit, float initialSize)
  */
   public DistortedNode attach(DistortedSurface surface, DistortedEffects effects, MeshBase mesh)
     {
-    DistortedNode node = new DistortedNode(surface,effects,mesh);
-    mJobs.add(new Job(ATTACH,node));
-    DistortedMaster.newSlave(this);
-    return node;
+    return mChildren.attach(surface,effects,mesh);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -965,41 +914,7 @@ public void setOrderIndependentTransparency(boolean oit, float initialSize)
  */
   public void detach(DistortedEffects effects)
     {
-    long id = effects.getID();
-    DistortedNode node;
-    boolean detached = false;
-
-    for(int i=0; i<mNumChildren; i++)
-      {
-      node = mChildren.get(i);
-
-      if( node.getEffects().getID()==id )
-        {
-        detached = true;
-        mJobs.add(new Job(DETACH,node));
-        DistortedMaster.newSlave(this);
-        break;
-        }
-      }
-
-    if( !detached )
-      {
-      // if we failed to detach any, it still might be the case that
-      // there's an ATTACH job that we need to cancel.
-      int num = mJobs.size();
-      Job job;
-
-      for(int i=0; i<num; i++)
-        {
-        job = mJobs.get(i);
-
-        if( job.type==ATTACH && job.node.getEffects()==effects )
-          {
-          mJobs.remove(i);
-          break;
-          }
-        }
-      }
+    mChildren.detach(effects);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1013,8 +928,7 @@ public void setOrderIndependentTransparency(boolean oit, float initialSize)
  */
   public void detach(DistortedNode node)
     {
-    mJobs.add(new Job(DETACH,node));
-    DistortedMaster.newSlave(this);
+    mChildren.detach(node);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1026,61 +940,6 @@ public void setOrderIndependentTransparency(boolean oit, float initialSize)
  */
   public void detachAll()
     {
-    mJobs.add(new Job(DETALL,null));
-    DistortedMaster.newSlave(this);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * This is not really part of the public API. Has to be public only because it is a part of the
- * DistortedSlave interface, which should really be a class that we extend here instead but
- * Java has no multiple inheritance.
- *
- * @y.exclude
- */
-  public void doWork()
-    {
-    int num = mJobs.size();
-
-    if( num>0 )
-      {
-      Job job;
-
-      for(int i=0; i<num; i++)
-        {
-        job = mJobs.remove(0);
-
-        switch(job.type)
-          {
-          case ATTACH: if( mChildren==null ) mChildren = new ArrayList<>(2);
-                       job.node.setSurfaceParent(this);
-                       DistortedMaster.addSortingByBuckets(mChildren,job.node);
-                       mNumChildren++;
-                       break;
-          case DETACH: if( mNumChildren>0 && mChildren.remove(job.node) )
-                         {
-                         job.node.setSurfaceParent(null);
-                         mNumChildren--;
-                         }
-                       break;
-          case DETALL: if( mNumChildren>0 )
-                         {
-                         DistortedNode tmp;
-
-                         for(int j=mNumChildren-1; j>=0; j--)
-                           {
-                           tmp = mChildren.remove(j);
-                           tmp.setSurfaceParent(null);
-                           }
-
-                         mNumChildren = 0;
-                         }
-                       break;
-          case SORT  : mChildren.remove(job.node);
-                       DistortedMaster.addSortingByBuckets(mChildren,job.node);
-                       break;
-          }
-        }
-      }
+    mChildren.detachAll();
     }
 }
diff --git a/src/main/java/org/distorted/library/main/EffectQueue.java b/src/main/java/org/distorted/library/main/EffectQueue.java
index 9f7aebb..da072d8 100644
--- a/src/main/java/org/distorted/library/main/EffectQueue.java
+++ b/src/main/java/org/distorted/library/main/EffectQueue.java
@@ -126,17 +126,6 @@ abstract class EffectQueue implements DistortedMaster.Slave
 
     int numNodes = (mNodes==null ? 0: mNodes.size());
     for(int i=0; i<numNodes; i++) mNodes.get(i).sort();
-
-/*
-    if( mIndex == EffectType.MATRIX.ordinal() )
-      android.util.Log.d("queue", "queueM id="+mID);
-    if( mIndex == EffectType.VERTEX.ordinal() )
-      android.util.Log.d("queue", "queueV id="+mID);
-    if( mIndex == EffectType.FRAGMENT.ordinal() )
-      android.util.Log.d("queue", "queueF id="+mID);
-    if( mIndex == EffectType.POSTPROCESS.ordinal() )
-      android.util.Log.d("queue", "queueP id="+mID);
-*/
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -341,6 +330,7 @@ abstract class EffectQueue implements DistortedMaster.Slave
  */
   public void doWork()
     {
+    boolean changed = false;
     int num = mJobs.size();
     Job job;
 
@@ -351,7 +341,6 @@ abstract class EffectQueue implements DistortedMaster.Slave
       switch(job.type)
         {
         case CREATE: int max = mMax[mIndex];
-
                      if( max>0 )
                        {
                        mUniforms        = new float[max*job.num];
@@ -368,6 +357,7 @@ abstract class EffectQueue implements DistortedMaster.Slave
                        mEffects[mNumEffects] = job.effect;
                        mName[mNumEffects] = job.effect.getName().ordinal();
                        mNumEffects++;
+                       changed = true;
                        }
                      else
                        {
@@ -379,12 +369,15 @@ abstract class EffectQueue implements DistortedMaster.Slave
                        if (mEffects[j] == job.effect)
                          {
                          remove(j);
+                         changed = true;
                          break;
                          }
                        }
                      break;
         case DETALL: for(int j=0; j<mNumEffects; j++ )
                        {
+                       changed = true;
+
                        if( job.notify )
                          {
                          for(int k=0; k<mNumListeners; k++)
@@ -399,6 +392,6 @@ abstract class EffectQueue implements DistortedMaster.Slave
         }
       }
 
-    if( num>0 && mIndex==EffectType.POSTPROCESS.ordinal() ) regenerateIDandSort();
+    if( changed && mIndex==EffectType.POSTPROCESS.ordinal() ) regenerateIDandSort();
     }
   }
