blob: fd76da9796a91044eb02923c551f8f1ad5fceb2b [file] [log] [blame]
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.core.client.impl;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.Scheduler;
/**
* This is used by Scheduler to collaborate with Impl in order to have
* FinallyCommands executed.
*/
public class SchedulerImpl extends Scheduler {
/**
* Metadata bag for command objects. It's a JSO so that a lightweight JsArray
* can be used instead of a Collections type.
*/
static final class Task extends JavaScriptObject {
public static native Task create(RepeatingCommand cmd) /*-{
return [cmd, true];
}-*/;
public static native Task create(ScheduledCommand cmd) /*-{
return [cmd, false];
}-*/;
protected Task() {
}
public boolean executeRepeating() {
return getRepeating().execute();
}
public void executeScheduled() {
getScheduled().execute();
}
/**
* Has implicit cast.
*/
public native RepeatingCommand getRepeating() /*-{
return this[0];
}-*/;
/**
* Has implicit cast.
*/
public native ScheduledCommand getScheduled() /*-{
return this[0];
}-*/;
public native boolean isRepeating() /*-{
return this[1];
}-*/;
}
/**
* Calls {@link SchedulerImpl#flushPostEventPumpCommands()}.
*/
private final class Flusher implements RepeatingCommand {
public boolean execute() {
flushRunning = true;
flushPostEventPumpCommands();
/*
* No finally here, we want this to be clear only on a normal exit. An
* abnormal exit would indicate that an exception isn't being caught
* correctly or that a slow script warning canceled the timer.
*/
flushRunning = false;
return shouldBeRunning = isWorkQueued();
}
}
/**
* Keeps {@link Flusher} running.
*/
private final class Rescuer implements RepeatingCommand {
public boolean execute() {
if (flushRunning) {
/*
* Since JS is single-threaded, if we're here, then than means that
* FLUSHER.execute() started, but did not finish. Reschedule FLUSHER.
*/
scheduleFixedDelay(flusher, FLUSHER_DELAY);
}
return shouldBeRunning;
}
}
/**
* Use a GWT.create() here to make it simple to hijack the default
* implementation.
*/
public static final SchedulerImpl INSTANCE = GWT.create(SchedulerImpl.class);
/**
* The delay between flushing the task queues.
*/
private static final int FLUSHER_DELAY = 1;
/**
* The delay between checking up on SSW problems.
*/
private static final int RESCUE_DELAY = 50;
/**
* The amount of time that we're willing to spend executing
* IncrementalCommands.
*/
private static final double TIME_SLICE = 100;
/**
* Extract boilerplate code.
*/
private static JsArray<Task> createQueue() {
return JavaScriptObject.createArray().cast();
}
/**
* Called from scheduledFixedInterval to give $entry a static function.
*/
private static boolean execute(RepeatingCommand cmd) {
return cmd.execute();
}
/**
* Provides lazy-init pattern for the task queues.
*/
private static JsArray<Task> push(JsArray<Task> queue, Task task) {
if (queue == null) {
queue = createQueue();
}
queue.push(task);
return queue;
}
/**
* Execute a list of Tasks that hold RepeatingCommands.
*
* @return A replacement array that is possibly a shorter copy of
* <code>tasks</code>
*/
private static JsArray<Task> runRepeatingTasks(JsArray<Task> tasks) {
assert tasks != null : "tasks";
int length = tasks.length();
if (length == 0) {
return null;
}
boolean canceledSomeTasks = false;
double start = Duration.currentTimeMillis();
while (Duration.currentTimeMillis() - start < TIME_SLICE) {
for (int i = 0; i < length; i++) {
assert tasks.length() == length : "Working array length changed "
+ tasks.length() + " != " + length;
Task t = tasks.get(i);
if (t == null) {
continue;
}
assert t.isRepeating() : "Found a non-repeating Task";
if (!t.executeRepeating()) {
tasks.set(i, null);
canceledSomeTasks = true;
}
}
}
if (canceledSomeTasks) {
JsArray<Task> newTasks = createQueue();
// Remove tombstones
for (int i = 0; i < length; i++) {
if (tasks.get(i) != null) {
newTasks.push(tasks.get(i));
}
}
assert newTasks.length() < length;
return newTasks.length() == 0 ? null : newTasks;
} else {
return tasks;
}
}
/**
* Execute a list of Tasks that hold both ScheduledCommands and
* RepeatingCommands. Any RepeatingCommands in the <code>tasks</code> queue
* that want to repeat will be pushed onto the <code>rescheduled</code> queue.
* The contents of <code>tasks</code> may not be altered while this method is
* executing.
*
* @return <code>rescheduled</code> or a newly-allocated array if
* <code>rescheduled</code> is null.
*/
private static JsArray<Task> runScheduledTasks(JsArray<Task> tasks,
JsArray<Task> rescheduled) {
assert tasks != null : "tasks";
for (int i = 0, j = tasks.length(); i < j; i++) {
assert tasks.length() == j : "Working array length changed "
+ tasks.length() + " != " + j;
Task t = tasks.get(i);
try {
// Move repeating commands to incremental commands queue
if (t.isRepeating()) {
if (t.executeRepeating()) {
rescheduled = push(rescheduled, t);
}
} else {
t.executeScheduled();
}
} catch (Throwable e) {
if (GWT.getUncaughtExceptionHandler() != null) {
GWT.getUncaughtExceptionHandler().onUncaughtException(e);
}
}
}
return rescheduled;
}
private static native void scheduleFixedDelayImpl(RepeatingCommand cmd,
int delayMs) /*-{
$wnd.setTimeout(function() {
// $entry takes care of uncaught exception handling
var ret = $entry(@com.google.gwt.core.client.impl.SchedulerImpl::execute(Lcom/google/gwt/core/client/Scheduler$RepeatingCommand;))(cmd);
if (!@com.google.gwt.core.client.GWT::isScript()()) {
// Unwrap from Development Mode
ret = ret == true;
}
if (ret) {
$wnd.setTimeout(arguments.callee, delayMs);
}
}, delayMs);
}-*/;
private static native void scheduleFixedPeriodImpl(RepeatingCommand cmd,
int delayMs) /*-{
var fn = function() {
// $entry takes care of uncaught exception handling
var ret = $entry(@com.google.gwt.core.client.impl.SchedulerImpl::execute(Lcom/google/gwt/core/client/Scheduler$RepeatingCommand;))(cmd);
if (!@com.google.gwt.core.client.GWT::isScript()()) {
// Unwrap from Development Mode
ret = ret == true;
}
if (!ret) {
// Either canceled or threw an exception
$wnd.clearInterval(arguments.callee.token);
}
};
fn.token = $wnd.setInterval(fn, delayMs);
}-*/;
/**
* A RepeatingCommand that calls flushPostEventPumpCommands(). It repeats if
* there are any outstanding deferred or incremental commands.
*/
Flusher flusher;
/**
* This provides some backup for the main flusher task in case it gets shut
* down by a slow-script warning.
*/
Rescuer rescue;
/*
* Work queues. Timers store their state on the function, so we don't need to
* track them. They are not final so that we don't have to shorten them.
* Processing the values in the queues is a one-shot, and then the array is
* discarded.
*/
JsArray<Task> deferredCommands;
JsArray<Task> entryCommands;
JsArray<Task> finallyCommands;
JsArray<Task> incrementalCommands;
/*
* These two flags are used to control the state of the flusher and rescuer
* commands.
*/
private boolean flushRunning = false;
private boolean shouldBeRunning = false;
/**
* Called by {@link Impl#entry(JavaScriptObject)}.
*/
public void flushEntryCommands() {
if (entryCommands != null) {
JsArray<Task> rescheduled = null;
// This do-while loop handles commands scheduling commands
do {
JsArray<Task> oldQueue = entryCommands;
entryCommands = null;
rescheduled = runScheduledTasks(oldQueue, rescheduled);
} while (entryCommands != null);
entryCommands = rescheduled;
}
}
/**
* Called by {@link Impl#entry(JavaScriptObject)}.
*/
public void flushFinallyCommands() {
if (finallyCommands != null) {
JsArray<Task> rescheduled = null;
// This do-while loop handles commands scheduling commands
do {
JsArray<Task> oldQueue = finallyCommands;
finallyCommands = null;
rescheduled = runScheduledTasks(oldQueue, rescheduled);
} while (finallyCommands != null);
finallyCommands = rescheduled;
}
}
@Override
public void scheduleDeferred(ScheduledCommand cmd) {
deferredCommands = push(deferredCommands, Task.create(cmd));
maybeSchedulePostEventPumpCommands();
}
@Override
public void scheduleEntry(RepeatingCommand cmd) {
entryCommands = push(entryCommands, Task.create(cmd));
}
@Override
public void scheduleEntry(ScheduledCommand cmd) {
entryCommands = push(entryCommands, Task.create(cmd));
}
@Override
public void scheduleFinally(RepeatingCommand cmd) {
finallyCommands = push(finallyCommands, Task.create(cmd));
}
@Override
public void scheduleFinally(ScheduledCommand cmd) {
finallyCommands = push(finallyCommands, Task.create(cmd));
}
@Override
public void scheduleFixedDelay(RepeatingCommand cmd, int delayMs) {
scheduleFixedDelayImpl(cmd, delayMs);
}
@Override
public void scheduleFixedPeriod(RepeatingCommand cmd, int delayMs) {
scheduleFixedPeriodImpl(cmd, delayMs);
}
@Override
public void scheduleIncremental(RepeatingCommand cmd) {
// Push repeating commands onto the same initial queue for relative order
deferredCommands = push(deferredCommands, Task.create(cmd));
maybeSchedulePostEventPumpCommands();
}
/**
* Called by Flusher.
*/
void flushPostEventPumpCommands() {
if (deferredCommands != null) {
JsArray<Task> oldDeferred = deferredCommands;
deferredCommands = null;
/* We might not have any incremental commands queued. */
if (incrementalCommands == null) {
incrementalCommands = createQueue();
}
runScheduledTasks(oldDeferred, incrementalCommands);
}
if (incrementalCommands != null) {
incrementalCommands = runRepeatingTasks(incrementalCommands);
}
}
boolean isWorkQueued() {
return deferredCommands != null || incrementalCommands != null;
}
private void maybeSchedulePostEventPumpCommands() {
if (!shouldBeRunning) {
shouldBeRunning = true;
if (flusher == null) {
flusher = new Flusher();
}
scheduleFixedDelayImpl(flusher, FLUSHER_DELAY);
if (rescue == null) {
rescue = new Rescuer();
}
scheduleFixedDelayImpl(rescue, RESCUE_DELAY);
}
}
}