diff --git a/3rdParty/Lua/CMakeLists.txt b/3rdParty/Lua/CMakeLists.txt index 04147a54d..a031ffca4 100644 --- a/3rdParty/Lua/CMakeLists.txt +++ b/3rdParty/Lua/CMakeLists.txt @@ -26,6 +26,10 @@ if(CMAKE_SYSTEM_NAME MATCHES "Darwin" AND DARWIN_MAJOR_VERSION VERSION_EQUAL 8) # localtime_r gmtime_r find_package(MacportsLegacySupport REQUIRED) target_link_libraries(lua_static PRIVATE MacportsLegacySupport::MacportsLegacySupport) +elseif(EMSCRIPTEN) + # Enable pthread support for Emscripten to match SDL2's USE_PTHREADS=1 + target_compile_options(lua_static PUBLIC -pthread) + target_link_options(lua_static PUBLIC -pthread) elseif(TARGET_PLATFORM STREQUAL "dos") target_compile_definitions(lua_static PUBLIC -DLUA_USE_C89) elseif(ANDROID AND ("${ANDROID_ABI}" STREQUAL "armeabi-v7a" OR "${ANDROID_ABI}" STREQUAL "x86")) diff --git a/CMake/platforms/emscripten.cmake b/CMake/platforms/emscripten.cmake index c5737c081..0e2c1378b 100644 --- a/CMake/platforms/emscripten.cmake +++ b/CMake/platforms/emscripten.cmake @@ -1,13 +1,15 @@ set(BUILD_TESTING OFF) set(BUILD_ASSETS_MPQ OFF) set(DISABLE_ZERO_TIER ON) +set(DISABLE_TCP ON) set(DEVILUTIONX_SYSTEM_SDL_AUDIOLIB OFF) set(DEVILUTIONX_SYSTEM_LIBSODIUM OFF) set(DEVILUTIONX_SYSTEM_LIBFMT OFF) - +set(NOEXIT ON) # Emscripten ports do have a bzip2 but it fails to link with this error: # warning: _BZ2_bzDecompress may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library # error: undefined symbol: BZ2_bzDecompressEnd (referenced by top-level compiled C/C++ code) set(DEVILUTIONX_SYSTEM_BZIP2 OFF) file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Packaging/emscripten/index.html" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Packaging/emscripten/file-manager.js" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/CMakeLists.txt b/CMakeLists.txt index 00d4a8566..9769df1f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,7 +271,10 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND NOT PS4) if(APPLE) add_link_options("$<$>:LINKER:-dead_strip>") else() - add_link_options("$<$>:LINKER:--gc-sections,--as-needed>") + add_link_options("$<$>:LINKER:--gc-sections>") + if(NOT EMSCRIPTEN) + add_link_options("$<$>:LINKER:--as-needed>") + endif() endif() endif() @@ -301,7 +304,7 @@ endif() # Not a genexp because CMake doesn't support it # https://gitlab.kitware.com/cmake/cmake/-/issues/20546 -if(NOT DISABLE_LTO) +if(NOT DISABLE_LTO AND NOT EMSCRIPTEN) # LTO if supported: include(CheckIPOSupported) check_ipo_supported(RESULT is_ipo_supported OUTPUT lto_error) @@ -369,7 +372,7 @@ else() Packaging/windows/devilutionx.rc Packaging/apple/LaunchScreen.storyboard) - if(CMAKE_STRIP AND NOT DEVILUTIONX_DISABLE_STRIP) + if(CMAKE_STRIP AND NOT DEVILUTIONX_DISABLE_STRIP AND NOT EMSCRIPTEN) add_custom_command( TARGET ${BIN_TARGET} POST_BUILD COMMAND $<$,$>:${CMAKE_STRIP}> @@ -398,7 +401,18 @@ include(Assets) include(Mods) if(EMSCRIPTEN) - target_link_options(${BIN_TARGET} PRIVATE --preload-file assets) + target_link_options(${BIN_TARGET} PRIVATE + --preload-file assets + -sFORCE_FILESYSTEM=1 + -sALLOW_MEMORY_GROWTH=1 + -sASYNCIFY + -lidbfs.js + --shell-file ${CMAKE_CURRENT_SOURCE_DIR}/Packaging/emscripten/index.html + ) + # Add JavaScript to load MPQ files from the server directory at runtime + target_link_options(${BIN_TARGET} PRIVATE --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/Packaging/emscripten/emscripten_pre.js) + # Disable fullscreen by default + target_compile_definitions(${BIN_TARGET} PRIVATE EMSCRIPTEN_NO_FULLSCREEN) endif() if(NOT USE_SDL1 AND NOT UWP_LIB) diff --git a/Packaging/emscripten/emscripten_pre.js b/Packaging/emscripten/emscripten_pre.js new file mode 100644 index 000000000..d5359e71f --- /dev/null +++ b/Packaging/emscripten/emscripten_pre.js @@ -0,0 +1,138 @@ +// Pre-load MPQ files from the server directory into Emscripten virtual filesystem +Module['preRun'] = Module['preRun'] || []; + +// Mount IDBFS for persistent save files +Module['preRun'].push(function() { + console.log('Setting up IDBFS for persistent saves...'); + + // SDL uses //libsdl/ as the base path for Emscripten + // Save files are in //libsdl/diasurgical/devilution/ + // Config files (diablo.ini) would be in //libsdl/diasurgical/ + try { + // Helper function to create directory if it doesn't exist + function mkdirSafe(path) { + try { + // Check if path exists + var stat = FS.stat(path); + // If it exists and is a directory, we're good + if (FS.isDir(stat.mode)) { + return; + } + // If it exists but is not a directory, this is an error + console.error('Path exists but is not a directory: ' + path); + return; + } catch (e) { + // Path doesn't exist, try to create it + try { + FS.mkdir(path); + } catch (mkdirErr) { + // Only throw if it's not an "already exists" error + if (mkdirErr.errno !== 20 && mkdirErr.errno !== 17) { + throw mkdirErr; + } + } + } + } + + // Create SDL directory hierarchy if needed + mkdirSafe('/libsdl'); + mkdirSafe('/libsdl/diasurgical'); + + // Mount the diasurgical directory as IDBFS to persist saves AND settings + FS.mount(IDBFS, {}, '/libsdl/diasurgical'); + console.log('IDBFS mounted successfully at /libsdl/diasurgical'); + + // Sync from IndexedDB to memory (load existing saves) + Module.addRunDependency('syncfs'); + FS.syncfs(true, function(err) { + if (err) { + console.error('Error loading saves from IndexedDB:', err); + } else { + console.log('Existing saves loaded from IndexedDB'); + } + Module.removeRunDependency('syncfs'); + }); + } catch (e) { + console.error('Error setting up IDBFS:', e); + } +}); + +// Load MPQ files from the server directory +Module['preRun'].push(function() { + // List of MPQ files to try loading (in priority order) + var mpqFiles = [ + 'spawn.mpq', + ]; + + // Create a promise-based loading system + var loadPromises = mpqFiles.map(function(filename) { + return new Promise(function(resolve) { + fetch(filename) + .then(function(response) { + if (response.ok) { + return response.arrayBuffer(); + } + throw new Error('File not found'); + }) + .then(function(data) { + console.log('Loading ' + filename + ' into virtual filesystem...'); + FS.writeFile('/' + filename, new Uint8Array(data)); + console.log('Successfully loaded ' + filename); + resolve(); + }) + .catch(function() { + // File doesn't exist, skip silently + resolve(); + }); + }); + }); + + // Wait for all MPQ files to load before continuing + Module.addRunDependency('loadMPQs'); + Promise.all(loadPromises).then(function() { + Module.removeRunDependency('loadMPQs'); + }); +}); + +// Track if a sync is in progress to prevent overlapping operations +var syncInProgress = false; + +// Expose function to manually save to IndexedDB +Module['saveToIndexedDB'] = function() { + if (syncInProgress) { + return; + } + + syncInProgress = true; + FS.syncfs(false, function(err) { + syncInProgress = false; + if (err) { + console.error('Error persisting saves to IndexedDB:', err); + } + }); +}; + +// Auto-sync to IndexedDB every 30 seconds as a fallback +Module['postRun'] = Module['postRun'] || []; +Module['postRun'].push(function() { + setInterval(function() { + if (!syncInProgress) { + syncInProgress = true; + FS.syncfs(false, function(err) { + syncInProgress = false; + if (err) { + console.error('Auto-sync error:', err); + } + }); + } + }, 30000); + + // Sync when the page is about to close + window.addEventListener('beforeunload', function() { + if (!syncInProgress) { + FS.syncfs(false, function(err) { + if (err) console.error('Error syncing on page unload:', err); + }); + } + }); +}); diff --git a/Packaging/emscripten/file-manager.js b/Packaging/emscripten/file-manager.js new file mode 100644 index 000000000..c8ab78d20 --- /dev/null +++ b/Packaging/emscripten/file-manager.js @@ -0,0 +1,232 @@ +// File Manager functionality +(function() { + const modal = document.getElementById('fileManagerModal'); + const fileManagerBtn = document.getElementById('fileManagerBtn'); + const closeModalBtn = document.getElementById('closeModal'); + const dropZone = document.getElementById('dropZone'); + const fileInput = document.getElementById('fileInput'); + const browseBtn = document.getElementById('browseBtn'); + const resetSettingsBtn = document.getElementById('resetSettingsBtn'); + const mpqFilesList = document.getElementById('mpqFilesList'); + + // Open/close modal + fileManagerBtn.addEventListener('click', () => { + modal.classList.add('show'); + refreshFileList(); + }); + + closeModalBtn.addEventListener('click', () => { + modal.classList.remove('show'); + }); + + modal.addEventListener('click', (e) => { + if (e.target === modal) { + modal.classList.remove('show'); + } + }); + + // Browse button + browseBtn.addEventListener('click', () => { + fileInput.click(); + }); + + // Drag and drop + dropZone.addEventListener('click', () => { + fileInput.click(); + }); + + dropZone.addEventListener('dragover', (e) => { + e.preventDefault(); + dropZone.classList.add('dragover'); + }); + + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('dragover'); + }); + + dropZone.addEventListener('drop', (e) => { + e.preventDefault(); + dropZone.classList.remove('dragover'); + handleFiles(e.dataTransfer.files); + }); + + fileInput.addEventListener('change', (e) => { + handleFiles(e.target.files); + }); + + // Handle file upload + function handleFiles(files) { + if (!files || files.length === 0) return; + + // Wait for Module and FS to be ready + if (typeof Module === 'undefined' || typeof FS === 'undefined') { + alert('Game is still loading. Please wait and try again.'); + return; + } + + const mpqFiles = Array.from(files).filter(f => + f.name.toLowerCase().endsWith('.mpq') + ); + + if (mpqFiles.length === 0) { + alert('Please select MPQ files only.'); + return; + } + + let processed = 0; + mpqFiles.forEach(file => { + const reader = new FileReader(); + reader.onload = function(e) { + try { + const data = new Uint8Array(e.target.result); + // Upload to the devilution subdirectory where the game searches + const path = '/libsdl/diasurgical/devilution/' + file.name; // Might want to make this dynamic later, since source mods might rename the paths + + // Create directory if it doesn't exist + try { + FS.mkdir('/libsdl/diasurgical/devilution'); + } catch (e) { + // Directory might already exist, ignore + } + + // Write file to IDBFS-backed directory + FS.writeFile(path, data); + console.log('Uploaded:', file.name, '(' + formatBytes(file.size) + ')'); + + processed++; + if (processed === mpqFiles.length) { + // Sync to IndexedDB + FS.syncfs(false, function(err) { + if (err) { + console.error('Error syncing files:', err); + alert('Error saving files. Check console.'); + } else { + alert('Files uploaded successfully! Reloading game...'); + setTimeout(() => location.reload(), 500); + } + }); + } + } catch (err) { + console.error('Error writing file:', err); + alert('Error uploading file: ' + file.name); + } + }; + reader.readAsArrayBuffer(file); + }); + } + + // Refresh file list + function refreshFileList() { + if (typeof Module === 'undefined' || typeof FS === 'undefined') { + mpqFilesList.innerHTML = '

Game is loading...

'; + return; + } + + try { + // Check if devilution directory exists + try { + FS.stat('/libsdl/diasurgical/devilution'); + } catch (e) { + // Directory doesn't exist yet + mpqFilesList.innerHTML = '

No MPQ files found.

'; + return; + } + + const files = FS.readdir('/libsdl/diasurgical/devilution'); + const mpqFiles = files.filter(f => + f.toLowerCase().endsWith('.mpq') && f !== '.' && f !== '..' + ); + + if (mpqFiles.length === 0) { + mpqFilesList.innerHTML = '

No MPQ files found.

'; + return; + } + + mpqFilesList.innerHTML = mpqFiles.map(filename => { + const path = '/libsdl/diasurgical/devilution/' + filename; + const stat = FS.stat(path); + return ` +
+ ${filename} + ${formatBytes(stat.size)} + +
+ `; + }).join(''); + } catch (err) { + console.error('Error reading files:', err); + mpqFilesList.innerHTML = '

Error reading files.

'; + } + } + + // Delete file + window.deleteFile = function(filename) { + if (!confirm('Delete ' + filename + '? This will reload the game.')) { + return; + } + + try { + const path = '/libsdl/diasurgical/devilution/' + filename; + FS.unlink(path); + + // Sync deletion to IndexedDB + FS.syncfs(false, function(err) { + if (err) { + console.error('Error syncing deletion:', err); + alert('Error deleting file. Check console.'); + } else { + alert('File deleted! Reloading game...'); + setTimeout(() => location.reload(), 500); + } + }); + } catch (err) { + console.error('Error deleting file:', err); + alert('Error deleting file: ' + filename); + } + }; + + // Reset settings + resetSettingsBtn.addEventListener('click', () => { + if (!confirm('Reset game settings? This will delete diablo.ini but keep your saves. The game will reload.')) { + return; + } + + try { + const iniPath = '/libsdl/diasurgical/devilution/diablo.ini'; + + // Check if file exists + try { + FS.stat(iniPath); + // File exists, delete it + FS.unlink(iniPath); + console.log('Deleted diablo.ini'); + } catch (e) { + // File doesn't exist, that's fine + console.log('diablo.ini not found (already reset)'); + } + + // Sync to IndexedDB + FS.syncfs(false, function(err) { + if (err) { + console.error('Error syncing settings reset:', err); + alert('Error resetting settings. Check console.'); + } else { + alert('Settings reset! Reloading game...'); + setTimeout(() => location.reload(), 500); + } + }); + } catch (err) { + console.error('Error resetting settings:', err); + alert('Error resetting settings.'); + } + }); + + // Helper function + function formatBytes(bytes) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; + } +})(); diff --git a/Packaging/emscripten/index.html b/Packaging/emscripten/index.html index e04a2fc67..9566d6c4d 100644 --- a/Packaging/emscripten/index.html +++ b/Packaging/emscripten/index.html @@ -25,12 +25,32 @@ div.emscripten_border { border: 1px solid black; + margin: 20px auto; + width: min(90vw, calc((100vh - 200px) * 4 / 3)); + max-width: 1280px; + aspect-ratio: 4 / 3; + display: flex; + justify-content: center; + align-items: center; } /* the canvas *must not* have any border or padding, or mouse coords will be wrong */ canvas.emscripten { border: 0px none; background-color: black; + width: 100%; + height: 100%; + max-width: 100%; + max-height: 100%; + object-fit: contain; + image-rendering: pixelated; + image-rendering: crisp-edges; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-user-drag: none; + pointer-events: auto; } #emscripten_logo { @@ -138,10 +158,154 @@ font-family: 'Lucida Console', Monaco, monospace; outline: none; } + + /* File Manager Styles */ + #fileManagerBtn { + position: fixed; + top: 20px; + right: 20px; + cursor: pointer; + z-index: 1000; + } + + #fileManagerModal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.5); + z-index: 2000; + justify-content: center; + align-items: center; + } + + #fileManagerModal.show { + display: flex; + } + + .modal-content { + background: white; + border: 1px solid black; + padding: 20px; + max-width: 600px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + } + + .modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + } + + .modal-header h2 { + margin: 0; + } + + .close-btn { + border: 1px solid black; + cursor: pointer; + padding: 5px 10px; + } + + .drop-zone { + border: 1px solid black; + padding: 20px; + text-align: center; + margin: 20px 0; + cursor: pointer; + } + + .drop-zone p { + margin: 10px 0; + } + + .file-list { + margin: 20px 0; + } + + .file-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + margin: 5px 0; + border: 1px solid black; + } + + .file-item-name { + flex: 1; + } + + .file-item-size { + margin: 0 15px; + } + + .btn { + padding: 8px 16px; + border: 1px solid black; + cursor: pointer; + } + + .section { + margin: 20px 0; + } + + #fileInput { + display: none; + } + + @media screen and (max-width: 700px) { + div.emscripten_border { + width: min(calc(100vw - 40px), calc((100vh - 200px) * 4 / 3)); + } + } + +
+ +
+
Downloading...
@@ -182,6 +346,9 @@ // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 canvas.addEventListener("webglcontextlost", function (e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); + // Prevent canvas from being dragged + canvas.addEventListener("dragstart", function (e) { e.preventDefault(); }, false); + return canvas; })(), setStatus: function (text) { @@ -222,6 +389,7 @@ }; }; + diff --git a/Source/engine/dx.cpp b/Source/engine/dx.cpp index 60c792ec8..3488f709c 100644 --- a/Source/engine/dx.cpp +++ b/Source/engine/dx.cpp @@ -17,6 +17,10 @@ #include #endif +#ifdef __EMSCRIPTEN__ +#include +#endif + #include "controls/control_mode.hpp" #include "controls/plrctrls.h" #include "engine/render/primitive_render.hpp" @@ -236,7 +240,12 @@ void RenderPresent() SDL_Surface *surface = GetOutputSurface(); if (!gbActive) { +#ifdef __EMSCRIPTEN__ + // Just yield to browser when inactive instead of blocking + emscripten_sleep(1); +#else LimitFrameRate(); +#endif return; } @@ -259,6 +268,11 @@ void RenderPresent() } SDL_RenderPresent(renderer); +#ifdef __EMSCRIPTEN__ + // Yield to browser to allow rendering + emscripten_sleep(1); +#endif + if (*GetOptions().Graphics.frameRateControl != FrameRateControl::VerticalSync) { LimitFrameRate(); } diff --git a/Source/interfac.cpp b/Source/interfac.cpp index 0ca8a58b2..578e72beb 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -48,7 +48,7 @@ #include "controls/touch/renderers.h" #endif -#ifdef __DJGPP__ +#if defined(__DJGPP__) || defined(__EMSCRIPTEN__) #define LOAD_ON_MAIN_THREAD #endif diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 010658713..dcf92e009 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -44,6 +44,10 @@ #include "utils/language.h" #include "utils/status_macros.hpp" +#ifdef __EMSCRIPTEN__ +#include +#endif + namespace devilution { bool gbIsHellfireSaveGame; @@ -2931,6 +2935,11 @@ void SaveGame() gbValidSaveFile = true; pfile_write_hero(/*writeGameData=*/true); sfile_write_stash(); + +#ifdef __EMSCRIPTEN__ + // Persist saves to IndexedDB for browser storage + emscripten_run_script("if (typeof Module !== 'undefined' && Module.saveToIndexedDB) Module.saveToIndexedDB();"); +#endif } void SaveLevel(SaveWriter &saveWriter) diff --git a/Source/options.cpp b/Source/options.cpp index 52e2730ed..6ad0ddfed 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -185,8 +185,9 @@ void SaveIni() #if SDL_VERSION_ATLEAST(2, 0, 0) bool HardwareCursorDefault() { -#if defined(__ANDROID__) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +#if defined(__ANDROID__) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) || defined(__EMSCRIPTEN__) // See https://github.com/diasurgical/devilutionX/issues/2502 + // Emscripten: Software cursor works better in browsers return false; #else return HardwareCursorSupported(); @@ -739,10 +740,16 @@ SDL_AudioDeviceID OptionEntryAudioDevice::id() const GraphicsOptions::GraphicsOptions() : OptionCategoryBase("Graphics", N_("Graphics"), N_("Graphics Settings")) - , fullscreen("Fullscreen", OnlyIfSupportsWindowed | OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, N_("Fullscreen"), N_("Display the game in windowed or fullscreen mode."), true) + , fullscreen("Fullscreen", OnlyIfSupportsWindowed | OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, N_("Fullscreen"), N_("Display the game in windowed or fullscreen mode."), +#ifdef __EMSCRIPTEN__ + false // Default to windowed mode for browser +#else + true +#endif + ) #if !defined(USE_SDL1) || defined(__3DS__) , fitToScreen("Fit to Screen", OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, N_("Fit to Screen"), N_("Automatically adjust the game window to your current desktop screen aspect ratio and resolution."), -#ifdef __DJGPP__ +#if defined(__DJGPP__) || defined(__EMSCRIPTEN__) false #else true diff --git a/Source/pfile.cpp b/Source/pfile.cpp index ec96c6608..2bdbfd88e 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -47,6 +47,10 @@ #include "mpq/mpq_reader.hpp" #endif +#ifdef __EMSCRIPTEN__ +#include +#endif + namespace devilution { #define PASSWORD_SPAWN_SINGLE "adslhfb1" @@ -627,6 +631,11 @@ void pfile_write_hero(bool writeGameData) { SaveWriter saveWriter = GetSaveWriter(gSaveNumber); pfile_write_hero(saveWriter, writeGameData); + +#ifdef __EMSCRIPTEN__ + // Persist saves to IndexedDB for browser storage + emscripten_run_script("if (typeof Module !== 'undefined' && Module.saveToIndexedDB) Module.saveToIndexedDB();"); +#endif } #ifndef DISABLE_DEMOMODE diff --git a/Source/utils/sdl_mutex.h b/Source/utils/sdl_mutex.h index 4bf4fca81..cc7f09718 100644 --- a/Source/utils/sdl_mutex.h +++ b/Source/utils/sdl_mutex.h @@ -18,7 +18,7 @@ namespace devilution { * RAII wrapper for SDL_mutex. Satisfies std's "Lockable" (SDL 2) or "BasicLockable" (SDL 1) * requirements so it can be used with std::lock_guard and friends. */ -#ifdef __DJGPP__ +#if defined(__DJGPP__) || defined(__EMSCRIPTEN__) class SdlMutex final { public: SdlMutex() noexcept { } diff --git a/Source/utils/sdl_thread.cpp b/Source/utils/sdl_thread.cpp index 8c96f2189..0279f23dc 100644 --- a/Source/utils/sdl_thread.cpp +++ b/Source/utils/sdl_thread.cpp @@ -1,22 +1,22 @@ -#include "utils/sdl_thread.h" - -namespace devilution { - -#ifndef __DJGPP__ -int SDLCALL SdlThread::ThreadTranslate(void *ptr) -{ - auto handler = (void (*)())ptr; - - handler(); - - return 0; -} - -void SdlThread::ThreadDeleter(SDL_Thread *thread) -{ - if (thread != nullptr) - app_fatal("Joinable thread destroyed"); -} -#endif - -} // namespace devilution +#include "utils/sdl_thread.h" + +namespace devilution { + +#ifndef __DJGPP__ +int SDLCALL SdlThread::ThreadTranslate(void *ptr) +{ + auto handler = (void (*)())ptr; + + handler(); + + return 0; +} + +void SdlThread::ThreadDeleter(SDL_Thread *thread) +{ + if (thread != nullptr) + app_fatal("Joinable thread destroyed"); +} +#endif + +} // namespace devilution diff --git a/Source/utils/sdl_thread.h b/Source/utils/sdl_thread.h index f9e0f8a21..517a5e5f9 100644 --- a/Source/utils/sdl_thread.h +++ b/Source/utils/sdl_thread.h @@ -24,7 +24,7 @@ inline SDL_ThreadID get_id() inline SDL_threadID get_id() #endif { -#if defined(__DJGPP__) +#if defined(__DJGPP__) || defined(__EMSCRIPTEN__) return 1; #else return SDL_GetThreadID(nullptr); @@ -32,7 +32,7 @@ inline SDL_threadID get_id() } } // namespace this_sdl_thread -#if defined(__DJGPP__) +#if defined(__DJGPP__) || defined(__EMSCRIPTEN__) class SdlThread final { public: SdlThread(int(SDLCALL *handler)(void *), void *data)