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