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