/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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 androidx.media.filterfw;
import android.os.ConditionVariable;
import android.os.SystemClock;
import android.util.Log;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.LinkedBlockingQueue;
/**
* A GraphRunner schedules and executes the filter nodes of a graph.
*
* Typically, you create a GraphRunner given a FilterGraph instance, and execute it by calling
* {@link #start(FilterGraph)}.
*
* The scheduling strategy determines how the filter nodes are selected
* for scheduling. More precisely, given the set of nodes that can be scheduled, the scheduling
* strategy determines which node of this set to select for execution. For instance, an LFU
* scheduler (the default) chooses the node that has been executed the least amount of times.
*/
public final class GraphRunner {
private static int PRIORITY_SLEEP = -1;
private static int PRIORITY_STOP = -2;
private static final Event BEGIN_EVENT = new Event(Event.BEGIN, null);
private static final Event FLUSH_EVENT = new Event(Event.FLUSH, null);
private static final Event HALT_EVENT = new Event(Event.HALT, null);
private static final Event KILL_EVENT = new Event(Event.KILL, null);
private static final Event PAUSE_EVENT = new Event(Event.PAUSE, null);
private static final Event RELEASE_FRAMES_EVENT = new Event(Event.RELEASE_FRAMES, null);
private static final Event RESTART_EVENT = new Event(Event.RESTART, null);
private static final Event RESUME_EVENT = new Event(Event.RESUME, null);
private static final Event STEP_EVENT = new Event(Event.STEP, null);
private static final Event STOP_EVENT = new Event(Event.STOP, null);
private static class State {
public static final int STOPPED = 1;
public static final int PREPARING = 2;
public static final int RUNNING = 4;
public static final int PAUSED = 8;
public static final int HALTED = 16;
private int mCurrent = STOPPED;
public synchronized void setState(int newState) {
mCurrent = newState;
}
public synchronized boolean check(int state) {
return ((mCurrent & state) == state);
}
public synchronized boolean addState(int state) {
if ((mCurrent & state) != state) {
mCurrent |= state;
return true;
}
return false;
}
public synchronized boolean removeState(int state) {
boolean result = (mCurrent & state) == state;
mCurrent &= (~state);
return result;
}
public synchronized int current() {
return mCurrent;
}
}
private static class Event {
public static final int PREPARE = 1;
public static final int BEGIN = 2;
public static final int STEP = 3;
public static final int STOP = 4;
public static final int PAUSE = 6;
public static final int HALT = 7;
public static final int RESUME = 8;
public static final int RESTART = 9;
public static final int FLUSH = 10;
public static final int TEARDOWN = 11;
public static final int KILL = 12;
public static final int RELEASE_FRAMES = 13;
public int code;
public Object object;
public Event(int code, Object object) {
this.code = code;
this.object = object;
}
}
private final class GraphRunLoop implements Runnable {
private State mState = new State();
private final boolean mAllowOpenGL;
private RenderTarget mRenderTarget = null;
private LinkedBlockingQueue<Event> mEventQueue = new LinkedBlockingQueue<Event>();
private Exception mCaughtException = null;
private boolean mClosedSuccessfully = true;
private Stack<Filter[]> mFilters = new Stack<Filter[]>();
private Stack<SubListener> mSubListeners = new Stack<SubListener>();
private Set<FilterGraph> mOpenedGraphs = new HashSet<FilterGraph>();
public ConditionVariable mStopCondition = new ConditionVariable(true);
private void loop() {
boolean killed = false;
while (!killed) {
try {
Event event = nextEvent();
if (event == null) continue;
switch (event.code) {
case Event.PREPARE:
onPrepare((FilterGraph)event.object);
break;
case Event.BEGIN:
onBegin();
break;
case Event.STEP:
onStep();
break;
case Event.STOP:
onStop();
break;
case Event.PAUSE:
onPause();
break;
case Event.HALT:
onHalt();
break;
case Event.RESUME:
onResume();
break;
case Event.RESTART:
onRestart();
break;
case Event.FLUSH:
onFlush();
break;
case Event.TEARDOWN:
onTearDown((FilterGraph)event.object);
break;
case Event.KILL:
killed = true;
break;
case Event.RELEASE_FRAMES:
onReleaseFrames();
break;
}
} catch (Exception e) {
if (mCaughtException == null) {
mCaughtException = e;
mClosedSuccessfully = true;
e.printStackTrace();
pushEvent(STOP_EVENT);
} else {
// Exception during exception recovery? Abort all processing. Do not
// overwrite the original exception.
mClosedSuccessfully = false;
mEventQueue.clear();
cleanUp();
}
}
}
}
public GraphRunLoop(boolean allowOpenGL) {
mAllowOpenGL = allowOpenGL;
}
@Override
public void run() {
onInit();
loop();
onDestroy();
}
public void enterSubGraph(FilterGraph graph, SubListener listener) {
if (mState.check(State.RUNNING)) {
onOpenGraph(graph);
mSubListeners.push(listener);
}
}
public void pushWakeEvent(Event event) {
// This is of course not race-condition proof. The worst case is that the event
// is pushed even though the queue was not empty, which is acceptible for our cases.
if (mEventQueue.isEmpty()) {
pushEvent(event);
}
}
public void pushEvent(Event event) {
mEventQueue.offer(event);
}
public void pushEvent(int eventId, Object object) {
mEventQueue.offer(new Event(eventId, object));
}
public boolean checkState(int state) {
return mState.check(state);
}
public ConditionVariable getStopCondition() {
return mStopCondition;
}
public boolean isOpenGLAllowed() {
// Does not need synchronization as mAllowOpenGL flag is final.
return mAllowOpenGL;
}
private Event nextEvent() {
try {
return mEventQueue.take();
} catch (InterruptedException e) {
// Ignore and keep going.
Log.w("GraphRunner", "Event queue processing was interrupted.");
return null;
}
}
private void onPause() {
mState.addState(State.PAUSED);
}
private void onResume() {
if (mState.removeState(State.PAUSED)) {
if (mState.current() == State.RUNNING) {
pushEvent(STEP_EVENT);
}
}
}
private void onHalt() {
if (mState.addState(State.HALTED) && mState.check(State.RUNNING)) {
closeAllFilters();
}
}
private void onRestart() {
if (mState.removeState(State.HALTED)) {
if (mState.current() == State.RUNNING) {
pushEvent(STEP_EVENT);
}
}
}
private void onDestroy() {
mFrameManager.destroyBackings();
if (mRenderTarget != null) {
mRenderTarget.release();
mRenderTarget = null;
}
}
private void onReleaseFrames() {
mFrameManager.destroyBackings();
}
private void onInit() {
mThreadRunner.set(GraphRunner.this);
if (getContext().isOpenGLSupported()) {
mRenderTarget = RenderTarget.newTarget(1, 1);
mRenderTarget.focus();
}
}
private void onPrepare(FilterGraph graph) {
if (mState.current() == State.STOPPED) {
mState.setState(State.PREPARING);
mCaughtException = null;
onOpenGraph(graph);
}
}
private void onOpenGraph(FilterGraph graph) {
loadFilters(graph);
mOpenedGraphs.add(graph);
mScheduler.prepare(currentFilters());
pushEvent(BEGIN_EVENT);
}
private void onBegin() {
if (mState.current() == State.PREPARING) {
mState.setState(State.RUNNING);
pushEvent(STEP_EVENT);
}
}
private void onStarve() {
mFilters.pop();
if (mFilters.empty()) {
onStop();
} else {
SubListener listener = mSubListeners.pop();
if (listener != null) {
listener.onSubGraphRunEnded(GraphRunner.this);
}
mScheduler.prepare(currentFilters());
pushEvent(STEP_EVENT);
}
}
private void onStop() {
if (mState.check(State.RUNNING)) {
// Close filters if not already halted (and already closed)
if (!mState.check(State.HALTED)) {
closeAllFilters();
}
cleanUp();
}
}
private void cleanUp() {
mState.setState(State.STOPPED);
if (flushOnClose()) {
onFlush();
}
mOpenedGraphs.clear();
mFilters.clear();
onRunnerStopped(mCaughtException, mClosedSuccessfully);
mStopCondition.open();
}
private void onStep() {
if (mState.current() == State.RUNNING) {
Filter bestFilter = null;
long maxPriority = PRIORITY_STOP;
mScheduler.beginStep();
Filter[] filters = currentFilters();
for (int i = 0; i < filters.length; ++i) {
Filter filter = filters[i];
long priority = mScheduler.priorityForFilter(filter);
if (priority > maxPriority) {
maxPriority = priority;
bestFilter = filter;
}
}
if (maxPriority == PRIORITY_SLEEP) {
// NOOP: When going into sleep mode, we simply do not schedule another node.
// If some other event (such as a resume()) does schedule, then we may schedule
// during sleeping. This is an edge case an irrelevant. (On the other hand,
// going into a dedicated "sleep state" requires highly complex synchronization
// to not "miss" a wake-up event. Thus we choose the more defensive approach
// here).
} else if (maxPriority == PRIORITY_STOP) {
onStarve();
} else {
scheduleFilter(bestFilter);
pushEvent(STEP_EVENT);
}
} else {
Log.w("GraphRunner", "State is not running! (" + mState.current() + ")");
}
}
private void onFlush() {
if (mState.check(State.HALTED) || mState.check(State.STOPPED)) {
for (FilterGraph graph : mOpenedGraphs) {
graph.flushFrames();
}
}
}
private void onTearDown(FilterGraph graph) {
for (Filter filter : graph.getAllFilters()) {
filter.performTearDown();
}
graph.wipe();
}
private void loadFilters(FilterGraph graph) {
Filter[] filters = graph.getAllFilters();
mFilters.push(filters);
}
private void closeAllFilters() {
for (FilterGraph graph : mOpenedGraphs) {
closeFilters(graph);
}
}
private void closeFilters(FilterGraph graph) {
// [Non-iterator looping]
Log.v("GraphRunner", "CLOSING FILTERS");
Filter[] filters = graph.getAllFilters();
boolean isVerbose = isVerbose();
for (int i = 0; i < filters.length; ++i) {
if (isVerbose) {
Log.i("GraphRunner", "Closing Filter " + filters[i] + "!");
}
filters[i].softReset();
}
}
private Filter[] currentFilters() {
return mFilters.peek();
}
private void scheduleFilter(Filter filter) {
long scheduleTime = 0;
if (isVerbose()) {
scheduleTime = SystemClock.elapsedRealtime();
Log.i("GraphRunner", scheduleTime + ": Scheduling " + filter + "!");
}
filter.execute();
if (isVerbose()) {
long nowTime = SystemClock.elapsedRealtime();
Log.i("GraphRunner",
"-> Schedule time (" + filter + ") = " + (nowTime - scheduleTime) + " ms.");
}
}
}
// GraphRunner.Scheduler classes ///////////////////////////////////////////////////////////////
private interface Scheduler {
public void prepare(Filter[] filters);
public int getStrategy();
public void beginStep();
public long priorityForFilter(Filter filter);
}
private class LruScheduler implements Scheduler {
private long mNow;
@Override
public void prepare(Filter[] filters) {
}
@Override
public int getStrategy() {
return STRATEGY_LRU;
}
@Override
public void beginStep() {
// TODO(renn): We could probably do this with a simple GraphRunner counter that would
// represent GraphRunner local time. This would allow us to use integers instead of
// longs, and save us calls to the system clock.
mNow = SystemClock.elapsedRealtime();
}
@Override
public long priorityForFilter(Filter filter) {
if (filter.isSleeping()) {
return PRIORITY_SLEEP;
} else if (filter.canSchedule()) {
return mNow - filter.getLastScheduleTime();
} else {
return PRIORITY_STOP;
}
}
}
private class LfuScheduler implements Scheduler {
private final int MAX_PRIORITY = Integer.MAX_VALUE;
@Override
public void prepare(Filter[] filters) {
// [Non-iterator looping]
for (int i = 0; i < filters.length; ++i) {
filters[i].resetScheduleCount();
}
}
@Override
public int getStrategy() {
return STRATEGY_LFU;
}
@Override
public void beginStep() {
}
@Override
public long priorityForFilter(Filter filter) {
return filter.isSleeping() ? PRIORITY_SLEEP
: (filter.canSchedule() ? (MAX_PRIORITY - filter.getScheduleCount())
: PRIORITY_STOP);
}
}
private class OneShotScheduler extends LfuScheduler {
private int mCurCount = 1;
@Override
public void prepare(Filter[] filters) {
// [Non-iterator looping]
for (int i = 0; i < filters.length; ++i) {
filters[i].resetScheduleCount();
}
}
@Override
public int getStrategy() {
return STRATEGY_ONESHOT;
}
@Override
public void beginStep() {
}
@Override
public long priorityForFilter(Filter filter) {
return filter.getScheduleCount() < mCurCount ? super.priorityForFilter(filter)
: PRIORITY_STOP;
}
}
// GraphRunner.Listener callback class /////////////////////////////////////////////////////////
public interface Listener {
/**
* Callback method that is called when the runner completes a run. This method is called
* only if the graph completed without an error.
*/
public void onGraphRunnerStopped(GraphRunner runner);
/**
* Callback method that is called when runner encounters an error.
*
* Any exceptions thrown in the GraphRunner's thread will cause the run to abort. The
* thrown exception is passed to the listener in this method. If no listener is set, the
* exception message is logged to the error stream. You will not receive an
* {@link #onGraphRunnerStopped(GraphRunner)} callback in case of an error.
*
* @param exception the exception that was thrown.
* @param closedSuccessfully true, if the graph was closed successfully after the error.
*/
public void onGraphRunnerError(Exception exception, boolean closedSuccessfully);
}
public interface SubListener {
public void onSubGraphRunEnded(GraphRunner runner);
}
/**
* Config class to setup a GraphRunner with a custom configuration.
*
* The configuration object is passed to the constructor. Any changes to it will not affect
* the created GraphRunner instance.
*/
public static class Config {
/** The runner's thread priority. */
public int threadPriority = Thread.NORM_PRIORITY;
/** Whether to allow filters to use OpenGL or not. */
public boolean allowOpenGL = true;
}
/** Parameters shared between run-thread and GraphRunner frontend. */
private class RunParameters {
public Listener listener = null;
public boolean isVerbose = false;
public boolean flushOnClose = true;
}
// GraphRunner implementation //////////////////////////////////////////////////////////////////
/** Schedule strategy: From set of candidates, pick a random one. */
public static final int STRATEGY_RANDOM = 1;
/** Schedule strategy: From set of candidates, pick node executed least recently executed. */
public static final int STRATEGY_LRU = 2;
/** Schedule strategy: From set of candidates, pick node executed least number of times. */
public static final int STRATEGY_LFU = 3;
/** Schedule strategy: Schedules no node more than once. */
public static final int STRATEGY_ONESHOT = 4;
private final MffContext mContext;
private FilterGraph mRunningGraph = null;
private Set<FilterGraph> mGraphs = new HashSet<FilterGraph>();
private Scheduler mScheduler;
private GraphRunLoop mRunLoop;
private Thread mRunThread = null;
private FrameManager mFrameManager = null;
private static ThreadLocal<GraphRunner> mThreadRunner = new ThreadLocal<GraphRunner>();
private RunParameters mParams = new RunParameters();
/**
* Creates a new GraphRunner with the default configuration. You must attach FilterGraph
* instances to this runner before you can execute any of these graphs.
*
* @param context The MffContext instance for this runner.
*/
public GraphRunner(MffContext context) {
mContext = context;
init(new Config());
}
/**
* Creates a new GraphRunner with the specified configuration. You must attach FilterGraph
* instances to this runner before you can execute any of these graphs.
*
* @param context The MffContext instance for this runner.
* @param config A Config instance with the configuration of this runner.
*/
public GraphRunner(MffContext context, Config config) {
mContext = context;
init(config);
}
/**
* Returns the currently running graph-runner.
* @return The currently running graph-runner.
*/
public static GraphRunner current() {
return mThreadRunner.get();
}
/**
* Returns the graph that this runner is currently executing. Returns null if no graph is
* currently being executed by this runner.
*
* @return the FilterGraph instance that this GraphRunner is executing.
*/
public synchronized FilterGraph getRunningGraph() {
return mRunningGraph;
}
/**
* Returns the context that this runner is bound to.
*
* @return the MffContext instance that this runner is bound to.
*/
public MffContext getContext() {
return mContext;
}
/**
* Begins graph execution. The graph filters are scheduled and executed until processing
* finishes or is stopped.
*/
public synchronized void start(FilterGraph graph) {
if (graph.mRunner != this) {
throw new IllegalArgumentException("Graph must be attached to runner!");
}
mRunningGraph = graph;
mRunLoop.getStopCondition().close();
mRunLoop.pushEvent(Event.PREPARE, graph);
}
/**
* Begin executing a sub-graph. This only succeeds if the current runner is already
* executing.
*/
public void enterSubGraph(FilterGraph graph, SubListener listener) {
if (Thread.currentThread() != mRunThread) {
throw new RuntimeException("enterSubGraph must be called from the runner's thread!");
}
mRunLoop.enterSubGraph(graph, listener);
}
/**
* Waits until graph execution has finished or stopped with an error.
* Care must be taken when using this method to not block the UI thread. This is typically
* used when a graph is run in one-shot mode to compute a result.
*/
public void waitUntilStop() {
mRunLoop.getStopCondition().block();
}
/**
* Pauses graph execution.
*/
public void pause() {
mRunLoop.pushEvent(PAUSE_EVENT);
}
/**
* Resumes graph execution after pausing.
*/
public void resume() {
mRunLoop.pushEvent(RESUME_EVENT);
}
/**
* Stops graph execution.
*/
public void stop() {
mRunLoop.pushEvent(STOP_EVENT);
}
/**
* Returns whether the graph is currently being executed. A graph is considered to be running,
* even if it is paused or in the process of being stopped.
*
* @return true, if the graph is currently being executed.
*/
public boolean isRunning() {
return !mRunLoop.checkState(State.STOPPED);
}
/**
* Returns whether the graph is currently paused.
*
* @return true, if the graph is currently paused.
*/
public boolean isPaused() {
return mRunLoop.checkState(State.PAUSED);
}
/**
* Returns whether the graph is currently stopped.
*
* @return true, if the graph is currently stopped.
*/
public boolean isStopped() {
return mRunLoop.checkState(State.STOPPED);
}
/**
* Sets the filter scheduling strategy. This method can not be called when the GraphRunner is
* running.
*
* @param strategy a constant specifying which scheduler strategy to use.
* @throws RuntimeException if the GraphRunner is running.
* @throws IllegalArgumentException if invalid strategy is specified.
* @see #getSchedulerStrategy()
*/
public void setSchedulerStrategy(int strategy) {
if (isRunning()) {
throw new RuntimeException(
"Attempting to change scheduling strategy on running " + "GraphRunner!");
}
createScheduler(strategy);
}
/**
* Returns the current scheduling strategy.
*
* @return the scheduling strategy used by this GraphRunner.
* @see #setSchedulerStrategy(int)
*/
public int getSchedulerStrategy() {
return mScheduler.getStrategy();
}
/**
* Set whether or not the runner is verbose. When set to true, the runner will output individual
* scheduling steps that may help identify and debug problems in the graph structure. The
* default is false.
*
* @param isVerbose true, if the GraphRunner should log scheduling details.
* @see #isVerbose()
*/
public void setIsVerbose(boolean isVerbose) {
synchronized (mParams) {
mParams.isVerbose = isVerbose;
}
}
/**
* Returns whether the GraphRunner is verbose.
*
* @return true, if the GraphRunner logs scheduling details.
* @see #setIsVerbose(boolean)
*/
public boolean isVerbose() {
synchronized (mParams) {
return mParams.isVerbose;
}
}
/**
* Returns whether Filters of this GraphRunner can use OpenGL.
*
* Filters may use OpenGL if the MffContext supports OpenGL and the GraphRunner allows it.
*
* @return true, if Filters are allowed to use OpenGL.
*/
public boolean isOpenGLSupported() {
return mRunLoop.isOpenGLAllowed() && mContext.isOpenGLSupported();
}
/**
* Enable flushing all frames from the graph when running completes.
*
* If this is set to false, then frames may remain in the pipeline even after running completes.
* The default value is true.
*
* @param flush true, if the GraphRunner should flush the graph when running completes.
* @see #flushOnClose()
*/
public void setFlushOnClose(boolean flush) {
synchronized (mParams) {
mParams.flushOnClose = flush;
}
}
/**
* Returns whether the GraphRunner flushes frames when running completes.
*
* @return true, if the GraphRunner flushes frames when running completes.
* @see #setFlushOnClose(boolean)
*/
public boolean flushOnClose() {
synchronized (mParams) {
return mParams.flushOnClose;
}
}
/**
* Sets the listener for receiving runtime events. A GraphRunner.Listener instance can be used
* to determine when certain events occur during graph execution (and react on them). See the
* {@link GraphRunner.Listener} class for details.
*
* @param listener the GraphRunner.Listener instance to set.
* @see #getListener()
*/
public void setListener(Listener listener) {
synchronized (mParams) {
mParams.listener = listener;
}
}
/**
* Returns the currently assigned GraphRunner.Listener.
*
* @return the currently assigned GraphRunner.Listener instance.
* @see #setListener(Listener)
*/
public Listener getListener() {
synchronized (mParams) {
return mParams.listener;
}
}
/**
* Returns the FrameManager that manages the runner's frames.
*
* @return the FrameManager instance that manages the runner's frames.
*/
public FrameManager getFrameManager() {
return mFrameManager;
}
/**
* Tear down a GraphRunner and all its resources.
* <p>
* You must make sure that before calling this, no more graphs are attached to this runner.
* Typically, graphs are removed from runners when they are torn down.
*
* @throws IllegalStateException if there are still graphs attached to this runner.
*/
public void tearDown() {
synchronized (mGraphs) {
if (!mGraphs.isEmpty()) {
throw new IllegalStateException("Attempting to tear down runner with "
+ mGraphs.size() + " graphs still attached!");
}
}
mRunLoop.pushEvent(KILL_EVENT);
// Wait for thread to complete, so that everything is torn down by the time we return.
try {
mRunThread.join();
} catch (InterruptedException e) {
Log.e("GraphRunner", "Error waiting for runner thread to finish!");
}
}
/**
* Release all frames managed by this runner.
* <p>
* Note, that you must make sure no graphs are attached to this runner before calling this
* method, as otherwise Filters in the graph may reference frames that are now released.
*
* TODO: Eventually, this method should be removed. Instead we should have better analysis
* that catches leaking frames from filters.
*
* @throws IllegalStateException if there are still graphs attached to this runner.
*/
public void releaseFrames() {
synchronized (mGraphs) {
if (!mGraphs.isEmpty()) {
throw new IllegalStateException("Attempting to release frames with "
+ mGraphs.size() + " graphs still attached!");
}
}
mRunLoop.pushEvent(RELEASE_FRAMES_EVENT);
}
// Core internal methods ///////////////////////////////////////////////////////////////////////
void attachGraph(FilterGraph graph) {
synchronized (mGraphs) {
mGraphs.add(graph);
}
}
void signalWakeUp() {
mRunLoop.pushWakeEvent(STEP_EVENT);
}
void begin() {
mRunLoop.pushEvent(BEGIN_EVENT);
}
/** Like pause(), but closes all filters. Can be resumed using restart(). */
void halt() {
mRunLoop.pushEvent(HALT_EVENT);
}
/** Resumes a previously halted runner, and restores it to its non-halted state. */
void restart() {
mRunLoop.pushEvent(RESTART_EVENT);
}
/**
* Tears down the specified graph.
*
* The graph must be attached to this runner.
*/
void tearDownGraph(FilterGraph graph) {
if (graph.getRunner() != this) {
throw new IllegalArgumentException("Attempting to tear down graph with foreign "
+ "GraphRunner!");
}
mRunLoop.pushEvent(Event.TEARDOWN, graph);
synchronized (mGraphs) {
mGraphs.remove(graph);
}
}
/**
* Remove all frames that are waiting to be processed.
*
* Removes and releases frames that are waiting in the graph connections of the currently
* halted graphs, i.e. frames that are waiting to be processed. This does not include frames
* that may be held or cached by filters themselves.
*
* TODO: With the new sub-graph architecture, this can now be simplified and made public.
* It can then no longer rely on opened graphs, and instead flush a graph and all its
* sub-graphs.
*/
void flushFrames() {
mRunLoop.pushEvent(FLUSH_EVENT);
}
// Private methods /////////////////////////////////////////////////////////////////////////////
private void init(Config config) {
mFrameManager = new FrameManager(this, FrameManager.FRAME_CACHE_LRU);
createScheduler(STRATEGY_LRU);
mRunLoop = new GraphRunLoop(config.allowOpenGL);
mRunThread = new Thread(mRunLoop);
mRunThread.setPriority(config.threadPriority);
mRunThread.start();
mContext.addRunner(this);
}
private void createScheduler(int strategy) {
switch (strategy) {
case STRATEGY_LRU:
mScheduler = new LruScheduler();
break;
case STRATEGY_LFU:
mScheduler = new LfuScheduler();
break;
case STRATEGY_ONESHOT:
mScheduler = new OneShotScheduler();
break;
default:
throw new IllegalArgumentException(
"Unknown schedule-strategy constant " + strategy + "!");
}
}
// Called within the runner's thread
private void onRunnerStopped(final Exception exception, final boolean closed) {
mRunningGraph = null;
synchronized (mParams) {
if (mParams.listener != null) {
getContext().postRunnable(new Runnable() {
@Override
public void run() {
if (exception == null) {
mParams.listener.onGraphRunnerStopped(GraphRunner.this);
} else {
mParams.listener.onGraphRunnerError(exception, closed);
}
}
});
} else if (exception != null) {
Log.e("GraphRunner",
"Uncaught exception during graph execution! Stack Trace: ");
exception.printStackTrace();
}
}
}
}