JAL-2759 improvements to speed up synchronisation; caching of size
[jalview.git] / src / jalview / datamodel / HiddenColumnsCursor.java
index 9d1351e..3284504 100644 (file)
 package jalview.datamodel;
 
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 
 public class HiddenColumnsCursor
 {
   // absolute position of first hidden column
   private int firstColumn;
 
-  // index of last visited region
-  private int regionIndex;
-
-  // number of hidden columns before last visited region
-  private int hiddenSoFar;
-
   private List<int[]> hiddenColumns;
 
+  // AtomicReference to hold the current region index and hidden column count
+  // Could be done with synchronisation but benchmarking shows this way is 2x
+  // faster
+  private final AtomicReference<HiddenCursorPosition> cursorPos = new AtomicReference<>(
+          new HiddenCursorPosition(0, 0, 0));
+
   protected HiddenColumnsCursor()
   {
 
   }
 
   /**
-   * Set the cursor to a position
+   * Reset the cursor with a new hidden columns collection. Calls to resetCursor
+   * should be made from within a writeLock in the HiddenColumns class - since
+   * changes to the hiddenColumns collection require a writeLock the lock should
+   * already exist.
    * 
    * @param hiddenCols
    */
   protected void resetCursor(List<int[]> hiddenCols)
   {
-    synchronized (this)
+    hiddenColumns = hiddenCols;
+    if ((hiddenCols != null) && (!hiddenCols.isEmpty()))
     {
-      hiddenColumns = hiddenCols;
-      if ((hiddenCols != null) && (!hiddenCols.isEmpty()))
-      {
-        firstColumn = hiddenColumns.get(0)[0];
-        regionIndex = 0;
-        hiddenSoFar = 0;
-      }
+      firstColumn = hiddenColumns.get(0)[0];
+      HiddenCursorPosition oldpos = cursorPos.get();
+      HiddenCursorPosition newpos = new HiddenCursorPosition(0, 0, 0);
+      cursorPos.compareAndSet(oldpos, newpos);
     }
   }
 
   /**
    * Delete the region the cursor is currently at. Avoids having to reset the
    * cursor just because we deleted a region.
+   * 
+   * Calls to updateForDeletedRegion should be made from within a writeLock in
+   * the HiddenColumns class - since changes to the hiddenColumns collection
+   * require a writeLock the lock should already exist.
    *
    * @param hiddenCols
    */
@@ -74,36 +80,23 @@ public class HiddenColumnsCursor
       // nothing changes; otherwise
       // we deleted the last region (index=hiddenCols.size()-1)
       // or the index was at the end of the alignment (index=hiddenCols.size())
-      if (regionIndex >= hiddenColumns.size() - 1)
+      HiddenCursorPosition oldpos = cursorPos.get();
+
+      int index = oldpos.getRegionIndex();
+      if (index >= hiddenColumns.size() - 1)
       {
         // deleted last region, index is now end of alignment
-        regionIndex = hiddenCols.size();
+        index = hiddenCols.size();
       }
-    }
-
-    hiddenColumns = hiddenCols;
-  }
 
-  protected void updateCursor(int index, int hiddenCount)
-  {
-    synchronized (this)
-    {
-      regionIndex = index;
-      hiddenSoFar = hiddenCount;
+      // update the cursor position
+      HiddenCursorPosition newpos = new HiddenCursorPosition(index,
+              oldpos.getHiddenSoFar(), oldpos.getNumColumns());
+      cursorPos.compareAndSet(oldpos, newpos);
     }
+    hiddenColumns = hiddenCols;
   }
 
-  protected synchronized int getIndex()
-  {
-    return regionIndex;
-  }
-
-  protected synchronized int getHiddenSoFar()
-  {
-    return hiddenSoFar;
-  }
-
-
   /**
    * Get the index of the region that column is within (if column is hidden) or
    * which is to the right of column (if column is visible). If no hidden
@@ -114,15 +107,16 @@ public class HiddenColumnsCursor
    *          absolute position of a column in the alignment
    * @return region index
    */
-  protected int findRegionForColumn(int column)
+  protected HiddenCursorPosition findRegionForColumn(int column)
   {
     if (hiddenColumns == null)
     {
-      return -1;
+      return null;
     }
 
-    int index = regionIndex;
-    int hiddenCount = hiddenSoFar;
+    HiddenCursorPosition oldpos = cursorPos.get();
+    int index = oldpos.getRegionIndex();
+    int hiddenCount = oldpos.getHiddenSoFar();
 
     if (index == hiddenColumns.size())
     {
@@ -175,8 +169,10 @@ public class HiddenColumnsCursor
         }
       }
     }
-    updateCursor(index, hiddenCount);
-    return index;
+    HiddenCursorPosition newpos = new HiddenCursorPosition(index,
+            hiddenCount, oldpos.getNumColumns());
+    cursorPos.compareAndSet(oldpos, newpos);
+    return newpos;
   }
 
   /**
@@ -187,15 +183,16 @@ public class HiddenColumnsCursor
    *          index of column in visible alignment
    * @return
    */
-  protected int getHiddenOffset(int column)
+  protected HiddenCursorPosition getHiddenOffset(int column)
   {
     if (hiddenColumns == null)
     {
-      return -1;
+      return null;
     }
 
-    int index = getIndex();
-    int hiddenCount = getHiddenSoFar();
+    HiddenCursorPosition oldpos = cursorPos.get();
+    int index = oldpos.getRegionIndex();
+    int hiddenCount = oldpos.getHiddenSoFar();
 
     if (column < firstColumn)
     {
@@ -226,7 +223,10 @@ public class HiddenColumnsCursor
       }
 
     }
-    updateCursor(index, hiddenCount);
-    return hiddenCount;
+
+    HiddenCursorPosition newpos = new HiddenCursorPosition(index,
+            hiddenCount, oldpos.getNumColumns());
+    cursorPos.compareAndSet(oldpos, newpos);
+    return newpos;
   }
 }