added counting active API threads
[rocksndiamonds.git] / src / libgame / system.c
index 702a86bc05e2f0881638fef4c38f0ef992b13644..3eb9c62d55dc4e42d32de21e5459c876d7542f3d 100644 (file)
@@ -4,7 +4,7 @@
 // (c) 1995-2014 by Artsoft Entertainment
 //                         Holger Schemel
 //                 info@artsoft.org
-//                 http://www.artsoft.org/
+//                 https://www.artsoft.org/
 // ----------------------------------------------------------------------------
 // system.c
 // ============================================================================
@@ -30,6 +30,7 @@
 
 struct ProgramInfo     program;
 struct NetworkInfo     network;
+struct RuntimeInfo     runtime;
 struct OptionInfo      options;
 struct VideoSystemInfo video;
 struct AudioSystemInfo audio;
@@ -39,6 +40,7 @@ struct OverlayInfo    overlay;
 struct ArtworkInfo     artwork;
 struct JoystickInfo    joystick;
 struct SetupInfo       setup;
+struct UserInfo                user;
 
 LevelDirTree          *leveldir_first_all = NULL;
 LevelDirTree          *leveldir_first = NULL;
@@ -55,9 +57,8 @@ DrawBuffer           *drawto = NULL;
 int                    button_status = MB_NOT_PRESSED;
 boolean                        motion_status = FALSE;
 int                    wheel_steps = DEFAULT_WHEEL_STEPS;
-#if defined(TARGET_SDL2)
 boolean                        keyrepeat_status = TRUE;
-#endif
+boolean                        textinput_status = FALSE;
 
 int                    redraw_mask = REDRAW_NONE;
 
@@ -79,7 +80,7 @@ void InitProgramInfo(char *argv0, char *config_filename, char *userdata_subdir,
   program.config_filename = config_filename;
 
   program.userdata_subdir = userdata_subdir;
-  program.userdata_path = getUserGameDataDir();
+  program.userdata_path = getMainUserGameDataDir();
 
   program.program_title = program_title;
   program.window_title = "(undefined)";
@@ -102,6 +103,8 @@ void InitProgramInfo(char *argv0, char *config_filename, char *userdata_subdir,
   program.log_file[LOG_OUT_ID] = program.log_file_default[LOG_OUT_ID] = stdout;
   program.log_file[LOG_ERR_ID] = program.log_file_default[LOG_ERR_ID] = stderr;
 
+  program.api_thread_count = 0;
+
   program.headless = FALSE;
 }
 
@@ -114,33 +117,20 @@ void InitNetworkInfo(boolean enabled, boolean connected, boolean serveronly,
 
   network.server_host = server_host;
   network.server_port = server_port;
+
+  network.server_thread = NULL;
+  network.is_server_thread = FALSE;
 }
 
-void InitScoresInfo(void)
+void InitRuntimeInfo()
 {
-  char *global_scores_dir = getPath2(getCommonDataDir(), SCORES_DIRECTORY);
-
-  program.global_scores = directoryExists(global_scores_dir);
-  program.many_scores_per_name = !program.global_scores;
-
-#if 0
-  if (options.debug)
-  {
-    if (program.global_scores)
-    {
-      Error(ERR_DEBUG, "Using global, multi-user scores directory '%s'.",
-           global_scores_dir);
-      Error(ERR_DEBUG, "Remove to enable single-user scores directory.");
-      Error(ERR_DEBUG, "(This enables multipe score entries per user.)");
-    }
-    else
-    {
-      Error(ERR_DEBUG, "Using private, single-user scores directory.");
-    }
-  }
+#if defined(HAS_TOUCH_DEVICE)
+  runtime.uses_touch_device = TRUE;
+#else
+  runtime.uses_touch_device = FALSE;
 #endif
 
-  free(global_scores_dir);
+  runtime.use_api_server = setup.use_api_server;
 }
 
 void SetWindowTitle(void)
@@ -174,19 +164,17 @@ void InitExitFunction(void (*exit_function)(int))
 
 void InitPlatformDependentStuff(void)
 {
+  InitEmscriptenFilesystem();
+
   // this is initialized in GetOptions(), but may already be used before
   options.verbose = TRUE;
 
   OpenLogFiles();
 
-#if defined(TARGET_SDL2)
-  int sdl_init_flags = SDL_INIT_EVENTS      | SDL_INIT_NOPARACHUTE;
-#else
-  int sdl_init_flags = SDL_INIT_EVENTTHREAD | SDL_INIT_NOPARACHUTE;
-#endif
+  int sdl_init_flags = SDL_INIT_EVENTS | SDL_INIT_NOPARACHUTE;
 
   if (SDL_Init(sdl_init_flags) < 0)
-    Error(ERR_EXIT, "SDL_Init() failed: %s", SDL_GetError());
+    Fail("SDL_Init() failed: %s", SDL_GetError());
 
   SDLNet_Init();
 }
@@ -252,9 +240,7 @@ void InitGfxWindowInfo(int win_xsize, int win_ysize)
   {
     ReCreateBitmap(&gfx.background_bitmap, win_xsize, win_ysize);
 
-#if defined(TARGET_SDL2)
     ReCreateBitmap(&gfx.final_screen_bitmap, win_xsize, win_ysize);
-#endif
 
     ReCreateBitmap(&gfx.fade_bitmap_backup, win_xsize, win_ysize);
     ReCreateBitmap(&gfx.fade_bitmap_source, win_xsize, win_ysize);
@@ -319,6 +305,12 @@ void InitGfxCustomArtworkInfo(void)
 void InitGfxOtherSettings(void)
 {
   gfx.cursor_mode = CURSOR_DEFAULT;
+  gfx.cursor_mode_override = CURSOR_UNDEFINED;
+  gfx.cursor_mode_final = gfx.cursor_mode;
+
+  // prevent initially displaying custom mouse cursor in upper left corner
+  gfx.mouse_x = POS_OFFSCREEN;
+  gfx.mouse_y = POS_OFFSCREEN;
 }
 
 void InitTileCursorInfo(void)
@@ -336,34 +328,41 @@ void InitTileCursorInfo(void)
 
   tile_cursor.sx = 0;
   tile_cursor.sy = 0;
+
+  tile_cursor.xsn_debug = FALSE;
 }
 
 void InitOverlayInfo(void)
 {
-  int nr = GRID_ACTIVE_NR();
-  int x, y;
-
   overlay.enabled = FALSE;
   overlay.active = FALSE;
 
   overlay.show_grid = FALSE;
 
-  overlay.grid_xsize = setup.touch.grid_xsize[nr];
-  overlay.grid_ysize = setup.touch.grid_ysize[nr];
-
-  for (x = 0; x < MAX_GRID_XSIZE; x++)
-    for (y = 0; y < MAX_GRID_YSIZE; y++)
-      overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
-
   overlay.grid_button_highlight = CHAR_GRID_BUTTON_NONE;
   overlay.grid_button_action = JOY_NO_ACTION;
 
+  SetOverlayGridSizeAndButtons();
+
 #if defined(USE_TOUCH_INPUT_OVERLAY)
   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
     overlay.enabled = TRUE;
 #endif
 }
 
+void SetOverlayGridSizeAndButtons(void)
+{
+  int nr = GRID_ACTIVE_NR();
+  int x, y;
+
+  overlay.grid_xsize = setup.touch.grid_xsize[nr];
+  overlay.grid_ysize = setup.touch.grid_ysize[nr];
+
+  for (x = 0; x < MAX_GRID_XSIZE; x++)
+    for (y = 0; y < MAX_GRID_YSIZE; y++)
+      overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
+}
+
 void SetTileCursorEnabled(boolean enabled)
 {
   tile_cursor.enabled = enabled;
@@ -425,6 +424,11 @@ void SetOverlayShowGrid(boolean show_grid)
     SetOverlayEnabled(TRUE);
 }
 
+boolean GetOverlayEnabled(void)
+{
+  return overlay.enabled;
+}
+
 boolean GetOverlayActive(void)
 {
   return overlay.active;
@@ -536,9 +540,7 @@ void InitVideoDisplay(void)
     return;
 
   SDLInitVideoDisplay();
-#if defined(TARGET_SDL2)
   SDLSetDisplaySize();
-#endif
 }
 
 void CloseVideoDisplay(void)
@@ -564,6 +566,7 @@ void InitVideoBuffer(int width, int height, int depth, boolean fullscreen)
 
   video.window_scaling_available = WINDOW_SCALING_STATUS;
 
+  video.frame_counter = 0;
   video.frame_delay = 0;
   video.frame_delay_value = GAME_FRAME_DELAY;
 
@@ -810,7 +813,6 @@ void BlitBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap,
   src_x += dst_x - dst_x_unclipped;
   src_y += dst_y - dst_y_unclipped;
 
-#if defined(TARGET_SDL2)
   // !!! 2013-12-11: An "old friend" is back. Same bug in SDL2 2.0.1 !!!
   // !!! 2009-03-30: Fixed by using self-compiled, patched SDL.dll !!!
   /* (This bug still exists in the actual (as of 2009-06-15) version 1.2.13,
@@ -857,7 +859,6 @@ void BlitBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap,
 
     return;
   }
-#endif
 
   sysCopyArea(src_bitmap, dst_bitmap,
              src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
@@ -1093,23 +1094,12 @@ Pixel GetPixelFromRGBcompact(Bitmap *bitmap, unsigned int color)
 
 void KeyboardAutoRepeatOn(void)
 {
-#if defined(TARGET_SDL2)
   keyrepeat_status = TRUE;
-#else
-  SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY / 2,
-                     SDL_DEFAULT_REPEAT_INTERVAL / 2);
-  SDL_EnableUNICODE(1);
-#endif
 }
 
 void KeyboardAutoRepeatOff(void)
 {
-#if defined(TARGET_SDL2)
   keyrepeat_status = FALSE;
-#else
-  SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
-  SDL_EnableUNICODE(0);
-#endif
 }
 
 boolean SetVideoMode(boolean fullscreen)
@@ -1154,10 +1144,10 @@ Bitmap *LoadCustomImage(char *basename)
   Bitmap *new_bitmap;
 
   if (filename == NULL)
-    Error(ERR_EXIT, "LoadCustomImage(): cannot find file '%s'", basename);
+    Fail("LoadCustomImage(): cannot find file '%s'", basename);
 
   if ((new_bitmap = LoadImage(filename)) == NULL)
-    Error(ERR_EXIT, "LoadImage('%s') failed: %s", basename, GetError());
+    Fail("LoadImage('%s') failed", basename);
 
   return new_bitmap;
 }
@@ -1169,7 +1159,8 @@ void ReloadCustomImage(Bitmap *bitmap, char *basename)
 
   if (filename == NULL)                // (should never happen)
   {
-    Error(ERR_WARN, "ReloadCustomImage(): cannot find file '%s'", basename);
+    Warn("ReloadCustomImage(): cannot find file '%s'", basename);
+
     return;
   }
 
@@ -1184,16 +1175,19 @@ void ReloadCustomImage(Bitmap *bitmap, char *basename)
 
   if ((new_bitmap = LoadImage(filename)) == NULL)
   {
-    Error(ERR_WARN, "LoadImage('%s') failed: %s", basename, GetError());
+    Warn("LoadImage('%s') failed", basename);
+
     return;
   }
 
   if (bitmap->width != new_bitmap->width ||
       bitmap->height != new_bitmap->height)
   {
-    Error(ERR_WARN, "ReloadCustomImage: new image '%s' has wrong dimensions",
+    Warn("ReloadCustomImage: new image '%s' has wrong dimensions",
          filename);
+
     FreeBitmap(new_bitmap);
+
     return;
   }
 
@@ -1201,7 +1195,7 @@ void ReloadCustomImage(Bitmap *bitmap, char *basename)
   free(new_bitmap);
 }
 
-static Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
+Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
 {
   return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height);
 }
@@ -1210,14 +1204,31 @@ void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
 {
   if (bitmaps[IMG_BITMAP_CUSTOM])
   {
-    FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
+    // check if original sized bitmap points to custom sized bitmap
+    if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] == bitmaps[IMG_BITMAP_CUSTOM])
+    {
+      SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
+
+      // keep pointer of previous custom size bitmap
+      bitmaps[IMG_BITMAP_OTHER] = bitmaps[IMG_BITMAP_CUSTOM];
+
+      // set original bitmap pointer to scaled original bitmap of other size
+      bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_OTHER];
+
+      SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
+    }
+    else
+    {
+      FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
+    }
 
     bitmaps[IMG_BITMAP_CUSTOM] = NULL;
   }
 
   if (gfx.game_tile_size == gfx.standard_tile_size)
   {
-    bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
+    // set game bitmap pointer to standard sized bitmap (already existing)
+    bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_STANDARD];
 
     return;
   }
@@ -1226,10 +1237,10 @@ void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
   int width  = bitmap->width  * gfx.game_tile_size / gfx.standard_tile_size;;
   int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;;
 
-  Bitmap *bitmap_new = ZoomBitmap(bitmap, width, height);
+  bitmaps[IMG_BITMAP_CUSTOM] = ZoomBitmap(bitmap, width, height);
 
-  bitmaps[IMG_BITMAP_CUSTOM] = bitmap_new;
-  bitmaps[IMG_BITMAP_GAME]   = bitmap_new;
+  // set game bitmap pointer to custom sized bitmap (newly created)
+  bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
 }
 
 static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
@@ -1379,9 +1390,33 @@ static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
       bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0;
 
     if (bitmaps[IMG_BITMAP_CUSTOM])
-      bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
+      bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
     else
-      bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
+      bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_STANDARD];
+
+    // store the "final" (up-scaled) original bitmap, if not already stored
+
+    int tmp_bitmap_final_nr = -1;
+
+    for (i = 0; i < NUM_IMG_BITMAPS; i++)
+      if (bitmaps[i] == tmp_bitmap_final)
+       tmp_bitmap_final_nr = i;
+
+    if (tmp_bitmap_final_nr == -1)     // scaled original bitmap not stored
+    {
+      // store pointer of scaled original bitmap (not used for any other size)
+      bitmaps[IMG_BITMAP_OTHER] = tmp_bitmap_final;
+
+      // set original bitmap pointer to scaled original bitmap of other size
+      bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_OTHER];
+    }
+    else
+    {
+      // set original bitmap pointer to corresponding sized bitmap
+      bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[tmp_bitmap_final_nr];
+    }
+
+    // free the "old" (unscaled) original bitmap, if not already stored
 
     boolean free_old_bitmap = TRUE;
 
@@ -1401,6 +1436,12 @@ static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
   else
   {
     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
+
+    // set original bitmap pointer to corresponding sized bitmap
+    bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_32x32];
+
+    if (old_bitmap != tmp_bitmap_1)
+      FreeBitmap(old_bitmap);
   }
 
   UPDATE_BUSY_STATE();
@@ -1416,12 +1457,18 @@ void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor,
 
 void CreateBitmapTextures(Bitmap **bitmaps)
 {
-  SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
+  if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL)
+    SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
+  else
+    SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
 }
 
 void FreeBitmapTextures(Bitmap **bitmaps)
 {
-  SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
+  if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL)
+    SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
+  else
+    SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
 }
 
 void ScaleBitmap(Bitmap **bitmaps, int zoom_factor)
@@ -1562,6 +1609,7 @@ void SetMouseCursor(int mode)
   static struct MouseCursorInfo *cursor_none = NULL;
   static struct MouseCursorInfo *cursor_playfield = NULL;
   struct MouseCursorInfo *cursor_new;
+  int mode_final = mode;
 
   if (cursor_none == NULL)
     cursor_none = get_cursor_from_image(cursor_image_none);
@@ -1569,13 +1617,39 @@ void SetMouseCursor(int mode)
   if (cursor_playfield == NULL)
     cursor_playfield = get_cursor_from_image(cursor_image_playfield);
 
-  cursor_new = (mode == CURSOR_DEFAULT   ? NULL :
-               mode == CURSOR_NONE      ? cursor_none :
-               mode == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
+  if (gfx.cursor_mode_override != CURSOR_UNDEFINED)
+    mode_final = gfx.cursor_mode_override;
+
+  cursor_new = (mode_final == CURSOR_DEFAULT   ? NULL :
+               mode_final == CURSOR_NONE      ? cursor_none :
+               mode_final == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
 
   SDLSetMouseCursor(cursor_new);
 
   gfx.cursor_mode = mode;
+  gfx.cursor_mode_final = mode_final;
+}
+
+void UpdateRawMousePosition(int mouse_x, int mouse_y)
+{
+  // mouse events do not contain logical screen size corrections yet
+  SDLCorrectRawMousePosition(&mouse_x, &mouse_y);
+
+  mouse_x -= video.screen_xoffset;
+  mouse_y -= video.screen_yoffset;
+
+  gfx.mouse_x = mouse_x;
+  gfx.mouse_y = mouse_y;
+}
+
+void UpdateMousePosition(void)
+{
+  int mouse_x, mouse_y;
+
+  SDL_PumpEvents();
+  SDL_GetMouseState(&mouse_x, &mouse_y);
+
+  UpdateRawMousePosition(mouse_x, mouse_y);
 }
 
 
@@ -1625,6 +1699,11 @@ void SetAudioMode(boolean enabled)
 // event functions
 // ============================================================================
 
+void InitEventFilter(EventFilter filter_function)
+{
+  SDL_SetEventFilter(filter_function, NULL);
+}
+
 boolean PendingEvent(void)
 {
   return (SDL_PollEvent(NULL) ? TRUE : FALSE);
@@ -1637,11 +1716,12 @@ void WaitEvent(Event *event)
 
 void PeekEvent(Event *event)
 {
-#if defined(TARGET_SDL2)
   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
-#else
-  SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_ALLEVENTS);
-#endif
+}
+
+void PumpEvents(void)
+{
+  SDL_PumpEvents();
 }
 
 void CheckQuitEvent(void)
@@ -1652,26 +1732,8 @@ void CheckQuitEvent(void)
 
 Key GetEventKey(KeyEvent *event, boolean with_modifiers)
 {
-#if defined(TARGET_SDL2)
   // key up/down events in SDL2 do not return text characters anymore
   return event->keysym.sym;
-#else
-
-#if ENABLE_UNUSED_CODE
-  printf("unicode == '%d', sym == '%d', mod == '0x%04x'\n",
-        (int)event->keysym.unicode,
-        (int)event->keysym.sym,
-        (int)SDL_GetModState());
-#endif
-
-  if (with_modifiers &&
-      event->keysym.unicode > 0x0000 &&
-      event->keysym.unicode < 0x2000)
-    return event->keysym.unicode;
-  else
-    return event->keysym.sym;
-
-#endif
 }
 
 KeyMod HandleKeyModState(Key key, int key_status)
@@ -1682,7 +1744,7 @@ KeyMod HandleKeyModState(Key key, int key_status)
   {
     KeyMod new_modifier = KMOD_None;
 
-    switch(key)
+    switch (key)
     {
       case KSYM_Shift_L:
        new_modifier = KMOD_Shift_L;
@@ -1739,7 +1801,8 @@ KeyMod GetKeyModStateFromEvents(void)
 
 void StartTextInput(int x, int y, int width, int height)
 {
-#if defined(TARGET_SDL2)
+  textinput_status = TRUE;
+
 #if defined(HAS_SCREEN_KEYBOARD)
   SDL_StartTextInput();
 
@@ -1750,12 +1813,12 @@ void StartTextInput(int x, int y, int width, int height)
     video.shifted_up = TRUE;
   }
 #endif
-#endif
 }
 
 void StopTextInput(void)
 {
-#if defined(TARGET_SDL2)
+  textinput_status = FALSE;
+
 #if defined(HAS_SCREEN_KEYBOARD)
   SDL_StopTextInput();
 
@@ -1766,15 +1829,20 @@ void StopTextInput(void)
     video.shifted_up = FALSE;
   }
 #endif
-#endif
 }
 
-boolean CheckCloseWindowEvent(ClientMessageEvent *event)
+void PushUserEvent(int code, int value1, int value2)
 {
-  if (event->type != EVENT_CLIENTMESSAGE)
-    return FALSE;
+  UserEvent event;
+
+  SDL_memset(&event, 0, sizeof(event));
 
-  return TRUE;         // the only possible message here is SDL_QUIT
+  event.type = EVENT_USER;
+  event.code = code;
+  event.value1 = value1;
+  event.value2 = value2;
+
+  SDL_PushEvent((SDL_Event *)&event);
 }
 
 
@@ -1812,3 +1880,43 @@ void ClearJoystickState(void)
 {
   SDLClearJoystickState();
 }
+
+
+// ============================================================================
+// Emscripten functions
+// ============================================================================
+
+void InitEmscriptenFilesystem(void)
+{
+#if defined(PLATFORM_EMSCRIPTEN)
+  EM_ASM
+  (
+    Module.sync_done = 0;
+
+    FS.mkdir('/persistent');           // create persistent data directory
+    FS.mount(IDBFS, {}, '/persistent');        // mount with IDBFS filesystem type
+    FS.syncfs(true, function(err)      // sync persistent data into memory
+    {
+      assert(!err);
+      Module.sync_done = 1;
+    });
+  );
+
+  // wait for persistent data to be synchronized to memory
+  while (emscripten_run_script_int("Module.sync_done") == 0)
+    Delay(20);
+#endif
+}
+
+void SyncEmscriptenFilesystem(void)
+{
+#if defined(PLATFORM_EMSCRIPTEN)
+  EM_ASM
+  (
+    FS.syncfs(function(err)
+    {
+      assert(!err);
+    });
+  );
+#endif
+}