Skip to content

Fix emscripten (webassembly) support #23515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Jan 21, 2025
Merged

Conversation

ethanaobrien
Copy link
Contributor

@ethanaobrien ethanaobrien commented Dec 31, 2024

This PR fixes up the (broken) emscripten support, and adds a bash script that will download and compile all dependencies

Originally: #23392

You can try the demo here: https://rct.ethanthesleepy.one/

Overall changes, all having to do with emscripten:

  • Removes/replaces outdated syntax when targeting emscripten
  • Screenshots are downloaded from the browser instead of saving them to the virtual file system
  • Corrects the OPENRCT2_ARCHITECTURE define when targeting webassembly
  • Moves emscripten main-loop logic to prevent a constant browser prompt dialog, when trying to read from stdin
  • Adds import/export emscripten button to the bottom of the settings menu, for emscripten only
  • Fixes any buttons that copy text to the users clipboard
  • Fixes using right-click to drag the mouse, since we cannot pin the cursor in the web browser
  • Fixes compiling with NO_TTF
  • Fixes duplicate symbols with emscriptens opengl emulation
  • Fixes canvas side on initial page load
  • Fixes any buttons that open an external web browser
  • Fixes the browser context menu opening when trying to use right-click to move the display
  • Adds relevant emscripten changes to the cmake file
  • Adds a relevant HTML and JavaScript file to load and handle OpenRCT2 in the web browser

@janisozaur
Copy link
Member

I will take a look at this next week

@ZehMatt
Copy link
Member

ZehMatt commented Jan 4, 2025

I'm not really a huge fan of all the macro guarded code, I'm not going to get this PR blocked but we should definitely aim to better separate this so that no one has to look at essentially dead code.

@ethanaobrien
Copy link
Contributor Author

I'm not really a huge fan of all the macro guarded code, I'm not going to get this PR blocked but we should definitely aim to better separate this so that no one has to look at essentially dead code.

I chose this approach since I'm not familiar enough with the codebase to move anything to a general "utility" class. However, if you'd like for me to give this a shot (with some information on where things would need to go) I would take a look.

I'm going to wait until this PR is approved/rejected before resolving merge conflicts

Copy link
Member

@janisozaur janisozaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall I like the changes. There's a bit of setup that needs to be improved, but the thing that worries me is how to reliably create assets and ensure we get the correct version

@janisozaur
Copy link
Member

Emscripten docker builder is merged in OpenRCT2/openrct2-docker-build#20 now. I've taken liberty to add ccache to the builder image, emscripten should cache well

@ethanaobrien
Copy link
Contributor Author

ethanaobrien commented Jan 15, 2025

Is there anything that you would like changed in this PR?

For clarification, the reason the GetVersion and main functions are only exported when building openrct2-gui, is because for some reason EXPORTED_FUNCTIONS causes cmake to not find threads. Not sure if theres another function I need to export there

@janisozaur
Copy link
Member

The final missing piece is the GitHub actions job that uses the new builder image

@ethanaobrien
Copy link
Contributor Author

Ah right, I do ask if you can be the one to do it, just because I'm not super familiar with the way deploys are done in this project. If you'd like me to take a shot I'll do that too

@janisozaur
Copy link
Member

OK, I'll probably take a look tomorrow. But it should be what you already had in the script, just executed in the container. This is a fairly concise example:

linux-clang:
name: Ubuntu Linux (noble, debug, [http, network, flac, vorbis OpenGL] disabled) using clang
runs-on: ubuntu-latest
needs: [check-code-formatting, build_variables]
container: openrct2/openrct2-build:18-noble
steps:
- name: Checkout
uses: actions/checkout@v4
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.13
with:
key: linux-clang
- name: Install GCC problem matcher
uses: ammaraskar/gcc-problem-matcher@master
- name: Build OpenRCT2
run: . scripts/setenv && build -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug -DDISABLE_NETWORK=ON -DDISABLE_HTTP=ON -DDISABLE_FLAC=ON -DDISABLE_VORBIS=ON -DDISABLE_OPENGL=ON

Note the container field, which makes github worker execute every following step inside said container.

@ethanaobrien
Copy link
Contributor Author

I went ahead and moved the dependency import/export functions to a JavaScript library, so that anyone wanting to just use the wasm in their project doesn't need to mess with module dependencies. Forgot I could do this when initially adding these functions

@janisozaur
Copy link
Member

I finally got some time to do this and my branch is here: https://github.com/janisozaur/OpenRCT2/tree/refs/heads/emscripten-test with the script in question: https://github.com/janisozaur/OpenRCT2/blob/79484aff77c065e843d62d4de7e533b0310274e2/scripts/build-emscripten

But I run into an issue not present in this PR:

-- Check if compiler accepts -pthread
-- Check if compiler accepts -pthread - no
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - not found
CMake Error at /usr/share/cmake/Modules/FindPackageHandleStandardArgs.cmake:233 (message):
  Could NOT find Threads (missing: Threads_FOUND)

Could you take a look?
https://github.com/janisozaur/OpenRCT2/actions/runs/12847931580/job/35824766654

@ethanaobrien
Copy link
Contributor Author

I'll take a look

@ethanaobrien
Copy link
Contributor Author

@janisozaur Managed to fix this. This appears to be some really stange (bug?)/issue with emscripten that seems to cause threads to not be found with certian ldflags set. I got around this by adding the variables after instead of before the pthread check

@janisozaur
Copy link
Member

I have pushed the script to build in CI, just need to verify if the uploaded assets work as expected.

@ethanaobrien thanks a lot for sticking with us and seeing this PR until completion and sorry once again it took so long on our end. We require at least one more approval from the team member to PRs from external contributors, I've already asked folks to take a look.

Some notes after testing this, which IMO can be addressed in future PR(s):

  1. assets should be versioned, such as assets-0.4.18.zip
  2. in my tests, GetVersion function is not getting exported, even though it is listed in EXPORTED_FUNCTIONS.
  3. scrolling with right mouse button does not lock the cursor (but it gets hidden), which leads to cursor potentially moving to browser's chrome and openrct2 losing focus
  4. we should figure out how to deploy this, at least for releases, this mostly falls on our team
  5. the resulting openrct2.wasm binary is 22MB, but can get reduced to 3.4MB only with brotli, it would be best to compress it this way

Would you be able to look into some of those?

@janisozaur
Copy link
Member

For anyone who'd like to test it:

# cd to repository root
docker run -p 8000:8000 -v $(pwd):/openrct2 -it openrct2/openrct2-build:19-emscripten
git config --global --add safe.directory /openrct2
cd /openrct2
. scripts/setenv
build-emscripten
cd /openrct2/build/www
# you need to get assets.zip, you can download (an old) one from https://rct.ethanthesleepy.one/assets.zip or use the `data` directory from a portable build
cors_serve.py # this script is included in docker image

What do you guys think of moving the data inside assets.zip to data/ subdirectory, that way we could just plop any of the windows portable builds as asset repositories without any modifications. It would be just slightly overweight with windows executables, but would certainly reduce the effort required to set it up locally. For actual hosting, we would have a zip without those binaries.

@ethanaobrien
Copy link
Contributor Author

  1. assets should be versioned, such as assets-0.4.18.zip

Are you saying to just return "assets" OPENRCT2_VERSION ".zip" for the GetVersion function?

  1. in my tests, GetVersion function is not getting exported, even though it is listed in EXPORTED_FUNCTIONS.

This should be fixed now. ccall was missing from the EXPORTED_RUNTIME_METHODS: ethanaobrien@2195abd

  1. scrolling with right mouse button does not lock the cursor (but it gets hidden), which leads to cursor potentially moving to browser's chrome and openrct2 losing focus

This should be possible. We'd need to requestpointerlock every time the user scrolls which should work

  1. we should figure out how to deploy this, at least for releases, this mostly falls on our team

As a note, GitHub pages wont work (unless behind Cloudflare) You need these headers set to expose the SharedArrayBuffer api
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

  1. the resulting openrct2.wasm binary is 22MB, but can get reduced to 3.4MB only with brotli, it would be best to compress it this way

This should be possible and fairly simple. This library should work: https://github.com/httptoolkit/brotli-wasm

Would you be able to look into some of those?

I can definitely look into some of these when I get the time. I also plan to eventually add networking support for emscripten, and I plan to fix the fullscreen option

@janisozaur
Copy link
Member

  1. assets should be versioned, such as assets-0.4.18.zip

Are you saying to just return "assets" OPENRCT2_VERSION ".zip" for the GetVersion function?

Pretty much, but with some counter if we change it mid-release

  1. scrolling with right mouse button does not lock the cursor (but it gets hidden), which leads to cursor potentially moving to browser's chrome and openrct2 losing focus

This should be possible. We'd need to requestpointerlock every time the user scrolls which should work

Some interesting discussion here: libsdl-org/SDL#7840

If I read that correctly, they suggest storing the cursor position, entering SDL_SetRelativeMouseMode, doing your own shenanigans, restoring position

  1. we should figure out how to deploy this, at least for releases, this mostly falls on our team

As a note, GitHub pages wont work (unless behind Cloudflare)

We no longer host https://openrct2.io from github pages, but thanks for the tip. I'll see how can we host it now.

@janisozaur janisozaur merged commit a002834 into OpenRCT2:develop Jan 21, 2025
24 checks passed
@janisozaur
Copy link
Member

One more comment on usability: there should some way to upload and download saves. For Endless Sky (https://play-endless-web.com/), this is how we handled it: thomasballinger/endless-web@0a1597a#diff-73b43b8cad5b4f26858d32ba065f423c6774b87cd6083ddfd5faf83e4b2f73ee

@ethanaobrien
Copy link
Contributor Author

If I read that correctly, they suggest storing the cursor position, entering SDL_SetRelativeMouseMode, doing your own shenanigans, restoring position

The issue here is, unless I'm missing something, we can't alter the position of a cursor in the web browser without locking the cursor

One more comment on usability: there should some way to upload and download saves

I do also want this. At the moment there is the import/export buttons in settings which will download all saves but this is inconvenient for just wanting to download one map. I want to add a simple upload file/download file to the save/load dialogs, but couldn't figure out how to get a pointer to a save data rather than writing it to the disk. Do you know how I could go about this, since I assume you know more about how OpenRCT2 saves than I do

@ethanaobrien
Copy link
Contributor Author

@janisozaur Would you rather pull in that brotli-wasm library, rely on the server to compress and decompress it, or zip the files and rely on JSZip for decompression which is already pulled for handling of assets and saves?

@janisozaur
Copy link
Member

Either way is fine, can they both be pre-compressed server-side? I tested with deflate and it produces slightly larger binary, at around 4.5MB, but this is still ok.

@ethanaobrien
Copy link
Contributor Author

Yes, both the JS and WASM could be compressed together

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants