Author: [email protected]
Date: Tue Apr 28 09:42:12 2009
New Revision: 5293

Modified:
    trunk/dev/core/src/com/google/gwt/dev/Link.java
    trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
    trunk/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
    trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
    trunk/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java

Log:
Changes the code splitter to use a simpler splitting strategy.
There is now only a single leftovers fragment.  Additionally,
each split point gets either a base or an exclusive fragment.
As a result, all code ends up in exactly one fragment.

An additional trick in the new splitting strategy is that
the splitter tries to predict which split point will be
reached first.  If there is such a one, it is given a base
fragment instead of an exclusive, and it can be loaded
without waiting for the leftovers.  This process is then
repeated, trying to find a split point definitely reached
second, etc., until no such split point is found.

Review by: scottb



Modified: trunk/dev/core/src/com/google/gwt/dev/Link.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/Link.java     (original)
+++ trunk/dev/core/src/com/google/gwt/dev/Link.java     Tue Apr 28 09:42:12 2009
@@ -248,62 +248,23 @@
    }

    /**
-   * <p>
-   * Computes and logs the "maximum total script size" for this  
permutation. The
-   * total script size for one sequence of split points reached is the sum  
of
-   * the scripts that are downloaded for that sequence. The maximum total  
script
-   * size is the maximum such size for all possible sequences of split  
points.
-   * </p>
+   * Logs the total script size for this permutation, as calculated by
+   * {...@link CodeSplitter#totalScriptSize(int[])}.
     */
    private static void logScriptSize(TreeLogger logger, int permId,
        StandardCompilationResult compilation) {
-    /*
-     * The total script size is fully determined by the first split point  
that
-     * is reached; the order that the remaining are reached doesn't  
matter. To
-     * find the maximum, divide the sum into two parts: first add the  
initial
-     * and exclusive fragments, and then calculate the adjustment that  
should be
-     * applied depending on which split point comes first. Choose among  
these
-     * adjustments the one that is largest.
-     */
-
      String[] javaScript = compilation.getJavaScript();
-    int numSplitPoints =  
CodeSplitter.numSplitPointsForFragments(javaScript.length);
-    int maxTotalSize;
-
-    if (numSplitPoints == 0) {
-      maxTotalSize = javaScript[0].length();
-    } else {
-      // Add up the initial and exclusive fragments
-      maxTotalSize = javaScript[0].length();
-      for (int sp = 1; sp <= numSplitPoints; sp++) {
-        int excl = CodeSplitter.getExclusiveFragmentNumber(sp,  
numSplitPoints);
-        maxTotalSize += javaScript[excl].length();
-      }
-
-      // Find the largest adjustment for any split point
-      boolean first = true;
-      int adjustment = 0;

-      for (int sp = 1; sp <= numSplitPoints; sp++) {
-        int excl = CodeSplitter.getExclusiveFragmentNumber(sp,  
numSplitPoints);
-        int base = CodeSplitter.getBaseFragmentNumber(sp, numSplitPoints);
-        int leftovers = CodeSplitter.getLeftoversFragmentNumber(sp,
-            numSplitPoints);
-        int thisAdjustment = javaScript[base].length()
-            + javaScript[leftovers].length() - javaScript[excl].length();
-        if (first || (thisAdjustment > adjustment)) {
-          adjustment = thisAdjustment;
-        }
-        first = false;
-      }
-
-      maxTotalSize += adjustment;
+    int[] jsLengths = new int[javaScript.length];
+    for (int i = 0; i < javaScript.length; i++) {
+      jsLengths[i] = javaScript[i].length();
      }

+    int totalSize = CodeSplitter.totalScriptSize(jsLengths);
+
      logger.log(TreeLogger.TRACE, "Permutation " + permId + " (strong name "
          + compilation.getStrongName() + ") has an initial download size  
of "
-        + javaScript[0].length() + " and max total script size of "
-        + maxTotalSize);
+        + javaScript[0].length() + " and total script size of " +  
totalSize);
    }

    private final LinkOptionsImpl options;

Modified:  
trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java     
 
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java     
 
Tue Apr 28 09:42:12 2009
@@ -117,6 +117,7 @@
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
+import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
@@ -187,6 +188,12 @@

        // (4) Optimize the normalized Java AST for each permutation.
        optimize(options, jprogram);
+
+      // (4.5) Choose an initial load order sequence for runAsync's
+      LinkedHashSet<Integer> initialLoadSequence = new  
LinkedHashSet<Integer>();
+      if (options.isAggressivelyOptimize() && options.isRunAsyncEnabled())  
{
+        initialLoadSequence = CodeSplitter.pickInitialLoadSequence(logger,  
jprogram);
+      }

        // (5) "Normalize" the high-level Java tree into a lower-level tree  
more
        // suited for JavaScript code generation. Don't go reordering these
@@ -260,7 +267,8 @@

        // (10.5) Split up the program into fragments
        if (options.isAggressivelyOptimize() && options.isRunAsyncEnabled())  
{
-        CodeSplitter.exec(logger, jprogram, jsProgram,  
postStringInterningMap);
+        CodeSplitter.exec(logger, jprogram, jsProgram,  
postStringInterningMap,
+            initialLoadSequence);
        }

        // (11) Perform any post-obfuscation normalizations.

Modified: trunk/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java     
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java    Tue  
Apr 28 09:42:12 2009
@@ -16,13 +16,17 @@
  package com.google.gwt.dev.jjs.impl;

  import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.jjs.SourceInfo;
  import com.google.gwt.dev.jjs.ast.Context;
  import com.google.gwt.dev.jjs.ast.JClassLiteral;
  import com.google.gwt.dev.jjs.ast.JDeclaredType;
  import com.google.gwt.dev.jjs.ast.JExpression;
  import com.google.gwt.dev.jjs.ast.JField;
  import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JNewArray;
  import com.google.gwt.dev.jjs.ast.JNode;
+import com.google.gwt.dev.jjs.ast.JPrimitiveType;
  import com.google.gwt.dev.jjs.ast.JProgram;
  import com.google.gwt.dev.jjs.ast.JReferenceType;
  import com.google.gwt.dev.jjs.ast.JStringLiteral;
@@ -40,15 +44,18 @@
  import com.google.gwt.dev.js.ast.JsVars;
  import com.google.gwt.dev.js.ast.JsVars.JsVar;
  import com.google.gwt.dev.util.PerfLogger;
+import com.google.gwt.dev.util.collect.HashMap;
+import com.google.gwt.dev.util.collect.HashSet;

  import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Queue;
  import java.util.Set;
  import java.util.concurrent.ArrayBlockingQueue;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;

  /**
   * <p>
@@ -64,28 +71,30 @@
   * The precise way the program is fragmented is an implementation detail  
that is
   * subject to change. Whenever the fragment strategy changes,
   * <code>AsyncFragmentLoader</code> must be updated in tandem. That said,  
the
- * current fragmentation strategy is to create an initial fragment and then
- * three more fragments for each split point. For each split point, there  
is:
+ * current fragmentation strategy is to create an initial fragment, a  
leftovers
+ * fragment, and one fragment per split point. Additionally, the splitter
+ * computes an initial load sequence. All runAsync calls in the initial  
load
+ * sequence are reached before any call not in the sequence. Further, any  
call
+ * in the sequence is reached before any call later in the sequence.
   * </p>
   *
- * <ul>
- * <li>a secondary base fragment, which is downloaded if this split point  
is the
- * first one reached. It contains enough code to continue running as soon  
as it
- * downloads.
- * <li>an exclusively live fragment, which is downloaded if this split  
point is
- * reached but is not the first one. It includes only that code that is
- * exclusively needed by this split point.
- * <li>a leftovers fragment, which includes all code that is in none of:  
the
- * initial download, any exclusive fragment, or the secondary base  
fragment for
- * this split point.
- * </ul>
+ * <p>
+ * The fragment for a split point contains different things depending on  
whether
+ * it is in the initial load sequence or not. If it's in the initial load
+ * sequence, then the fragment includes the code newly live once that split
+ * point is crossed, that wasn't already live for the set of split points
+ * earlier in the sequence. For a split point not in the initial load  
sequence,
+ * the fragment contains only code exclusive to that split point, that is,  
code
+ * that cannot be reached except via that split point. All other code goes  
into
+ * the leftovers fragment.
+ * </p>
   */
  public class CodeSplitter {
    /**
     * A statement logger that immediately prints out everything live that it
     * sees.
     */
-  public class EchoStatementLogger implements StatementLogger {
+  private class EchoStatementLogger implements StatementLogger {
      public void logStatement(JsStatement stat, boolean isIncluded) {
        if (isIncluded) {
          if (stat instanceof JsExprStmt) {
@@ -119,10 +128,13 @@
    }

    /**
-   * A map from program atoms to the fragment they should be placed in. An  
entry
-   * of 0 means it did not go into any fragment in particular.
+   * A map from program atoms to the split point, if any, that they are
+   * exclusive to. Atoms not exclusive to any split point are either  
mapped to 0
+   * or left out of the map entirely. Note that the map is incomplete; any  
entry
+   * not included has not been proven to be exclusive. Also, note that the
+   * initial load sequence is assumed to already be loaded.
     */
-  private static class FragmentMap {
+  private static class ExclusivityMap {
      public Map<JField, Integer> fields = new HashMap<JField, Integer>();
      public Map<JMethod, Integer> methods = new HashMap<JMethod, Integer>();
      public Map<String, Integer> strings = new HashMap<String, Integer>();
@@ -130,16 +142,15 @@
    }

    /**
-   * A liveness predicate that is based on a fragment map. See
-   * {...@link #mapFragments()}. Note that all non-zero fragments are assumed  
to
-   * load after fragment 0, and so everything in fragment 0 is always live.
+   * A liveness predicate that is based on an exclusivity map.
     */
-  private static class FragmentMapLivenessPredicate implements
+  private static class ExclusivityMapLivenessPredicate implements
        LivenessPredicate {
      private final int fragment;
-    private final FragmentMap fragmentMap;
+    private final ExclusivityMap fragmentMap;

-    public FragmentMapLivenessPredicate(FragmentMap fragmentMap, int  
fragment) {
+    public ExclusivityMapLivenessPredicate(ExclusivityMap fragmentMap,
+        int fragment) {
        this.fragmentMap = fragmentMap;
        this.fragment = fragment;
      }
@@ -175,46 +186,11 @@
      }
    }

-  /**
-   * A liveness predicate that checks two separate underlying predicates.
-   */
-  private static class UnionLivenessPredicate implements LivenessPredicate  
{
-    private final LivenessPredicate pred1;
-    private final LivenessPredicate pred2;
-
-    public UnionLivenessPredicate(LivenessPredicate pred1,
-        LivenessPredicate pred2) {
-      this.pred1 = pred1;
-      this.pred2 = pred2;
-    }
-
-    public boolean isLive(JField field) {
-      return pred1.isLive(field) || pred2.isLive(field);
-    }
-
-    public boolean isLive(JMethod method) {
-      return pred1.isLive(method) || pred2.isLive(method);
-    }
-
-    public boolean isLive(JReferenceType type) {
-      return pred1.isLive(type) || pred2.isLive(type);
-    }
-
-    public boolean isLive(String literal) {
-      return pred1.isLive(literal) || pred2.isLive(literal);
-    }
-
-    public boolean miscellaneousStatementsAreLive() {
-      return pred1.miscellaneousStatementsAreLive()
-          || pred2.miscellaneousStatementsAreLive();
-    }
-  }
+  private static final Pattern LOADER_CLASS_PATTERN =  
Pattern.compile(FragmentLoaderCreator.ASYNC_LOADER_PACKAGE
+      + "." + FragmentLoaderCreator.ASYNC_LOADER_CLASS_PREFIX  
+ "([0-9]+)");

    /**
     * A Java property that causes the fragment map to be logged.
-   *
-   * TODO(spoon) save the logging data to an auxiliary compiler output  
and, if
-   * the logging is not too slow, always enable it.
     */
    private static String PROP_LOG_FRAGMENT_MAP = "gwt.jjs.logFragmentMap";

@@ -229,17 +205,14 @@
    }

    public static void exec(TreeLogger logger, JProgram jprogram,
-      JsProgram jsprogram, JavaToJavaScriptMap map) {
+      JsProgram jsprogram, JavaToJavaScriptMap map,
+      LinkedHashSet<Integer> initialLoadSequence) {
      if (jprogram.entryMethods.size() == 1) {
        // Don't do anything if there is no call to runAsync
        return;
      }

-    new CodeSplitter(logger, jprogram, jsprogram, map).execImpl();
-  }
-
-  public static int getBaseFragmentNumber(int sp, int numSplitPoints) {
-    return numSplitPoints + 2 * sp - 1;
+    new CodeSplitter(logger, jprogram, jsprogram, map,  
initialLoadSequence).execImpl();
    }

    public static int getExclusiveFragmentNumber(int splitPoint,
@@ -247,17 +220,95 @@
      return splitPoint;
    }

-  public static int getLeftoversFragmentNumber(int splitPoint,
-      int numSplitPoints) {
-    return numSplitPoints + 2 * splitPoint;
+  public static int getLeftoversFragmentNumber(int numSplitPoints) {
+    return numSplitPoints + 1;
    }

    /**
     * Infer the number of split points for a given number of code fragments.
     */
    public static int numSplitPointsForFragments(int codeFragments) {
-    assert (((codeFragments - 1) % 3) == 0);
-    return (codeFragments - 1) / 3;
+    assert (codeFragments != 2);
+
+    if (codeFragments == 1) {
+      return 0;
+    }
+
+    return codeFragments - 2;
+  }
+
+  /**
+   * Choose an initial load sequence of split points for the specified  
program.
+   * Do so by identifying split points whose code always load first,  
before any
+   * other split points. As a side effect, modifies
+   * {...@link  
com.google.gwt.core.client.AsyncFragmentLoader#initialLoadSequence}
+   * in the program being compiled.
+   */
+  public static LinkedHashSet<Integer> pickInitialLoadSequence(
+      TreeLogger logger, JProgram program) {
+    LinkedHashSet<Integer> initialLoadSequence = new  
LinkedHashSet<Integer>();
+    int numSplitPoints = program.entryMethods.size() - 1;
+
+    if (numSplitPoints != 0) {
+      Map<Integer, JMethod> splitPointToMethod =  
findRunAsyncMethods(program);
+      assert (splitPointToMethod.size() == numSplitPoints);
+
+      ControlFlowAnalyzer cfa = computeInitiallyLive(program);
+
+      while (true) {
+        Set<Integer> nextSplitPoints = splitPointsReachable(cfa,
+            splitPointToMethod, numSplitPoints);
+        nextSplitPoints.removeAll(initialLoadSequence);
+
+        if (nextSplitPoints.size() != 1) {
+          break;
+        }
+
+        int nextSplitPoint = nextSplitPoints.iterator().next();
+        initialLoadSequence.add(nextSplitPoint);
+        CodeSplitter.traverseEntry(program, cfa, nextSplitPoint);
+      }
+
+      logInitialLoadSequence(logger, initialLoadSequence);
+      installInitialLoadSequenceField(program, initialLoadSequence);
+    }
+
+    return initialLoadSequence;
+  }
+
+  /**
+   * <p>
+   * Computes the "maximum total script size" for one permutation. The  
total
+   * script size for one sequence of split points reached is the sum of the
+   * scripts that are downloaded for that sequence. The maximum total  
script
+   * size is the maximum such size for all possible sequences of split  
points.
+   * </p>
+   *
+   * @param jsLengths The lengths of the fragments for the compilation of  
one
+   *          permutation
+   */
+  public static int totalScriptSize(int[] jsLengths) {
+    /*
+     * The total script size is currently simple: it's the sum of all the
+     * individual script files.
+     */
+
+    int maxTotalSize;
+    int numSplitPoints = numSplitPointsForFragments(jsLengths.length);
+    if (numSplitPoints == 0) {
+      maxTotalSize = jsLengths[0];
+    } else {
+      // Add up the initial and exclusive fragments
+      maxTotalSize = jsLengths[0];
+      for (int sp = 1; sp <= numSplitPoints; sp++) {
+        int excl = getExclusiveFragmentNumber(sp, numSplitPoints);
+        maxTotalSize += jsLengths[excl];
+      }
+
+      // Add the leftovers
+      maxTotalSize +=  
jsLengths[getLeftoversFragmentNumber(numSplitPoints)];
+    }
+    return maxTotalSize;
    }

    private static Map<JField, JClassLiteral> buildFieldToClassLiteralMap(
@@ -273,6 +324,33 @@
      return map;
    }

+  /**
+   * Maps each split point number to its corresponding generated
+   * <code>runAsync</code> method. If that method has been discarded, then  
map
+   * the split point number to <code>null</code>.
+   */
+  private static Map<Integer, JMethod> findRunAsyncMethods(JProgram  
program)
+      throws NumberFormatException {
+    Map<Integer, JMethod> splitPointToLoadMethod = new HashMap<Integer,  
JMethod>();
+    // These methods aren't indexed, so scan the whole program
+
+    for (JReferenceType type : program.getDeclaredTypes()) {
+      Matcher matcher = LOADER_CLASS_PATTERN.matcher(type.getName());
+      if (matcher.matches()) {
+        int sp = Integer.parseInt(matcher.group(1));
+        JMethod loadMethod = null;
+        for (JMethod meth : type.methods) {
+          if (meth.getName().equals(
+              FragmentLoaderCreator.LOADER_METHOD_RUN_ASYNC)) {
+            loadMethod = meth;
+          }
+        }
+        splitPointToLoadMethod.put(sp, loadMethod);
+      }
+    }
+    return splitPointToLoadMethod;
+  }
+
    private static String fullNameString(JField field) {
      return field.getEnclosingType().getName() + "." + field.getName();
    }
@@ -287,6 +365,65 @@
      return (value == null) ? 0 : value;
    }

+  private static void installInitialLoadSequenceField(JProgram program,
+      LinkedHashSet<Integer> initialLoadSequence) {
+    JField initLoadSeqField =  
program.getIndexedField("AsyncFragmentLoader.initialLoadSequence");
+    SourceInfo info =  
program.createSourceInfoSynthetic(ReplaceRunAsyncs.class,
+        "array with initial load sequence");
+    List<JExpression> intExprs = new ArrayList<JExpression>();
+    for (int sp : initialLoadSequence) {
+      intExprs.add(program.getLiteralInt(sp));
+    }
+    /*
+     * Note: the following field is known to have a manually installed
+     * initializer, of new int[0].
+     */
+    initLoadSeqField.getDeclarationStatement().initializer =  
JNewArray.createInitializers(
+        program, info, program.getTypeArray(JPrimitiveType.INT, 1),  
intExprs);
+  }
+
+  private static void logInitialLoadSequence(TreeLogger logger,
+      LinkedHashSet<Integer> initialLoadSequence) {
+    StringBuffer message = new StringBuffer();
+    message.append("Initial load sequence of split points: ");
+    if (initialLoadSequence.isEmpty()) {
+      message.append("(none)");
+    } else {
+      boolean first = true;
+      for (int sp : initialLoadSequence) {
+        if (first) {
+          first = false;
+        } else {
+          message.append(", ");
+        }
+        message.append(sp);
+      }
+    }
+
+    logger.log(TreeLogger.TRACE, message.toString());
+  }
+
+  /**
+   * Find all split points reachable in the specified ControlFlowAnalyzer.
+   *
+   * @param cfa the control-flow analyzer to search
+   * @param splitPointToMethod a map from split points to methods,  
computed with
+   *          {...@link #findRunAsyncMethods(JProgram)}.
+   * @param numSplitPoints the number of split points in the program
+   */
+  private static Set<Integer> splitPointsReachable(ControlFlowAnalyzer cfa,
+      Map<Integer, JMethod> splitPointToMethod, int numSplitPoints) {
+    Set<Integer> nextSplitPoints = new HashSet<Integer>();
+
+    for (int sp = 1; sp <= numSplitPoints; sp++) {
+      if  
(cfa.getLiveFieldsAndMethods().contains(splitPointToMethod.get(sp))) {
+        nextSplitPoints.add(sp);
+      }
+    }
+
+    return nextSplitPoints;
+  }
+
    /**
     * Traverse all code in the program that is reachable via split point
     * <code>splitPoint</code>.
@@ -331,6 +468,7 @@

    private final Map<JField, JClassLiteral> fieldToLiteralOfClass;
    private final FragmentExtractor fragmentExtractor;
+  private final LinkedHashSet<Integer> initialLoadSequence;

    /**
     * Code that is initially live when the program first downloads.
@@ -338,6 +476,12 @@
    private final ControlFlowAnalyzer initiallyLive;
    private JProgram jprogram;
    private JsProgram jsprogram;
+
+  /**
+   * Computed during {...@link #execImpl()}, so that intermediate steps of it  
can
+   * be used as they are created.
+   */
+  private ControlFlowAnalyzer liveAfterInitialSequence;
    private final TreeLogger logger;
    private final boolean logging;
    private JavaToJavaScriptMap map;
@@ -345,12 +489,14 @@
    private final int numEntries;

    private CodeSplitter(TreeLogger logger, JProgram jprogram,
-      JsProgram jsprogram, JavaToJavaScriptMap map) {
+      JsProgram jsprogram, JavaToJavaScriptMap map,
+      LinkedHashSet<Integer> initialLoadSequence) {
      this.logger = logger.branch(TreeLogger.TRACE,
          "Splitting JavaScript for incremental download");
      this.jprogram = jprogram;
      this.jsprogram = jsprogram;
      this.map = map;
+    this.initialLoadSequence = initialLoadSequence;

      numEntries = jprogram.entryMethods.size();
      logging = Boolean.getBoolean(PROP_LOG_FRAGMENT_MAP);
@@ -363,18 +509,21 @@
    }

    /**
-   * Create a new fragment and add it to the list.
+   * Create a new fragment and add it to the table of fragments.
     *
+   * @param splitPoint The split point to associate this code with
     * @param alreadyLoaded The code that should be assumed to have already  
been
     *          loaded
-   * @param liveNow The code that needs to be live once this fragment loads
-   * @param statsToAppend Additional statements to append to the end of  
the new
+   * @param liveNow The code that is assumed live once this fragment loads;
+   *          anything in here but not in <code>alreadyLoaded</code> will  
be
+   *          included in the created fragment
+   * @param stmtsToAppend Additional statements to append to the end of  
the new
     *          fragment
     * @param fragmentStats The list of fragments to append to
     */
-  private void addFragment(LivenessPredicate alreadyLoaded,
-      LivenessPredicate liveNow, List<JsStatement> statsToAppend,
-      List<List<JsStatement>> fragmentStats) {
+  private void addFragment(int splitPoint, LivenessPredicate alreadyLoaded,
+      LivenessPredicate liveNow, List<JsStatement> stmtsToAppend,
+      Map<Integer, List<JsStatement>> fragmentStats) {
      if (logging) {
        System.out.println();
        System.out.println("==== Fragment " + fragmentStats.size() + "  
====");
@@ -382,20 +531,26 @@
      }
      List<JsStatement> stats = fragmentExtractor.extractStatements(liveNow,
          alreadyLoaded);
-    stats.addAll(statsToAppend);
-    fragmentStats.add(stats);
+    stats.addAll(stmtsToAppend);
+    fragmentStats.put(splitPoint, stats);
    }

    /**
-   * For each split point other than the initial one (0), compute a CFA  
that
-   * traces every other split point.
+   * For each split point other than those in the initial load sequence,  
compute
+   * a CFA that traces every other split point. For those that are in the
+   * initial load sequence, add a <code>null</code> to the list.
     */
    private List<ControlFlowAnalyzer> computeAllButOneCfas() {
      List<ControlFlowAnalyzer> allButOnes = new  
ArrayList<ControlFlowAnalyzer>(
          numEntries - 1);

      for (int entry = 1; entry < numEntries; entry++) {
-      ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(initiallyLive);
+      if (isInitial(entry)) {
+        allButOnes.add(null);
+        continue;
+      }
+      ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(
+          liveAfterInitialSequence);
        traverseAllButEntry(cfa, entry);
        // Traverse leftoversFragmentHasLoaded, because it should not
        // go into any of the exclusive fragments.
@@ -418,13 +573,23 @@
      return everything;
    }

-  private void execImpl() {
-    PerfLogger.start("CodeSplitter");
+  /**
+   * Map each program atom as exclusive to some split point, whenever  
possible.
+   * Also fixes up load order problems that could result from splitting  
code
+   * based on this assumption.
+   */
+  private ExclusivityMap determineExclusivity() {
+    ExclusivityMap fragmentMap = new ExclusivityMap();

-    FragmentMap fragmentMap = mapFragments();
+    mapExclusiveAtoms(fragmentMap);
+    fixUpLoadOrderDependencies(fragmentMap);

-    List<List<JsStatement>> fragmentStats = new  
ArrayList<List<JsStatement>>(
-        3 * numEntries - 2);
+    return fragmentMap;
+  }
+
+  private void execImpl() {
+    PerfLogger.start("CodeSplitter");
+    Map<Integer, List<JsStatement>> fragmentStats = new HashMap<Integer,  
List<JsStatement>>();

      {
        /*
@@ -434,46 +599,59 @@
        LivenessPredicate alreadyLoaded = new NothingAlivePredicate();
        LivenessPredicate liveNow = new CfaLivenessPredicate(initiallyLive);
        List<JsStatement> noStats = new ArrayList<JsStatement>();
-      addFragment(alreadyLoaded, liveNow, noStats, fragmentStats);
+      addFragment(0, alreadyLoaded, liveNow, noStats, fragmentStats);
      }

      /*
+     * Compute the base fragments, for split points in the initial load
+     * sequence.
+     */
+    liveAfterInitialSequence = new ControlFlowAnalyzer(initiallyLive);
+    for (int sp : initialLoadSequence) {
+      LivenessPredicate alreadyLoaded = new CfaLivenessPredicate(
+          liveAfterInitialSequence);
+
+      ControlFlowAnalyzer liveAfterSp = new ControlFlowAnalyzer(
+          liveAfterInitialSequence);
+      traverseEntry(liveAfterSp, sp);
+      LivenessPredicate liveNow = new CfaLivenessPredicate(liveAfterSp);
+
+      List<JsStatement> statsToAppend =  
fragmentExtractor.createCallsToEntryMethods(sp);
+
+      addFragment(sp, alreadyLoaded, liveNow, statsToAppend,  
fragmentStats);
+
+      liveAfterInitialSequence = liveAfterSp;
+    }
+
+    ExclusivityMap fragmentMap = determineExclusivity();
+
+    /*
       * Compute the exclusively live fragments. Each includes everything
       * exclusively live after entry point i.
       */
      for (int i = 1; i < numEntries; i++) {
-      LivenessPredicate alreadyLoaded = new FragmentMapLivenessPredicate(
+      if (isInitial(i)) {
+        continue;
+      }
+      LivenessPredicate alreadyLoaded = new  
ExclusivityMapLivenessPredicate(
            fragmentMap, 0);
-      LivenessPredicate liveNow = new  
FragmentMapLivenessPredicate(fragmentMap,
-          i);
+      LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate(
+          fragmentMap, i);
        List<JsStatement> statsToAppend =  
fragmentExtractor.createCallsToEntryMethods(i);
-      addFragment(alreadyLoaded, liveNow, statsToAppend, fragmentStats);
+      addFragment(i, alreadyLoaded, liveNow, statsToAppend, fragmentStats);
      }

      /*
-     * Add secondary base fragments and their associated leftover  
fragments.
+     * Compute the leftovers fragment.
       */
-    for (int base = 1; base < numEntries; base++) {
-      ControlFlowAnalyzer baseCfa = new ControlFlowAnalyzer(initiallyLive);
-      traverseEntry(baseCfa, base);
-      LivenessPredicate baseLive = new CfaLivenessPredicate(baseCfa);
-
-      // secondary base
-      List<JsStatement> baseStatsToAppend =  
fragmentExtractor.createCallsToEntryMethods(base);
-      addFragment(new CfaLivenessPredicate(initiallyLive), baseLive,
-          baseStatsToAppend, fragmentStats);
-
-      // leftovers
-      LivenessPredicate globalLeftoversLive = new  
FragmentMapLivenessPredicate(
+    {
+      LivenessPredicate alreadyLoaded = new CfaLivenessPredicate(
+          liveAfterInitialSequence);
+      LivenessPredicate liveNow = new ExclusivityMapLivenessPredicate(
            fragmentMap, 0);
-      LivenessPredicate associatedExclusives = new  
FragmentMapLivenessPredicate(
-          fragmentMap, base);
-      // Be sure to add in anything in the exclusives for base that is
-      // not in its secondary base.
-      LivenessPredicate leftoversLive = new UnionLivenessPredicate(
-          globalLeftoversLive, associatedExclusives);
        List<JsStatement> statsToAppend =  
fragmentExtractor.createCallToLeftoversFragmentHasLoaded();
-      addFragment(baseLive, leftoversLive, statsToAppend, fragmentStats);
+      addFragment(numEntries, alreadyLoaded, liveNow, statsToAppend,
+          fragmentStats);
      }

      // now install the new statements in the program fragments
@@ -503,7 +681,7 @@
     * happen, so it seems better to keep the compiler simpler.
     * </p>
     */
-  private void fixUpLoadOrderDependencies(FragmentMap fragmentMap) {
+  private void fixUpLoadOrderDependencies(ExclusivityMap fragmentMap) {
      fixUpLoadOrderDependenciesForMethods(fragmentMap);
      fixUpLoadOrderDependenciesForTypes(fragmentMap);
      fixUpLoadOrderDependenciesForClassLiterals(fragmentMap);
@@ -511,7 +689,7 @@
    }

    private void fixUpLoadOrderDependenciesForClassLiterals(
-      FragmentMap fragmentMap) {
+      ExclusivityMap fragmentMap) {
      int numClassLitStrings = 0;
      int numFixups = 0;
      for (JField field : fragmentMap.fields.keySet()) {
@@ -535,7 +713,7 @@
    }

    private void fixUpLoadOrderDependenciesForFieldsInitializedToStrings(
-      FragmentMap fragmentMap) {
+      ExclusivityMap fragmentMap) {
      int numFixups = 0;
      int numFieldStrings = 0;

@@ -559,7 +737,7 @@
          + +numFieldStrings);
    }

-  private void fixUpLoadOrderDependenciesForMethods(FragmentMap  
fragmentMap) {
+  private void fixUpLoadOrderDependenciesForMethods(ExclusivityMap  
fragmentMap) {
      int numFixups = 0;

      for (JDeclaredType type : jprogram.getDeclaredTypes()) {
@@ -589,7 +767,7 @@
              + jprogram.getDeclaredTypes().size());
    }

-  private void fixUpLoadOrderDependenciesForTypes(FragmentMap fragmentMap)  
{
+  private void fixUpLoadOrderDependenciesForTypes(ExclusivityMap  
fragmentMap) {
      int numFixups = 0;
      Queue<JReferenceType> typesToCheck = new  
ArrayBlockingQueue<JReferenceType>(
          jprogram.getDeclaredTypes().size());
@@ -614,12 +792,16 @@
              + jprogram.getDeclaredTypes().size());
    }

+  private boolean isInitial(int entry) {
+    return initialLoadSequence.contains(entry);
+  }
+
    /**
-   * Map code to fragments. Do this by trying to find code atoms that are  
only
-   * needed by a single split point. Such code can be moved to the  
exclusively
-   * live fragment associated with that split point.
+   * Map atoms to exclusive fragments. Do this by trying to find code  
atoms that
+   * are only needed by a single split point. Such code can be moved to the
+   * exclusively live fragment associated with that split point.
     */
-  private void mapExclusiveAtoms(FragmentMap fragmentMap) {
+  private void mapExclusiveAtoms(ExclusivityMap fragmentMap) {
      List<ControlFlowAnalyzer> allButOnes = computeAllButOneCfas();

      ControlFlowAnalyzer everything = computeCompleteCfa();
@@ -638,6 +820,9 @@
      allFields.addAll(everything.getFieldsWritten());

      for (int entry = 1; entry < numEntries; entry++) {
+      if (isInitial(entry)) {
+        continue;
+      }
        ControlFlowAnalyzer allButOne = allButOnes.get(entry - 1);
        Set<JNode> allLiveNodes = union(allButOne.getLiveFieldsAndMethods(),
            allButOne.getFieldsWritten());
@@ -649,21 +834,6 @@
        updateMap(entry, fragmentMap.types, allButOne.getInstantiatedTypes(),
            everything.getInstantiatedTypes());
      }
-  }
-
-  /**
-   * Map each program atom to a fragment. Atoms are mapped to a non-zero
-   * fragment whenever they are known not to be needed whenever that  
fragment's
-   * split point has not been reached. Any atoms that cannot be so mapped  
are
-   * left in fragment zero.
-   */
-  private FragmentMap mapFragments() {
-    FragmentMap fragmentMap = new FragmentMap();
-
-    mapExclusiveAtoms(fragmentMap);
-    fixUpLoadOrderDependencies(fragmentMap);
-
-    return fragmentMap;
    }

    /**

Modified:  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
==============================================================================
---  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java       
 
(original)
+++  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java       
 
Tue Apr 28 09:42:12 2009
@@ -44,6 +44,7 @@
    public static final String ASYNC_FRAGMENT_LOADER  
= "com.google.gwt.core.client.AsyncFragmentLoader";
    public static final String ASYNC_LOADER_CLASS_PREFIX = "AsyncLoader";
    public static final String ASYNC_LOADER_PACKAGE  
= "com.google.gwt.lang.asyncloaders";
+  public static final String LOADER_METHOD_RUN_ASYNC = "runAsync";
    public static final String RUN_ASYNC_CALLBACK  
= "com.google.gwt.core.client.RunAsyncCallback";
    private static final String GWT_CLASS =  
FindDeferredBindingSitesVisitor.MAGIC_CLASS;
    private static final String PROP_RUN_ASYNC_NEVER_RUNS  
= "gwt.jjs.runAsyncNeverRuns";
@@ -160,7 +161,8 @@
     * <code>GWT.runAsync</code> are replaced by calls to this method.
     */
    private void generateRunAsyncMethod(PrintWriter srcWriter) {
-    srcWriter.println("public static void runAsync(RunAsyncCallback  
callback) {");
+    srcWriter.println("public static void " + LOADER_METHOD_RUN_ASYNC
+        + "(RunAsyncCallback callback) {");
      srcWriter.println(getCallbackListSimpleName() + " newCallback = new "
          + getCallbackListSimpleName() + "();");
      srcWriter.println("newCallback.callback = callback;");

Modified: trunk/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java
==============================================================================
--- trunk/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java   
(original)
+++ trunk/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java  Tue  
Apr 28 09:42:12 2009
@@ -19,8 +19,10 @@
  import com.google.gwt.xhr.client.XMLHttpRequest;

  import java.util.ArrayList;
+import java.util.HashMap;
  import java.util.LinkedList;
  import java.util.List;
+import java.util.Map;
  import java.util.Queue;

  /**
@@ -35,14 +37,8 @@
   *
   * <ul>
   * <li>0 -- the <em>base</em> fragment, which is initially downloaded
- * <li>1-m -- the <em>exclusively live</em> fragments, holding the code  
needed
- * exclusively for each split point
- * <li>m -- the <em>secondary base</em> fragment for entry point 1. It  
holds
- * precisely that code needed if entry point 1 is the first one reached  
after
- * the application downloads.
- * <li>m+1 -- the <em>leftovers fragment</em> for entry point 1. It holds  
all
- * code not in fragments 0-(m-1) nor in fragment m.
- * <li>(m+2)..(3m) -- secondary bases and leftovers for entry points 2..m
+ * <li>1..m -- fragments for each split point
+ * <li>m+1 -- the <em>leftovers</em> fragment of code that goes nowhere  
else
   * </ul>
   *
   * <p>
@@ -81,32 +77,12 @@

      private static final String LEFTOVERS_DOWNLOAD = "leftoversDownload";

-    /**
-     * @param splitPoint
-     * @return
-     */
      private static String downloadGroup(int splitPoint) {
        return "download" + splitPoint;
      }
    }

    /**
-   * Handles a failure to download a base fragment.
-   */
-  private static class BaseDownloadFailed implements LoadErrorHandler {
-    private final LoadErrorHandler chainedHandler;
-
-    public BaseDownloadFailed(LoadErrorHandler chainedHandler) {
-      this.chainedHandler = chainedHandler;
-    }
-
-    public void loadFailed(Throwable reason) {
-      baseLoading = false;
-      chainedHandler.loadFailed(reason);
-    }
-  }
-
-  /**
     * An exception indicating than at HTTP download failed.
     */
    private static class HttpDownloadFailure extends RuntimeException {
@@ -123,25 +99,37 @@
    }

    /**
-   * Handles a failure to download a leftovers fragment.
+   * Handles a failure to download a fragment in the initial sequence.
     */
-  private static class LeftoversDownloadFailed implements LoadErrorHandler  
{
+  private static class InitialFragmentDownloadFailed implements
+      LoadErrorHandler {
      public void loadFailed(Throwable reason) {
-      leftoversLoading = false;
+      initialFragmentsLoading = false;
+
+      // Cancel all pending downloads.

        /*
-       * Cancel all other pending downloads. If any exception is thrown  
while
-       * cancelling any of them, throw only the last one.
+       * Make a local list of the handlers to run, in case one of them  
calls
+       * another runAsync
         */
-      RuntimeException lastException = null;
+      List<LoadErrorHandler> handlersToRun = new  
ArrayList<LoadErrorHandler>();

-      assert waitingForLeftovers.size() ==  
waitingForLeftoversErrorHandlers.size();
+      // add handlers that are waiting pending the initials download
+      assert waitingForInitialFragments.length() ==  
waitingForInitialFragmentsErrorHandlers.size();
+      while (waitingForInitialFragments.length() > 0) {
+         
handlersToRun.add(waitingForInitialFragmentsErrorHandlers.remove());
+        waitingForInitialFragments.shift();
+      }

-      // Copy the list in case a handler makes another runAsync call
-      List<LoadErrorHandler> handlersToRun = new  
ArrayList<LoadErrorHandler>(
-          waitingForLeftoversErrorHandlers);
-      waitingForLeftoversErrorHandlers.clear();
-      waitingForLeftovers.clear();
+      // add handlers for pending initial fragment downloads
+      handlersToRun.addAll(initialFragmentErrorHandlers.values());
+      initialFragmentErrorHandlers.clear();
+
+      /*
+       * If an exception is thrown while canceling any of them, remember  
and
+       * throw the last one.
+       */
+      RuntimeException lastException = null;

        for (LoadErrorHandler handler : handlersToRun) {
          try {
@@ -157,16 +145,6 @@
      }
    }

-  /**
-   * The first entry point reached after the program started.
-   */
-  private static int base = -1;
-
-  /**
-   * Whether the secondary base fragment is currently loading.
-   */
-  private static boolean baseLoading = false;
-
    private static final String HTTP_GET = "GET";

    /**
@@ -179,64 +157,79 @@
    private static final int HTTP_STATUS_OK = 200;

    /**
-   * Whether the leftovers fragment has loaded yet.
+   * Error handlers for failure to download an initial fragment.
+   *
+   * TODO(spoon) make it a lightweight integer map
     */
-  private static boolean leftoversLoaded = false;
+  private static Map<Integer, LoadErrorHandler>  
initialFragmentErrorHandlers = new HashMap<Integer, LoadErrorHandler>();

    /**
-   * Whether the leftovers fragment is currently loading.
+   * Indicates that the next fragment in {...@link  
#remainingInitialFragments} is
+   * currently downloading.
     */
-  private static boolean leftoversLoading = false;
+  private static boolean initialFragmentsLoading = false;
+
+  /**
+   * The sequence of fragments to load initially, before anything else can  
be
+   * loaded. This array will hold the initial sequence of bases followed  
by the
+   * leftovers fragment. It is filled in by
+   * {...@link com.google.gwt.dev.jjs.impl.CodeSplitter}.  It does *not*  
include
+   * the leftovers fragment, which must be loaded once all of these are  
finished.
+   */
+  private static int[] initialLoadSequence = new int[] { };

    /**
     * The total number of split points in the program, counting the initial  
entry
-   * as a split point. This is changed to the correct value by
+   * as an honorary split point. This is changed to the correct value by
     * {...@link com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs}.
     */
    private static int numEntries = 1;

    /**
+   * Base fragments that remain to be downloaded. It is lazily initialized  
in
+   * the first call to {...@link #startLoadingNextInitial()}.  It does include
+   * the leftovers fragment.
+   */
+  private static JsArrayInteger remainingInitialFragments = null;
+
+  /**
     * Split points that have been reached, but that cannot be downloaded  
until
-   * the leftovers fragment finishes downloading.
+   * the initial fragments finish downloading.
     */
-  private static Queue<Integer> waitingForLeftovers = new  
LinkedList<Integer>();
+  private static JsArrayInteger waitingForInitialFragments =  
createJsArrayInteger();

    /**
     * Error handlers for the above queue.
+   *
+   * TODO(spoon) change this to a lightweight JS collection
     */
-  private static Queue<LoadErrorHandler> waitingForLeftoversErrorHandlers  
= new LinkedList<LoadErrorHandler>();
+  private static Queue<LoadErrorHandler>  
waitingForInitialFragmentsErrorHandlers = new  
LinkedList<LoadErrorHandler>();

    /**
-   * Inform the loader that the code for an entry point has now finished
-   * loading.
-   *
-   * @param entry The entry whose code fragment is now loaded.
+   * Inform the loader that a fragment has now finished loading.
     */
-  public static void fragmentHasLoaded(int entry) {
-    int fragment = base >= 0 ? entry : baseFragmentNumber(entry);
-    logEventProgress(LwmLabels.downloadGroup(entry), LwmLabels.END,  
fragment,
-        null);
-
-    if (base < 0) {
-      // The base fragment has loaded
-      base = entry;
-      baseLoading = false;
+  public static void fragmentHasLoaded(int fragment) {
+    logFragmentLoaded(fragment);
+
+    if (isInitial(fragment)) {
+      assert (fragment == remainingInitialFragments.get(0));
+      remainingInitialFragments.shift();
+      initialFragmentErrorHandlers.remove(fragment);

-      // Go ahead and download the appropriate leftovers fragment
-      startLoadingLeftovers();
+      startLoadingNextInitial();
      }
    }

    /**
     * Loads the specified split point.
     *
-   * @param splitPoint the fragment to load
+   * @param splitPoint the split point whose code needs to be loaded
     */
    public static void inject(int splitPoint, LoadErrorHandler  
loadErrorHandler) {
-    if (leftoversLoaded) {
+    if (haveInitialFragmentsLoaded()) {
        /*
-       * A base and a leftovers fragment have loaded. Load an exclusively  
live
-       * fragment.
+       * The initial fragments has loaded. Immediately start loading the
+       * requested code.
         */
        logEventProgress(LwmLabels.downloadGroup(splitPoint),  
LwmLabels.BEGIN,
            splitPoint, null);
@@ -244,46 +237,36 @@
        return;
      }

-    if (baseLoading || leftoversLoading) {
+    if (isInitial(splitPoint)) {
        /*
-       * Wait until the leftovers fragment has loaded before loading this  
one.
+       * The loading of an initial fragment will happen via
+       * startLoadingNextInitial(), so don't start it here. Do, however,  
record
+       * the error handler.
         */
-      assert (waitingForLeftovers.size() ==  
waitingForLeftoversErrorHandlers.size());
-      waitingForLeftovers.add(splitPoint);
-      waitingForLeftoversErrorHandlers.add(loadErrorHandler);
-
+      initialFragmentErrorHandlers.put(splitPoint, loadErrorHandler);
+    } else {
        /*
-       * Also, restart the leftovers download if it previously failed.
+       * For a non-initial fragment, queue it for later loading, once the
+       * initial fragments have all been loaded.
         */
-      if (!leftoversLoading) {
-        startLoadingLeftovers();
-      }

-      return;
+      assert (waitingForInitialFragments.length() ==  
waitingForInitialFragmentsErrorHandlers.size());
+      waitingForInitialFragments.push(splitPoint);
+      waitingForInitialFragmentsErrorHandlers.add(loadErrorHandler);
      }

-    // Nothing has loaded or started to load. Treat this fragment as the  
base.
-    baseLoading = true;
-    logEventProgress(LwmLabels.downloadGroup(splitPoint), LwmLabels.BEGIN,
-        baseFragmentNumber(splitPoint), null);
-    startLoadingFragment(baseFragmentNumber(splitPoint),
-        new BaseDownloadFailed(loadErrorHandler));
-  }
+    /*
+     * Start the initial downloads if they aren't running already.
+     */
+    if (!initialFragmentsLoading) {
+      startLoadingNextInitial();
+    }

-  /**
-   * Inform the loader that the "leftovers" fragment has loaded.
-   */
+    return;
+  }
+
    public static void leftoversFragmentHasLoaded() {
-    leftoversLoaded = true;
-    leftoversLoading = false;
-    logEventProgress(LwmLabels.LEFTOVERS_DOWNLOAD, LwmLabels.END,
-        leftoversFragmentNumber(), null);
-
-    assert (waitingForLeftovers.size() ==  
waitingForLeftoversErrorHandlers.size());
-    while (!waitingForLeftovers.isEmpty()) {
-      inject(waitingForLeftovers.remove(),
-          waitingForLeftoversErrorHandlers.remove());
-    }
+    fragmentHasLoaded(leftoversFragment());
    }

    /**
@@ -293,18 +276,14 @@
      logEventProgress(eventGroup, type, null, null);
    }

-  /**
-   * Compute the fragment number for the base fragment of
-   * <code>splitPoint</code>.
-   */
-  private static int baseFragmentNumber(int splitPoint) {
-    return numEntries + 2 * (splitPoint - 1);
-  }
+  private static native JsArrayInteger createJsArrayInteger() /*-{
+    return [];
+  }-*/;

    private static native JavaScriptObject createStatsEvent(String  
eventGroup,
        String type, Integer fragment, Integer size) /*-{
      var evt = {
-      moduleName: @com.google.gwt.core.client.GWT::getModuleName()(),
+     moduleName: @com.google.gwt.core.client.GWT::getModuleName()(),
        subSystem: 'runAsync',
        evtGroup: eventGroup,
        millis: (new Date()).getTime(),
@@ -319,8 +298,12 @@
      return evt;
    }-*/;

+  private static native void gwtInstallCode(String text) /*-{
+    __gwtInstallCode(text);
+  }-*/;
+
    /**
-   * Use the linker-supplied __gwtStartLoadingFragment function. It should
+   * Call the linker-supplied __gwtStartLoadingFragment function. It should
     * either start the download and return null or undefined, or it should  
return
     * a URL that should be downloaded to get the code. If it starts the  
download
     * itself, it can synchronously load it, e.g. from cache, if that makes  
sense.
@@ -329,21 +312,32 @@
      return __gwtStartLoadingFragment(fragment);
    }-*/;

-  private static native void installCode(String text) /*-{
-    __gwtInstallCode(text);
-  }-*/;
+  /**
+   * Return whether all initial fragments have completed loading.
+   */
+  private static boolean haveInitialFragmentsLoaded() {
+    return remainingInitialFragments != null
+        && remainingInitialFragments.length() > 0;
+  }
+
+  private static boolean isInitial(int splitPoint) {
+    if (splitPoint == leftoversFragment()) {
+      return true;
+    }
+    for (int sp : initialLoadSequence) {
+      if (sp == splitPoint) {
+        return true;
+      }
+    }
+    return false;
+  }

    private static native boolean isStatsAvailable() /*-{
      return !!$stats;
    }-*/;

-  /**
-   * Compute the leftovers fragment number. This method can only be called  
once
-   * <code>base</code> has been set.
-   */
-  private static int leftoversFragmentNumber() {
-    assert (base >= 0);
-    return numEntries + 2 * (base - 1) + 1;
+  private static int leftoversFragment() {
+    return numEntries;
    }

    /**
@@ -358,6 +352,12 @@
          && stats(createStatsEvent(eventGroup, type, fragment, size));
    }

+  private static void logFragmentLoaded(int fragment) {
+    String logGroup = (fragment == leftoversFragment())
+        ? LwmLabels.LEFTOVERS_DOWNLOAD : LwmLabels.downloadGroup(fragment);
+    logEventProgress(logGroup, LwmLabels.END, fragment, null);
+  }
+
    private static void startLoadingFragment(int fragment,
        final LoadErrorHandler loadErrorHandler) {
      String fragmentUrl = gwtStartLoadingFragment(fragment);
@@ -376,7 +376,7 @@
                  && xhr.getResponseText() != null
                  && xhr.getResponseText().length() != 0) {
                try {
-                installCode(xhr.getResponseText());
+                gwtInstallCode(xhr.getResponseText());
                } catch (RuntimeException e) {
                  loadErrorHandler.loadFailed(e);
                }
@@ -392,12 +392,40 @@
      }
    }

-  private static void startLoadingLeftovers() {
-    leftoversLoading = true;
-    logEventProgress(LwmLabels.LEFTOVERS_DOWNLOAD, LwmLabels.BEGIN,
-        leftoversFragmentNumber(), null);
-    startLoadingFragment(leftoversFragmentNumber(),
-        new LeftoversDownloadFailed());
+  /**
+   * Start downloading the next fragment in the initial sequence, if there  
are
+   * any left.
+   */
+  private static void startLoadingNextInitial() {
+    if (remainingInitialFragments == null) {
+      // first call, so initialize remainingInitialFragments
+      remainingInitialFragments = createJsArrayInteger();
+      for (int sp : initialLoadSequence) {
+        remainingInitialFragments.push(sp);
+      }
+      remainingInitialFragments.push(leftoversFragment());
+    }
+
+    if (remainingInitialFragments.length() > 0) {
+      // start loading the next initial fragment
+      initialFragmentsLoading = true;
+      int nextSplitPoint = remainingInitialFragments.get(0);
+      logEventProgress(LwmLabels.downloadGroup(nextSplitPoint),  
LwmLabels.BEGIN,
+          nextSplitPoint, null);
+      startLoadingFragment(nextSplitPoint, new  
InitialFragmentDownloadFailed());
+      return;
+    }
+
+    // all initials are finished
+    initialFragmentsLoading = false;
+    assert (haveInitialFragmentsLoaded());
+
+    // start loading any pending fragments
+    assert (waitingForInitialFragments.length() ==  
waitingForInitialFragmentsErrorHandlers.size());
+    while (waitingForInitialFragments.length() > 0) {
+      startLoadingFragment(waitingForInitialFragments.shift(),
+          waitingForInitialFragmentsErrorHandlers.remove());
+    }
    }

    /**

--~--~---------~--~----~------------~-------~--~----~
http://groups.google.com/group/Google-Web-Toolkit-Contributors
-~----------~----~----~----~------~----~------~--~---

Reply via email to