Persisting data with Emscripten

When porting your game to Emscripten, you will soon enough notice that the default virtual file system it use doesn’t persist data, everything you write in a file is lost from one session to another. Hopefully, Emscripten has a specific file system, called IDBFS which persist your data (using the HTML5 API Indexed Db). Now, it’s important to note that you have to call manually the sync operation, and also to handle the delay between the moment you call for synchronization and the moment data are effectively synchronized, since the API is asynchronous. For more details on Emscripten File System API, the official website offer a lot of informations :
Emscripten API

On my existing C games, I persist data through SQLite. The huge advantage of SQLite is that you can use SQL language to select / manage your data and take advantage of most DBMS functionalities without the need to have any kind of server running in the background, since everything in SQLite is saved on a single db file.

Using IDBFS, it’s possible to keep your entire existing SQLite management code, the only thing you need is to call Emscripten sync API when needed to persist any changes:

Initialisation of the IDBFS filesystem :

EM_ASM(
       //create your directory where we keep our persistent data
       FS.mkdir('/persistent_data'); 

       //mount persistent directory as IDBFS
       FS.mount(IDBFS,{},'/persistent_data');

       Module.print("start file sync..");
       //flag to check when data are synchronized
       Module.syncdone = 0;

       //populate persistent_data directory with existing persistent source data 
      //stored with Indexed Db
      //first parameter = "true" mean synchronize from Indexed Db to 
      //Emscripten file system,
      // "false" mean synchronize from Emscripten file system to Indexed Db
      //second parameter = function called when data are synchronized
      FS.syncfs(true, function(err) {
                       assert(!err);
                       Module.print("end file sync..");
                       Module.syncdone = 1;
      });
);

(the EM_ASM macro allow us to call JavaScript code within c)

At the first run, the “persistent_data” folder is always empty, you can’t mount a directory that is already mounted with the default virtual file system.

After synchronisation is done, you can either start using the existing files that are now into the “persistent_data” folder, or add the base data into your folder if it’s the first time a player run your game.

if(!save_mngr.ready)
{
     //check the Module.syncdone flag value
    if(emscripten_run_script_int("Module.syncdone") == 1)
    {
         //check that our base SQLite data exist, if not copy it 
        //in the "persistent_data" folder
        FILE* file = fopen(save_path,"r");

       if(file == NULL)
       {
           logprint("save.db file doesn't exist in file system, copying it...");
           size_t size;
           //copy file
           //in this case I copy a base SQLite file into my persistent folder,
           // "base_data" is a folder in the default Emscripten file system
           unsigned char* buffer = get_resx_content(&game_state->resx_mngr,"/base_data/save.db",&size,NULL);

           file = fopen(save_path,"w");

           fwrite(buffer,sizeof(unsigned char),size,file);
           fclose(file);

           //persist Emscripten current data to Indexed Db
           EM_ASM(
                  Module.print("Start File sync..");
                  Module.syncdone = 0;
                  FS.syncfs(false, function(err) {
                                    assert(!err);
                                   Module.print("End File sync..");
                                   Module.syncdone = 1;
                                   });
            );
        }
        else
        {
            fclose(file);
        }

        save_mngr_init(&save_mngr,save_path); //here I load my SQLite file like usual and start using it, the save_mngr.ready flag is now set to true
    }
    else
    {
        return; //I prevent any further code to run as long as the save data are not ready
    }
}

(the save_mngr function is the object that handle all my save data using SQLite)
(this code is put at the beginning of my gameloop function, since you have to wait for data to be synchronized, and that there is no proper way of waiting for code execution with Emscripten (the same way sleep() or a while loop would do on desktop versions))

After that, you can persist any changes to your SQLite file with this code, for example after any data insertion or data update :

EM_ASM(
        //persist changes
        FS.syncfs(false,function (err) {
                          assert(!err);
        });
);

and that’s all! The initialisation code will then load the modified SQLite data next time the player run the game. It’s possible to check Indexed Db content afterwards with Chrome, go to Developments tools > Resources tab > Indexed Db.

This entry was posted in Emscripten, HTML5, WebGL. Bookmark the permalink.

Comments are closed.