SOURCE / PINNED RELEASE
Made of little things.
Powder Tool V600Billion
- Release
- 142767edcab8…
- Author-recorded commit
- 6d92971effd0…
- License
- LICENSE
- Author’s source reference
- nostr://npub1fllw8kw0thjj55wds0uugcnp5kej2nfxd36eruq39d56wwz8r44q5q78wj/wss%3A%2F%2Fgit.napplet.soy%2F/powder-toy
Archive hash verified: ed7d6a8ea7083197…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
The Powder Toy v100.1.400 (d768aeb89acad986bd252d7e904bf44bb374545f) for the web
without threads, for the Powder Toy napplet (BIMbeamFLX/powder-toy-napplet, GPL-3.0 like upstream).
Upstream's Emscripten build uses pthreads, which need SharedArrayBuffer and so a
cross-origin isolated page; a napplet frame (sandbox="allow-scripts", srcdoc) is not.
With -Demscripten_threads=false (default true, upstream's behaviour):
- meson: no USE_PTHREADS, no threads dependency (Meson makes it -pthread), no
pthread-only link symbol.
- Task::Start does the work at once; Poll reports it as before.
- The tasks that waited for HTTP on a worker (SaveUploadTask, and removing,
(un)publishing and (un)favouriting selected saves) become RequestTasks: their
requests start one after another from Start and Poll, nothing blocks. This
form needs no thread in any build.
- The separate rendering thread stays off; gravity's FFT runs on dispatch.
- The request manager defers its wake-up with emscripten_async_call.
Applied by scripts/build-wasm.sh with git apply.
diff --git a/meson.build b/meson.build
index dad30d7..9dd0ff8 100644
--- a/meson.build
+++ b/meson.build
@@ -225,7 +225,12 @@ project_link_args = []
project_cpp_args = []
fftw_dep = dependency('fftw3f', static: is_static)
-threads_dep = dependency('threads')
+emscripten_threads = host_platform == 'emscripten' and get_option('emscripten_threads')
+if host_platform == 'emscripten' and not emscripten_threads
+ threads_dep = []
+else
+ threads_dep = dependency('threads')
+endif
if host_platform == 'emscripten'
app_exe_jssafe = app_exe.underscorify()
png_dep = []
@@ -241,18 +246,26 @@ if host_platform == 'emscripten'
'-s', 'FS_DEBUG',
'-s', 'MODULARIZE',
'-s', 'EXPORT_NAME=create_' + app_exe_jssafe,
- '-Wl,-u,_emscripten_run_callback_on_thread',
'-lidbfs.js',
'--source-map-base=./',
]
+ if emscripten_threads
+ project_link_args += [
+ '-Wl,-u,_emscripten_run_callback_on_thread',
+ ]
+ endif
emcc_args = [
'-s', 'USE_SDL=2',
'-s', 'USE_BZIP2=1',
'-s', 'USE_LIBPNG',
- '-s', 'USE_PTHREADS',
'-s', 'DISABLE_EXCEPTION_CATCHING=0',
'-gsource-map',
]
+ if emscripten_threads
+ emcc_args += [
+ '-s', 'USE_PTHREADS',
+ ]
+ endif
project_link_args += emcc_args
project_cpp_args += emcc_args
else
diff --git a/meson_options.txt b/meson_options.txt
index fb453c3..bd76359 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -300,3 +300,9 @@ option(
value: false,
description: 'Run clang-tidy to lint programming issues'
)
+option(
+ 'emscripten_threads',
+ type: 'boolean',
+ value: true,
+ description: 'Build the Emscripten target with pthreads; without them it runs in pages that are not cross-origin isolated'
+)
diff --git a/src/client/http/requestmanager/Emscripten.cpp b/src/client/http/requestmanager/Emscripten.cpp
index ce06564..7093276 100644
--- a/src/client/http/requestmanager/Emscripten.cpp
+++ b/src/client/http/requestmanager/Emscripten.cpp
@@ -130,7 +130,12 @@ namespace http
void RequestManagerImpl::Wake()
{
+#ifdef __EMSCRIPTEN_PTHREADS__
emscripten_async_run_in_main_runtime_thread(EM_FUNC_SIG_V, &HandleWakeThunk);
+#else
+ // already on the main thread; still leave the caller's stack first, like the threaded version
+ emscripten_async_call([](void *) { HandleWakeThunk(); }, nullptr, 0);
+#endif
}
void RequestManagerImpl::RegisterRequestHandle(std::shared_ptr<RequestHandle> requestHandle)
diff --git a/src/gui/game/GameModel.cpp b/src/gui/game/GameModel.cpp
index 90fa9c2..7a958da 100644
--- a/src/gui/game/GameModel.cpp
+++ b/src/gui/game/GameModel.cpp
@@ -112,7 +112,7 @@ GameModel::GameModel(GameView *newView):
rendererSettings.gravityFieldEnabled = prefs.Get("Renderer.GravityField", false);
rendererSettings.decorationLevel = prefs.Get("Renderer.Decorations", true) ? RendererSettings::decorationEnabled : RendererSettings::decorationDisabled;
rendererSettings.gridCheckerboard = prefs.Get("Renderer.GridCheckerboard", false);
- threadedRendering = prefs.Get("Renderer.SeparateThread", true);
+ SetThreadedRendering(prefs.Get("Renderer.SeparateThread", true));
//Load config into simulation
edgeMode = prefs.Get("Simulation.EdgeMode", NUM_EDGEMODES, EDGE_VOID);
@@ -338,6 +338,10 @@ void GameModel::SetTemperatureScale(TempScale temperatureScale)
void GameModel::SetThreadedRendering(bool newThreadedRendering)
{
+#if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)
+ // there is no thread to render on
+ newThreadedRendering = false;
+#endif
threadedRendering = newThreadedRendering;
}
diff --git a/src/gui/options/OptionsView.cpp b/src/gui/options/OptionsView.cpp
index 380337a..dd0fb9b 100644
--- a/src/gui/options/OptionsView.cpp
+++ b/src/gui/options/OptionsView.cpp
@@ -423,6 +423,9 @@ OptionsView::OptionsView() : ui::Window(ui::Point(-1, -1), ui::Point(320, 340))
threadedRendering = addCheckbox(0, "Separate rendering thread", "May increase framerate when fancy effects are in use", [this] {
c->SetThreadedRendering(threadedRendering->GetChecked());
});
+#if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)
+ threadedRendering->Enabled = false;
+#endif
decoSpace = addDropDown("Colour space used by decoration tools", {
{ "sRGB", DECOSPACE_SRGB },
{ "Linear", DECOSPACE_LINEAR },
diff --git a/src/gui/save/ServerSaveActivity.cpp b/src/gui/save/ServerSaveActivity.cpp
index 1b2e2c0..fe882ee 100644
--- a/src/gui/save/ServerSaveActivity.cpp
+++ b/src/gui/save/ServerSaveActivity.cpp
@@ -13,32 +13,28 @@
#include "client/ThumbnailRendererTask.h"
#include "client/GameSave.h"
#include "client/http/UploadSaveRequest.h"
-#include "tasks/Task.h"
+#include "tasks/RequestTask.h"
#include "gui/Style.h"
-class SaveUploadTask: public Task
+class SaveUploadTask: public RequestTask
{
SaveInfo &save;
- void before() override
+ std::unique_ptr<http::Request> Next(int index) override
{
-
- }
-
- void after() override
- {
-
+ if (index)
+ {
+ return nullptr;
+ }
+ notifyProgress(-1);
+ return std::make_unique<http::UploadSaveRequest>(save);
}
- bool doWork() override
+ bool Done(int index, http::Request &request) override
{
- notifyProgress(-1);
- auto uploadSaveRequest = std::make_unique<http::UploadSaveRequest>(save);
- uploadSaveRequest->Start();
- uploadSaveRequest->Wait();
try
{
- save.SetID(uploadSaveRequest->Finish());
+ save.SetID(static_cast<http::UploadSaveRequest &>(request).Finish());
}
catch (const http::RequestError &ex)
{
diff --git a/src/gui/search/SearchController.cpp b/src/gui/search/SearchController.cpp
index e70a916..b5f751f 100644
--- a/src/gui/search/SearchController.cpp
+++ b/src/gui/search/SearchController.cpp
@@ -16,7 +16,7 @@
#include "common/platform/Platform.h"
#include "graphics/Graphics.h"
#include "graphics/VideoBuffer.h"
-#include "tasks/Task.h"
+#include "tasks/RequestTask.h"
#include "tasks/TaskWindow.h"
#include "gui/dialogues/ConfirmPrompt.h"
@@ -266,35 +266,43 @@ void SearchController::RemoveSelected()
void SearchController::removeSelectedC()
{
- class RemoveSavesTask : public Task
+ class RemoveSavesTask : public RequestTask
{
SearchController *c;
std::vector<int> saves;
- public:
- RemoveSavesTask(std::vector<int> saves_, SearchController *c_) { saves = saves_; c = c_; }
- bool doWork() override
+
+ std::unique_ptr<http::Request> Next(int i) override
{
- for (size_t i = 0; i < saves.size(); i++)
+ if (i == int(saves.size()))
{
- notifyStatus(String::Build("Deleting save [", saves[i], "] ..."));
- auto deleteSaveRequest = std::make_unique<http::DeleteSaveRequest>(saves[i]);
- deleteSaveRequest->Start();
- deleteSaveRequest->Wait();
- try
- {
- deleteSaveRequest->Finish();
- }
- catch (const http::RequestError &ex)
- {
- notifyError(String::Build("Failed to delete [", saves[i], "]: ", ByteString(ex.what()).FromAscii()));
- c->Refresh();
- return false;
- }
- notifyProgress((i + 1) * 100 / saves.size());
+ return nullptr;
}
- c->Refresh();
+ notifyStatus(String::Build("Deleting save [", saves[i], "] ..."));
+ return std::make_unique<http::DeleteSaveRequest>(saves[i]);
+ }
+
+ bool Done(int i, http::Request &request) override
+ {
+ try
+ {
+ static_cast<http::DeleteSaveRequest &>(request).Finish();
+ }
+ catch (const http::RequestError &ex)
+ {
+ notifyError(String::Build("Failed to delete [", saves[i], "]: ", ByteString(ex.what()).FromAscii()));
+ return false;
+ }
+ notifyProgress((i + 1) * 100 / saves.size());
return true;
}
+
+ void Stopped(bool success) override
+ {
+ c->Refresh();
+ }
+
+ public:
+ RemoveSavesTask(std::vector<int> saves_, SearchController *c_) { saves = saves_; c = c_; }
};
std::vector<int> selected = searchModel->GetSelected();
@@ -317,65 +325,63 @@ void SearchController::UnpublishSelected(bool publish)
void SearchController::unpublishSelectedC(bool publish)
{
- class UnpublishSavesTask : public Task
+ class UnpublishSavesTask : public RequestTask
{
std::vector<int> saves;
SearchController *c;
bool publish;
- public:
- UnpublishSavesTask(std::vector<int> saves_, SearchController *c_, bool publish_) { saves = saves_; c = c_; publish = publish_; }
-
- void PublishSave(int saveID)
- {
- notifyStatus(String::Build("Publishing save [", saveID, "]"));
- auto publishSaveRequest = std::make_unique<http::PublishSaveRequest>(saveID);
- publishSaveRequest->Start();
- publishSaveRequest->Wait();
- publishSaveRequest->Finish();
- }
- void UnpublishSave(int saveID)
+ std::unique_ptr<http::Request> Next(int i) override
{
- notifyStatus(String::Build("Unpublishing save [", saveID, "]"));
- auto unpublishSaveRequest = std::make_unique<http::UnpublishSaveRequest>(saveID);
- unpublishSaveRequest->Start();
- unpublishSaveRequest->Wait();
- unpublishSaveRequest->Finish();
+ if (i == int(saves.size()))
+ {
+ return nullptr;
+ }
+ if (publish)
+ {
+ notifyStatus(String::Build("Publishing save [", saves[i], "]"));
+ return std::make_unique<http::PublishSaveRequest>(saves[i]);
+ }
+ notifyStatus(String::Build("Unpublishing save [", saves[i], "]"));
+ return std::make_unique<http::UnpublishSaveRequest>(saves[i]);
}
- bool doWork() override
+ bool Done(int i, http::Request &request) override
{
- for (size_t i = 0; i < saves.size(); i++)
+ try
{
- try
+ if (publish)
{
- if (publish)
- {
- PublishSave(saves[i]);
- }
- else
- {
- UnpublishSave(saves[i]);
- }
+ static_cast<http::PublishSaveRequest &>(request).Finish();
}
- catch (const http::RequestError &ex)
+ else
{
- if (publish) // uses html page so error message will be spam
- {
- notifyError(String::Build("Failed to publish [", saves[i], "], is this save yours?"));
- }
- else
- {
- notifyError(String::Build("Failed to unpublish [", saves[i], "]: ", ByteString(ex.what()).FromAscii()));
- }
- c->Refresh();
- return false;
+ static_cast<http::UnpublishSaveRequest &>(request).Finish();
}
- notifyProgress((i + 1) * 100 / saves.size());
}
- c->Refresh();
+ catch (const http::RequestError &ex)
+ {
+ if (publish) // uses html page so error message will be spam
+ {
+ notifyError(String::Build("Failed to publish [", saves[i], "], is this save yours?"));
+ }
+ else
+ {
+ notifyError(String::Build("Failed to unpublish [", saves[i], "]: ", ByteString(ex.what()).FromAscii()));
+ }
+ return false;
+ }
+ notifyProgress((i + 1) * 100 / saves.size());
return true;
}
+
+ void Stopped(bool success) override
+ {
+ c->Refresh();
+ }
+
+ public:
+ UnpublishSavesTask(std::vector<int> saves_, SearchController *c_, bool publish_) { saves = saves_; c = c_; publish = publish_; }
};
std::vector<int> selected = searchModel->GetSelected();
@@ -384,66 +390,82 @@ void SearchController::unpublishSelectedC(bool publish)
void SearchController::FavouriteSelected()
{
- class FavouriteSavesTask : public Task
+ class FavouriteSavesTask : public RequestTask
{
std::vector<int> saves;
SearchController *c;
- public:
- FavouriteSavesTask(std::vector<int> saves_, SearchController *c_) { saves = saves_; c = c_; }
- bool doWork() override
+
+ std::unique_ptr<http::Request> Next(int i) override
{
- for (size_t i = 0; i < saves.size(); i++)
+ if (i == int(saves.size()))
{
- notifyStatus(String::Build("Favouring save [", saves[i], "]"));
- auto favouriteSaveRequest = std::make_unique<http::FavouriteSaveRequest>(saves[i], true);
- favouriteSaveRequest->Start();
- favouriteSaveRequest->Wait();
- try
- {
- favouriteSaveRequest->Finish();
- }
- catch (const http::RequestError &ex)
- {
- notifyError(String::Build("Failed to favourite [", saves[i], "]: ", ByteString(ex.what()).FromAscii()));
- c->Refresh();
- return false;
- }
- notifyProgress((i + 1) * 100 / saves.size());
+ return nullptr;
}
- c->Refresh();
+ notifyStatus(String::Build("Favouring save [", saves[i], "]"));
+ return std::make_unique<http::FavouriteSaveRequest>(saves[i], true);
+ }
+
+ bool Done(int i, http::Request &request) override
+ {
+ try
+ {
+ static_cast<http::FavouriteSaveRequest &>(request).Finish();
+ }
+ catch (const http::RequestError &ex)
+ {
+ notifyError(String::Build("Failed to favourite [", saves[i], "]: ", ByteString(ex.what()).FromAscii()));
+ return false;
+ }
+ notifyProgress((i + 1) * 100 / saves.size());
return true;
}
+
+ void Stopped(bool success) override
+ {
+ c->Refresh();
+ }
+
+ public:
+ FavouriteSavesTask(std::vector<int> saves_, SearchController *c_) { saves = saves_; c = c_; }
};
- class UnfavouriteSavesTask : public Task
+ class UnfavouriteSavesTask : public RequestTask
{
std::vector<int> saves;
SearchController *c;
- public:
- UnfavouriteSavesTask(std::vector<int> saves_, SearchController *c_) { saves = saves_; c = c_; }
- bool doWork() override
+
+ std::unique_ptr<http::Request> Next(int i) override
{
- for (size_t i = 0; i < saves.size(); i++)
+ if (i == int(saves.size()))
{
- notifyStatus(String::Build("Unfavouring save [", saves[i], "]"));
- auto unfavouriteSaveRequest = std::make_unique<http::FavouriteSaveRequest>(saves[i], false);
- unfavouriteSaveRequest->Start();
- unfavouriteSaveRequest->Wait();
- try
- {
- unfavouriteSaveRequest->Finish();
- }
- catch (const http::RequestError &ex)
- {
- notifyError(String::Build("Failed to unfavourite [", saves[i], "]: ", ByteString(ex.what()).FromAscii()));
- c->Refresh();
- return false;
- }
- notifyProgress((i + 1) * 100 / saves.size());
+ return nullptr;
}
- c->Refresh();
+ notifyStatus(String::Build("Unfavouring save [", saves[i], "]"));
+ return std::make_unique<http::FavouriteSaveRequest>(saves[i], false);
+ }
+
+ bool Done(int i, http::Request &request) override
+ {
+ try
+ {
+ static_cast<http::FavouriteSaveRequest &>(request).Finish();
+ }
+ catch (const http::RequestError &ex)
+ {
+ notifyError(String::Build("Failed to unfavourite [", saves[i], "]: ", ByteString(ex.what()).FromAscii()));
+ return false;
+ }
+ notifyProgress((i + 1) * 100 / saves.size());
return true;
}
+
+ void Stopped(bool success) override
+ {
+ c->Refresh();
+ }
+
+ public:
+ UnfavouriteSavesTask(std::vector<int> saves_, SearchController *c_) { saves = saves_; c = c_; }
};
std::vector<int> selected = searchModel->GetSelected();
diff --git a/src/simulation/gravity/Fft.cpp b/src/simulation/gravity/Fft.cpp
index 027c335..4c3b31d 100644
--- a/src/simulation/gravity/Fft.cpp
+++ b/src/simulation/gravity/Fft.cpp
@@ -74,11 +74,16 @@ GravityImpl::~GravityImpl()
void GravityImpl::Dispatch()
{
+#if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)
+ // no worker thread, so compute now; Exchange hands the result over next time as usual
+ Work();
+#else
{
std::unique_lock lk(stateMx);
working = true;
}
stateCv.notify_one();
+#endif
}
void GravityImpl::Stop()
@@ -88,7 +93,10 @@ void GravityImpl::Stop()
shouldStop = true;
}
stateCv.notify_one();
- thr.join();
+ if (thr.joinable())
+ {
+ thr.join();
+ }
}
void GravityImpl::Wait()
@@ -183,6 +191,9 @@ void GravityImpl::Init()
//clear padded gravmap
std::fill(massBig.get(), massBig.get() + blocks.X * blocks.Y, 0.f);
+#if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)
+ return;
+#endif
thr = std::thread([this]() {
while (true)
{
diff --git a/src/tasks/RequestTask.cpp b/src/tasks/RequestTask.cpp
new file mode 100644
index 0000000..99cb34d
--- /dev/null
+++ b/src/tasks/RequestTask.cpp
@@ -0,0 +1,58 @@
+#include "RequestTask.h"
+
+void RequestTask::Start()
+{
+ before();
+ running = true;
+ Step();
+}
+
+void RequestTask::Poll()
+{
+ if (running)
+ {
+ Step();
+ }
+ Task::Poll();
+}
+
+void RequestTask::Step()
+{
+ while (running)
+ {
+ if (!request)
+ {
+ request = Next(index);
+ if (!request)
+ {
+ End(true);
+ return;
+ }
+ request->Start();
+ }
+ if (!request->CheckDone())
+ {
+ return;
+ }
+ auto ok = Done(index, *request);
+ request.reset();
+ index += 1;
+ if (!ok)
+ {
+ End(false);
+ }
+ }
+}
+
+void RequestTask::End(bool newSuccess)
+{
+ running = false;
+ Stopped(newSuccess);
+ std::lock_guard<std::mutex> g(taskMutex);
+ thSuccess = newSuccess;
+ thDone = true;
+}
+
+void RequestTask::Stopped(bool success)
+{
+}
diff --git a/src/tasks/RequestTask.h b/src/tasks/RequestTask.h
new file mode 100644
index 0000000..54e477e
--- /dev/null
+++ b/src/tasks/RequestTask.h
@@ -0,0 +1,28 @@
+#pragma once
+#include "Task.h"
+#include "client/http/Request.h"
+#include <memory>
+
+// A task made of HTTP requests, started one after another from Start and Poll.
+// Nothing blocks on the network, so no worker thread is needed.
+class RequestTask : public Task
+{
+ std::unique_ptr<http::Request> request;
+ int index = 0;
+ bool running = false;
+
+ void Step();
+ void End(bool newSuccess);
+
+protected:
+ // The request with this index, or nullptr once there are no more.
+ virtual std::unique_ptr<http::Request> Next(int index) = 0;
+ // The request with this index is done; return false to stop the task unsuccessfully.
+ virtual bool Done(int index, http::Request &request) = 0;
+ // Called once when the task stops.
+ virtual void Stopped(bool success);
+
+public:
+ void Start() override;
+ void Poll() override;
+};
diff --git a/src/tasks/Task.cpp b/src/tasks/Task.cpp
index 50d5300..2530a54 100644
--- a/src/tasks/Task.cpp
+++ b/src/tasks/Task.cpp
@@ -12,7 +12,12 @@ void Task::AddTaskListener(TaskListener * listener)
void Task::Start()
{
before();
+#if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_PTHREADS__)
+ // no thread to hand the work to, so do it now; Poll reports it as usual
+ doWork_wrapper();
+#else
std::thread([this]() { doWork_wrapper(); }).detach();
+#endif
}
int Task::GetProgress()
diff --git a/src/tasks/meson.build b/src/tasks/meson.build
index 99ba36f..7e9af66 100644
--- a/src/tasks/meson.build
+++ b/src/tasks/meson.build
@@ -1,5 +1,6 @@
powder_files += files(
'AbandonableTask.cpp',
+ 'RequestTask.cpp',
'Task.cpp',
'TaskWindow.cpp',
)
