1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14
15package androidx.media.filterfw;
16
17import android.os.ConditionVariable;
18import android.os.SystemClock;
19import android.util.Log;
20
21import java.util.HashSet;
22import java.util.Set;
23import java.util.Stack;
24import java.util.concurrent.LinkedBlockingQueue;
25
26/**
27 * A GraphRunner schedules and executes the filter nodes of a graph.
28 *
29 * Typically, you create a GraphRunner given a FilterGraph instance, and execute it by calling
30 * {@link #start(FilterGraph)}.
31 *
32 * The scheduling strategy determines how the filter nodes are selected
33 * for scheduling. More precisely, given the set of nodes that can be scheduled, the scheduling
34 * strategy determines which node of this set to select for execution. For instance, an LFU
35 * scheduler (the default) chooses the node that has been executed the least amount of times.
36 */
37public final class GraphRunner {
38
39    private static int PRIORITY_SLEEP = -1;
40    private static int PRIORITY_STOP = -2;
41
42    private static final Event BEGIN_EVENT = new Event(Event.BEGIN, null);
43    private static final Event FLUSH_EVENT = new Event(Event.FLUSH, null);
44    private static final Event HALT_EVENT = new Event(Event.HALT, null);
45    private static final Event KILL_EVENT = new Event(Event.KILL, null);
46    private static final Event PAUSE_EVENT = new Event(Event.PAUSE, null);
47    private static final Event RELEASE_FRAMES_EVENT = new Event(Event.RELEASE_FRAMES, null);
48    private static final Event RESTART_EVENT = new Event(Event.RESTART, null);
49    private static final Event RESUME_EVENT = new Event(Event.RESUME, null);
50    private static final Event STEP_EVENT = new Event(Event.STEP, null);
51    private static final Event STOP_EVENT = new Event(Event.STOP, null);
52
53    private static class State {
54        public static final int STOPPED = 1;
55        public static final int PREPARING = 2;
56        public static final int RUNNING = 4;
57        public static final int PAUSED = 8;
58        public static final int HALTED = 16;
59
60        private int mCurrent = STOPPED;
61
62        public synchronized void setState(int newState) {
63            mCurrent = newState;
64        }
65
66        public synchronized boolean check(int state) {
67            return ((mCurrent & state) == state);
68        }
69
70        public synchronized boolean addState(int state) {
71            if ((mCurrent & state) != state) {
72                mCurrent |= state;
73                return true;
74            }
75            return false;
76        }
77
78        public synchronized boolean removeState(int state) {
79            boolean result = (mCurrent & state) == state;
80            mCurrent &= (~state);
81            return result;
82        }
83
84        public synchronized int current() {
85            return mCurrent;
86        }
87    }
88
89    private static class Event {
90        public static final int PREPARE = 1;
91        public static final int BEGIN = 2;
92        public static final int STEP = 3;
93        public static final int STOP = 4;
94        public static final int PAUSE = 6;
95        public static final int HALT = 7;
96        public static final int RESUME = 8;
97        public static final int RESTART = 9;
98        public static final int FLUSH = 10;
99        public static final int TEARDOWN = 11;
100        public static final int KILL = 12;
101        public static final int RELEASE_FRAMES = 13;
102
103        public int code;
104        public Object object;
105
106        public Event(int code, Object object) {
107            this.code = code;
108            this.object = object;
109        }
110    }
111
112    private final class GraphRunLoop implements Runnable {
113
114        private State mState = new State();
115        private final boolean mAllowOpenGL;
116        private RenderTarget mRenderTarget = null;
117        private LinkedBlockingQueue<Event> mEventQueue = new LinkedBlockingQueue<Event>();
118        private Exception mCaughtException = null;
119        private boolean mClosedSuccessfully = true;
120        private Stack<Filter[]> mFilters = new Stack<Filter[]>();
121        private Stack<SubListener> mSubListeners = new Stack<SubListener>();
122        private Set<FilterGraph> mOpenedGraphs = new HashSet<FilterGraph>();
123        public ConditionVariable mStopCondition = new ConditionVariable(true);
124
125        private void loop() {
126            boolean killed = false;
127            while (!killed) {
128                try {
129                    Event event = nextEvent();
130                    if (event == null) continue;
131                    switch (event.code) {
132                        case Event.PREPARE:
133                            onPrepare((FilterGraph)event.object);
134                            break;
135                        case Event.BEGIN:
136                            onBegin();
137                            break;
138                        case Event.STEP:
139                            onStep();
140                            break;
141                        case Event.STOP:
142                            onStop();
143                            break;
144                        case Event.PAUSE:
145                            onPause();
146                            break;
147                        case Event.HALT:
148                            onHalt();
149                            break;
150                        case Event.RESUME:
151                            onResume();
152                            break;
153                        case Event.RESTART:
154                            onRestart();
155                            break;
156                        case Event.FLUSH:
157                            onFlush();
158                            break;
159                        case Event.TEARDOWN:
160                            onTearDown((FilterGraph)event.object);
161                            break;
162                        case Event.KILL:
163                            killed = true;
164                            break;
165                        case Event.RELEASE_FRAMES:
166                            onReleaseFrames();
167                            break;
168                    }
169                } catch (Exception e) {
170                    if (mCaughtException == null) {
171                        mCaughtException = e;
172                        mClosedSuccessfully = true;
173                        e.printStackTrace();
174                        pushEvent(STOP_EVENT);
175                    } else {
176                        // Exception during exception recovery? Abort all processing. Do not
177                        // overwrite the original exception.
178                        mClosedSuccessfully = false;
179                        mEventQueue.clear();
180                        cleanUp();
181                    }
182                }
183            }
184        }
185
186        public GraphRunLoop(boolean allowOpenGL) {
187            mAllowOpenGL = allowOpenGL;
188        }
189
190        @Override
191        public void run() {
192            onInit();
193            loop();
194            onDestroy();
195        }
196
197        public void enterSubGraph(FilterGraph graph, SubListener listener) {
198            if (mState.check(State.RUNNING)) {
199                onOpenGraph(graph);
200                mSubListeners.push(listener);
201            }
202        }
203
204        public void pushWakeEvent(Event event) {
205            // This is of course not race-condition proof. The worst case is that the event
206            // is pushed even though the queue was not empty, which is acceptible for our cases.
207            if (mEventQueue.isEmpty()) {
208                pushEvent(event);
209            }
210        }
211
212        public void pushEvent(Event event) {
213            mEventQueue.offer(event);
214        }
215
216        public void pushEvent(int eventId, Object object) {
217            mEventQueue.offer(new Event(eventId, object));
218        }
219
220        public boolean checkState(int state) {
221            return mState.check(state);
222        }
223
224        public ConditionVariable getStopCondition() {
225            return mStopCondition;
226        }
227
228        public boolean isOpenGLAllowed() {
229            // Does not need synchronization as mAllowOpenGL flag is final.
230            return mAllowOpenGL;
231        }
232
233        private Event nextEvent() {
234            try {
235                return mEventQueue.take();
236            } catch (InterruptedException e) {
237                // Ignore and keep going.
238                Log.w("GraphRunner", "Event queue processing was interrupted.");
239                return null;
240            }
241        }
242
243        private void onPause() {
244            mState.addState(State.PAUSED);
245        }
246
247        private void onResume() {
248            if (mState.removeState(State.PAUSED)) {
249                if (mState.current() == State.RUNNING) {
250                    pushEvent(STEP_EVENT);
251                }
252            }
253        }
254
255        private void onHalt() {
256            if (mState.addState(State.HALTED) && mState.check(State.RUNNING)) {
257                closeAllFilters();
258            }
259        }
260
261        private void onRestart() {
262            if (mState.removeState(State.HALTED)) {
263                if (mState.current() == State.RUNNING) {
264                    pushEvent(STEP_EVENT);
265                }
266            }
267        }
268
269        private void onDestroy() {
270            mFrameManager.destroyBackings();
271            if (mRenderTarget != null) {
272                mRenderTarget.release();
273                mRenderTarget = null;
274            }
275        }
276
277        private void onReleaseFrames() {
278            mFrameManager.destroyBackings();
279        }
280
281        private void onInit() {
282            mThreadRunner.set(GraphRunner.this);
283            if (getContext().isOpenGLSupported()) {
284                mRenderTarget = RenderTarget.newTarget(1, 1);
285                mRenderTarget.focus();
286            }
287        }
288
289        private void onPrepare(FilterGraph graph) {
290            if (mState.current() == State.STOPPED) {
291                mState.setState(State.PREPARING);
292                mCaughtException = null;
293                onOpenGraph(graph);
294            }
295        }
296
297        private void onOpenGraph(FilterGraph graph) {
298            loadFilters(graph);
299            mOpenedGraphs.add(graph);
300            mScheduler.prepare(currentFilters());
301            pushEvent(BEGIN_EVENT);
302        }
303
304        private void onBegin() {
305            if (mState.current() == State.PREPARING) {
306                mState.setState(State.RUNNING);
307                pushEvent(STEP_EVENT);
308            }
309        }
310
311        private void onStarve() {
312            mFilters.pop();
313            if (mFilters.empty()) {
314                onStop();
315            } else {
316                SubListener listener = mSubListeners.pop();
317                if (listener != null) {
318                    listener.onSubGraphRunEnded(GraphRunner.this);
319                }
320                mScheduler.prepare(currentFilters());
321                pushEvent(STEP_EVENT);
322            }
323        }
324
325        private void onStop() {
326            if (mState.check(State.RUNNING)) {
327                // Close filters if not already halted (and already closed)
328                if (!mState.check(State.HALTED)) {
329                    closeAllFilters();
330                }
331                cleanUp();
332            }
333        }
334
335        private void cleanUp() {
336            mState.setState(State.STOPPED);
337            if (flushOnClose()) {
338                onFlush();
339            }
340            mOpenedGraphs.clear();
341            mFilters.clear();
342            onRunnerStopped(mCaughtException, mClosedSuccessfully);
343            mStopCondition.open();
344        }
345
346        private void onStep() {
347            if (mState.current() == State.RUNNING) {
348                Filter bestFilter = null;
349                long maxPriority = PRIORITY_STOP;
350                mScheduler.beginStep();
351                Filter[] filters = currentFilters();
352                for (int i = 0; i < filters.length; ++i) {
353                    Filter filter = filters[i];
354                    long priority = mScheduler.priorityForFilter(filter);
355                    if (priority > maxPriority) {
356                        maxPriority = priority;
357                        bestFilter = filter;
358                    }
359                }
360                if (maxPriority == PRIORITY_SLEEP) {
361                    // NOOP: When going into sleep mode, we simply do not schedule another node.
362                    // If some other event (such as a resume()) does schedule, then we may schedule
363                    // during sleeping. This is an edge case an irrelevant. (On the other hand,
364                    // going into a dedicated "sleep state" requires highly complex synchronization
365                    // to not "miss" a wake-up event. Thus we choose the more defensive approach
366                    // here).
367                } else if (maxPriority == PRIORITY_STOP) {
368                    onStarve();
369                } else {
370                    scheduleFilter(bestFilter);
371                    pushEvent(STEP_EVENT);
372                }
373            } else {
374                Log.w("GraphRunner", "State is not running! (" + mState.current() + ")");
375            }
376        }
377
378        private void onFlush() {
379           if (mState.check(State.HALTED) || mState.check(State.STOPPED)) {
380               for (FilterGraph graph : mOpenedGraphs) {
381                   graph.flushFrames();
382               }
383           }
384        }
385
386        private void onTearDown(FilterGraph graph) {
387            for (Filter filter : graph.getAllFilters()) {
388                filter.performTearDown();
389            }
390            graph.wipe();
391        }
392
393        private void loadFilters(FilterGraph graph) {
394            Filter[] filters = graph.getAllFilters();
395            mFilters.push(filters);
396        }
397
398        private void closeAllFilters() {
399            for (FilterGraph graph : mOpenedGraphs) {
400                closeFilters(graph);
401            }
402        }
403
404        private void closeFilters(FilterGraph graph) {
405            // [Non-iterator looping]
406            Log.v("GraphRunner", "CLOSING FILTERS");
407            Filter[] filters = graph.getAllFilters();
408            boolean isVerbose = isVerbose();
409            for (int i = 0; i < filters.length; ++i) {
410                if (isVerbose) {
411                    Log.i("GraphRunner", "Closing Filter " + filters[i] + "!");
412                }
413                filters[i].softReset();
414            }
415        }
416
417        private Filter[] currentFilters() {
418            return mFilters.peek();
419        }
420
421        private void scheduleFilter(Filter filter) {
422            long scheduleTime = 0;
423            if (isVerbose()) {
424                scheduleTime = SystemClock.elapsedRealtime();
425                Log.i("GraphRunner", scheduleTime + ": Scheduling " + filter + "!");
426            }
427            filter.execute();
428            if (isVerbose()) {
429                long nowTime = SystemClock.elapsedRealtime();
430                Log.i("GraphRunner",
431                        "-> Schedule time (" + filter + ") = " + (nowTime - scheduleTime) + " ms.");
432            }
433        }
434
435    }
436
437    // GraphRunner.Scheduler classes ///////////////////////////////////////////////////////////////
438    private interface Scheduler {
439        public void prepare(Filter[] filters);
440
441        public int getStrategy();
442
443        public void beginStep();
444
445        public long priorityForFilter(Filter filter);
446
447    }
448
449    private class LruScheduler implements Scheduler {
450
451        private long mNow;
452
453        @Override
454        public void prepare(Filter[] filters) {
455        }
456
457        @Override
458        public int getStrategy() {
459            return STRATEGY_LRU;
460        }
461
462        @Override
463        public void beginStep() {
464            // TODO(renn): We could probably do this with a simple GraphRunner counter that would
465            // represent GraphRunner local time. This would allow us to use integers instead of
466            // longs, and save us calls to the system clock.
467            mNow = SystemClock.elapsedRealtime();
468        }
469
470        @Override
471        public long priorityForFilter(Filter filter) {
472            if (filter.isSleeping()) {
473                return PRIORITY_SLEEP;
474            } else if (filter.canSchedule()) {
475                return mNow - filter.getLastScheduleTime();
476            } else {
477                return PRIORITY_STOP;
478            }
479        }
480
481    }
482
483    private class LfuScheduler implements Scheduler {
484
485        private final int MAX_PRIORITY = Integer.MAX_VALUE;
486
487        @Override
488        public void prepare(Filter[] filters) {
489            // [Non-iterator looping]
490            for (int i = 0; i < filters.length; ++i) {
491                filters[i].resetScheduleCount();
492            }
493        }
494
495        @Override
496        public int getStrategy() {
497            return STRATEGY_LFU;
498        }
499
500        @Override
501        public void beginStep() {
502        }
503
504        @Override
505        public long priorityForFilter(Filter filter) {
506            return filter.isSleeping() ? PRIORITY_SLEEP
507                    : (filter.canSchedule() ? (MAX_PRIORITY - filter.getScheduleCount())
508                            : PRIORITY_STOP);
509        }
510
511    }
512
513    private class OneShotScheduler extends LfuScheduler {
514        private int mCurCount = 1;
515
516        @Override
517        public void prepare(Filter[] filters) {
518            // [Non-iterator looping]
519            for (int i = 0; i < filters.length; ++i) {
520                filters[i].resetScheduleCount();
521            }
522        }
523
524        @Override
525        public int getStrategy() {
526            return STRATEGY_ONESHOT;
527        }
528
529        @Override
530        public void beginStep() {
531        }
532
533        @Override
534        public long priorityForFilter(Filter filter) {
535            return filter.getScheduleCount() < mCurCount ? super.priorityForFilter(filter)
536                    : PRIORITY_STOP;
537        }
538
539    }
540
541    // GraphRunner.Listener callback class /////////////////////////////////////////////////////////
542    public interface Listener {
543        /**
544         * Callback method that is called when the runner completes a run. This method is called
545         * only if the graph completed without an error.
546         */
547        public void onGraphRunnerStopped(GraphRunner runner);
548
549        /**
550         * Callback method that is called when runner encounters an error.
551         *
552         *  Any exceptions thrown in the GraphRunner's thread will cause the run to abort. The
553         * thrown exception is passed to the listener in this method. If no listener is set, the
554         * exception message is logged to the error stream. You will not receive an
555         * {@link #onGraphRunnerStopped(GraphRunner)} callback in case of an error.
556         *
557         * @param exception the exception that was thrown.
558         * @param closedSuccessfully true, if the graph was closed successfully after the error.
559         */
560        public void onGraphRunnerError(Exception exception, boolean closedSuccessfully);
561    }
562
563    public interface SubListener {
564        public void onSubGraphRunEnded(GraphRunner runner);
565    }
566
567    /**
568     * Config class to setup a GraphRunner with a custom configuration.
569     *
570     * The configuration object is passed to the constructor. Any changes to it will not affect
571     * the created GraphRunner instance.
572     */
573    public static class Config {
574        /** The runner's thread priority. */
575        public int threadPriority = Thread.NORM_PRIORITY;
576        /** Whether to allow filters to use OpenGL or not. */
577        public boolean allowOpenGL = true;
578    }
579
580    /** Parameters shared between run-thread and GraphRunner frontend. */
581    private class RunParameters {
582        public Listener listener = null;
583        public boolean isVerbose = false;
584        public boolean flushOnClose = true;
585    }
586
587    // GraphRunner implementation //////////////////////////////////////////////////////////////////
588    /** Schedule strategy: From set of candidates, pick a random one. */
589    public static final int STRATEGY_RANDOM = 1;
590    /** Schedule strategy: From set of candidates, pick node executed least recently executed. */
591    public static final int STRATEGY_LRU = 2;
592    /** Schedule strategy: From set of candidates, pick node executed least number of times. */
593    public static final int STRATEGY_LFU = 3;
594    /** Schedule strategy: Schedules no node more than once. */
595    public static final int STRATEGY_ONESHOT = 4;
596
597    private final MffContext mContext;
598
599    private FilterGraph mRunningGraph = null;
600    private Set<FilterGraph> mGraphs = new HashSet<FilterGraph>();
601
602    private Scheduler mScheduler;
603
604    private GraphRunLoop mRunLoop;
605
606    private Thread mRunThread = null;
607
608    private FrameManager mFrameManager = null;
609
610    private static ThreadLocal<GraphRunner> mThreadRunner = new ThreadLocal<GraphRunner>();
611
612    private RunParameters mParams = new RunParameters();
613
614    /**
615     * Creates a new GraphRunner with the default configuration. You must attach FilterGraph
616     * instances to this runner before you can execute any of these graphs.
617     *
618     * @param context The MffContext instance for this runner.
619     */
620    public GraphRunner(MffContext context) {
621        mContext = context;
622        init(new Config());
623    }
624
625    /**
626     * Creates a new GraphRunner with the specified configuration. You must attach FilterGraph
627     * instances to this runner before you can execute any of these graphs.
628     *
629     * @param context The MffContext instance for this runner.
630     * @param config A Config instance with the configuration of this runner.
631     */
632    public GraphRunner(MffContext context, Config config) {
633        mContext = context;
634        init(config);
635    }
636
637    /**
638     * Returns the currently running graph-runner.
639     * @return The currently running graph-runner.
640     */
641    public static GraphRunner current() {
642        return mThreadRunner.get();
643    }
644
645    /**
646     * Returns the graph that this runner is currently executing. Returns null if no graph is
647     * currently being executed by this runner.
648     *
649     * @return the FilterGraph instance that this GraphRunner is executing.
650     */
651    public synchronized FilterGraph getRunningGraph() {
652        return mRunningGraph;
653    }
654
655    /**
656     * Returns the context that this runner is bound to.
657     *
658     * @return the MffContext instance that this runner is bound to.
659     */
660    public MffContext getContext() {
661        return mContext;
662    }
663
664    /**
665     * Begins graph execution. The graph filters are scheduled and executed until processing
666     * finishes or is stopped.
667     */
668    public synchronized void start(FilterGraph graph) {
669        if (graph.mRunner != this) {
670            throw new IllegalArgumentException("Graph must be attached to runner!");
671        }
672        mRunningGraph = graph;
673        mRunLoop.getStopCondition().close();
674        mRunLoop.pushEvent(Event.PREPARE, graph);
675    }
676
677    /**
678     * Begin executing a sub-graph. This only succeeds if the current runner is already
679     * executing.
680     */
681    public void enterSubGraph(FilterGraph graph, SubListener listener) {
682        if (Thread.currentThread() != mRunThread) {
683            throw new RuntimeException("enterSubGraph must be called from the runner's thread!");
684        }
685        mRunLoop.enterSubGraph(graph, listener);
686    }
687
688    /**
689     * Waits until graph execution has finished or stopped with an error.
690     * Care must be taken when using this method to not block the UI thread. This is typically
691     * used when a graph is run in one-shot mode to compute a result.
692     */
693    public void waitUntilStop() {
694        mRunLoop.getStopCondition().block();
695    }
696
697    /**
698     * Pauses graph execution.
699     */
700    public void pause() {
701        mRunLoop.pushEvent(PAUSE_EVENT);
702    }
703
704    /**
705     * Resumes graph execution after pausing.
706     */
707    public void resume() {
708        mRunLoop.pushEvent(RESUME_EVENT);
709    }
710
711    /**
712     * Stops graph execution.
713     */
714    public void stop() {
715        mRunLoop.pushEvent(STOP_EVENT);
716    }
717
718    /**
719     * Returns whether the graph is currently being executed. A graph is considered to be running,
720     * even if it is paused or in the process of being stopped.
721     *
722     * @return true, if the graph is currently being executed.
723     */
724    public boolean isRunning() {
725        return !mRunLoop.checkState(State.STOPPED);
726    }
727
728    /**
729     * Returns whether the graph is currently paused.
730     *
731     * @return true, if the graph is currently paused.
732     */
733    public boolean isPaused() {
734        return mRunLoop.checkState(State.PAUSED);
735    }
736
737    /**
738     * Returns whether the graph is currently stopped.
739     *
740     * @return true, if the graph is currently stopped.
741     */
742    public boolean isStopped() {
743        return mRunLoop.checkState(State.STOPPED);
744    }
745
746    /**
747     * Sets the filter scheduling strategy. This method can not be called when the GraphRunner is
748     * running.
749     *
750     * @param strategy a constant specifying which scheduler strategy to use.
751     * @throws RuntimeException if the GraphRunner is running.
752     * @throws IllegalArgumentException if invalid strategy is specified.
753     * @see #getSchedulerStrategy()
754     */
755    public void setSchedulerStrategy(int strategy) {
756        if (isRunning()) {
757            throw new RuntimeException(
758                    "Attempting to change scheduling strategy on running " + "GraphRunner!");
759        }
760        createScheduler(strategy);
761    }
762
763    /**
764     * Returns the current scheduling strategy.
765     *
766     * @return the scheduling strategy used by this GraphRunner.
767     * @see #setSchedulerStrategy(int)
768     */
769    public int getSchedulerStrategy() {
770        return mScheduler.getStrategy();
771    }
772
773    /**
774     * Set whether or not the runner is verbose. When set to true, the runner will output individual
775     * scheduling steps that may help identify and debug problems in the graph structure. The
776     * default is false.
777     *
778     * @param isVerbose true, if the GraphRunner should log scheduling details.
779     * @see #isVerbose()
780     */
781    public void setIsVerbose(boolean isVerbose) {
782        synchronized (mParams) {
783            mParams.isVerbose = isVerbose;
784        }
785    }
786
787    /**
788     * Returns whether the GraphRunner is verbose.
789     *
790     * @return true, if the GraphRunner logs scheduling details.
791     * @see #setIsVerbose(boolean)
792     */
793    public boolean isVerbose() {
794        synchronized (mParams) {
795            return mParams.isVerbose;
796        }
797    }
798
799    /**
800     * Returns whether Filters of this GraphRunner can use OpenGL.
801     *
802     * Filters may use OpenGL if the MffContext supports OpenGL and the GraphRunner allows it.
803     *
804     * @return true, if Filters are allowed to use OpenGL.
805     */
806    public boolean isOpenGLSupported() {
807        return mRunLoop.isOpenGLAllowed() && mContext.isOpenGLSupported();
808    }
809
810    /**
811     * Enable flushing all frames from the graph when running completes.
812     *
813     * If this is set to false, then frames may remain in the pipeline even after running completes.
814     * The default value is true.
815     *
816     * @param flush true, if the GraphRunner should flush the graph when running completes.
817     * @see #flushOnClose()
818     */
819    public void setFlushOnClose(boolean flush) {
820        synchronized (mParams) {
821            mParams.flushOnClose = flush;
822        }
823    }
824
825    /**
826     * Returns whether the GraphRunner flushes frames when running completes.
827     *
828     * @return true, if the GraphRunner flushes frames when running completes.
829     * @see #setFlushOnClose(boolean)
830     */
831    public boolean flushOnClose() {
832        synchronized (mParams) {
833            return mParams.flushOnClose;
834        }
835    }
836
837    /**
838     * Sets the listener for receiving runtime events. A GraphRunner.Listener instance can be used
839     * to determine when certain events occur during graph execution (and react on them). See the
840     * {@link GraphRunner.Listener} class for details.
841     *
842     * @param listener the GraphRunner.Listener instance to set.
843     * @see #getListener()
844     */
845    public void setListener(Listener listener) {
846        synchronized (mParams) {
847            mParams.listener = listener;
848        }
849    }
850
851    /**
852     * Returns the currently assigned GraphRunner.Listener.
853     *
854     * @return the currently assigned GraphRunner.Listener instance.
855     * @see #setListener(Listener)
856     */
857    public Listener getListener() {
858        synchronized (mParams) {
859            return mParams.listener;
860        }
861    }
862
863    /**
864     * Returns the FrameManager that manages the runner's frames.
865     *
866     * @return the FrameManager instance that manages the runner's frames.
867     */
868    public FrameManager getFrameManager() {
869        return mFrameManager;
870    }
871
872    /**
873     * Tear down a GraphRunner and all its resources.
874     * <p>
875     * You must make sure that before calling this, no more graphs are attached to this runner.
876     * Typically, graphs are removed from runners when they are torn down.
877     *
878     * @throws IllegalStateException if there are still graphs attached to this runner.
879     */
880    public void tearDown() {
881        synchronized (mGraphs) {
882            if (!mGraphs.isEmpty()) {
883                throw new IllegalStateException("Attempting to tear down runner with "
884                        + mGraphs.size() + " graphs still attached!");
885            }
886        }
887        mRunLoop.pushEvent(KILL_EVENT);
888        // Wait for thread to complete, so that everything is torn down by the time we return.
889        try {
890            mRunThread.join();
891        } catch (InterruptedException e) {
892            Log.e("GraphRunner", "Error waiting for runner thread to finish!");
893        }
894    }
895
896    /**
897     * Release all frames managed by this runner.
898     * <p>
899     * Note, that you must make sure no graphs are attached to this runner before calling this
900     * method, as otherwise Filters in the graph may reference frames that are now released.
901     *
902     * TODO: Eventually, this method should be removed. Instead we should have better analysis
903     * that catches leaking frames from filters.
904     *
905     * @throws IllegalStateException if there are still graphs attached to this runner.
906     */
907    public void releaseFrames() {
908        synchronized (mGraphs) {
909            if (!mGraphs.isEmpty()) {
910                throw new IllegalStateException("Attempting to release frames with "
911                        + mGraphs.size() + " graphs still attached!");
912            }
913        }
914        mRunLoop.pushEvent(RELEASE_FRAMES_EVENT);
915    }
916
917    // Core internal methods ///////////////////////////////////////////////////////////////////////
918    void attachGraph(FilterGraph graph) {
919        synchronized (mGraphs) {
920            mGraphs.add(graph);
921        }
922    }
923
924    void signalWakeUp() {
925        mRunLoop.pushWakeEvent(STEP_EVENT);
926    }
927
928    void begin() {
929        mRunLoop.pushEvent(BEGIN_EVENT);
930    }
931
932    /** Like pause(), but closes all filters. Can be resumed using restart(). */
933    void halt() {
934        mRunLoop.pushEvent(HALT_EVENT);
935    }
936
937    /** Resumes a previously halted runner, and restores it to its non-halted state. */
938    void restart() {
939        mRunLoop.pushEvent(RESTART_EVENT);
940    }
941
942    /**
943     * Tears down the specified graph.
944     *
945     * The graph must be attached to this runner.
946     */
947    void tearDownGraph(FilterGraph graph) {
948        if (graph.getRunner() != this) {
949            throw new IllegalArgumentException("Attempting to tear down graph with foreign "
950                    + "GraphRunner!");
951        }
952        mRunLoop.pushEvent(Event.TEARDOWN, graph);
953        synchronized (mGraphs) {
954            mGraphs.remove(graph);
955        }
956    }
957
958    /**
959     * Remove all frames that are waiting to be processed.
960     *
961     * Removes and releases frames that are waiting in the graph connections of the currently
962     * halted graphs, i.e. frames that are waiting to be processed. This does not include frames
963     * that may be held or cached by filters themselves.
964     *
965     * TODO: With the new sub-graph architecture, this can now be simplified and made public.
966     * It can then no longer rely on opened graphs, and instead flush a graph and all its
967     * sub-graphs.
968     */
969    void flushFrames() {
970        mRunLoop.pushEvent(FLUSH_EVENT);
971    }
972
973    // Private methods /////////////////////////////////////////////////////////////////////////////
974    private void init(Config config) {
975        mFrameManager = new FrameManager(this, FrameManager.FRAME_CACHE_LRU);
976        createScheduler(STRATEGY_LRU);
977        mRunLoop = new GraphRunLoop(config.allowOpenGL);
978        mRunThread = new Thread(mRunLoop);
979        mRunThread.setPriority(config.threadPriority);
980        mRunThread.start();
981        mContext.addRunner(this);
982    }
983
984    private void createScheduler(int strategy) {
985        switch (strategy) {
986            case STRATEGY_LRU:
987                mScheduler = new LruScheduler();
988                break;
989            case STRATEGY_LFU:
990                mScheduler = new LfuScheduler();
991                break;
992            case STRATEGY_ONESHOT:
993                mScheduler = new OneShotScheduler();
994                break;
995            default:
996                throw new IllegalArgumentException(
997                        "Unknown schedule-strategy constant " + strategy + "!");
998        }
999    }
1000
1001    // Called within the runner's thread
1002    private void onRunnerStopped(final Exception exception, final boolean closed) {
1003        mRunningGraph = null;
1004        synchronized (mParams) {
1005            if (mParams.listener != null) {
1006                getContext().postRunnable(new Runnable() {
1007                    @Override
1008                    public void run() {
1009                        if (exception == null) {
1010                            mParams.listener.onGraphRunnerStopped(GraphRunner.this);
1011                        } else {
1012                            mParams.listener.onGraphRunnerError(exception, closed);
1013                        }
1014                    }
1015                });
1016            } else if (exception != null) {
1017                Log.e("GraphRunner",
1018                        "Uncaught exception during graph execution! Stack Trace: ");
1019                exception.printStackTrace();
1020            }
1021        }
1022    }
1023}
1024