1 /++
2 Runtime is a fundamental module for building two-dimensional games. It is he 
3 who connects to the window manager of the operating system, initializes the 
4 sound device and loads the necessary libraries for the game. Also, it is able 
5 to store program arguments in itself, in order to pass them later to 
6 other functions.
7 
8 How do I create a runtime?:
9 Creating a runtime is quick and easy. There is a class, it has a static 
10 method $(HREF ../tida/runtime/ITidaRuntime.initialize.html, initialize), which allocates memory, and then calls the internal 
11 functions of the object to execute its functions. This is done 
12 in the following way:
13 ---
14 import tida;
15 
16 int main(string[] args)
17 {
18     TidaRuntime.initialize(args);
19 
20     return 0;
21 }
22 ---
23 
24 As you can see, the program arguments are passed to the function. 
25 This is necessary in order to later use them outside the main function.
26 
27 Also, the second parameter can be a list of libraries to load. 
28 All kinds of libraries that runtime can load are described in $(HREF #OpenAL, here).
29 ---
30 import tida;
31 
32 int main(string[] args)
33 {
34     TidaRuntime.initialize(args, [FreeType, OpenAL]);
35 
36     return 0;
37 }
38 ---
39 
40 Or you can use $(LREF AllLibrary) to say that you need to load everything, 
41 or $(LREF WithoutLibrary) that you need to load nothing.
42 
43 Next, to take advantage of the runtime, you can refer to the $(LREF runtime) function, 
44 which will give the runtime object as long as you can do something. 
45 All actions are described in $(HREF ../tida/runtime/ITidaRuntime.html, ITidaRuntime).
46 
47 Macros:
48     LREF = <a href="#$1">$1</a>
49     HREF = <a href="$1">$2</a>
50     OBJECTREF = <a href="https://dlang.org/library/object.html#$1">$1</a>
51 
52 Authors: $(HREF https://github.com/TodNaz, TodNaz)
53 Copyright: Copyright (c) 2020 - 2021, TodNaz.
54 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE, MIT)
55 +/
56 module tida.runtime;
57 
58 __gshared TidaRuntime _runtimeObject;
59 
60 /++
61 Global access to runtime. An object can be called from anywhere, 
62 but cannot be replaced.
63 +/
64 @property TidaRuntime runtime() @trusted
65 {
66     return _runtimeObject;
67 }
68 
69 enum : int
70 {
71     OpenAL = 0, /// Open Audio Library
72     FreeType, /// Free Type Library
73     GLX, /// Graphics library X11
74     EGL /// EGL library wayland
75 }
76 
77 alias LibraryUnite = int; /// 
78 
79 /++
80 An array of library indexes. Indicates that all libraries should be loaded, 
81 when specified in the 
82 $(HREF ../tida/runtime/ITidaRuntime.initialize.html, initialization) of the runtime.
83 +/
84 enum AllLibrary = [OpenAL, FreeType, GLX];
85 
86 /++
87 An array of only important libraries. Loads only the necessary libraries 
88 (for connecting to the window manager) when specified in the 
89 $(HREF ../tida/runtime/ITidaRuntime.initialize.html, initialization) of the runtime.
90 +/
91 enum WithoutLibrary  = [GLX];
92 
93 /++
94 The interface of interaction between the program and the window manager.
95 +/
96 interface ITidaRuntime
97 {
98     import tida.sound : Device;
99 
100 @safe:
101     /++
102     A function for loading external libraries that are needed when implementing
103     internal functions.
104 
105     Params:
106         libs =  What external libraries need to be loaded. 
107                 (`AllLibrary` to load all external libraries,
108                  `WithoutLibrary` in order not to load unnecessary 
109                  external libraries.)
110 
111     Throws:
112     $(OBJECTREF Exception) If the libraries were not installed on the machine 
113     being started or they were damaged.
114     +/
115     void loadExternalLibraries(LibraryUnite[] libs);
116 
117     /++
118     Connects to the window manager.
119 
120     Throws:
121     $(OBJECTREF Exception) If the libraries were not installed on the machine 
122     being started or they were damaged. And also if the connection to the 
123     window manager was not successful (for example, there is no such component 
124     in the OS or the connection to an unknown window manager is not supported).
125     +/
126     void connectToWndMng();
127 
128     /++
129     Closes the session with the window manager.
130     +/
131     void closeWndMngSession();
132 
133     /++
134     Arguments given to the program.
135     +/
136     @property string[] mainArguments();
137 
138     /++
139     Accepts program arguments for subsequent operations with them.
140     +/
141     void acceptArguments(string[] arguments);
142 
143     @property Device device() @safe;
144 
145     uint[2] monitorSize();
146 
147 @trusted:
148     /++
149     Static function to initialize the runtime. Allocates memory for runtime and 
150     executes its functions of accepting arguments, loading the necessary 
151     libraries and connecting to the window manager through its 
152     interface functions.
153     
154     An example of how to load individual libraries:
155     ---
156     import tida.runtime;
157     
158     int main(string[] args)
159     {
160         TidaRuntime.initialize(args, [FreeType, OpenAL]);
161         
162         return 0;
163     }
164     ---
165     
166     Params:
167         arguments = Arguments that were nested in the main function. They will 
168                     be stored in the runtime memory from where they can be read. 
169                     Rantheim doesn't read such arguments.
170         libs      = List of libraries to download. Please note that this does 
171                     not affect libraries that the framework does not use. 
172                     Notable libraries are listed 
173                     $(HREF ../runtime.html#EGL, here).
174                     
175     Throws: $(OBJECTREF Exception) if during initialization the libraries 
176     were not loaded or the runner could not connect to the window manager.
177     +/
178     static final void initialize(   string[] arguments  = [], 
179                                     LibraryUnite[] libs = AllLibrary)
180     {
181         _runtimeObject = new TidaRuntime();
182         _runtimeObject.acceptArguments(arguments);
183         _runtimeObject.loadExternalLibraries(libs);
184         _runtimeObject.connectToWndMng();
185     }
186 }
187 
188 /++
189 The interface of interaction between the program and the window manager.
190 +/
191 version (Posix)
192 class TidaRuntime : ITidaRuntime
193 {
194     import x11.X, x11.Xlib, x11.Xutil;
195     import std.exception : enforce;
196     import tida.sound : initSoundlibrary, Device;
197     import tida.text : initFontLibrary;
198 
199     enum SessionType
200     {
201         unknown,
202         x11,
203         wayland
204     }
205 
206     enum SessionWarning =
207     "WARNING! The session is not defined. Perhaps she simply does not exist?";
208 
209 private:
210     Display* _display;
211     int _displayID;
212     string[] arguments;
213     SessionType _session;
214     Device _device;
215 
216 public @trusted:
217     this()
218     {
219         import std.process;
220         import std.stdio : stderr, writeln;
221 
222         auto env = environment.get("XDG_SESSION_TYPE");
223         if (env == "x11")
224         {
225             _session = SessionType.x11;
226         } else
227         if (env == "wayland")
228         {
229             _session = SessionType.wayland;
230         } else
231         {
232             _session = SessionType.unknown;
233             stderr.writeln(SessionWarning);
234         }
235 
236     }
237 
238     override @property Device device()
239     {
240         return _device;
241     }
242 
243     override void loadExternalLibraries(LibraryUnite[] libs)
244     {
245         import dglx.glx : loadGLXLibrary;
246 
247         foreach (e; libs)
248         {
249             if (e == OpenAL)
250             {
251                 initSoundlibrary();
252                 _device = new Device();
253                 _device.open();
254             }
255             else
256             if (e == FreeType)
257                 initFontLibrary();
258             else
259             if (e == GLX)
260             {
261                 if (_session == SessionType.x11)
262                     loadGLXLibrary();
263                 else
264                 	throw new Exception("[Wayland] The creation of runtime via wayland is not implemented.");
265             }
266         }
267     }
268 
269     override void connectToWndMng()
270     {
271         this._display = XOpenDisplay(null);
272         enforce!Exception(this._display,
273         "Failed to connect to window manager.");
274 
275         this._displayID = DefaultScreen(this._display);
276     }
277 
278     override void closeWndMngSession()
279     {
280         XCloseDisplay(this._display);
281         this._display = null;
282         this._displayID = 0;
283     }
284 
285     override void acceptArguments(string[] arguments)
286     {
287         this.arguments = arguments;
288     }
289 
290     @property override string[] mainArguments()
291     {
292         return this.arguments;
293     }
294     
295     @property Window rootWindow()
296     {
297         return RootWindow(_display, _displayID);
298     }
299     
300     uint[2] monitorSize()
301     {
302         auto screen = ScreenOfDisplay(_display, 0);
303         return [screen.width, screen.height];
304     }
305 
306 @safe:
307     /// An instance for contacting the manager's server.
308     @property Display* display()
309     {
310         return this._display;
311     }
312 
313     /// Default screen number
314     @property int displayID()
315     {
316         return this._displayID;
317     }
318 
319     ~this()
320     {
321         closeWndMngSession();
322     }
323 }
324 
325 /++
326 Required to enable high performance on devices with nvidia optimus.
327 
328 If necessary, include the "WitoutHighNvidiaOptimus" version in the flags.
329 
330 See_Also:
331     https://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf
332 +/
333 version (Windows)
334 {
335     version (WitoutHighNvidiaOptimus)
336     {
337         // Nothing
338     } else
339     {
340         extern(C) 
341         {
342             export ulong NvOptimusEnablement = 0x00000001;
343         }
344     }
345 }
346 
347 version (Windows)
348 class TidaRuntime : ITidaRuntime
349 {
350     import core.sys.windows.windows;
351     import std.exception : enforce;
352     import tida.sound : initSoundlibrary, Device;
353     import tida.text : initFontLibrary;
354 
355     pragma(lib, "opengl32.lib");
356     pragma(lib, "winmm.lib");
357 
358 private:
359     HINSTANCE hInstance;
360     string[] arguments;
361     Device _device;
362 
363 public @trusted:
364     override void loadExternalLibraries(LibraryUnite[] libs)
365     {
366         foreach (e; libs)
367         {
368             if (e == OpenAL)
369             {
370                 initSoundlibrary();
371                 _device = new Device();
372                 _device.open();
373             }
374             else
375             if (e == FreeType)
376                 initFontLibrary();
377         }
378     }
379 
380     override Device device() @safe
381     {
382         return _device;
383     }
384 
385     override void connectToWndMng()
386     {
387         this.hInstance = GetModuleHandle(null);
388         debug {} else
389             ShowWindow(GetConsoleWindow(), SW_HIDE);
390 
391         enforce!Exception(this.hInstance, 
392         "Failed to connect to window manager.");
393     }
394 
395     override void closeWndMngSession()
396     {
397         this.hInstance = null;
398     }
399 
400     override void acceptArguments(string[] arguments)
401     {
402         this.arguments = arguments;
403     }
404     
405     override uint[2] monitorSize()
406     {
407         HDC hScreenDC = GetDC(GetDesktopWindow());
408         int width = GetDeviceCaps(hScreenDC, HORZRES);
409         int height = GetDeviceCaps(hScreenDC, VERTRES);
410         ReleaseDC(GetDesktopWindow(), hScreenDC);
411         
412         return [width, height];
413     }
414 
415     @property override string[] mainArguments()
416     {
417         return this.arguments;
418     }
419 
420 @safe:
421     @property HINSTANCE instance()
422     {
423         return this.hInstance;
424     }
425 }