| /* |
| * 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. |
| */ |
| @SuppressWarnings("unused") |
| 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 (RuntimeException 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); |
| } |
| } |
| } |