1 module awebview.gui.application;
2 
3 
4 import core.thread;
5 
6 import std.exception;
7 import std.file;
8 import std.concurrency;
9 import std.datetime;
10 
11 import awebview.wrapper.webcore;
12 import awebview.gui.resourceinterceptor;
13 import awebview.sound;
14 import awebview.gui.activity;
15 import awebview.gui.html;
16 import derelict.sdl2.sdl;
17 import derelict.sdl2.mixer;
18 import msgpack;
19 import carbon.utils;
20 import carbon.channel;
21 
22 
23 
24 struct ImmediateMessage 
25 {
26     string fromId;
27     string toId;
28     string type;
29     immutable(ubyte)[] data;
30 }
31 
32 
33 abstract class Application
34 {
35     this(string savedFileName)
36     {
37         _savedFileName = savedFileName;
38         if(exists(savedFileName))
39             _savedData = unpack!(ubyte[][string])(cast(ubyte[])std.file.read(savedFileName));
40 
41         _ch = channel!(ImmediateMessage);
42         _ownerTid = thisTid;
43     }
44 
45 
46     void onDestroy()
47     {
48         if(_savedData.length)
49             std.file.write(_savedFileName, pack(_savedData));
50     }
51 
52 
53     final
54     @property
55     ref ubyte[][string] savedData() pure nothrow @safe @nogc { return _savedData; }
56 
57     void addActivity(Activity activity);
58 
59     final
60     void opOpAssign(string op : "~")(Activity activity)
61     {
62         addActivity(activity);
63     }
64 
65 
66     bool hasActivity(string id);
67     Activity getActivity(string id);
68 
69     final
70     Activity opIndex(string id) { return getActivity(id); }
71 
72     int opApplyActivities(scope int delegate(Activity activity));
73 
74     void attachActivity(string id);
75     void detachActivity(string id);
76     void destroyActivity(string id);
77 
78     void runAtNextFrame(void delegate());
79 
80     void run();
81     bool isRunning() @property;
82 
83     void shutdown();
84 
85     final
86     @property
87     string exeDir() const
88     {
89         import std.path : dirName;
90         import std.file : thisExePath;
91 
92         return dirName(thisExePath);
93     }
94 
95 
96     static
97     void sendMessage(string fromId, string toId, string type, immutable(ubyte)[] data)
98     {
99         shared msg = ImmediateMessage(fromId, toId, type, data);
100         _ch.put(msg);
101         _ownerTid.send(EventNotification.init);
102     }
103 
104 
105     void onReceiveImmediateMessage(ImmediateMessage msg);
106 
107 
108   private:
109     ubyte[][string] _savedData;
110     string _savedFileName;
111 
112 
113     static shared typeof(channel!(ImmediateMessage)) _ch;
114     __gshared Tid _ownerTid;
115 
116     static struct EventNotification {}
117 }
118 
119 
120 class SDLApplication : Application
121 {
122     static immutable savedDataFileName = "saved.mpac";
123 
124     private
125     this()
126     {
127         super(savedDataFileName);
128         _soundManager = new shared SoundManager();
129     }
130 
131 
132     static
133     SDLApplication instance() @property
134     {
135         if(_instance is null){
136             DerelictSDL2.load();
137             DerelictSDL2Mixer.load();
138 
139             enforce(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) >= 0);
140 
141             {
142                 int flags = MIX_INIT_FLAC | MIX_INIT_MP3 | MIX_INIT_OGG;
143                 int ini = Mix_Init(flags);
144                 enforce((ini&flags) == flags, "Failed to init required .ogg and .mod\nMix_Init: " ~ to!string(Mix_GetError()));
145                 enforce(Mix_OpenAudio(44100, AUDIO_S16SYS, 2, 4096) >= 0);
146             }
147 
148             _instance = new SDLApplication();
149 
150             auto config = WebConfig();
151             config.additionalOptions ~= "--use-gl=desktop";
152             WebCore.initialize(config);
153             WebCore.instance.resourceInterceptor = new LocalResourceInterceptor(_instance, getcwd());
154         }
155 
156         return _instance;
157     }
158 
159 
160     final
161     shared(SoundManager) soundManager() pure nothrow @safe @nogc
162     {
163         return _soundManager;
164     }
165 
166 
167     override
168     void onDestroy()
169     {
170         while(_acts.length){
171             foreach(id, activity; _acts.maybeModified){
172                 activity.onDetach();
173                 activity.onDestroy();
174                 if(_acts[id] is activity)
175                     _acts.remove(id);
176             }
177         }
178 
179         while(_detachedActs.length){
180             foreach(id, activity; _detachedActs.maybeModified){
181                 activity.onDestroy();
182                 if(_detachedActs[id] is activity)
183                     _detachedActs.remove(id);
184             }
185         }
186 
187         super.onDestroy();
188         Mix_CloseAudio();
189         Mix_Quit();
190         SDL_Quit();
191     }
192 
193 
194     auto newFactoryOf(A)(WebPreferences pref)
195     if(is(A : Activity))
196     {
197         auto session = WebCore.instance.createWebSession(WebString(""), pref);
198         auto factory = A.factory();
199         factory.webSession = session;
200         return factory;
201     }
202 
203 
204     override
205     void addActivity(Activity act)
206     in {
207       assert(typeid(act) == typeid(SDLActivity));
208     }
209     body {
210         addActivity(cast(SDLActivity)act);
211     }
212 
213 
214     void addActivity(SDLActivity act)
215     in {
216         assert(act !is null);
217     }
218     body {
219         _acts[act.id] = act;
220 
221         if(_isRunning){
222             act.onStart(this);
223             act.onAttach();
224         }
225     }
226 
227 
228     SDLPopupActivity initPopup(WebPreferences pref)
229     {
230         alias A = typeof(return);
231 
232         A act;
233         with(this.newFactoryOf!A(pref)){
234             index = 0;
235             act = newInstance;
236             this.addActivity(act);
237         }
238         this.detachActivity(act.id);
239         _popupRoot = act;
240         return act;
241     }
242 
243 
244     final
245     @property
246     auto activities(this This)() pure nothrow @safe @nogc
247     {
248         static struct Result{
249             auto opIndex(string id) { return _app.getActivity(id); }
250             auto opIndex(uint windowID) { return _app.getActivity(windowID); }
251             auto opIndex(SDL_Window* sdlWindow) { return _app.getActivity(sdlWindow); }
252 
253             auto opBinaryRight(string op : "in")(string id)
254             {
255                 if(auto p = id in _app._acts)
256                     return p;
257                 else if(auto p = id in _app._detachedActs)
258                     return p;
259                 else
260                     return null;
261             }
262 
263           private:
264             This _app;
265         }
266 
267         return Result(this);
268     }
269 
270 
271     final override
272     bool hasActivity(string id)
273     {
274         if(auto p = id in _acts)
275             return true;
276         else if(auto p = id in _detachedActs)
277             return true;
278         else
279             return false;
280     }
281 
282 
283     final
284     SDLActivity getActivity(uint windowID)
285     {
286         foreach(k, a; _acts)
287             if(a.windowID == windowID)
288                 return a;
289 
290         foreach(k, a; _detachedActs)
291             if(a.windowID == windowID)
292                 return a;
293 
294         return null;
295     }
296 
297 
298     final override
299     SDLActivity getActivity(string id)
300     {
301         return _acts.get(id, _detachedActs.get(id, null));
302     }
303 
304 
305     final
306     SDLActivity getActivity(SDL_Window* sdlWind)
307     {
308         foreach(k, a; _acts)
309             if(a.sdlWindow == sdlWind)
310                 return a;
311 
312         foreach(k, a; _detachedActs)
313             if(a.sdlWindow == sdlWind)
314                 return a;
315 
316         return null;
317     }
318 
319 
320     final override
321     int opApplyActivities(scope int delegate(Activity activity) dg)
322     {
323         foreach(k, ref e; _acts)
324             if(auto res = dg(e))
325                 return res;
326 
327         foreach(k, ref e; _detachedActs)
328             if(auto res = dg(e))
329                 return res;
330 
331         return 0;
332     }
333 
334 
335     final
336     @property
337     SDLPopupActivity popupActivity()
338     {
339         return _popupRoot;
340     }
341 
342 
343     override
344     void attachActivity(string id)
345     {
346         if(id in _acts)
347             return;
348 
349         auto act = _detachedActs[id];
350         if(_isRunning) act.onAttach();
351         _detachedActs.remove(id);
352         _acts[id] = act;
353     }
354 
355 
356     override
357     void detachActivity(string id)
358     {
359         if(id in _detachedActs)
360             return;
361 
362         auto act = _acts[id];
363         if(_isRunning) act.onDetach();
364         _acts.remove(id);
365         _detachedActs[id] = act;
366     }
367 
368 
369     override
370     void destroyActivity(string id)
371     {
372         if(auto p = id in _acts){
373             auto act = *p;
374             act.onDetach();
375             act.onDestroy();
376             _acts.remove(id);
377         }else if(auto p = id in _detachedActs){
378             auto act = *p;
379             act.onDestroy();
380             _detachedActs.remove(id);
381         }else
382             enforce(0);
383     }
384 
385 
386     override
387     void runAtNextFrame(void delegate() dg)
388     {
389         _runNextFrame ~= dg;
390     }
391 
392 
393     override
394     void run()
395     {
396         _isRunning = true;
397 
398         auto wc = WebCore.instance;
399         wc.update();
400 
401         foreach(k, a; _acts.maybeModified){
402             a.onStart(this);
403             a.onAttach();
404         }
405 
406         foreach(k, a; _detachedActs.maybeModified){
407             a.onStart(this);
408         }
409 
410       LInf:
411         while(!_isShouldQuit)
412         {
413             foreach(e; _runNextFrame)
414                 e();
415 
416             _runNextFrame.length = 0;
417 
418             {
419                 SDL_Event event;
420                 while(SDL_PollEvent(&event)){
421                     onSDLEvent(&event);
422                     if(_isShouldQuit)
423                         break LInf;
424                 }
425             }
426 
427             foreach(k, a; _acts.maybeModified){
428                 a.onUpdate();
429 
430                 if(a.isShouldClosed)
431                     destroyActivity(a.id);
432 
433                 if(_isShouldQuit)
434                     break LInf;
435             }
436 
437             if(_acts.length == 0)
438                 shutdown();
439 
440             foreach(k, a; _detachedActs.maybeModified){
441                 if(a.isShouldClosed)
442                     destroyActivity(a.id);
443 
444                 if(_isShouldQuit)
445                     break LInf;
446             }
447 
448             //Thread.sleep(dur!"msecs"(5));
449             auto nowTime = Clock.currTime;
450             auto target = nowTime + dur!"msecs"(5);
451             while(target > nowTime
452                 && receiveTimeout(target - nowTime,
453                     (Application.EventNotification){
454                         if(auto p = Application._ch.pop!(ImmediateMessage)){
455                             auto msg = *p;
456                             onReceiveImmediateMessage(msg);
457                         }
458                     }
459                 )
460             )
461             {
462                 nowTime = Clock.currTime;
463             }
464 
465             wc.update();
466         }
467         _isRunning = false;
468 
469         shutdown();
470     }
471 
472 
473     override
474     @property
475     bool isRunning() { return _isRunning; }
476 
477 
478     override
479     void shutdown()
480     {
481         if(!_isShouldQuit && _isRunning)
482             _isShouldQuit = true;
483         else{
484             _isRunning = false;
485             _isShouldQuit = true;
486 
487             this.onDestroy();
488 
489             SDL_Quit();
490             WebCore.shutdown();
491         }
492     }
493 
494 
495     void onSDLEvent(const SDL_Event* event)
496     {
497         foreach(k, a; _acts.maybeModified)
498             a.onSDLEvent(event);
499 
500         switch(event.type)
501         {
502           case SDL_QUIT:
503             shutdown();
504             break;
505 
506           default:
507             break;
508         }
509     }
510 
511 
512 
513     override
514     void onReceiveImmediateMessage(ImmediateMessage msg)
515     {
516         foreach(k, e; _acts.maybeModified) e.onReceiveImmediateMessage(msg);
517         foreach(k, e; _detachedActs.maybeModified) e.onReceiveImmediateMessage(msg);
518     }
519 
520 
521   private:
522     SDLActivity[string] _acts;
523     SDLActivity[string] _detachedActs;
524     SDLPopupActivity _popupRoot;
525     bool _isRunning;
526     bool _isShouldQuit;
527     ubyte[][string] _savedData;
528 
529     void delegate()[] _runNextFrame;
530 
531     shared(SoundManager) _soundManager;
532 
533     static SDLApplication _instance;
534 }