summary refs log tree commit diff
path: root/src/Platform_Web.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Platform_Web.c
initial commit
Diffstat (limited to 'src/Platform_Web.c')
-rw-r--r--src/Platform_Web.c438
1 files changed, 438 insertions, 0 deletions
diff --git a/src/Platform_Web.c b/src/Platform_Web.c
new file mode 100644
index 0000000..c0a73c5
--- /dev/null
+++ b/src/Platform_Web.c
@@ -0,0 +1,438 @@
+#include "Core.h"
+#if defined CC_BUILD_WEB
+
+#include "_PlatformBase.h"
+#include "Stream.h"
+#include "ExtMath.h"
+#include "SystemFonts.h"
+#include "Funcs.h"
+#include "Window.h"
+#include "Utils.h"
+#include "Errors.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.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) */
+/* So just use the same errono numbers as interop_web.js */
+#define _ENOENT        2
+#define _EAGAIN        6 /* same as EWOULDBLOCK */
+#define _EEXIST       17
+#define _EHOSTUNREACH 23
+#define _EINPROGRESS  26
+
+const cc_result ReturnCode_FileShareViolation = 1000000000; /* Not used in web filesystem backend */
+const cc_result ReturnCode_FileNotFound     = _ENOENT;
+const cc_result ReturnCode_SocketInProgess  = _EINPROGRESS;
+const cc_result ReturnCode_SocketWouldBlock = _EAGAIN;
+const cc_result ReturnCode_DirectoryExists  = _EEXIST;
+const char* Platform_AppNameSuffix = "";
+cc_bool Platform_SingleProcess;
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Memory----------------------------------------------------------*
+*#########################################################################################################################*/
+void* Mem_Set(void*  dst, cc_uint8 value,  cc_uint32 numBytes) { return memset( dst, value, numBytes); }
+void* Mem_Copy(void* dst, const void* src, cc_uint32 numBytes) { return memcpy( dst, src,   numBytes); }
+void* Mem_Move(void* dst, const void* src, cc_uint32 numBytes) { return memmove(dst, src,   numBytes); }
+
+void* Mem_TryAlloc(cc_uint32 numElems, cc_uint32 elemsSize) {
+	cc_uint32 size = CalcMemSize(numElems, elemsSize);
+	return size ? malloc(size) : NULL;
+}
+
+void* Mem_TryAllocCleared(cc_uint32 numElems, cc_uint32 elemsSize) {
+	return calloc(numElems, elemsSize);
+}
+
+void* Mem_TryRealloc(void* mem, cc_uint32 numElems, cc_uint32 elemsSize) {
+	cc_uint32 size = CalcMemSize(numElems, elemsSize);
+	return size ? realloc(mem, size) : NULL;
+}
+
+void Mem_Free(void* mem) {
+	if (mem) free(mem);
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------------Logging/Time-------------------------------------------------------*
+*#########################################################################################################################*/
+cc_uint64 Stopwatch_ElapsedMicroseconds(cc_uint64 beg, cc_uint64 end) {
+	if (end < beg) return 0;
+	return end - beg;
+}
+
+cc_uint64 Stopwatch_Measure(void) {
+	/* time is a milliseconds double */
+	/*  convert to microseconds */
+	return (cc_uint64)(emscripten_get_now() * 1000);
+}
+
+extern void interop_Log(const char* msg, int len);
+void Platform_Log(const char* msg, int len) {
+	interop_Log(msg, len);
+}
+
+TimeMS DateTime_CurrentUTC(void) {
+	struct timeval cur;
+	gettimeofday(&cur, NULL);
+	return (cc_uint64)cur.tv_sec + UNIX_EPOCH_SECONDS;
+}
+
+extern void interop_GetLocalTime(struct DateTime* t);
+void DateTime_CurrentLocal(struct DateTime* t) {
+	interop_GetLocalTime(t);
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------Directory/File------------------------------------------------------*
+*#########################################################################################################################*/
+void Directory_GetCachePath(cc_string* path) { }
+
+extern void interop_InitFilesystem(void);
+cc_result Directory_Create(const cc_string* path) {
+	/* Web filesystem doesn't need directories */
+	return 0;
+}
+
+extern int interop_FileExists(const char* path);
+int File_Exists(const cc_string* path) {
+	char str[NATIVE_STR_LEN];
+	String_EncodeUtf8(str, path);
+	return interop_FileExists(str);
+}
+
+static void* enum_obj;
+static Directory_EnumCallback enum_callback;
+EMSCRIPTEN_KEEPALIVE void Directory_IterCallback(const char* src) {
+	cc_string path; char pathBuffer[FILENAME_SIZE];
+
+	String_InitArray(path, pathBuffer);
+	String_AppendUtf8(&path, src, String_Length(src));
+	enum_callback(&path, enum_obj);
+}
+
+extern int interop_DirectoryIter(const char* path);
+cc_result Directory_Enum(const cc_string* path, void* obj, Directory_EnumCallback callback) {
+	char str[NATIVE_STR_LEN];
+	String_EncodeUtf8(str, path);
+
+	enum_obj      = obj;
+	enum_callback = callback;
+	/* returned result is negative for error */
+	return -interop_DirectoryIter(str);
+}
+
+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;
+	String_EncodeUtf8(str, path);
+	res = interop_FileCreate(str, mode);
+
+	/* returned result is negative for error */
+	if (res >= 0) {
+		*file = res; return 0;
+	} else {
+		*file = -1; return -res;
+	}
+}
+
+cc_result File_Open(cc_file* file, const cc_string* path) {
+	return File_Do(file, path, O_RDONLY);
+}
+cc_result File_Create(cc_file* file, const cc_string* path) {
+	return File_Do(file, path, O_RDWR | O_CREAT | O_TRUNC);
+}
+cc_result File_OpenOrCreate(cc_file* file, const cc_string* path) {
+	return File_Do(file, path, O_RDWR | O_CREAT);
+}
+
+extern int interop_FileRead(int fd, void* data, int count);
+cc_result File_Read(cc_file file, void* data, cc_uint32 count, cc_uint32* bytesRead) {
+	int res = interop_FileRead(file, data, count);
+
+	/* returned result is negative for error */
+	if (res >= 0) {
+		*bytesRead = res; return 0;
+	} else {
+		*bytesRead = -1;   return -res;
+	}
+}
+
+extern int interop_FileWrite(int fd, const void* data, int count);
+cc_result File_Write(cc_file file, const void* data, cc_uint32 count, cc_uint32* bytesWrote) {
+	int res = interop_FileWrite(file, data, count);
+
+	/* returned result is negative for error */
+	if (res >= 0) {
+		*bytesWrote = res; return 0;
+	} else {
+		*bytesWrote = -1;  return -res;
+	}
+}
+
+extern int interop_FileClose(int fd);
+cc_result File_Close(cc_file file) {
+	/* returned result is negative for error */
+	return -interop_FileClose(file);
+}
+
+extern int interop_FileSeek(int fd, int offset, int whence);
+cc_result File_Seek(cc_file file, int offset, int seekType) {
+	/* returned result is negative for error */
+	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) {
+	/* 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;
+	} else {
+		*pos = -1;  return -res;
+	}
+}
+
+extern int interop_FileLength(int fd);
+cc_result File_Length(cc_file file, cc_uint32* len) {
+	int res = interop_FileLength(file);
+	/* returned result is negative for error */
+	if (res >= 0) {
+		*len = res; return 0;
+	} else {
+		*len = -1;  return -res;
+	}
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------Threading--------------------------------------------------------*
+*#########################################################################################################################*/
+/* No real threading support with emscripten backend */
+void  Thread_Sleep(cc_uint32 milliseconds) { }
+
+void* Mutex_Create(const char* name) { return NULL; }
+void  Mutex_Free(void* handle) { }
+void  Mutex_Lock(void* handle) { }
+void  Mutex_Unlock(void* handle) { }
+
+void* Waitable_Create(const char* name) { return NULL; }
+void  Waitable_Free(void* handle) { }
+void  Waitable_Signal(void* handle) { }
+void  Waitable_Wait(void* handle) { }
+void  Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { }
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------Font/Text--------------------------------------------------------*
+*#########################################################################################################################*/
+void Platform_LoadSysFonts(void) { }
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Socket----------------------------------------------------------*
+*#########################################################################################################################*/
+extern void interop_InitSockets(void);
+
+cc_result Socket_ParseAddress(const cc_string* address, int port, cc_sockaddr* addrs, int* numValidAddrs) {
+	int len = String_EncodeUtf8(addrs[0].data, address);
+	/* TODO can this ever happen */
+	if (len >= CC_SOCKETADDR_MAXSIZE) Logger_Abort("Overrun in Socket_ParseAddress");
+
+	addrs[0].size  = port;
+	*numValidAddrs = 1;
+	return 0;
+}
+
+extern int interop_SocketCreate(void);
+extern int interop_SocketConnect(int sock, const cc_uint8* host, int port);
+cc_result Socket_Connect(cc_socket* s, cc_sockaddr* addr, cc_bool nonblocking) {
+	int res;
+
+	*s  = interop_SocketCreate();
+	/* size is used to store port number instead */
+	/* returned result is negative for error */
+	res = -interop_SocketConnect(*s, addr->data, addr->size);
+
+	/* error returned when invalid address provided */
+	if (res == _EHOSTUNREACH) return ERR_INVALID_ARGUMENT;
+	return res;
+}
+
+extern int interop_SocketRecv(int sock, void* data, int len);
+cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* read) {
+	int res; 
+	*read = 0;
+
+	/* interop_SocketRecv only reads one WebSocket frame at most, hence call it multiple times */
+	while (count) {
+		/* returned result is negative for error */
+		res = interop_SocketRecv(s, data, count);
+
+		if (res >= 0) {
+			*read += res;
+			data  += res; count -= res;
+		} else {
+			/* EAGAIN when no more data available */
+			if (res == -_EAGAIN) return *read == 0 ? _EAGAIN : 0;
+
+			return -res;
+		}
+	}
+	return 0;
+}
+
+extern int interop_SocketSend(int sock, const void* data, int len);
+cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) {
+	/* returned result is negative for error */
+	int res = interop_SocketSend(s, data, count);
+
+	if (res >= 0) {
+		*modified = res; return 0;
+	} else {
+		*modified = 0; return -res;
+	}
+}
+
+extern int interop_SocketClose(int sock);
+void Socket_Close(cc_socket s) {
+	interop_SocketClose(s);
+}
+
+extern int interop_SocketWritable(int sock, cc_bool* writable);
+cc_result Socket_CheckWritable(cc_socket s, cc_bool* writable) {
+	/* returned result is negative for error */
+	return -interop_SocketWritable(s, writable);
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------Process/Module------------------------------------------------------*
+*#########################################################################################################################*/
+cc_bool Process_OpenSupported = true;
+
+void Process_Exit(cc_result code) {
+	/* 'Window' (i.e. the web canvas) isn't implicitly closed when process is exited */
+	if (code) Window_RequestClose();
+	/* game normally calls exit with code = 0 due to async IndexedDB loading */
+	if (code) exit(code);
+}
+
+extern int interop_OpenTab(const char* url);
+cc_result Process_StartOpen(const cc_string* args) {
+	char str[NATIVE_STR_LEN];
+	cc_result res;
+	String_EncodeUtf8(str, args);
+
+	res = interop_OpenTab(str);
+	/* interop error code -> ClassiCube error code */
+	if (res == 1) res = ERR_INVALID_OPEN_URL;
+	return res;
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------Platform---------------------------------------------------------*
+*#########################################################################################################################*/
+cc_bool Platform_DescribeError(cc_result res, cc_string* dst) {
+	char* str;
+	int len;
+
+	/* For unrecognised error codes, strerror might return messages */
+	/*  such as 'No error information', which is not very useful */
+	if (res >= 1000) return false;
+
+	str = strerror(res);
+	if (!str) return false;
+
+	len = String_CalcLen(str, NATIVE_STR_LEN);
+	String_AppendUtf8(dst, str, len);
+	return true;
+}
+
+EMSCRIPTEN_KEEPALIVE void Platform_LogError(const char* msg) {
+	cc_string str = String_FromReadonly(msg);
+	Logger_WarnFunc(&str);
+}
+
+extern void interop_InitModule(void);
+void Platform_Init(void) {
+	interop_InitModule();
+	interop_InitFilesystem();
+	interop_InitSockets();
+}
+void Platform_Free(void) { }
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------Encryption--------------------------------------------------------*
+*#########################################################################################################################*/
+cc_result Platform_Encrypt(const void* data, int len, cc_string* dst) { return ERR_NOT_SUPPORTED; }
+cc_result Platform_Decrypt(const void* data, int len, cc_string* dst) { return ERR_NOT_SUPPORTED; }
+
+
+/*########################################################################################################################*
+*------------------------------------------------------Main driver--------------------------------------------------------*
+*#########################################################################################################################*/
+int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* args) {
+	int i, count;
+	argc--; argv++; /* skip executable path argument */
+
+	count = min(argc, GAME_MAX_CMDARGS);
+	for (i = 0; i < count; i++) { args[i] = String_FromReadonly(argv[i]); }
+	return count;
+}
+
+
+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);
+
+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");
+}
+
+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