diff --git a/source/EsUuid.cpp b/source/EsUuid.cpp
index 0182f32..15679c5 100644
--- a/source/EsUuid.cpp
+++ b/source/EsUuid.cpp
@@ -117,8 +117,13 @@ EsUuid::UuidType EsUuid::MakeUuid()
Logger::Log("Created locally unique UUID only", Logger::Level::WARNING);
else if(status == RPC_S_UUID_NO_ADDRESS)
throw runtime_error("Failed to create UUID");
+#else
+#ifdef __EMSCRIPTEN__
+ // Emscripten's uuid_generate makes random (version 4) UUIDs.
+ uuid_generate(value.id);
#else
uuid_generate_random(value.id);
+#endif
#endif
return value;
}
diff --git a/source/FrameTimer.cpp b/source/FrameTimer.cpp
index dcdf0e6..bf5a93a 100644
--- a/source/FrameTimer.cpp
+++ b/source/FrameTimer.cpp
@@ -21,6 +21,10 @@ this program. If not, see .
#include
#else
#include
+
+#ifdef __EMSCRIPTEN__
+#include
+#endif
#endif
using namespace std;
@@ -70,6 +74,9 @@ void FrameTimer::Wait()
// See the thread starting with https://sourceforge.net/p/mingw-w64/mailman/message/37013810/.
#ifdef _WIN32
Sleep(chrono::duration_cast(next - now).count());
+#elif defined(__EMSCRIPTEN__)
+ // Hand the browser its turn (Asyncify) instead of blocking it.
+ emscripten_sleep(chrono::duration_cast(next - now).count());
#else
this_thread::sleep_until(next);
#endif
diff --git a/source/GameWindow.cpp b/source/GameWindow.cpp
index cd82653..3490afd 100644
--- a/source/GameWindow.cpp
+++ b/source/GameWindow.cpp
@@ -31,6 +31,10 @@ this program. If not, see .
using namespace std;
+#ifdef __EMSCRIPTEN__
+#include
+#endif
+
namespace {
// The minimal screen resolution requirements.
constexpr int minWidth = 1024;
@@ -188,9 +192,15 @@ bool GameWindow::Init(bool headless)
SDL_EnableScreenSaver();
// Settings that must be declared before the window creation.
+#ifdef __EMSCRIPTEN__
+ // The page sizes the canvas (wg_resize below) and owns full screen.
+ Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI;
+ if(false)
+#else
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
if(Preferences::ScreenModeSetting() == "fullscreen")
+#endif
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
else if(Preferences::Has("maximized"))
flags |= SDL_WINDOW_MAXIMIZED;
@@ -489,6 +499,9 @@ bool GameWindow::IsMaximized()
bool GameWindow::IsFullscreen()
{
+#ifdef __EMSCRIPTEN__
+ return EM_ASM_INT(return !!document.fullscreenElement);
+#endif
return (SDL_GetWindowFlags(mainWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP);
}
@@ -496,6 +509,11 @@ bool GameWindow::IsFullscreen()
void GameWindow::ToggleFullscreen()
{
+#ifdef __EMSCRIPTEN__
+ // The page's own full screen.
+ EM_ASM(Module.wgToggleFullscreen && Module.wgToggleFullscreen());
+ return;
+#endif
// This will generate a window size change event,
// no need to adjust the viewport here.
if(IsFullscreen())
@@ -568,3 +586,14 @@ void GameWindow::UpdateWindowRounding()
WinWindow::UpdateWindowRounding(mainWindow);
}
#endif
+
+
+
+#ifdef __EMSCRIPTEN__
+// The page calls this with the size the game should have, in CSS pixels.
+extern "C" EMSCRIPTEN_KEEPALIVE void wg_resize(int width, int height)
+{
+ if(mainWindow)
+ SDL_SetWindowSize(mainWindow, width, height);
+}
+#endif
diff --git a/source/TaskQueue.cpp b/source/TaskQueue.cpp
index 7f17774..5ba0389 100644
--- a/source/TaskQueue.cpp
+++ b/source/TaskQueue.cpp
@@ -23,6 +23,109 @@ this program. If not, see .
using namespace std;
+#ifdef __EMSCRIPTEN__
+// The web build has no threads (they would need cross-origin isolation): the
+// "async" tasks are queued and run on the main thread, a few milliseconds'
+// worth at a time whenever a queue processes its sync tasks, and all of a
+// queue's tasks when it is waited on.
+#include
+#include
+
+namespace {
+ deque tasks;
+}
+
+
+
+void TaskQueue::Execute(Task &task)
+{
+ try {
+ if(task.async)
+ task.async();
+ }
+ catch(...)
+ {
+ auto exception = current_exception();
+ task.sync = [exception] { rethrow_exception(exception); };
+ }
+ if(task.sync)
+ task.queue->syncTasks.push(std::move(task.sync));
+ task.futurePromise.set_value();
+ task.queue->futures.erase(task.futureIt);
+}
+
+
+
+void TaskQueue::SetWorkerThreadCount(uint64_t)
+{
+}
+
+
+
+TaskQueue::~TaskQueue()
+{
+ Wait();
+}
+
+
+
+shared_future TaskQueue::Run(function asyncTask, function syncTask)
+{
+ tasks.push_back(Task{this, std::move(asyncTask), std::move(syncTask)});
+ shared_future result = futures.emplace_back(tasks.back().futurePromise.get_future());
+ tasks.back().futureIt = prev(futures.end());
+ return result;
+}
+
+
+
+void TaskQueue::ProcessSyncTasks()
+{
+ // Run queued tasks (of any queue), then this queue's sync tasks: for up to
+ // 8 ms a call, or 40 ms while there are many (the game is loading).
+ auto end = chrono::steady_clock::now() + chrono::milliseconds(tasks.size() > 50 ? 40 : 8);
+ while(!tasks.empty() && chrono::steady_clock::now() < end)
+ {
+ Task task = std::move(tasks.front());
+ tasks.pop_front();
+ Execute(task);
+ }
+ for(int i = 0; !syncTasks.empty() && i < MAX_SYNC_TASKS; ++i)
+ {
+ auto task = std::move(syncTasks.front());
+ syncTasks.pop();
+ task();
+ }
+}
+
+
+
+void TaskQueue::Wait()
+{
+ while(!IsDone())
+ for(auto it = tasks.begin(); it != tasks.end(); ++it)
+ if(it->queue == this)
+ {
+ Task task = std::move(*it);
+ tasks.erase(it);
+ Execute(task);
+ break;
+ }
+}
+
+
+
+bool TaskQueue::IsDone() const
+{
+ return futures.empty();
+}
+
+
+
+void TaskQueue::ThreadLoop() noexcept
+{
+}
+#else
namespace {
// The main task queue used by the worker threads.
@@ -195,3 +298,4 @@ void TaskQueue::ThreadLoop() noexcept
asyncCondition.wait(lock, [] { return shouldQuit || !tasks.empty(); });
}
}
+#endif
diff --git a/source/TaskQueue.h b/source/TaskQueue.h
index aa42292..5063748 100644
--- a/source/TaskQueue.h
+++ b/source/TaskQueue.h
@@ -84,6 +84,10 @@ private:
public:
// Thread entry point.
static void ThreadLoop() noexcept;
+#ifdef __EMSCRIPTEN__
+ // Runs a task on the main thread (the web build has no worker threads).
+ static void Execute(Task &task);
+#endif
private:
diff --git a/source/audio/Audio.cpp b/source/audio/Audio.cpp
index aa2df8b..9def51a 100644
--- a/source/audio/Audio.cpp
+++ b/source/audio/Audio.cpp
@@ -168,7 +168,14 @@ void Audio::LoadSounds(const vector &sources)
}
// Begin loading the files.
if(!loadQueue.empty())
+ {
+#ifdef __EMSCRIPTEN__
+ // No threads on the web: load them now.
+ Load();
+#else
loadThread = thread(&Load);
+#endif
+ }
}
diff --git a/source/audio/Music.cpp b/source/audio/Music.cpp
index efa8242..e5f743d 100644
--- a/source/audio/Music.cpp
+++ b/source/audio/Music.cpp
@@ -16,9 +16,11 @@ this program. If not, see .
#include "Music.h"
#include "../Files.h"
-#include "supplier/FlacSupplier.h"
#include "../text/Format.h"
+#ifndef __EMSCRIPTEN__
+#include "supplier/FlacSupplier.h"
#include "supplier/Mp3Supplier.h"
+#endif
#include