1 /**
2     Application
3 
4     Copyright: (c) Enalye 2017
5     License: Zlib
6     Authors: Enalye
7 */
8 
9 module atelier.common.application;
10 
11 import bindbc.sdl;
12 
13 import core.thread;
14 import std.datetime;
15 
16 import atelier.core;
17 import atelier.render;
18 import atelier.ui;
19 
20 import atelier.common.event;
21 import atelier.common.settings;
22 import atelier.common.resource;
23 
24 alias ApplicationUpdate = void function(float);
25 
26 private {
27     float _deltatime = 1f;
28     float _currentFps;
29     long _tickStartFrame;
30 
31     bool _isChildGrabbed;
32     uint _idChildGrabbed;
33     GuiElement[] _children;
34 
35     bool _isInitialized;
36     uint _nominalFPS = 60u;
37 
38     ApplicationUpdate[] _applicationUpdates;
39 }
40 
41 /// Actual framerate divided by the nominal framerate
42 /// 1 if the same, less if the application slow down,
43 /// more if the application runs too quickly.
44 float getDeltatime() {
45     return _deltatime;
46 }
47 
48 /// Actual framerate of the application.
49 float getCurrentFPS() {
50     return _currentFps;
51 }
52 
53 /// Maximum framerate of the application. \
54 /// The deltatime is equal to 1 if the framerate is exactly that.
55 uint getNominalFPS() {
56     return _nominalFPS;
57 }
58 /// Ditto
59 uint setNominalFPS(uint fps) {
60     return _nominalFPS = fps;
61 }
62 
63 /// Application startup
64 void createApplication(Vec2i size, string title = "Atelier") {
65     if (_isInitialized)
66         throw new Exception("The application cannot be run twice.");
67     _isInitialized = true;
68     createWindow(size, title);
69     initializeEvents();
70     initFont();
71     _tickStartFrame = Clock.currStdTime();
72 }
73 
74 /// Main application loop
75 void runApplication() {
76     if (!_isInitialized)
77         throw new Exception("Cannot run the application.");
78 
79     while (processEvents()) {
80         updateEvents(_deltatime);
81         foreach (applicationUpdate; _applicationUpdates) {
82             applicationUpdate(_deltatime);
83         }
84         processModalBack();
85         processOverlayBack();
86         updateRoots(_deltatime);
87         drawRoots();
88         processOverlayFront(_deltatime);
89         renderWindow();
90         endOverlay();
91 
92         long deltaTicks = Clock.currStdTime() - _tickStartFrame;
93         if (deltaTicks < (10_000_000 / _nominalFPS))
94             Thread.sleep(dur!("hnsecs")((10_000_000 / _nominalFPS) - deltaTicks));
95 
96         deltaTicks = Clock.currStdTime() - _tickStartFrame;
97         _deltatime = (cast(float)(deltaTicks) / 10_000_000f) * _nominalFPS;
98         _currentFps = (_deltatime == .0f) ? .0f : (10_000_000f / cast(float)(deltaTicks));
99         _tickStartFrame = Clock.currStdTime();
100     }
101 }
102 
103 /// Cleanup and kill the application
104 void destroyApplication() {
105     destroyEvents();
106     destroyWindow();
107 }
108 
109 /// Add a callback function called for each game loop
110 void addApplicationUpdate(ApplicationUpdate applicationUpdate) {
111     _applicationUpdates ~= applicationUpdate;
112 }