commit 07206c715ed80e550175706c12829ef5563dd22c
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Sat May 30 19:44:18 2020 +0100

    First attempt at Deferred Mesh Jobs.
    Only apply(VertexEffect) supported for now.

diff --git a/src/main/java/org/distorted/library/main/DistortedLibrary.java b/src/main/java/org/distorted/library/main/DistortedLibrary.java
index a84bd50..ddf485c 100644
--- a/src/main/java/org/distorted/library/main/DistortedLibrary.java
+++ b/src/main/java/org/distorted/library/main/DistortedLibrary.java
@@ -35,6 +35,8 @@ import org.distorted.library.effect.EffectType;
 import org.distorted.library.effect.FragmentEffect;
 import org.distorted.library.effect.PostprocessEffect;
 import org.distorted.library.effect.VertexEffect;
+import org.distorted.library.effectqueue.EffectQueueVertex;
+import org.distorted.library.mesh.DeferredJobs;
 import org.distorted.library.mesh.MeshBase;
 import org.distorted.library.message.EffectMessageSender;
 import org.distorted.library.program.DistortedProgram;
@@ -475,7 +477,7 @@ public class DistortedLibrary
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // execute all VertexEffects and adjust all vertices
 
-  public static void adjustVertices(MeshBase mesh)
+  public static void adjustVertices(MeshBase mesh, EffectQueueVertex queue)
     {
     if( mFullProgram!=null )
       {
@@ -486,8 +488,8 @@ public class DistortedLibrary
 
       mFullProgram.useProgram();
       mesh.bindVertexAttribs(mFullProgram);
-      mesh.computeQueue();
-      mesh.sendQueue();
+      queue.compute(1);
+      queue.send(0.0f,3);
       mesh.send(3);
 
       GLES30.glBindBufferBase(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfo );
@@ -979,6 +981,7 @@ public class DistortedLibrary
     DistortedEffects.onDestroy();
     EffectQueue.onDestroy();
     Effect.onDestroy();
+    DeferredJobs.onDestroy();
     EffectMessageSender.stopSending();
 
     mInitialized = false;
diff --git a/src/main/java/org/distorted/library/mesh/DeferredJobs.java b/src/main/java/org/distorted/library/mesh/DeferredJobs.java
new file mode 100644
index 0000000..1d011cb
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/DeferredJobs.java
@@ -0,0 +1,251 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 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.mesh;
+
+import org.distorted.library.effect.VertexEffect;
+import org.distorted.library.effectqueue.EffectQueueVertex;
+import org.distorted.library.main.DistortedLibrary;
+
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be cleaned from the main package)
+ *
+ * @y.exclude
+ */
+public class DeferredJobs
+  {
+  private static final int JOB_TYPE_VERTEX = 0;
+  private static final int JOB_TYPE_MERGE  = 1;
+  private static final int JOB_TYPE_JOIN   = 2;
+  private static final int JOB_TYPE_COPY   = 3;
+
+  private static ArrayList<JobNode> mJobs = new ArrayList<>();
+
+  //////////////////////////////////////////////////////////////////////////
+
+  private static class Job
+    {
+    private int mType;
+    private MeshBase mTarget;
+    private MeshBase[] mSource;
+    private EffectQueueVertex mEffects;
+
+    Job(int type, MeshBase target, MeshBase[] source, VertexEffect effect)
+      {
+      mType   = type;
+      mTarget = target;
+      mSource = source;
+      mEffects= new EffectQueueVertex();
+      mEffects.add(effect);
+      }
+
+    Job(Job job)
+      {
+      mType   = job.mType;
+      mTarget = job.mTarget;
+      mSource = job.mSource;
+      mEffects= new EffectQueueVertex(job.mEffects);
+      }
+
+    void addEffect(VertexEffect effect)
+      {
+      mEffects.add(effect);
+      }
+
+    void execute()
+      {
+      switch(mType)
+        {
+        case JOB_TYPE_VERTEX: DistortedLibrary.adjustVertices(mTarget, mEffects);
+                              break;
+        case JOB_TYPE_MERGE : break;
+        case JOB_TYPE_JOIN  : break;
+        case JOB_TYPE_COPY  : break;
+        }
+      }
+
+    void clear()
+      {
+      mEffects.removeAll(false);
+      }
+    }
+
+  //////////////////////////////////////////////////////////////////////////
+
+  static class JobNode
+    {
+    private ArrayList<JobNode> mPrevJobs;
+    private ArrayList<JobNode> mNextJobs;
+    private Job mJob;
+
+    JobNode(Job job)
+      {
+      mPrevJobs = new ArrayList<>();
+      mNextJobs = new ArrayList<>();
+      mJob      = job;
+      }
+
+    JobNode(JobNode node)
+      {
+      mPrevJobs = new ArrayList<>();
+      mNextJobs = new ArrayList<>();
+      mJob      = new Job(node.mJob);
+
+      int numPrev = node.mPrevJobs.size();
+
+      for(int i=0; i<numPrev; i++)
+        {
+        mPrevJobs.add(node.mPrevJobs.get(i));
+        }
+
+      int numNext = node.mNextJobs.size();
+
+      for(int i=0; i<numNext; i++)
+        {
+        mNextJobs.add(node.mNextJobs.get(i));
+        }
+      }
+
+    void execute()
+      {
+      JobNode node;
+      int numPrev = mPrevJobs.size();
+
+      for(int i=0; i<numPrev; i++)
+        {
+        node = mPrevJobs.get(i);
+        node.execute();
+        }
+
+      removeNode(this);
+      mJob.execute();
+      }
+
+    void clear()
+      {
+      mPrevJobs.clear();
+      mPrevJobs = null;
+      mNextJobs.clear();
+      mNextJobs = null;
+
+      mJob.clear();
+      mJob = null;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void removeNode(JobNode node)
+    {
+    mJobs.remove(node);
+    JobNode jn;
+
+    int numPrev = node.mPrevJobs.size();
+
+    for(int i=0; i<numPrev; i++)
+      {
+      jn = node.mPrevJobs.get(i);
+      jn.mNextJobs.remove(node);
+      }
+
+    int numNext = node.mNextJobs.size();
+
+    for(int i=0; i<numNext; i++)
+      {
+      jn = node.mNextJobs.get(i);
+      jn.mPrevJobs.remove(node);
+      }
+
+    node.mJob.mTarget.mJobNode = null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode vertex(MeshBase target, VertexEffect effect)
+    {
+    JobNode jn = target.mJobNode;
+
+    if( jn==null )
+      {
+      Job job = new Job(JOB_TYPE_VERTEX,target,null,effect);
+      JobNode node = new JobNode(job);
+      mJobs.add(node);
+      return node;
+      }
+    else
+      {
+      if( jn.mJob.mType==JOB_TYPE_VERTEX )
+        {
+        jn.mJob.addEffect(effect);
+        return jn;
+        }
+      else
+        {
+        Job job = new Job(JOB_TYPE_VERTEX,target,null,effect);
+        JobNode node = new JobNode(job);
+        node.mPrevJobs.add(jn);
+        jn.mNextJobs.add(node);
+        mJobs.add(node);
+        return node;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode merge(MeshBase target)
+    {
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode join(MeshBase target, MeshBase[] meshes)
+    {
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode copy(MeshBase target, MeshBase mesh)
+    {
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Only for use by the library itself.
+ *
+ * @y.exclude
+ */
+  public static void onDestroy()
+    {
+    int num = mJobs.size();
+
+    for(int i=0; i<num; i++)
+      {
+      mJobs.get(i).clear();
+      }
+
+    mJobs.clear();
+    }
+  }
diff --git a/src/main/java/org/distorted/library/mesh/MeshBase.java b/src/main/java/org/distorted/library/mesh/MeshBase.java
index f08b911..c8e9e58 100644
--- a/src/main/java/org/distorted/library/mesh/MeshBase.java
+++ b/src/main/java/org/distorted/library/mesh/MeshBase.java
@@ -24,14 +24,10 @@ import android.util.Log;
 
 import org.distorted.library.effect.VertexEffect;
 import org.distorted.library.effectqueue.EffectQueue;
-import org.distorted.library.effectqueue.EffectQueueVertex;
-import org.distorted.library.main.DistortedLibrary;
 import org.distorted.library.main.InternalBuffer;
 import org.distorted.library.program.DistortedProgram;
 import org.distorted.library.type.Static4D;
 
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
@@ -47,7 +43,7 @@ import java.util.ArrayList;
 public abstract class MeshBase
    {
    private static final int MAX_COMPONENTS= 100;
-   static final int DEFAULT_ASSOCIATION = 0xffffffff;
+   private static final int DEFAULT_ASSOCIATION = 0xffffffff;
 
    // sizes of attributes of an individual vertex.
    private static final int POS_DATA_SIZE= 3; // vertex coordinates: x,y,z
@@ -84,11 +80,11 @@ public abstract class MeshBase
    private float[] mVertAttribs1;             // packed: PosX,PosY,PosZ, NorX,NorY,NorZ, InfX,InfY,InfZ
    private float[] mVertAttribs2;             // packed: TexS,TexT, Component
    private float mInflate;
-   private boolean[] mNeedsAdjustment;
    private int[] mAssociation;
 
+   DeferredJobs.JobNode mJobNode;
+
    private static int[] mComponentAssociationH = new int[EffectQueue.MAIN_VARIANTS];
-   private EffectQueueVertex mQueue;
 
    private static class Component
      {
@@ -130,9 +126,6 @@ public abstract class MeshBase
 
      for(int i=0; i<MAX_COMPONENTS; i++) mAssociation[i] = DEFAULT_ASSOCIATION;
 
-     mNeedsAdjustment = new boolean[1];
-     mQueue = new EffectQueueVertex();
-
      mVBO1= new InternalBuffer(GLES30.GL_ARRAY_BUFFER             , GLES30.GL_STATIC_READ);
      mVBO2= new InternalBuffer(GLES30.GL_ARRAY_BUFFER             , GLES30.GL_STATIC_READ);
      mTFO = new InternalBuffer(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, GLES30.GL_STATIC_READ);
@@ -149,7 +142,6 @@ public abstract class MeshBase
 
      int size = original.mComponent.size();
      mComponent = new ArrayList<>();
-     mQueue = new EffectQueueVertex(original.mQueue);
 
      for(int i=0; i<size; i++)
        {
@@ -162,8 +154,10 @@ public abstract class MeshBase
 
      if( deep )
        {
-       mNeedsAdjustment = new boolean[1];
-       mNeedsAdjustment[0] = original.mNeedsAdjustment[0];
+       if( original.mJobNode!=null )
+         {
+         mJobNode = new DeferredJobs.JobNode(original.mJobNode);
+         }
 
        mVBO1= new InternalBuffer(GLES30.GL_ARRAY_BUFFER, GLES30.GL_STATIC_READ);
        mVertAttribs1= new float[mNumVertices*VERT1_ATTRIBS];
@@ -172,7 +166,7 @@ public abstract class MeshBase
        }
      else
        {
-       mNeedsAdjustment = original.mNeedsAdjustment;
+       mJobNode         = original.mJobNode;
        mVBO1            = original.mVBO1;
        mVertAttribs1    = original.mVertAttribs1;
        }
@@ -362,30 +356,6 @@ public abstract class MeshBase
        }
 
      GLES30.glUnmapBuffer(GLES30.GL_TRANSFORM_FEEDBACK);
-
-     mQueue.removeAll(false);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void computeQueue()
-     {
-     mQueue.compute(1);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void sendQueue()
-     {
-     mQueue.send(0.0f,3);
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -429,10 +399,9 @@ public abstract class MeshBase
  */
    public void bindVertexAttribs(DistortedProgram program)
      {
-     if( mNeedsAdjustment[0] )
+     if( mJobNode!=null )
        {
-       mNeedsAdjustment[0] = false;
-       DistortedLibrary.adjustVertices(this);
+       mJobNode.execute();  // this will set itself to null
        }
 
      int index1 = mVBO1.createImmediately(mNumVertices*VERT1_SIZE, mVertAttribs1);
@@ -485,28 +454,9 @@ public abstract class MeshBase
      return mInflate;
      }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Read this mesh from a InputStream.
- */
-  void read(final InputStream stream)
-    {
-
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Save the Mesh to a OutputStream.
- */
-  public void save(final OutputStream stream)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
 /**
  * When rendering this Mesh, do we want to render the Normal vectors as well?
  * <p>
@@ -558,8 +508,7 @@ public abstract class MeshBase
  */
    public void apply(VertexEffect effect)
      {
-     mQueue.add(effect);
-     mNeedsAdjustment[0] = true;
+     mJobNode = DeferredJobs.vertex(this,effect);
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
