mirror of https://github.com/OpenTTD/OpenTTD.git
Add: support for emscripten (play-OpenTTD-in-the-browser)
Emscripten compiles to WASM, which can be loaded via HTML / JavaScript. This allows you to play OpenTTD inside a browser. Co-authored-by: milek7 <me@milek7.pl>
This commit is contained in:
parent
2da07f7615
commit
d15dc9f40f
|
@ -10,6 +10,55 @@ env:
|
|||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
|
||||
jobs:
|
||||
emscripten:
|
||||
name: Emscripten
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
container:
|
||||
# If you change this version, change the number in the cache step too.
|
||||
image: emscripten/emsdk:2.0.10
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /emsdk/upstream/emscripten/cache
|
||||
key: 2.0.10-${{ runner.os }}
|
||||
|
||||
- name: Build (host tools)
|
||||
run: |
|
||||
mkdir build-host
|
||||
cd build-host
|
||||
|
||||
echo "::group::CMake"
|
||||
cmake .. -DOPTION_TOOLS_ONLY=ON
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Build"
|
||||
echo "Running on $(nproc) cores"
|
||||
make -j$(nproc) tools
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Install GCC problem matcher
|
||||
uses: ammaraskar/gcc-problem-matcher@master
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
|
||||
echo "::group::CMake"
|
||||
emcmake cmake .. -DHOST_BINARY_DIR=../build-host
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Build"
|
||||
echo "Running on $(nproc) cores"
|
||||
emmake make -j$(nproc)
|
||||
echo "::endgroup::"
|
||||
|
||||
linux:
|
||||
name: Linux
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif()
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/os/emscripten/cmake")
|
||||
endif()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9)
|
||||
|
||||
|
@ -232,6 +236,39 @@ if(APPLE)
|
|||
)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
add_library(WASM::WASM INTERFACE IMPORTED)
|
||||
|
||||
# Allow heap-growth, and start with a bigger memory size.
|
||||
target_link_libraries(WASM::WASM INTERFACE "-s ALLOW_MEMORY_GROWTH=1")
|
||||
target_link_libraries(WASM::WASM INTERFACE "-s INITIAL_MEMORY=33554432")
|
||||
|
||||
# Export functions to Javascript.
|
||||
target_link_libraries(WASM::WASM INTERFACE "-s EXPORTED_FUNCTIONS='[\"_main\", \"_em_openttd_add_server\"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"cwrap\"]'")
|
||||
|
||||
# Preload all the files we generate during build.
|
||||
# As we do not compile with FreeType / FontConfig, we also have no way to
|
||||
# render several languages (like Chinese, ..), so where do you draw the
|
||||
# line what languages to include and which not? In the end, especially as
|
||||
# the more languages you add the slower downloading becomes, we decided to
|
||||
# only ship the English language.
|
||||
target_link_libraries(WASM::WASM INTERFACE "--preload-file ${CMAKE_BINARY_DIR}/baseset@/baseset")
|
||||
target_link_libraries(WASM::WASM INTERFACE "--preload-file ${CMAKE_BINARY_DIR}/lang/english.lng@/lang/english.lng")
|
||||
target_link_libraries(WASM::WASM INTERFACE "--preload-file ${CMAKE_SOURCE_DIR}/bin/ai@/ai")
|
||||
target_link_libraries(WASM::WASM INTERFACE "--preload-file ${CMAKE_SOURCE_DIR}/bin/game@/game")
|
||||
|
||||
# We use IDBFS for persistent storage.
|
||||
target_link_libraries(WASM::WASM INTERFACE "-lidbfs.js")
|
||||
|
||||
# Use custom pre-js and shell.html.
|
||||
target_link_libraries(WASM::WASM INTERFACE "--pre-js ${CMAKE_SOURCE_DIR}/os/emscripten/pre.js")
|
||||
target_link_libraries(WASM::WASM INTERFACE "--shell-file ${CMAKE_SOURCE_DIR}/os/emscripten/shell.html")
|
||||
|
||||
# Build the .html (which builds the .js, .wasm, and .data too).
|
||||
set_target_properties(openttd PROPERTIES SUFFIX ".html")
|
||||
target_link_libraries(openttd WASM::WASM)
|
||||
endif()
|
||||
|
||||
if(NOT PERSONAL_DIR STREQUAL "(not set)")
|
||||
add_definitions(
|
||||
-DWITH_PERSONAL_DIR
|
||||
|
|
|
@ -55,7 +55,13 @@ function(set_options)
|
|||
option(OPTION_DEDICATED "Build dedicated server only (no GUI)" OFF)
|
||||
option(OPTION_INSTALL_FHS "Install with Filesystem Hierarchy Standard folders" ${DEFAULT_OPTION_INSTALL_FHS})
|
||||
option(OPTION_USE_ASSERTS "Use assertions; leave enabled for nightlies, betas, and RCs" ON)
|
||||
option(OPTION_USE_THREADS "Use threads" ON)
|
||||
if(EMSCRIPTEN)
|
||||
# Although pthreads is supported, it is not in a way yet that is
|
||||
# useful for us.
|
||||
option(OPTION_USE_THREADS "Use threads" OFF)
|
||||
else()
|
||||
option(OPTION_USE_THREADS "Use threads" ON)
|
||||
endif()
|
||||
option(OPTION_USE_NSIS "Use NSIS to create windows installer; enable only for stable releases" OFF)
|
||||
option(OPTION_TOOLS_ONLY "Build only tools target" OFF)
|
||||
option(OPTION_DOCS_ONLY "Build only docs target" OFF)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
FROM emscripten/emsdk
|
||||
|
||||
COPY emsdk-liblzma.patch /
|
||||
RUN cd /emsdk/upstream/emscripten && patch -p1 < /emsdk-liblzma.patch
|
|
@ -0,0 +1,40 @@
|
|||
## How to build with Emscripten
|
||||
|
||||
Building with Emscripten works with emsdk 2.0.10 and above.
|
||||
|
||||
Currently there is no LibLZMA support upstream; for this we suggest to apply
|
||||
the provided patch in this folder to your emsdk installation.
|
||||
|
||||
For convenience, a Dockerfile is supplied that does this patches for you
|
||||
against upstream emsdk docker. Best way to use it:
|
||||
|
||||
Build the docker image:
|
||||
```
|
||||
docker build -t emsdk-lzma .
|
||||
```
|
||||
|
||||
Build the host tools first:
|
||||
```
|
||||
mkdir build-host
|
||||
docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build-host emsdk-lzma cmake .. -DOPTION_TOOLS_ONLY=ON
|
||||
docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build-host emsdk-lzma make -j5 tools
|
||||
```
|
||||
|
||||
Next, build the game with emscripten:
|
||||
|
||||
```
|
||||
mkdir build
|
||||
docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build emsdk-lzma emcmake cmake .. -DHOST_BINARY_DIR=$(pwd)/build-host -DCMAKE_BUILD_TYPE=RelWithDebInfo -DOPTION_USE_ASSERTS=OFF
|
||||
docker run -it --rm -v $(pwd):$(pwd) -u $(id -u):$(id -g) --workdir $(pwd)/build emsdk-lzma emmake make -j5
|
||||
```
|
||||
|
||||
And now you have in your build folder files like "openttd.html".
|
||||
|
||||
To run it locally, you would have to start a local webserver, like:
|
||||
|
||||
```
|
||||
cd build
|
||||
python3 -m http.server
|
||||
````
|
||||
|
||||
Now you can play the game via http://127.0.0.1:8000/openttd.html .
|
|
@ -0,0 +1,20 @@
|
|||
# LibLZMA is a recent addition to the emscripten SDK, so it is possible
|
||||
# someone hasn't updated his SDK yet. Test out if the SDK supports LibLZMA.
|
||||
include(CheckCXXSourceCompiles)
|
||||
set(CMAKE_REQUIRED_FLAGS "-sUSE_LIBLZMA=1")
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include <lzma.h>
|
||||
int main() { return 0; }"
|
||||
LIBLZMA_FOUND
|
||||
)
|
||||
|
||||
if (LIBLZMA_FOUND)
|
||||
add_library(LibLZMA::LibLZMA INTERFACE IMPORTED)
|
||||
set_target_properties(LibLZMA::LibLZMA PROPERTIES
|
||||
INTERFACE_COMPILE_OPTIONS "-sUSE_LIBLZMA=1"
|
||||
INTERFACE_LINK_LIBRARIES "-sUSE_LIBLZMA=1"
|
||||
)
|
||||
else()
|
||||
message(WARNING "You are using an emscripten SDK without LibLZMA support. Many savegames won't be able to load in OpenTTD. Please apply 'emsdk-liblzma.patch' to your local emsdk installation.")
|
||||
endif()
|
|
@ -0,0 +1,7 @@
|
|||
add_library(PNG::PNG INTERFACE IMPORTED)
|
||||
set_target_properties(PNG::PNG PROPERTIES
|
||||
INTERFACE_COMPILE_OPTIONS "-sUSE_LIBPNG=1"
|
||||
INTERFACE_LINK_LIBRARIES "-sUSE_LIBPNG=1"
|
||||
)
|
||||
|
||||
set(PNG_FOUND on)
|
|
@ -0,0 +1,7 @@
|
|||
add_library(SDL2::SDL2 INTERFACE IMPORTED)
|
||||
set_target_properties(SDL2::SDL2 PROPERTIES
|
||||
INTERFACE_COMPILE_OPTIONS "-sUSE_SDL=2"
|
||||
INTERFACE_LINK_LIBRARIES "-sUSE_SDL=2"
|
||||
)
|
||||
|
||||
set(SDL2_FOUND on)
|
|
@ -0,0 +1,7 @@
|
|||
add_library(ZLIB::ZLIB INTERFACE IMPORTED)
|
||||
set_target_properties(ZLIB::ZLIB PROPERTIES
|
||||
INTERFACE_COMPILE_OPTIONS "-sUSE_ZLIB=1"
|
||||
INTERFACE_LINK_LIBRARIES "-sUSE_ZLIB=1"
|
||||
)
|
||||
|
||||
set(ZLIB_FOUND on)
|
|
@ -0,0 +1,213 @@
|
|||
From 90dd4d4c6b1cedec338ff5b375fffca93700f7bc Mon Sep 17 00:00:00 2001
|
||||
From: milek7 <me@milek7.pl>
|
||||
Date: Tue, 8 Dec 2020 01:03:31 +0100
|
||||
Subject: [PATCH] Add liblzma port
|
||||
|
||||
---
|
||||
Source: https://github.com/emscripten-core/emscripten/pull/12990
|
||||
|
||||
Modifed by OpenTTD to have the bare minimum needed to work. Otherwise there
|
||||
are constantly conflicts when trying to apply this patch to different versions
|
||||
of emsdk.
|
||||
|
||||
diff --git a/embuilder.py b/embuilder.py
|
||||
index 818262190ed..ab7d5adb7b2 100755
|
||||
--- a/embuilder.py
|
||||
+++ b/embuilder.py
|
||||
@@ -60,6 +60,7 @@
|
||||
'harfbuzz',
|
||||
'icu',
|
||||
'libjpeg',
|
||||
+ 'liblzma',
|
||||
'libpng',
|
||||
'ogg',
|
||||
'regal',
|
||||
@@ -197,6 +198,8 @@ def main():
|
||||
build_port('ogg', libname('libogg'))
|
||||
elif what == 'libjpeg':
|
||||
build_port('libjpeg', libname('libjpeg'))
|
||||
+ elif what == 'liblzma':
|
||||
+ build_port('liblzma', libname('liblzma'))
|
||||
elif what == 'libpng':
|
||||
build_port('libpng', libname('libpng'))
|
||||
elif what == 'sdl2':
|
||||
diff --git a/src/settings.js b/src/settings.js
|
||||
index 61cd98939ba..be6fcb678c6 100644
|
||||
--- a/src/settings.js
|
||||
+++ b/src/settings.js
|
||||
@@ -1197,6 +1197,9 @@ var USE_BZIP2 = 0;
|
||||
// 1 = use libjpeg from emscripten-ports
|
||||
var USE_LIBJPEG = 0;
|
||||
|
||||
+// 1 = use liblzma from emscripten-ports
|
||||
+var USE_LIBLZMA = 0;
|
||||
+
|
||||
// 1 = use libpng from emscripten-ports
|
||||
var USE_LIBPNG = 0;
|
||||
|
||||
diff --git a/tools/ports/liblzma.py b/tools/ports/liblzma.py
|
||||
new file mode 100644
|
||||
index 00000000000..e9567ef36ff
|
||||
--- /dev/null
|
||||
+++ b/tools/ports/liblzma.py
|
||||
@@ -0,0 +1,160 @@
|
||||
+# Copyright 2020 The Emscripten Authors. All rights reserved.
|
||||
+# Emscripten is available under two separate licenses, the MIT license and the
|
||||
+# University of Illinois/NCSA Open Source License. Both these licenses can be
|
||||
+# found in the LICENSE file.
|
||||
+
|
||||
+import os
|
||||
+import shutil
|
||||
+
|
||||
+VERSION = '5.2.5'
|
||||
+HASH = '7443674247deda2935220fbc4dfc7665e5bb5a260be8ad858c8bd7d7b9f0f868f04ea45e62eb17c0a5e6a2de7c7500ad2d201e2d668c48ca29bd9eea5a73a3ce'
|
||||
+
|
||||
+
|
||||
+def needed(settings):
|
||||
+ return settings.USE_LIBLZMA
|
||||
+
|
||||
+
|
||||
+def get(ports, settings, shared):
|
||||
+ libname = ports.get_lib_name('liblzma')
|
||||
+ ports.fetch_project('liblzma', 'https://tukaani.org/xz/xz-' + VERSION + '.tar.gz', 'xz-' + VERSION, sha512hash=HASH)
|
||||
+
|
||||
+ def create():
|
||||
+ ports.clear_project_build('liblzma')
|
||||
+
|
||||
+ source_path = os.path.join(ports.get_dir(), 'liblzma', 'xz-' + VERSION)
|
||||
+ dest_path = os.path.join(ports.get_build_dir(), 'liblzma')
|
||||
+
|
||||
+ shared.try_delete(dest_path)
|
||||
+ os.makedirs(dest_path)
|
||||
+ shutil.rmtree(dest_path, ignore_errors=True)
|
||||
+ shutil.copytree(source_path, dest_path)
|
||||
+
|
||||
+ build_flags = ['-DHAVE_CONFIG_H', '-DTUKLIB_SYMBOL_PREFIX=lzma_', '-fvisibility=hidden']
|
||||
+ exclude_dirs = ['xzdec', 'xz', 'lzmainfo']
|
||||
+ exclude_files = ['crc32_small.c', 'crc64_small.c', 'crc32_tablegen.c', 'crc64_tablegen.c', 'price_tablegen.c', 'fastpos_tablegen.c'
|
||||
+ 'tuklib_exit.c', 'tuklib_mbstr_fw.c', 'tuklib_mbstr_width.c', 'tuklib_open_stdxxx.c', 'tuklib_progname.c']
|
||||
+ include_dirs_rel = ['../common', 'api', 'common', 'check', 'lz', 'rangecoder', 'lzma', 'delta', 'simple']
|
||||
+
|
||||
+ open(os.path.join(dest_path, 'src', 'config.h'), 'w').write(config_h)
|
||||
+
|
||||
+ final = os.path.join(dest_path, libname)
|
||||
+ include_dirs = [os.path.join(dest_path, 'src', 'liblzma', p) for p in include_dirs_rel]
|
||||
+ ports.build_port(os.path.join(dest_path, 'src'), final, flags=build_flags, exclude_dirs=exclude_dirs, exclude_files=exclude_files, includes=include_dirs)
|
||||
+
|
||||
+ ports.install_headers(os.path.join(dest_path, 'src', 'liblzma', 'api'), 'lzma.h')
|
||||
+ ports.install_headers(os.path.join(dest_path, 'src', 'liblzma', 'api', 'lzma'), '*.h', 'lzma')
|
||||
+
|
||||
+ return final
|
||||
+
|
||||
+ return [shared.Cache.get(libname, create, what='port')]
|
||||
+
|
||||
+
|
||||
+def clear(ports, settings, shared):
|
||||
+ shared.Cache.erase_file(ports.get_lib_name('liblzma'))
|
||||
+
|
||||
+
|
||||
+def process_args(ports):
|
||||
+ return []
|
||||
+
|
||||
+
|
||||
+def show():
|
||||
+ return 'liblzma (USE_LIBLZMA=1; public domain)'
|
||||
+
|
||||
+
|
||||
+config_h = r'''
|
||||
+#define ASSUME_RAM 128
|
||||
+#define ENABLE_NLS 1
|
||||
+#define HAVE_CHECK_CRC32 1
|
||||
+#define HAVE_CHECK_CRC64 1
|
||||
+#define HAVE_CHECK_SHA256 1
|
||||
+#define HAVE_CLOCK_GETTIME 1
|
||||
+#define HAVE_DCGETTEXT 1
|
||||
+#define HAVE_DECL_CLOCK_MONOTONIC 1
|
||||
+#define HAVE_DECL_PROGRAM_INVOCATION_NAME 1
|
||||
+#define HAVE_DECODERS 1
|
||||
+#define HAVE_DECODER_ARM 1
|
||||
+#define HAVE_DECODER_ARMTHUMB 1
|
||||
+#define HAVE_DECODER_DELTA 1
|
||||
+#define HAVE_DECODER_IA64 1
|
||||
+#define HAVE_DECODER_LZMA1 1
|
||||
+#define HAVE_DECODER_LZMA2 1
|
||||
+#define HAVE_DECODER_POWERPC 1
|
||||
+#define HAVE_DECODER_SPARC 1
|
||||
+#define HAVE_DECODER_X86 1
|
||||
+#define HAVE_DLFCN_H 1
|
||||
+#define HAVE_ENCODERS 1
|
||||
+#define HAVE_ENCODER_ARM 1
|
||||
+#define HAVE_ENCODER_ARMTHUMB 1
|
||||
+#define HAVE_ENCODER_DELTA 1
|
||||
+#define HAVE_ENCODER_IA64 1
|
||||
+#define HAVE_ENCODER_LZMA1 1
|
||||
+#define HAVE_ENCODER_LZMA2 1
|
||||
+#define HAVE_ENCODER_POWERPC 1
|
||||
+#define HAVE_ENCODER_SPARC 1
|
||||
+#define HAVE_ENCODER_X86 1
|
||||
+#define HAVE_FCNTL_H 1
|
||||
+#define HAVE_FUTIMENS 1
|
||||
+#define HAVE_GETOPT_H 1
|
||||
+#define HAVE_GETOPT_LONG 1
|
||||
+#define HAVE_GETTEXT 1
|
||||
+#define HAVE_IMMINTRIN_H 1
|
||||
+#define HAVE_INTTYPES_H 1
|
||||
+#define HAVE_LIMITS_H 1
|
||||
+#define HAVE_MBRTOWC 1
|
||||
+#define HAVE_MEMORY_H 1
|
||||
+#define HAVE_MF_BT2 1
|
||||
+#define HAVE_MF_BT3 1
|
||||
+#define HAVE_MF_BT4 1
|
||||
+#define HAVE_MF_HC3 1
|
||||
+#define HAVE_MF_HC4 1
|
||||
+#define HAVE_OPTRESET 1
|
||||
+#define HAVE_POSIX_FADVISE 1
|
||||
+#define HAVE_PTHREAD_CONDATTR_SETCLOCK 1
|
||||
+#define HAVE_PTHREAD_PRIO_INHERIT 1
|
||||
+#define HAVE_STDBOOL_H 1
|
||||
+#define HAVE_STDINT_H 1
|
||||
+#define HAVE_STDLIB_H 1
|
||||
+#define HAVE_STRINGS_H 1
|
||||
+#define HAVE_STRING_H 1
|
||||
+#define HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC 1
|
||||
+#define HAVE_SYS_PARAM_H 1
|
||||
+#define HAVE_SYS_STAT_H 1
|
||||
+#define HAVE_SYS_TIME_H 1
|
||||
+#define HAVE_SYS_TYPES_H 1
|
||||
+#define HAVE_UINTPTR_T 1
|
||||
+#define HAVE_UNISTD_H 1
|
||||
+#define HAVE_VISIBILITY 1
|
||||
+#define HAVE_WCWIDTH 1
|
||||
+#define HAVE__BOOL 1
|
||||
+#define HAVE___BUILTIN_ASSUME_ALIGNED 1
|
||||
+#define HAVE___BUILTIN_BSWAPXX 1
|
||||
+#define MYTHREAD_POSIX 1
|
||||
+#define NDEBUG 1
|
||||
+#define PACKAGE "xz"
|
||||
+#define PACKAGE_BUGREPORT "lasse.collin@tukaani.org"
|
||||
+#define PACKAGE_NAME "XZ Utils"
|
||||
+#define PACKAGE_STRING "XZ Utils 5.2.5"
|
||||
+#define PACKAGE_TARNAME "xz"
|
||||
+#define PACKAGE_VERSION "5.2.5"
|
||||
+#define SIZEOF_SIZE_T 4
|
||||
+#define STDC_HEADERS 1
|
||||
+#define TUKLIB_CPUCORES_SYSCONF 1
|
||||
+#define TUKLIB_FAST_UNALIGNED_ACCESS 1
|
||||
+#define TUKLIB_PHYSMEM_SYSCONF 1
|
||||
+#ifndef _ALL_SOURCE
|
||||
+# define _ALL_SOURCE 1
|
||||
+#endif
|
||||
+#ifndef _GNU_SOURCE
|
||||
+# define _GNU_SOURCE 1
|
||||
+#endif
|
||||
+#ifndef _POSIX_PTHREAD_SEMANTICS
|
||||
+# define _POSIX_PTHREAD_SEMANTICS 1
|
||||
+#endif
|
||||
+#ifndef _TANDEM_SOURCE
|
||||
+# define _TANDEM_SOURCE 1
|
||||
+#endif
|
||||
+#ifndef __EXTENSIONS__
|
||||
+# define __EXTENSIONS__ 1
|
||||
+#endif
|
||||
+#define VERSION "5.2.5"
|
||||
+'''
|
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
|
@ -0,0 +1,93 @@
|
|||
Module.arguments.push('-mnull', '-snull', '-vsdl:relative_mode');
|
||||
Module['websocket'] = { url: function(host, port, proto) {
|
||||
/* openttd.org hosts a WebSocket proxy for the content service. */
|
||||
if (host == "content.openttd.org" && port == 3978 && proto == "tcp") {
|
||||
return "wss://content.openttd.org/";
|
||||
}
|
||||
|
||||
/* Everything else just tries to make a default WebSocket connection.
|
||||
* If you run your own server you can setup your own WebSocket proxy in
|
||||
* front of it and let people connect to your server via the proxy. You
|
||||
* are best to add another "if" statement as above for this. */
|
||||
return null;
|
||||
} };
|
||||
|
||||
Module.preRun.push(function() {
|
||||
personal_dir = '/home/web_user/.openttd';
|
||||
content_download_dir = personal_dir + '/content_download'
|
||||
|
||||
/* Because of the "-c" above, all user-data is stored in /user_data. */
|
||||
FS.mkdir(personal_dir);
|
||||
FS.mount(IDBFS, {}, personal_dir);
|
||||
|
||||
Module.addRunDependency('syncfs');
|
||||
FS.syncfs(true, function (err) {
|
||||
/* FS.mkdir() tends to fail if parent folders do not exist. */
|
||||
if (!FS.analyzePath(content_download_dir).exists) {
|
||||
FS.mkdir(content_download_dir);
|
||||
}
|
||||
if (!FS.analyzePath(content_download_dir + '/baseset').exists) {
|
||||
FS.mkdir(content_download_dir + '/baseset');
|
||||
}
|
||||
|
||||
/* Check if the OpenGFX baseset is already downloaded. */
|
||||
if (!FS.analyzePath(content_download_dir + '/baseset/opengfx-0.6.0.tar').exists) {
|
||||
window.openttd_downloaded_opengfx = true;
|
||||
FS.createPreloadedFile(content_download_dir + '/baseset', 'opengfx-0.6.0.tar', 'https://installer.cdn.openttd.org/emscripten/opengfx-0.6.0.tar', true, true);
|
||||
} else {
|
||||
/* Fake dependency increase, so the counter is stable. */
|
||||
Module.addRunDependency('opengfx');
|
||||
Module.removeRunDependency('opengfx');
|
||||
}
|
||||
|
||||
Module.removeRunDependency('syncfs');
|
||||
});
|
||||
|
||||
window.openttd_syncfs_shown_warning = false;
|
||||
window.openttd_syncfs = function() {
|
||||
/* Copy the virtual FS to the persistent storage. */
|
||||
FS.syncfs(false, function (err) { });
|
||||
|
||||
/* On first time, warn the user about the volatile behaviour of
|
||||
* persistent storage. */
|
||||
if (!window.openttd_syncfs_shown_warning) {
|
||||
window.openttd_syncfs_shown_warning = true;
|
||||
Module.onWarningFs();
|
||||
}
|
||||
}
|
||||
|
||||
window.openttd_exit = function() {
|
||||
Module.onExit();
|
||||
}
|
||||
|
||||
window.openttd_abort = function() {
|
||||
Module.onAbort();
|
||||
}
|
||||
|
||||
window.openttd_server_list = function() {
|
||||
add_server = Module.cwrap("em_openttd_add_server", null, ["string", "number"]);
|
||||
|
||||
/* Add servers that support WebSocket here. Example:
|
||||
* add_server("localhost", 3979); */
|
||||
}
|
||||
|
||||
/* https://github.com/emscripten-core/emscripten/pull/12995 implements this
|
||||
* properly. Till that time, we use a polyfill. */
|
||||
SOCKFS.websocket_sock_ops.createPeer_ = SOCKFS.websocket_sock_ops.createPeer;
|
||||
SOCKFS.websocket_sock_ops.createPeer = function(sock, addr, port)
|
||||
{
|
||||
let func = Module['websocket']['url'];
|
||||
Module['websocket']['url'] = func(addr, port, (sock.type == 2) ? 'udp' : 'tcp');
|
||||
let ret = SOCKFS.websocket_sock_ops.createPeer_(sock, addr, port);
|
||||
Module['websocket']['url'] = func;
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
|
||||
Module.postRun.push(function() {
|
||||
/* Check if we downloaded OpenGFX; if so, sync the virtual FS back to the
|
||||
* IDBFS so OpenGFX is stored persistent. */
|
||||
if (window['openttd_downloaded_opengfx']) {
|
||||
FS.syncfs(false, function (err) { });
|
||||
}
|
||||
});
|
File diff suppressed because one or more lines are too long
|
@ -26,7 +26,6 @@
|
|||
#include "safeguards.h"
|
||||
|
||||
static const int ASCII_LETTERSTART = 32; ///< First printable ASCII letter.
|
||||
static const int MAX_FONT_SIZE = 72; ///< Maximum font size.
|
||||
|
||||
/** Default heights for the different sizes of fonts. */
|
||||
static const int _default_font_height[FS_END] = {10, 6, 18, 10};
|
||||
|
@ -202,6 +201,8 @@ bool SpriteFontCache::GetDrawGlyphShadow()
|
|||
|
||||
FreeTypeSettings _freetype;
|
||||
|
||||
static const int MAX_FONT_SIZE = 72; ///< Maximum font size.
|
||||
|
||||
static const byte FACE_COLOUR = 1;
|
||||
static const byte SHADOW_COLOUR = 2;
|
||||
|
||||
|
|
|
@ -162,7 +162,9 @@ struct DrawPixelInfo {
|
|||
union Colour {
|
||||
uint32 data; ///< Conversion of the channel information to a 32 bit number.
|
||||
struct {
|
||||
#if TTD_ENDIAN == TTD_BIG_ENDIAN
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
uint8 r, g, b, a; ///< colour channels as used in browsers
|
||||
#elif TTD_ENDIAN == TTD_BIG_ENDIAN
|
||||
uint8 a, r, g, b; ///< colour channels in BE order
|
||||
#else
|
||||
uint8 b, g, r, a; ///< colour channels in LE order
|
||||
|
@ -177,7 +179,9 @@ union Colour {
|
|||
* @param a The channel for the alpha/transparency.
|
||||
*/
|
||||
Colour(uint8 r, uint8 g, uint8 b, uint8 a = 0xFF) :
|
||||
#if TTD_ENDIAN == TTD_BIG_ENDIAN
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
r(r), g(g), b(b), a(a)
|
||||
#elif TTD_ENDIAN == TTD_BIG_ENDIAN
|
||||
a(a), r(r), g(g), b(b)
|
||||
#else
|
||||
b(b), g(g), r(r), a(a)
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
#include "string_func.h"
|
||||
#include "fileio_func.h"
|
||||
#include <fstream>
|
||||
#ifdef __EMSCRIPTEN__
|
||||
# include <emscripten.h>
|
||||
#endif
|
||||
|
||||
#if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L) || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 500)
|
||||
# include <unistd.h>
|
||||
|
@ -115,6 +118,10 @@ bool IniFile::SaveToDisk(const char *filename)
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -299,7 +299,15 @@ static SOCKET ConnectLoopProc(addrinfo *runp)
|
|||
|
||||
if (!SetNoDelay(sock)) DEBUG(net, 1, "[%s] setting TCP_NODELAY failed", type);
|
||||
|
||||
if (connect(sock, runp->ai_addr, (int)runp->ai_addrlen) != 0) {
|
||||
int err = connect(sock, runp->ai_addr, (int)runp->ai_addrlen);
|
||||
#ifdef __EMSCRIPTEN__
|
||||
/* Emscripten is asynchronous, and as such a connect() is still in
|
||||
* progress by the time the call returns. */
|
||||
if (err != 0 && errno != EINPROGRESS)
|
||||
#else
|
||||
if (err != 0)
|
||||
#endif
|
||||
{
|
||||
DEBUG(net, 1, "[%s] could not connect %s socket: %s", type, family, strerror(errno));
|
||||
closesocket(sock);
|
||||
return INVALID_SOCKET;
|
||||
|
|
|
@ -83,6 +83,16 @@ typedef unsigned long in_addr_t;
|
|||
# include <errno.h>
|
||||
# include <sys/time.h>
|
||||
# include <netdb.h>
|
||||
|
||||
# if defined(__EMSCRIPTEN__)
|
||||
/* Emscripten doesn't support AI_ADDRCONFIG and errors out on it. */
|
||||
# undef AI_ADDRCONFIG
|
||||
# define AI_ADDRCONFIG 0
|
||||
/* Emscripten says it supports FD_SETSIZE fds, but it really only supports 64.
|
||||
* https://github.com/emscripten-core/emscripten/issues/1711 */
|
||||
# undef FD_SETSIZE
|
||||
# define FD_SETSIZE 64
|
||||
# endif
|
||||
#endif /* UNIX */
|
||||
|
||||
/* OS/2 stuff */
|
||||
|
@ -148,12 +158,16 @@ typedef unsigned long in_addr_t;
|
|||
*/
|
||||
static inline bool SetNonBlocking(SOCKET d)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
u_long nonblocking = 1;
|
||||
#ifdef __EMSCRIPTEN__
|
||||
return true;
|
||||
#else
|
||||
# ifdef _WIN32
|
||||
u_long nonblocking = 1;
|
||||
# else
|
||||
int nonblocking = 1;
|
||||
#endif
|
||||
# endif
|
||||
return ioctlsocket(d, FIONBIO, &nonblocking) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -163,10 +177,14 @@ static inline bool SetNonBlocking(SOCKET d)
|
|||
*/
|
||||
static inline bool SetNoDelay(SOCKET d)
|
||||
{
|
||||
#ifdef __EMSCRIPTEN__
|
||||
return true;
|
||||
#else
|
||||
/* XXX should this be done at all? */
|
||||
int b = 1;
|
||||
/* The (const char*) cast is needed for windows */
|
||||
return setsockopt(d, IPPROTO_TCP, TCP_NODELAY, (const char*)&b, sizeof(b)) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Make sure these structures have the size we expect them to be */
|
||||
|
|
|
@ -1154,3 +1154,14 @@ bool IsNetworkCompatibleVersion(const char *other)
|
|||
const char *hash2 = ExtractNetworkRevisionHash(other);
|
||||
return hash1 && hash2 && (strncmp(hash1, hash2, GITHASH_SUFFIX_LEN) == 0);
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
extern "C" {
|
||||
|
||||
void CDECL em_openttd_add_server(const char *host, int port)
|
||||
{
|
||||
NetworkUDPQueryServer(NetworkAddress(host, port), true);
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
# include <emscripten.h>
|
||||
#endif
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
extern bool HasScenario(const ContentInfo *ci, bool md5sum);
|
||||
|
@ -289,6 +293,13 @@ void ClientNetworkContentSocketHandler::DownloadSelectedContent(uint &files, uin
|
|||
{
|
||||
bytes = 0;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
/* Emscripten is loaded via an HTTPS connection. As such, it is very
|
||||
* difficult to make HTTP connections. So always use the TCP method of
|
||||
* downloading content. */
|
||||
fallback = true;
|
||||
#endif
|
||||
|
||||
ContentIDList content;
|
||||
for (const ContentInfo *ci : this->infos) {
|
||||
if (!ci->IsSelected() || ci->state == ContentInfo::ALREADY_HERE) continue;
|
||||
|
@ -535,6 +546,10 @@ void ClientNetworkContentSocketHandler::AfterDownload()
|
|||
unlink(GetFullFilename(this->curInfo, false));
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
|
||||
#endif
|
||||
|
||||
this->OnDownloadComplete(this->curInfo->id);
|
||||
} else {
|
||||
ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_EXTRACT, INVALID_STRING_ID, WL_ERROR);
|
||||
|
|
|
@ -39,6 +39,9 @@
|
|||
|
||||
#include "../safeguards.h"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
# include <emscripten.h>
|
||||
#endif
|
||||
|
||||
static void ShowNetworkStartServerWindow();
|
||||
static void ShowNetworkLobbyWindow(NetworkGameList *ngl);
|
||||
|
@ -475,6 +478,14 @@ public:
|
|||
this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR;
|
||||
this->SetFocusedWidget(WID_NG_FILTER);
|
||||
|
||||
/* As the master-server doesn't support "websocket" servers yet, we
|
||||
* let "os/emscripten/pre.js" hardcode a list of servers people can
|
||||
* join. This means the serverlist is curated for now, but it is the
|
||||
* best we can offer. */
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_ASM(if (window["openttd_server_list"]) openttd_server_list());
|
||||
#endif
|
||||
|
||||
this->last_joined = NetworkGameListAddItem(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port));
|
||||
this->server = this->last_joined;
|
||||
if (this->last_joined != nullptr) NetworkUDPQueryServer(this->last_joined->address);
|
||||
|
@ -615,6 +626,12 @@ public:
|
|||
this->GetWidget<NWidgetStacked>(WID_NG_NEWGRF_SEL)->SetDisplayedPlane(sel == nullptr || !sel->online || sel->info.grfconfig == nullptr);
|
||||
this->GetWidget<NWidgetStacked>(WID_NG_NEWGRF_MISSING_SEL)->SetDisplayedPlane(sel == nullptr || !sel->online || sel->info.grfconfig == nullptr || !sel->info.version_compatible || sel->info.compatible);
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
this->SetWidgetDisabledState(WID_NG_FIND, true);
|
||||
this->SetWidgetDisabledState(WID_NG_ADD, true);
|
||||
this->SetWidgetDisabledState(WID_NG_START, true);
|
||||
#endif
|
||||
|
||||
this->DrawWidgets();
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,11 @@
|
|||
|
||||
#include "safeguards.h"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
# include <emscripten.h>
|
||||
# include <emscripten/html5.h>
|
||||
#endif
|
||||
|
||||
void CallLandscapeTick();
|
||||
void IncreaseDate();
|
||||
void DoPaletteAnimations();
|
||||
|
@ -104,6 +109,15 @@ void CDECL usererror(const char *s, ...)
|
|||
ShowOSErrorBox(buf, false);
|
||||
if (VideoDriver::GetInstance() != nullptr) VideoDriver::GetInstance()->Stop();
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
emscripten_exit_pointerlock();
|
||||
/* In effect, the game ends here. As emscripten_set_main_loop() caused
|
||||
* the stack to be unwound, the code after MainLoop() in
|
||||
* openttd_main() is never executed. */
|
||||
EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
|
||||
EM_ASM(if (window["openttd_abort"]) openttd_abort());
|
||||
#endif
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,9 @@
|
|||
#include "../error.h"
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#ifdef __EMSCRIPTEN__
|
||||
# include <emscripten.h>
|
||||
#endif
|
||||
|
||||
#include "table/strings.h"
|
||||
|
||||
|
@ -2495,6 +2498,10 @@ static void SaveFileDone()
|
|||
|
||||
InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SAVELOAD_FINISH);
|
||||
_sl.saveinprogress = false;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Set the error message from outside of the actual loading/saving of the game (AfterLoadGame and friends) */
|
||||
|
|
|
@ -27,6 +27,10 @@
|
|||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <algorithm>
|
||||
#ifdef __EMSCRIPTEN__
|
||||
# include <emscripten.h>
|
||||
# include <emscripten/html5.h>
|
||||
#endif
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
|
@ -673,7 +677,19 @@ void VideoDriver_SDL::LoopOnce()
|
|||
InteractiveRandom(); // randomness
|
||||
|
||||
while (PollEvent() == -1) {}
|
||||
if (_exit_game) return;
|
||||
if (_exit_game) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
/* Emscripten is event-driven, and as such the main loop is inside
|
||||
* the browser. So if _exit_game goes true, the main loop ends (the
|
||||
* cancel call), but we still have to call the cleanup that is
|
||||
* normally done at the end of the main loop for non-Emscripten.
|
||||
* After that, Emscripten just halts, and the HTML shows a nice
|
||||
* "bye, see you next time" message. */
|
||||
emscripten_cancel_main_loop();
|
||||
MainLoopCleanup();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
mod = SDL_GetModState();
|
||||
keys = SDL_GetKeyboardState(&numkeys);
|
||||
|
@ -722,9 +738,17 @@ void VideoDriver_SDL::LoopOnce()
|
|||
_local_palette = _cur_palette;
|
||||
} else {
|
||||
/* Release the thread while sleeping */
|
||||
if (_draw_mutex != nullptr) draw_lock.unlock();
|
||||
CSleep(1);
|
||||
if (_draw_mutex != nullptr) draw_lock.lock();
|
||||
if (_draw_mutex != nullptr) {
|
||||
draw_lock.unlock();
|
||||
CSleep(1);
|
||||
draw_lock.lock();
|
||||
} else {
|
||||
/* Emscripten is running an event-based mainloop; there is already some
|
||||
* downtime between each iteration, so no need to sleep. */
|
||||
#ifndef __EMSCRIPTEN__
|
||||
CSleep(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
NetworkDrawChatMessage();
|
||||
DrawMouseCursor();
|
||||
|
@ -778,6 +802,10 @@ void VideoDriver_SDL::MainLoop()
|
|||
|
||||
DEBUG(driver, 1, "SDL2: using %sthreads", _draw_threaded ? "" : "no ");
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
/* Run the main loop event-driven, based on RequestAnimationFrame. */
|
||||
emscripten_set_main_loop_arg(&this->EmscriptenLoop, this, 0, 1);
|
||||
#else
|
||||
while (!_exit_game) {
|
||||
LoopOnce();
|
||||
}
|
||||
|
@ -803,6 +831,15 @@ void VideoDriver_SDL::MainLoopCleanup()
|
|||
_draw_mutex = nullptr;
|
||||
_draw_signal = nullptr;
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
emscripten_exit_pointerlock();
|
||||
/* In effect, the game ends here. As emscripten_set_main_loop() caused
|
||||
* the stack to be unwound, the code after MainLoop() in
|
||||
* openttd_main() is never executed. */
|
||||
EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
|
||||
EM_ASM(if (window["openttd_exit"]) openttd_exit());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool VideoDriver_SDL::ChangeResolution(int w, int h)
|
||||
|
|
|
@ -46,6 +46,11 @@ private:
|
|||
void MainLoopCleanup();
|
||||
bool CreateMainSurface(uint w, uint h, bool resize);
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
/* Convert a constant pointer back to a non-constant pointer to a member function. */
|
||||
static void EmscriptenLoop(void *self) { ((VideoDriver_SDL *)self)->LoopOnce(); }
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This is true to indicate that keyboard input is in text input mode, and SDL_TEXTINPUT events are enabled.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue