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 }