1 /**
2  * ae.ui.video.threadedvideo
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.ui.video.threadedvideo;
15 
16 import core.thread;
17 
18 import ae.ui.video.video;
19 import ae.ui.app.application;
20 import ae.ui.video.renderer;
21 
22 class ThreadedVideo : Video
23 {
24 	this()
25 	{
26 		starting = false;
27 		renderThread = new Thread(&renderThreadProc);
28 		renderThread.start();
29 	}
30 
31 	override void shutdown()
32 	{
33 		stopping = quitting = true;
34 		renderThread.join();
35 	}
36 
37 	override void start(Application application)
38 	{
39 		initMain(application);
40 
41 		if (!initializeVideoInRenderThread)
42 			initVary();
43 
44 		renderCallback.bind(&application.render);
45 
46 		started = stopping = false;
47 		starting = true;
48 		while (!started) wait();
49 	}
50 
51 	override void stop()
52 	{
53 		stopped = false;
54 		stopping = true;
55 		while (!stopped) wait();
56 
57 		if (!initializeVideoInRenderThread)
58 			doneVary();
59 		doneMain();
60 	}
61 
62 	override void stopAsync(AppCallback callback)
63 	{
64 		stopCallback = callback;
65 		stopped = false;
66 		stopping = true;
67 	}
68 
69 protected:
70 	/// Allows varying the thread from which initVary gets called.
71 	abstract @property bool initializeVideoInRenderThread();
72 
73 	/// When initializing video in the render thread, block main thread while video is initializing?
74 	/// Some renderers may require the main thread to be responsive during graphics initialization,
75 	/// to pump events - thus, initialization must be asynchronous.
76 	@property bool initializeVideoSynchronously() { return true; }
77 
78 	abstract Renderer getRenderer();
79 
80 	/// Main thread initialization.
81 	abstract void initMain(Application application);
82 
83 	/// Main/render thread initialization (depends on initializeVideoInRenderThread).
84 	abstract void initVary();
85 
86 	/// Main thread finalization.
87 	abstract void doneMain();
88 
89 	/// Main/render thread finalization (depends on initializeVideoInRenderThread).
90 	abstract void doneVary();
91 
92 private:
93 	final void wait()
94 	{
95 		if (error)
96 			renderThread.join(); // collect exception
97 		nop();
98 	}
99 
100 	final void nop() { Thread.sleep(msecs(1)); }
101 
102 	Thread renderThread;
103 	shared bool starting, started, stopping, stopped, quitting, quit, error;
104 	AppCallback stopCallback;
105 	AppCallbackEx!(Renderer) renderCallback;
106 
107 	final void renderThreadProc()
108 	{
109 		scope(failure) error = true;
110 
111 		// SDL expects that only one thread across the program's
112 		// lifetime will do OpenGL initialization.
113 		// Thus, re-initialization must happen from only one thread.
114 		// This thread sleeps and polls while it's not told to run.
115 	outer:
116 		while (!quitting)
117 		{
118 			while (!starting)
119 			{
120 				// TODO: use proper semaphores
121 				if (quitting) return;
122 				nop();
123 			}
124 
125 			if (initializeVideoInRenderThread &&  initializeVideoSynchronously)
126 				initVary();
127 
128 			started = true; starting = false;
129 			scope(failure) if (errorCallback) try { errorCallback.call(); } catch (Exception) {}
130 
131 			if (initializeVideoInRenderThread && !initializeVideoSynchronously)
132 				initVary();
133 
134 			auto renderer = getRenderer();
135 
136 			while (!stopping)
137 			{
138 				// TODO: predict flip (vblank wait) duration and render at the last moment
139 				renderCallback.call(renderer);
140 				renderer.present();
141 			}
142 			renderer.shutdown();
143 
144 			if (initializeVideoInRenderThread)
145 				doneVary();
146 
147 			if (stopCallback)
148 				stopCallback.call();
149 
150 			stopped = true; stopping = false;
151 		}
152 	}
153 }