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 }