Skip to content

Commit

Permalink
Merge pull request #967 from UnknownShadow200/WebStartup1
Browse files Browse the repository at this point in the history
Load IndexedDB and textures asynchronously in webclient
  • Loading branch information
UnknownShadow200 committed Sep 22, 2022
2 parents 24a54e1 + 54f14fc commit 3f6d9d4
Show file tree
Hide file tree
Showing 13 changed files with 487 additions and 214 deletions.
30 changes: 0 additions & 30 deletions doc/compile-fixes.md
Expand Up @@ -23,36 +23,6 @@ Add ```-lexecinfo``` when compiling. Occurs when using musl.

Webclient patches
---------------------
#### Starting game **Error 00000002 when setting current directory**
This is caused by IndexedDB not being initialised, which also means saved maps are lost when the tab is closed.

Due to how IndexedDB works, you must load and initialise it before the game starts. Change:

```
<script type='text/javascript'>
var Module = {
preRun: [],
...
```
to
```
<script type='text/javascript'>
// need to load IndexedDB before running the game
function preloadIndexedDB() {
addRunDependency('load-idb');
FS.mkdir('/classicube');
FS.mount(IDBFS, {}, '/classicube');
FS.syncfs(true, function(err) {
if (err) window.cc_idbErr = err;
removeRunDependency('load-idb');
})
}
var Module = {
preRun: [ preloadIndexedDB ],
...
```

#### Mouse scrolling not properly prevented
With recent chrome/firefox versions, page is still scrolled and console is spammed with\
```"[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive."```
Expand Down
15 changes: 6 additions & 9 deletions doc/hosting-flask.md
Expand Up @@ -72,9 +72,6 @@ if __name__ == "__main__":
</div>
</div>
<script type='text/javascript'>
// need to load IndexedDB before running the game
function preloadIndexedDB() { _interop_LoadIndexedDB(); }
function resizeGameCanvas() {
var cc_canv = $('canvas#canvas');
var dpi = window.devicePixelRatio;
Expand All @@ -98,22 +95,22 @@ if __name__ == "__main__":
}
var Module = {
preRun: [ preloadIndexedDB, resizeGameCanvas ],
preRun: [ resizeGameCanvas ],
postRun: [],
arguments: {{game_args|safe}},
print: function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
},
printErr: function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
},
canvas: (function() { return document.getElementById('canvas'); })(),
setStatus: function(text) {
console.log(text);
document.getElementById('logmsg').innerHTML = text;
},
console.log(text);
document.getElementById('logmsg').innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Expand Down
5 changes: 1 addition & 4 deletions doc/hosting-webclient.md
Expand Up @@ -26,11 +26,8 @@ You are required to have this HTML code somewhere in the page:
<span id="logmsg"></span>

<script type='text/javascript'>
// need to load IndexedDB before running the game
function preloadIndexedDB() { _interop_LoadIndexedDB(); }
var Module = {
preRun: [ preloadIndexedDB ],
preRun: [],
postRun: [],
arguments: [ {username}, {mppass}, {server ip}, {server port} ],
print: function(text) {
Expand Down
4 changes: 1 addition & 3 deletions misc/buildbot.sh
Expand Up @@ -110,10 +110,8 @@ WEB_CC="/home/buildbot/emsdk/emscripten/1.38.31/emcc"
build_web() {
echo "Building web.."
rm cc.js
$WEB_CC *.c -O1 -o cc.js --js-library interop_web.js -s WASM=0 -s LEGACY_VM_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 -s ABORTING_MALLOC=0 -s ENVIRONMENT=web --preload-file texpacks/default.zip -w
$WEB_CC *.c -O1 -o cc.js --js-library interop_web.js -s WASM=0 -s LEGACY_VM_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 -s ABORTING_MALLOC=0 -s ENVIRONMENT=web -w
if [ $? -ne 0 ]; then echo "Failed to compile Webclient" >> "$ERRS_FILE"; fi
# so game loads textures from classicube.net/static/default.zip
sed -i 's#cc.data#/static/default.zip#g' cc.js
# fix mouse wheel scrolling page not being properly prevented
# "[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive."
sed -i 's#eventHandler.useCapture);#{ useCapture: eventHandler.useCapture, passive: false });#g' cc.js
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Expand Up @@ -168,7 +168,7 @@ Install libsdl2_devel, openal_devel, and libexecinfo_devel package if needed

#### Web

```emcc *.c -s ALLOW_MEMORY_GROWTH=1 --js-library interop_web.js --preload-file texpacks/default.zip```
```emcc *.c -s ALLOW_MEMORY_GROWTH=1 --js-library interop_web.js```

The generated javascript file has some issues. [See here for how to fix](doc/compile-fixes.md#webclient-patches)

Expand Down
8 changes: 5 additions & 3 deletions src/Menus.c
Expand Up @@ -1572,7 +1572,7 @@ static void TexturePackScreen_FilterFiles(const cc_string* path, void* obj) {
}

static void TexturePackScreen_LoadEntries(struct ListScreen* s) {
static const cc_string path = String_FromConst(TEXPACKS_DIR);
static const cc_string path = String_FromConst("texpacks");
Directory_Enum(&path, &s->entries, TexturePackScreen_FilterFiles);
StringsBuffer_Sort(&s->entries);
}
Expand All @@ -1581,11 +1581,13 @@ static void TexturePackScreen_LoadEntries(struct ListScreen* s) {
extern void interop_UploadTexPack(const char* path);
static void TexturePackScreen_UploadCallback(const cc_string* path) {
char str[NATIVE_STR_LEN];
Platform_EncodeUtf8(str, path);
cc_string relPath = *path;
Platform_EncodeUtf8(str, path);
Utils_UNSAFE_GetFilename(&relPath);

interop_UploadTexPack(str);
TexturePackScreen_Show();
TexturePack_SetDefault(path);
TexturePack_SetDefault(&relPath);
TexturePack_ExtractCurrent(true);
}

Expand Down
1 change: 1 addition & 0 deletions src/Platform.h
Expand Up @@ -22,6 +22,7 @@ typedef int cc_file;
#define UPDATE_FILE "ClassiCube.update"

/* Origin points for when seeking in a file. */
/* NOTE: These have same values as SEEK_SET/SEEK_CUR/SEEK_END, do not change them */
enum File_SeekFrom { FILE_SEEKFROM_BEGIN, FILE_SEEKFROM_CURRENT, FILE_SEEKFROM_END };
/* Number of milliseconds since 01/01/0001 to start of unix time. */
#define UNIX_EPOCH 62135596800000ULL
Expand Down
119 changes: 55 additions & 64 deletions src/Platform_Web.c
Expand Up @@ -13,11 +13,15 @@
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <emscripten.h>

#define O_RDONLY 0x000
#define O_WRONLY 0x001
#define O_RDWR 0x002
#define O_CREAT 0x040
#define O_EXCL 0x080
#define O_TRUNC 0x200

/* Unfortunately, errno constants are different in some older emscripten versions */
/* (linux errno numbers compared to WASI errno numbers) */
Expand All @@ -31,8 +35,6 @@ const cc_result ReturnCode_FileNotFound = ENOENT;
const cc_result ReturnCode_SocketInProgess = _EINPROGRESS;
const cc_result ReturnCode_SocketWouldBlock = _EAGAIN;
const cc_result ReturnCode_DirectoryExists = EEXIST;
#include <emscripten.h>
#include "Chat.h"


/*########################################################################################################################*
Expand Down Expand Up @@ -63,11 +65,9 @@ void Mem_Free(void* mem) {
/*########################################################################################################################*
*------------------------------------------------------Logging/Time-------------------------------------------------------*
*#########################################################################################################################*/
/* TODO: check this is actually accurate */
static cc_uint64 sw_freqMul = 1, sw_freqDiv = 1;
cc_uint64 Stopwatch_ElapsedMicroseconds(cc_uint64 beg, cc_uint64 end) {
if (end < beg) return 0;
return ((end - beg) * sw_freqMul) / sw_freqDiv;
return end - beg;
}

extern void interop_Log(const char* msg, int len);
Expand All @@ -89,6 +89,7 @@ void DateTime_CurrentLocal(struct DateTime* t) {

cc_uint64 Stopwatch_Measure(void) {
/* time is a milliseconds double */
/* convert to microseconds */
return (cc_uint64)(emscripten_get_now() * 1000);
}

Expand All @@ -97,16 +98,9 @@ cc_uint64 Stopwatch_Measure(void) {
*-----------------------------------------------------Directory/File------------------------------------------------------*
*#########################################################################################################################*/
extern void interop_InitFilesystem(void);
extern void interop_LoadIndexedDB(void);
extern int interop_DirectoryCreate(const char* path, int perms);
cc_result Directory_Create(const cc_string* path) {
char str[NATIVE_STR_LEN];
Platform_EncodeUtf8(str, path);
/* read/write/search permissions for owner and group, and with read/search permissions for others. */
/* TODO: Is the default mode in all cases */

/* returned result is negative for error */
return -interop_DirectoryCreate(str, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
/* Web filesystem doesn't need directories */
return 0;
}

extern int interop_FileExists(const char* path);
Expand All @@ -121,10 +115,6 @@ static Directory_EnumCallback enum_callback;
EMSCRIPTEN_KEEPALIVE void Directory_IterCallback(const char* src) {
cc_string path; char pathBuffer[FILENAME_SIZE];

/* ignore . and .. entry */
if (src[0] == '.' && src[1] == '\0') return;
if (src[0] == '.' && src[1] == '.' && src[2] == '\0') return;

String_InitArray(path, pathBuffer);
String_AppendUtf8(&path, src, String_Length(src));
enum_callback(&path, enum_obj);
Expand All @@ -141,12 +131,12 @@ cc_result Directory_Enum(const cc_string* path, void* obj, Directory_EnumCallbac
return -interop_DirectoryIter(str);
}

extern int interop_FileCreate(const char* path, int mode, int perms);
extern int interop_FileCreate(const char* path, int mode);
static cc_result File_Do(cc_file* file, const cc_string* path, int mode) {
char str[NATIVE_STR_LEN];
int res;
Platform_EncodeUtf8(str, path);
res = interop_FileCreate(str, mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
res = interop_FileCreate(str, mode);

/* returned result is negative for error */
if (res >= 0) {
Expand Down Expand Up @@ -198,15 +188,15 @@ cc_result File_Close(cc_file file) {

extern int interop_FileSeek(int fd, int offset, int whence);
cc_result File_Seek(cc_file file, int offset, int seekType) {
static cc_uint8 modes[3] = { SEEK_SET, SEEK_CUR, SEEK_END };
/* returned result is negative for error */
int res = interop_FileSeek(file, offset, modes[seekType]);
int res = interop_FileSeek(file, offset, seekType);
/* FileSeek returns current position, discard that */
return res >= 0 ? 0 : -res;
}

cc_result File_Position(cc_file file, cc_uint32* pos) {
int res = interop_FileSeek(file, 0, SEEK_CUR);
/* FILE_SEEKFROM_CURRENT is same as SEEK_CUR */
int res = interop_FileSeek(file, 0, FILE_SEEKFROM_CURRENT);
/* returned result is negative for error */
if (res >= 0) {
*pos = res; return 0;
Expand Down Expand Up @@ -357,14 +347,11 @@ cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
/*########################################################################################################################*
*-----------------------------------------------------Process/Module------------------------------------------------------*
*#########################################################################################################################*/
cc_result Process_StartGame(const cc_string* args, int numArgs) {
return ERR_NOT_SUPPORTED;
}
void Process_Exit(cc_result code) {
/* Window isn't implicitly closed when process is exited */
/* 'Window' (i.e. the web canvas) isn't implicitly closed when process is exited */
if (code) Window_Close();

exit(code);
/* game normally calls exit with code = 0 due to async IndexedDB loading */
if (code) exit(code);
}

extern int interop_OpenTab(const char* url);
Expand All @@ -380,26 +367,6 @@ cc_result Process_StartOpen(const cc_string* args) {
}


/*########################################################################################################################*
*--------------------------------------------------------Updater----------------------------------------------------------*
*#########################################################################################################################*/
const struct UpdaterInfo Updater_Info = { "", 0 };

cc_result Updater_GetBuildTime(cc_uint64* t) { return ERR_NOT_SUPPORTED; }
cc_bool Updater_Clean(void) { return true; }
cc_result Updater_Start(const char** action) { return ERR_NOT_SUPPORTED; }
cc_result Updater_MarkExecutable(void) { return 0; }
cc_result Updater_SetNewBuildTime(cc_uint64 t) { return ERR_NOT_SUPPORTED; }


/*########################################################################################################################*
*-------------------------------------------------------Dynamic lib-------------------------------------------------------*
*#########################################################################################################################*/
void* DynamicLib_Load2(const cc_string* path) { return NULL; }
void* DynamicLib_Get2(void* lib, const char* name) { return NULL; }
cc_bool DynamicLib_DescribeError(cc_string* dst) { return false; }


/*########################################################################################################################*
*--------------------------------------------------------Platform---------------------------------------------------------*
*#########################################################################################################################*/
Expand Down Expand Up @@ -442,13 +409,7 @@ extern void interop_InitModule(void);
void Platform_Init(void) {
interop_InitModule();
interop_InitFilesystem();
interop_LoadIndexedDB();
interop_InitSockets();

/* NOTE: You must pre-load IndexedDB before main() */
/* (because pre-loading only works asynchronously) */
/* If you don't, you'll get errors later trying to sync local to remote */
/* See doc/hosting-webclient.md for example preloading IndexedDB code */
}
void Platform_Free(void) { }

Expand All @@ -461,7 +422,7 @@ cc_result Platform_Decrypt(const void* data, int len, cc_string* dst) { return E


/*########################################################################################################################*
*-----------------------------------------------------Configuration-------------------------------------------------------*
*------------------------------------------------------Main driver--------------------------------------------------------*
*#########################################################################################################################*/
int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* args) {
int i, count;
Expand All @@ -472,9 +433,39 @@ int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* arg
return count;
}

extern int interop_DirectorySetWorking(const char* path);
cc_result Platform_SetDefaultCurrentDirectory(int argc, char **argv) {
/* returned result is negative for error */
return -interop_DirectorySetWorking("/classicube");

cc_result Platform_SetDefaultCurrentDirectory(int argc, char** argv) { return 0; }
static int _argc;
static char** _argv;

extern void interop_FS_Init(void);
extern void interop_DirectorySetWorking(const char* path);
extern void interop_AsyncDownloadTexturePack(const char* path, const char* url);

int main(int argc, char** argv) {
_argc = argc; _argv = argv;

/* Game loads resources asynchronously, then actually starts itself */
/* main */
/* > texture pack download (async) */
/* > load indexedDB (async) */
/* > web_main (game actually starts) */
interop_FS_Init();
interop_DirectorySetWorking("/classicube");
interop_AsyncDownloadTexturePack("texpacks/default.zip", "/static/default.zip");
}

extern void interop_LoadIndexedDB(void);
extern void interop_AsyncLoadIndexedDB(void);
/* Asynchronous callback after texture pack is downloaded */
EMSCRIPTEN_KEEPALIVE void main_phase1(void) {
interop_LoadIndexedDB(); /* legacy compatibility */
interop_AsyncLoadIndexedDB();
}

extern int web_main(int argc, char** argv);
/* Asynchronous callback after IndexedDB is loaded */
EMSCRIPTEN_KEEPALIVE void main_phase2(void) {
web_main(_argc, _argv);
}
#endif
7 changes: 5 additions & 2 deletions src/Program.c
Expand Up @@ -116,7 +116,7 @@ static int RunProgram(int argc, char** argv) {
/* ClassiCube is sort of and sort of not the executable */
/* on iOS - UIKit is responsible for kickstarting the game. */
/* (this is handled in interop_ios.m as the code is Objective C) */
int main_real(int argc, char** argv) {
int ios_main(int argc, char** argv) {
SetupProgram(argc, argv);
for (;;) { RunProgram(argc, argv); }
return 0;
Expand All @@ -136,8 +136,11 @@ void android_main(void) {
/* Normally, the final code produced for "main" is our "main" combined with crt's main */
/* (mingw-w64-crt/crt/gccmain.c) - alas this immediately crashes the game on startup. */
/* Using main_real instead and setting main_real as the entrypoint fixes the crash. */
#ifdef CC_NOMAIN
#if defined CC_NOMAIN
int main_real(int argc, char** argv) {
#elif defined CC_BUILD_WEB
/* webclient does some asynchronous initialisation first, then kickstarts the game after that */
int web_main(int argc, char** argv) {
#else
int main(int argc, char** argv) {
#endif
Expand Down
2 changes: 1 addition & 1 deletion src/TexturePack.c
Expand Up @@ -341,7 +341,7 @@ static cc_result ExtractFromFile(const cc_string* filename) {
cc_result res;

String_InitArray(path, pathBuffer);
String_Format1(&path, TEXPACKS_DIR "/%s", filename);
String_Format1(&path, "texpacks/%s", filename);

res = Stream_OpenFile(&stream, &path);
if (res) {
Expand Down

0 comments on commit 3f6d9d4

Please sign in to comment.