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
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5293 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
index 4d78f19..a1fec18 100644
--- a/dev/core/src/com/google/gwt/dev/Link.java
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -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;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 78008d7..a1d50fc 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -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.
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
index 8db2341..dd7403c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
@@ -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;
}
+ /**
+ * 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();
+
+ mapExclusiveAtoms(fragmentMap);
+ fixUpLoadOrderDependencies(fragmentMap);
+
+ return fragmentMap;
+ }
+
private void execImpl() {
PerfLogger.start("CodeSplitter");
-
- FragmentMap fragmentMap = mapFragments();
-
- List<List<JsStatement>> fragmentStats = new ArrayList<List<JsStatement>>(
- 3 * numEntries - 2);
+ 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());
@@ -652,21 +837,6 @@
}
/**
- * 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;
- }
-
- /**
* Traverse <code>exp</code> and find all string literals within it.
*/
private Set<String> stringsIn(JExpression exp) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
index c5344bd..5eb4790 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
@@ -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;");
diff --git a/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java b/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java
index ab6fe02..5d13a86 100644
--- a/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java
+++ b/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java
@@ -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,26 +99,38 @@
}
/**
- * 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
+ */
+ List<LoadErrorHandler> handlersToRun = new ArrayList<LoadErrorHandler>();
+
+ // add handlers that are waiting pending the initials download
+ assert waitingForInitialFragments.length() == waitingForInitialFragmentsErrorHandlers.size();
+ while (waitingForInitialFragments.length() > 0) {
+ handlersToRun.add(waitingForInitialFragmentsErrorHandlers.remove());
+ waitingForInitialFragments.shift();
+ }
+
+ // 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;
- assert waitingForLeftovers.size() == waitingForLeftoversErrorHandlers.size();
-
- // Copy the list in case a handler makes another runAsync call
- List<LoadErrorHandler> handlersToRun = new ArrayList<LoadErrorHandler>(
- waitingForLeftoversErrorHandlers);
- waitingForLeftoversErrorHandlers.clear();
- waitingForLeftovers.clear();
-
for (LoadErrorHandler handler : handlersToRun) {
try {
handler.loadFailed(reason);
@@ -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;
/**
- * Split points that have been reached, but that cannot be downloaded until
- * the leftovers fragment finishes downloading.
+ * 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 Queue<Integer> waitingForLeftovers = new LinkedList<Integer>();
+ private static JsArrayInteger remainingInitialFragments = null;
+
+ /**
+ * Split points that have been reached, but that cannot be downloaded until
+ * the initial fragments finish downloading.
+ */
+ 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);
+ public static void fragmentHasLoaded(int fragment) {
+ logFragmentLoaded(fragment);
- if (base < 0) {
- // The base fragment has loaded
- base = entry;
- baseLoading = false;
+ 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();
+ }
+
+ return;
}
-
- /**
- * Inform the loader that the "leftovers" fragment has loaded.
- */
+
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());
+ }
}
/**